[ARVADOS-DEV] created: f1a99fd359995d13096de04c48da4466f5cd1239

git at public.curoverse.com git at public.curoverse.com
Tue Jan 19 10:51:50 EST 2016


        at  f1a99fd359995d13096de04c48da4466f5cd1239 (commit)


commit f1a99fd359995d13096de04c48da4466f5cd1239
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 19 10:44:03 2016 -0500

    8080: Add ARVBOX_PUBLISH_IP

diff --git a/arvbox/README.md b/arvbox/README.md
index bfd390e..b4a9c25 100644
--- a/arvbox/README.md
+++ b/arvbox/README.md
@@ -91,6 +91,9 @@ default: $ARVBOX_DATA/arvados-dev
 The root directory of the SSO source tree
 default: $ARVBOX_DATA/sso-devise-omniauth-provider
 
+### ARVBOX_PUBLISH_IP
+The IP address on which to publish services when running in public
+configuration.  Overrides default detection of the host's IP address.
 
 ## Notes
 
diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index b87d1f7..284f0ef 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -97,8 +97,12 @@ run() {
     fi
 
     if echo "$1" | grep '^public' ; then
-        defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
-        localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+        if test -n "$ARVBOX_PUBLISH_IP" ; then
+            localip=$ARVBOX_PUBLISH_IP
+        else
+            defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
+            localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+        fi
         iptemp=$(tempfile)
         echo $localip > $iptemp
         chmod og+r $iptemp

commit 75592aa246ecae9c1616108658e477c938829892
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 19 09:16:13 2016 -0500

    8080: Fix typo $localip

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index a3a53e1..b87d1f7 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -84,7 +84,7 @@ wait_for_arvbox() {
     rm $FF
     echo
     if test -n "$localip" ; then
-        echo "export ARVADOS_API_HOST=localip:8000"
+        echo "export ARVADOS_API_HOST=$localip:8000"
     else
         echo "export ARVADOS_API_HOST=$(getip):8000"
     fi

commit 87b03de6b853c00b6c275df6d8595bb5f5de76be
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 17:38:24 2016 -0500

    8080: Quickstart tells you to use "localdemo" by default.

diff --git a/arvbox/README.md b/arvbox/README.md
index b924b2d..bfd390e 100644
--- a/arvbox/README.md
+++ b/arvbox/README.md
@@ -4,7 +4,7 @@ Self-contained development, demonstration and testing environment for Arvados.
 
 ## Quick start
 
-$ bin/arvbox reboot dev
+$ bin/arvbox reboot localdemo
 
 ## Usage
 

commit 1db2227f01a7ac88078ac04504f6b3d4c3623ec7
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 17:35:43 2016 -0500

    8080: Fix typo postgres -> postgresql data volume.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 82af625..a3a53e1 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -124,7 +124,7 @@ run() {
         fi
 
         if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
-            docker create -v /var/lib/postgres -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
+            docker create -v /var/lib/postgresql -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
         fi
 
         docker run \
@@ -216,11 +216,13 @@ stop() {
     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker stop $ARVBOX_CONTAINER
     fi
+
+    VOLUMES=--volumes=true
     if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then
-        docker rm --volumes=true $ARVBOX_CONTAINER
+        docker rm $VOLUMES $ARVBOX_CONTAINER
     fi
     if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
-        docker rm --volumes=true $ARVBOX_CONTAINER
+        docker rm $VOLUMES $ARVBOX_CONTAINER
     fi
 }
 

commit cdd4636a69dcc7e77a353c4939e12720ec5da17d
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 17:17:19 2016 -0500

    8080: Scrub default postgres database, demo config finally works!

diff --git a/arvbox/lib/arvbox/docker/Dockerfile.base b/arvbox/lib/arvbox/docker/Dockerfile.base
index 23fca74..cdbface 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -14,6 +14,8 @@ RUN apt-get update && \
 RUN curl -sSL https://get.docker.com/ | sh
 VOLUME /var/lib/docker
 
+RUN rm -rf /var/lib/postgresql && mkdir -p /var/lib/postgresql
+
 RUN cd /root && \
     GOPATH=$PWD go get github.com/curoverse/runsvinit && \
     install bin/runsvinit /usr/local/bin
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
index d40cd5c..1085abc 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -5,7 +5,6 @@ RUN cd /usr/src && \
     git clone https://github.com/curoverse/sso-devise-omniauth-provider.git sso
 
 RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
-RUN rm -rf /var/lib/postgresql && mkdir -p /var/lib/postgresql
 
 RUN sudo -u arvbox /usr/local/lib/arvbox/service/sso/run-service --only-deps
 RUN sudo -u arvbox /usr/local/lib/arvbox/service/api/run-service --only-deps

commit 5c79db7500c16043edc2aabc5ef0c9a73205719c
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 16:21:54 2016 -0500

    8080: Fixes for "publicdemo" mode

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 3d58315..82af625 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -101,6 +101,7 @@ run() {
         localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
         iptemp=$(tempfile)
         echo $localip > $iptemp
+        chmod og+r $iptemp
         PUBLIC="--volume=$iptemp:/var/run/localip_override
               --publish=80:80
               --publish=8000:8000
diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index a90992a..cc7e11f 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -20,7 +20,7 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
     useradd --groups docker,fuse crunch
 
-    chown arvbox:arvbox -R /usr/local /var/lib/arvados /var/lib/gems /var/lib/passenger
+    chown arvbox:arvbox -R /usr/local /var/lib/arvados /var/lib/gems /var/lib/passenger /var/lib/postgresql
 
     mkdir -p /var/lib/gems/ruby/2.1.0
     chown arvbox:arvbox -R /var/lib/gems/ruby/2.1.0
diff --git a/arvbox/lib/arvbox/docker/service/ready/run-service b/arvbox/lib/arvbox/docker/service/ready/run-service
index c027360..f560de0 100755
--- a/arvbox/lib/arvbox/docker/service/ready/run-service
+++ b/arvbox/lib/arvbox/docker/service/ready/run-service
@@ -21,7 +21,7 @@ waiting=""
 for s in "${!services[@]}"
 do
   if ! [[ -f /tmp/arvbox-ready/$s ]] ; then
-    if nc -z $localip ${services[$s]} ; then
+    if nc -z localhost ${services[$s]} ; then
       echo "$s is ready at $localip:${services[$s]}"
       touch /tmp/arvbox-ready/$s
     else

commit 1773318d588bc85cd9a56d520e5e4abaa7a78eb1
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 17:00:27 2016 -0500

    8080: Working on demo mode.

diff --git a/arvbox/README.md b/arvbox/README.md
index 3537119..b924b2d 100644
--- a/arvbox/README.md
+++ b/arvbox/README.md
@@ -4,7 +4,7 @@ Self-contained development, demonstration and testing environment for Arvados.
 
 ## Quick start
 
-$ ./bin/arvbox reboot localdemo
+$ bin/arvbox reboot dev
 
 ## Usage
 
@@ -31,18 +31,18 @@ clone <from> <to>   clone an arvbox
 
 ### dev
 Development configuration.  Boots a complete Arvados environment inside the
-container.  The "arvados", "arvado-dev" and "sso" code directories along data
-directories "postresql", "var", "passenger" and "gems" are bind mounted from
-the host file system for easy access and persistence across container rebuilds.
-Services are bound to the Docker container's network IP address and can only be
-access on the local host.
+container.  The "arvados", "arvado-dev" and "sso-devise-omniauth-provider" code
+directories along data directories "postgres", "var", "passenger" and "gems"
+are bind mounted from the host file system for easy access and persistence
+across container rebuilds.  Services are bound to the Docker container's
+network IP address and can only be accessed on the local host.
 
 ### localdemo
 Demo configuration.  Boots a complete Arvados environment inside the container.
 Unlike the development configuration, code directories are included in the demo
 image, and data directories are stored in a separate data volume container.
 Services are bound to the Docker container's network IP address and can only be
-access on the local host.
+accessed on the local host.
 
 ### test
 Run the test suite.
@@ -50,13 +50,16 @@ Run the test suite.
 ### publicdev
 Publicly accessible development configuration.  Similar to 'dev' except that
 service ports are published to the host's IP address and can accessed by anyone
-who can connect to the host system.
+who can connect to the host system.  WARNING! The public arvbox configuration
+is NOT SECURE and must not be placed on a public IP address or used for
+production work.
 
 ### publicdemo
 Publicly accessible development configuration.  Similar to 'localdemo' except
 that service ports are published to the host's IP address and can accessed by
-anyone who can connect to the host system.
-
+anyone who can connect to the host system.  WARNING! The public arvbox configuration
+is NOT SECURE and must not be placed on a public IP address or used for
+production work.
 
 ## Environment variables
 
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
index 1085abc..d40cd5c 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -5,6 +5,7 @@ RUN cd /usr/src && \
     git clone https://github.com/curoverse/sso-devise-omniauth-provider.git sso
 
 RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
+RUN rm -rf /var/lib/postgresql && mkdir -p /var/lib/postgresql
 
 RUN sudo -u arvbox /usr/local/lib/arvbox/service/sso/run-service --only-deps
 RUN sudo -u arvbox /usr/local/lib/arvbox/service/api/run-service --only-deps

commit 7d7a71dc92338c5a8194f10dbd42f7cf0a34eea6
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 16:43:50 2016 -0500

    8080: Tweak formatting

diff --git a/arvbox/README.md b/arvbox/README.md
index c50a547..3537119 100644
--- a/arvbox/README.md
+++ b/arvbox/README.md
@@ -26,10 +26,10 @@ log       <service> tail log of specified service
 svrestart <service> restart specified service inside arvbox
 clone <from> <to>   clone an arvbox
 
-## Configs
 
-* dev
+## Configs
 
+### dev
 Development configuration.  Boots a complete Arvados environment inside the
 container.  The "arvados", "arvado-dev" and "sso" code directories along data
 directories "postresql", "var", "passenger" and "gems" are bind mounted from
@@ -37,60 +37,58 @@ the host file system for easy access and persistence across container rebuilds.
 Services are bound to the Docker container's network IP address and can only be
 access on the local host.
 
-* localdemo
-
+### localdemo
 Demo configuration.  Boots a complete Arvados environment inside the container.
 Unlike the development configuration, code directories are included in the demo
 image, and data directories are stored in a separate data volume container.
 Services are bound to the Docker container's network IP address and can only be
 access on the local host.
 
-* test
-
+### test
 Run the test suite.
 
-* publicdev
-
+### publicdev
 Publicly accessible development configuration.  Similar to 'dev' except that
 service ports are published to the host's IP address and can accessed by anyone
 who can connect to the host system.
 
-* publicdemo
-
+### publicdemo
 Publicly accessible development configuration.  Similar to 'localdemo' except
 that service ports are published to the host's IP address and can accessed by
 anyone who can connect to the host system.
 
+
 ## Environment variables
 
-ARVBOX_DOCKER
+### ARVBOX_DOCKER
 The location of Dockerfile.base and associated files used by "arvbox build".
 default: result of $(readlink -f $(dirname $0)/../lib/arvbox/docker)
 
-ARVBOX_CONTAINER
+### ARVBOX_CONTAINER
 The name of the Docker container to manipulate.
 default: arvbox
 
-ARVBOX_BASE
+### ARVBOX_BASE
 The base directory to store persistent data for arvbox containers.
 default: $HOME/.arvbox
 
-ARVBOX_DATA
+### ARVBOX_DATA
 The base directory to store persistent data for the current container.
 default: $ARVBOX_BASE/$ARVBOX_CONTAINER
 
-ARVADOS_ROOT
+### ARVADOS_ROOT
 The root directory of the Arvados source tree
 default: $ARVBOX_DATA/arvados
 
-ARVADOS_DEV_ROOT
+### ARVADOS_DEV_ROOT
 The root directory of the Arvados-dev source tree
 default: $ARVBOX_DATA/arvados-dev
 
-SSO_ROOT
+### SSO_ROOT
 The root directory of the SSO source tree
 default: $ARVBOX_DATA/sso-devise-omniauth-provider
 
+
 ## Notes
 
 Services are designed to install and auto-configure on start or restart.  For

commit 2ea94dd246e2124720f810c6dad6a25d941ebd62
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 16:42:05 2016 -0500

    8080: Add more documentation about different configurations

diff --git a/arvbox/README b/arvbox/README.md
similarity index 57%
rename from arvbox/README
rename to arvbox/README.md
index d8179bc..c50a547 100644
--- a/arvbox/README
+++ b/arvbox/README.md
@@ -1,14 +1,12 @@
-Arvados-in-a-box
-
-Development and demonstration environment for Arvados.
+# Arvados-in-a-box
 
+Self-contained development, demonstration and testing environment for Arvados.
 
-Quick start:
+## Quick start
 
-$ ./bin/arvbox reboot dev
+$ ./bin/arvbox reboot localdemo
 
-
-Usage:
+## Usage
 
 Arvados-in-a-box
 
@@ -28,20 +26,42 @@ log       <service> tail log of specified service
 svrestart <service> restart specified service inside arvbox
 clone <from> <to>   clone an arvbox
 
+## Configs
 
-Notes:
+* dev
 
-Services are designed to install and auto-configure on start or restart.  For
-example, the service script for keepstore always compiles keepstore from source
-and registers the daemon with the API server.
+Development configuration.  Boots a complete Arvados environment inside the
+container.  The "arvados", "arvado-dev" and "sso" code directories along data
+directories "postresql", "var", "passenger" and "gems" are bind mounted from
+the host file system for easy access and persistence across container rebuilds.
+Services are bound to the Docker container's network IP address and can only be
+access on the local host.
 
-Services are run with process supervision, so a service which exits will be
-restarted.  Dependencies between services are handled by repeatedly trying and
-failing the service script until dependencies are fulfilled (by other service
-scripts) enabling the service script to complete.
+* localdemo
+
+Demo configuration.  Boots a complete Arvados environment inside the container.
+Unlike the development configuration, code directories are included in the demo
+image, and data directories are stored in a separate data volume container.
+Services are bound to the Docker container's network IP address and can only be
+access on the local host.
+
+* test
+
+Run the test suite.
 
+* publicdev
 
-Environment variables:
+Publicly accessible development configuration.  Similar to 'dev' except that
+service ports are published to the host's IP address and can accessed by anyone
+who can connect to the host system.
+
+* publicdemo
+
+Publicly accessible development configuration.  Similar to 'localdemo' except
+that service ports are published to the host's IP address and can accessed by
+anyone who can connect to the host system.
+
+## Environment variables
 
 ARVBOX_DOCKER
 The location of Dockerfile.base and associated files used by "arvbox build".
@@ -70,3 +90,14 @@ default: $ARVBOX_DATA/arvados-dev
 SSO_ROOT
 The root directory of the SSO source tree
 default: $ARVBOX_DATA/sso-devise-omniauth-provider
+
+## Notes
+
+Services are designed to install and auto-configure on start or restart.  For
+example, the service script for keepstore always compiles keepstore from source
+and registers the daemon with the API server.
+
+Services are run with process supervision, so a service which exits will be
+restarted.  Dependencies between services are handled by repeatedly trying and
+failing the service script until dependencies are fulfilled (by other service
+scripts) enabling the service script to complete.
diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 73c3803..3d58315 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -351,7 +351,7 @@ case "$subcmd" in
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|destroy|log|svrestart)"
+        echo "$(basename $0) (build|start|run|open|shell|ip|stop|reboot|reset|destroy|log|svrestart)"
         echo
         echo "build <config>      build arvbox Docker image"
         echo "start|run <config>  start $ARVBOX_CONTAINER container"

commit 80391687c4cdf1ab167e67f9a48d4c7354c3a4d8
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 18 14:37:10 2016 -0500

    8080: Add "public" configuration which is configured to advertise services with
    the host's IP addresses and uses --publish to expose the service ports on the
    host.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index ecbbfbf..73c3803 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -70,28 +70,71 @@ EOF
     fi
 }
 
+wait_for_arvbox() {
+    FF=/tmp/arvbox-fifo-$$
+    mkfifo $FF
+    docker logs -f $ARVBOX_CONTAINER > $FF &
+    LOGPID=$!
+    while read line ; do
+        echo $line
+        if echo $line | grep "Workbench is running at" >/dev/null ; then
+            kill $LOGPID
+        fi
+    done < $FF
+    rm $FF
+    echo
+    if test -n "$localip" ; then
+        echo "export ARVADOS_API_HOST=localip:8000"
+    else
+        echo "export ARVADOS_API_HOST=$(getip):8000"
+    fi
+}
+
 run() {
     if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
         echo "Container $ARVBOX_CONTAINER is already running, use stop, restart or reboot"
         exit 0
     fi
 
-    if test "$1" = demo ; then
+    if echo "$1" | grep '^public' ; then
+        defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
+        localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+        iptemp=$(tempfile)
+        echo $localip > $iptemp
+        PUBLIC="--volume=$iptemp:/var/run/localip_override
+              --publish=80:80
+              --publish=8000:8000
+              --publish=8900:8900
+              --publish=9001:9001
+              --publish=9002:9002
+              --publish=25100:25100
+              --publish=25107:25107
+              --publish=25108:25108
+              --publish=8001:8001"
+    else
+        PUBLIC=""
+    fi
+
+    if echo "$1" | grep 'demo$' ; then
         if test -d "$ARVBOX_DATA" ; then
             echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
-            echo "Set ARVBOX_CONTAINER to set the demo container name"
+            echo "Set ARVBOX_CONTAINER to set a different name for your demo container"
             exit 1
         fi
 
         if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
             docker create -v /var/lib/postgres -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
         fi
+
         docker run \
+               --detach \
                --name=$ARVBOX_CONTAINER \
                --privileged \
                --volumes-from $ARVBOX_CONTAINER-data \
+               $PUBLIC \
                arvados/arvbox-demo
         updateconf
+        wait_for_arvbox
     else
         mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS"
 
@@ -159,21 +202,10 @@ run() {
                    "--volume=$VAR_DATA:/var/lib/arvados:rw" \
                    "--volume=$PASSENGER:/var/lib/passenger:rw" \
                    "--volume=$GEMS:/var/lib/gems:rw" \
+                   $PUBLIC \
                    arvados/arvbox-dev
             updateconf
-            FF=/tmp/arvbox-fifo-$$
-            mkfifo $FF
-            docker logs -f $ARVBOX_CONTAINER > $FF &
-            LOGPID=$!
-            while read line ; do
-                echo $line
-                if echo $line | grep "Workbench is running at" >/dev/null ; then
-                    kill $LOGPID
-                fi
-            done < $FF
-            rm $FF
-            echo
-            echo "export ARVADOS_API_HOST=$(getip):8000"
+            wait_for_arvbox
             echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
         fi
     fi
@@ -183,6 +215,9 @@ stop() {
     if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker stop $ARVBOX_CONTAINER
     fi
+    if docker ps -a --filter "status=created" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker rm --volumes=true $ARVBOX_CONTAINER
+    fi
     if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker rm --volumes=true $ARVBOX_CONTAINER
     fi
@@ -194,18 +229,23 @@ build() {
         exit 1
     fi
     docker build -t arvados/arvbox-base -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
-    if test "$1" = dev -o "$1" = test ; then
-        docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
-    else
+    if test "$1" = localdemo -o "$1" = publicdemo ; then
         docker build -t arvados/arvbox-demo -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
+    else
+        docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
     fi
 }
 
 check() {
-    if test -z "$1" ; then
-        echo "Argument to $subcmd must be one of dev, test, demo"
-        exit 1
-    fi
+    case "$1" in
+        localdemo|publicdemo|dev|publicdev|test)
+            true
+            ;;
+        *)
+            echo "Argument to $subcmd must be one of localdemo, publicdemo, dev, publicdev, test"
+            exit 1
+        ;;
+    esac
 }
 
 subcmd="$1"
diff --git a/arvbox/lib/arvbox/docker/common.sh b/arvbox/lib/arvbox/docker/common.sh
index 69869a7..4c2de47 100644
--- a/arvbox/lib/arvbox/docker/common.sh
+++ b/arvbox/lib/arvbox/docker/common.sh
@@ -1,5 +1,11 @@
 
-localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
+if test -s /var/run/localip_override ; then
+    localip=$(cat /var/run/localip_override)
+else
+    defaultdev=$(/sbin/ip route|awk '/default/ { print $5 }')
+    localip=$(ip addr show $defaultdev | grep 'inet ' | sed 's/ *inet \(.*\)\/.*/\1/')
+fi
+
 export GEM_HOME=/var/lib/gems
 export GEM_PATH=/var/lib/gems
 

commit 50aded0e2ff25457b78c2a3b5967b42c6ec2c75f
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Fri Jan 15 16:38:30 2016 -0500

    8080: Fix typo.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 3e765db..ecbbfbf 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -5,7 +5,7 @@ set -e
 if ! test -d /sys/fs/cgroup ; then
      echo "Arvbox requires cgroups to be mounted at /sys/fs/cgroup in order to use"
      echo "Docker-in-Docker.  Older operating systems that put cgroups in other"
-     echo "places (such as /cgroup) are not supported.""
+     echo "places (such as /cgroup) are not supported."
      exit 1
 fi
 

commit e1d3dce509cc5e139ca6de2c3adc96c9f8e23cf6
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Fri Jan 15 09:18:21 2016 -0500

    8080: Add unsupported error if /sys/fs/cgroups not found (e.g. Centos6).  Set
    default_replication to 1.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 691b79d..3e765db 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -2,7 +2,14 @@
 
 set -e
 
-if ! which docker >/dev/null 2>/dev/null ; then 
+if ! test -d /sys/fs/cgroup ; then
+     echo "Arvbox requires cgroups to be mounted at /sys/fs/cgroup in order to use"
+     echo "Docker-in-Docker.  Older operating systems that put cgroups in other"
+     echo "places (such as /cgroup) are not supported.""
+     exit 1
+fi
+
+if ! which docker >/dev/null 2>/dev/null ; then
   echo "Arvbox requires Docker.  To install, run the following command as root:"
   echo "curl -sSL https://get.docker.com/ | sh"
   exit 1
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index aa41f6b..31f90d1 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -61,6 +61,7 @@ development:
   auto_setup_new_users: true
   auto_setup_new_users_with_vm_uuid: $vm_uuid
   auto_setup_new_users_with_repository: true
+  default_collection_replication: 1
 EOF
 
 if ! test -f /var/lib/arvados/api_database_pw ; then
diff --git a/arvbox/lib/arvbox/docker/service/docker/run b/arvbox/lib/arvbox/docker/service/docker/run
index 67bd58f..1ecdc16 100755
--- a/arvbox/lib/arvbox/docker/service/docker/run
+++ b/arvbox/lib/arvbox/docker/service/docker/run
@@ -10,17 +10,14 @@ dmsetup mknodes
 : {LOG:=stdio}
 
 # First, make sure that cgroups are mounted correctly.
-for CGROUP in /sys/fs/cgroup /cgroup ; do
-    [ -d $CGROUP ] || mkdir $CGROUP
-
-    if mountpoint -q $CGROUP ; then
-        break
-    else
-	if mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP ; then
-            break
-        fi
-    fi
-done
+CGROUP=/sys/fs/cgroup
+[ -d $CGROUP ] || mkdir $CGROUP
+
+if mountpoint -q $CGROUP ; then
+    break
+else
+    mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP
+fi
 
 if ! mountpoint -q $CGROUP ; then
     echo "Could not find or mount cgroups. Tried /sys/fs/cgroup and /cgroup.  Did you use --privileged?"

commit 53b5baa5eda1499e4b5c15f71adefc3b85a8c71c
Author: peter <peter at peter-centos6-test.ny3nnviyjnku5mvo2sgdg2iv2h.bx.internal.cloudapp.net>
Date:   Thu Jan 14 17:03:52 2016 +0000

    8080: Check for Docker.  Suppress some messages generated by `which`.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index d1d1bb2..691b79d 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -2,8 +2,14 @@
 
 set -e
 
+if ! which docker >/dev/null 2>/dev/null ; then 
+  echo "Arvbox requires Docker.  To install, run the following command as root:"
+  echo "curl -sSL https://get.docker.com/ | sh"
+  exit 1
+fi
+
 if test -z "$ARVBOX_DOCKER" ; then
-    if which greadlink >/dev/null ; then
+    if which greadlink >/dev/null 2>/dev/null ; then
         ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker)
     else
         ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker)

commit d42510f7df41351c540f7caa991720e7293ae669
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Jan 13 13:05:26 2016 -0500

    8080: Automatically set up new users with VM and git repo.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index f6aa0bc..d1d1bb2 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -160,7 +160,7 @@ run() {
             done < $FF
             rm $FF
             echo
-            echo "export ARVADOS_API_HOST="
+            echo "export ARVADOS_API_HOST=$(getip):8000"
             echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
         fi
     fi
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 508c411..aa41f6b 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -37,6 +37,13 @@ test -s /var/lib/arvados/self-signed.key
 
 sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
 
+if test -s /var/lib/arvados/vm-uuid ; then
+    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+else
+    vm_uuid=$uuid_prefix-2x53u-$(ruby -e 'puts rand(2**400).to_s(36)[0,15]')
+    echo $vm_uuid > /var/lib/arvados/vm-uuid
+fi
+
 cat >config/application.yml <<EOF
 development:
   uuid_prefix: $uuid_prefix
@@ -49,7 +56,11 @@ development:
   workbench_address: "http://$localip/"
   git_repo_ssh_base: "git@$localip:"
   git_repo_https_base: "http://$localip:${services[arv-git-httpd]}/"
+  new_users_are_active: true
   auto_admin_first_user: true
+  auto_setup_new_users: true
+  auto_setup_new_users_with_vm_uuid: $vm_uuid
+  auto_setup_new_users_with_repository: true
 EOF
 
 if ! test -f /var/lib/arvados/api_database_pw ; then
diff --git a/arvbox/lib/arvbox/docker/service/docker/run b/arvbox/lib/arvbox/docker/service/docker/run
index 64fcc75..67bd58f 100755
--- a/arvbox/lib/arvbox/docker/service/docker/run
+++ b/arvbox/lib/arvbox/docker/service/docker/run
@@ -22,6 +22,11 @@ for CGROUP in /sys/fs/cgroup /cgroup ; do
     fi
 done
 
+if ! mountpoint -q $CGROUP ; then
+    echo "Could not find or mount cgroups. Tried /sys/fs/cgroup and /cgroup.  Did you use --privileged?"
+    exit 1
+fi
+
 if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
 then
     mount -t securityfs none /sys/kernel/security || {
diff --git a/arvbox/lib/arvbox/docker/service/gitolite/run-service b/arvbox/lib/arvbox/docker/service/gitolite/run-service
index 2e5f1e9..8e6cb0e 100755
--- a/arvbox/lib/arvbox/docker/service/gitolite/run-service
+++ b/arvbox/lib/arvbox/docker/service/gitolite/run-service
@@ -33,8 +33,11 @@ fi
 if ! test -f /var/lib/arvados/gitolite-setup ; then
     cd ~git
 
+    # Do a no-op login to populate known_hosts
+    # with the hostkey, so it won't try to ask
+    # about it later.
     cp .ssh/id_rsa.pub .ssh/authorized_keys
-    ssh -o stricthostkeychecking=no git at localhost
+    ssh -o stricthostkeychecking=no git at localhost true
     rm .ssh/authorized_keys
 
     cp -r /usr/local/lib/arvbox/gitolite.rc .
@@ -53,6 +56,10 @@ if ! test -f /var/lib/arvados/gitolite-setup ; then
 
     touch /var/lib/arvados/gitolite-setup
 else
+    # Do a no-op login to populate known_hosts
+    # with the hostkey, so it won't try to ask
+    # about it later.  Don't run anything,
+    # get the default gitolite behavior.
     ssh -o stricthostkeychecking=no git at localhost
 fi
 
diff --git a/arvbox/lib/arvbox/docker/service/ready/run-service b/arvbox/lib/arvbox/docker/service/ready/run-service
index 52c0adc..c027360 100755
--- a/arvbox/lib/arvbox/docker/service/ready/run-service
+++ b/arvbox/lib/arvbox/docker/service/ready/run-service
@@ -44,6 +44,22 @@ if ! (ps x | grep -v grep | grep "crunch-dispatch") > /dev/null ; then
     waiting="$waiting crunch-dispatch"
 fi
 
+export ARVADOS_API_HOST=$localip:${services[api]}
+export ARVADOS_API_HOST_INSECURE=1
+
+vm_ok=0
+if test -s /var/lib/arvados/vm-uuid -a -s /var/lib/arvados/superuser_token; then
+    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+    export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+    if (which arv && arv virtual_machine get --uuid $vm_uuid) >/dev/null 2>/dev/null ; then
+        vm_ok=1
+    fi
+fi
+
+if test $vm_ok = 0 ; then
+    waiting="$waiting vm"
+fi
+
 if ! [[ -z "$waiting" ]] ; then
     if ps x | grep -v grep | grep "bundle install" > /dev/null; then
         gemcount=$(ls /var/lib/gems/ruby/2.1.0/gems 2>/dev/null | wc -l)
diff --git a/arvbox/lib/arvbox/docker/service/vm/run-service b/arvbox/lib/arvbox/docker/service/vm/run-service
index a845e44..5bb6825 100755
--- a/arvbox/lib/arvbox/docker/service/vm/run-service
+++ b/arvbox/lib/arvbox/docker/service/vm/run-service
@@ -18,25 +18,23 @@ set -u
 export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
+export ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
 
 set +e
 read -rd $'\000' vm <<EOF
 {
+ "uuid": "$ARVADOS_VIRTUAL_MACHINE_UUID",
  "hostname":"$localip"
 }
 EOF
 set -e
 
-if test -s /var/lib/arvados/vm-uuid ; then
-    ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+if arv virtual_machine get --uuid $ARVADOS_VIRTUAL_MACHINE_UUID ; then
     arv virtual_machine update --uuid $ARVADOS_VIRTUAL_MACHINE_UUID --virtual-machine "$vm"
 else
-    ARVADOS_VIRTUAL_MACHINE_UUID=$(arv --format=uuid virtual_machine create --virtual-machine "$vm")
-    echo $ARVADOS_VIRTUAL_MACHINE_UUID > /var/lib/arvados/vm-uuid
+    arv virtual_machine create --virtual-machine "$vm"
 fi
 
-export ARVADOS_VIRTUAL_MACHINE_UUID
-
 while true ; do
       bundle exec arvados-login-sync
       sleep 120

commit 9e77eee58a6b6d4470b405dcf9d285c452e278eb
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Jan 13 10:20:14 2016 -0500

    8080: Use run-tests --temp.  Attempt to add support for centos6 for docker-in-docker.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 83a09b5..f6aa0bc 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -96,6 +96,8 @@ run() {
                 git clone https://github.com/curoverse/arvados-dev.git "$ARVADOS_DEV_ROOT"
             fi
 
+            mkdir -p $VAR_DATA/test
+
             docker run \
                    --detach \
                    --name=$ARVBOX_CONTAINER \
@@ -129,12 +131,8 @@ run() {
                    $ARVBOX_CONTAINER \
                    /usr/local/lib/arvbox/runsu.sh \
                    /usr/src/arvados-dev/jenkins/run-tests.sh \
-                   --leave-temp \
+                   --temp /var/lib/arvados/test \
                    WORKSPACE=/usr/src/arvados \
-                   VENVDIR=/var/lib/arvados/tests-venv \
-                   VENV3DIR=/var/lib/arvados/tests-venv3 \
-                   GOPATH=/var/lib/arvados/tests-gostuff \
-                   GEMHOME=/var/lib/gems/ruby/2.1.0 \
                    GEM_HOME=/var/lib/gems \
                    "$@"
         else
@@ -273,7 +271,7 @@ case "$subcmd" in
     log|svrestart)
         if test -n "$1" ; then
             if test "$subcmd" = log ; then
-                docker exec -ti $ARVBOX_CONTAINER tail -n100 "/etc/service/$1/log/main/current"
+                docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM less --follow-name +GF "/etc/service/$1/log/main/current"
             fi
             if test "$subcmd" = svrestart ; then
                 docker exec -ti $ARVBOX_CONTAINER sv restart "$1"
diff --git a/arvbox/lib/arvbox/docker/service/docker/run b/arvbox/lib/arvbox/docker/service/docker/run
index 83537d3..64fcc75 100755
--- a/arvbox/lib/arvbox/docker/service/docker/run
+++ b/arvbox/lib/arvbox/docker/service/docker/run
@@ -4,23 +4,23 @@
 
 exec 2>&1
 
-#!/bin/bash
-
 # Ensure that all nodes in /dev/mapper correspond to mapped devices currently loaded by the device-mapper kernel driver
 dmsetup mknodes
 
-# First, make sure that cgroups are mounted correctly.
-CGROUP=/sys/fs/cgroup
 : {LOG:=stdio}
 
-[ -d $CGROUP ] ||
-	mkdir $CGROUP
-
-mountpoint -q $CGROUP ||
-	mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
-		echo "Could not make a tmpfs mount. Did you use --privileged?"
-		exit 1
-	}
+# First, make sure that cgroups are mounted correctly.
+for CGROUP in /sys/fs/cgroup /cgroup ; do
+    [ -d $CGROUP ] || mkdir $CGROUP
+
+    if mountpoint -q $CGROUP ; then
+        break
+    else
+	if mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP ; then
+            break
+        fi
+    fi
+done
 
 if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
 then
diff --git a/jenkins/run-tests.sh b/jenkins/run-tests.sh
index e50d485..a7b5fda 100755
--- a/jenkins/run-tests.sh
+++ b/jenkins/run-tests.sh
@@ -589,6 +589,7 @@ bundle_install_trylocal() {
             echo "(Running bundle install again, without --local.)"
             bundle install --no-deployment
         fi
+        bundle package --all
     )
 }
 

commit 92546f434119eba728ef2e1cb4591d36324fa703
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 12 16:02:12 2016 -0500

    8080: Add waitforpostgres.sh script.  Expand README.

diff --git a/arvbox/README b/arvbox/README
index bfc4ab4..d8179bc 100644
--- a/arvbox/README
+++ b/arvbox/README
@@ -2,9 +2,32 @@ Arvados-in-a-box
 
 Development and demonstration environment for Arvados.
 
+
+Quick start:
+
+$ ./bin/arvbox reboot dev
+
+
 Usage:
 
-$ ./bin/arvbox reboot
+Arvados-in-a-box
+
+arvbox (build|start|run|open|shell|ip|stop|reboot|reset|destroy|log|svrestart)
+
+build <config>      build arvbox Docker image
+start|run <config>  start arvbox container
+open       open arvbox workbench in a web browser
+shell      enter arvbox shell
+ip         print arvbox ip address
+stop       stop arvbox container
+restart <config>  stop, then run again
+reboot  <config>  stop, build arvbox Docker image, run
+reset      delete arvbox arvados data (be careful!)
+destroy    delete all arvbox code and data (be careful!)
+log       <service> tail log of specified service
+svrestart <service> restart specified service inside arvbox
+clone <from> <to>   clone an arvbox
+
 
 Notes:
 
@@ -16,3 +39,34 @@ Services are run with process supervision, so a service which exits will be
 restarted.  Dependencies between services are handled by repeatedly trying and
 failing the service script until dependencies are fulfilled (by other service
 scripts) enabling the service script to complete.
+
+
+Environment variables:
+
+ARVBOX_DOCKER
+The location of Dockerfile.base and associated files used by "arvbox build".
+default: result of $(readlink -f $(dirname $0)/../lib/arvbox/docker)
+
+ARVBOX_CONTAINER
+The name of the Docker container to manipulate.
+default: arvbox
+
+ARVBOX_BASE
+The base directory to store persistent data for arvbox containers.
+default: $HOME/.arvbox
+
+ARVBOX_DATA
+The base directory to store persistent data for the current container.
+default: $ARVBOX_BASE/$ARVBOX_CONTAINER
+
+ARVADOS_ROOT
+The root directory of the Arvados source tree
+default: $ARVBOX_DATA/arvados
+
+ARVADOS_DEV_ROOT
+The root directory of the Arvados-dev source tree
+default: $ARVBOX_DATA/arvados-dev
+
+SSO_ROOT
+The root directory of the SSO source tree
+default: $ARVBOX_DATA/sso-devise-omniauth-provider
diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 2e23a98..83a09b5 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -39,6 +39,24 @@ VAR_DATA="$ARVBOX_DATA/var"
 PASSENGER="$ARVBOX_DATA/passenger"
 GEMS="$ARVBOX_DATA/gems"
 
+getip() {
+    docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-
+}
+
+updateconf() {
+    if test -f ~/.config/arvados/$ARVBOX_CONTAINER.conf ; then
+        sed "s/ARVADOS_API_HOST=.*/ARVADOS_API_HOST=$(getip):8000/" <$HOME/.config/arvados/$ARVBOX_CONTAINER.conf >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf.tmp
+        mv ~/.config/arvados/$ARVBOX_CONTAINER.conf.tmp ~/.config/arvados/$ARVBOX_CONTAINER.conf
+    else
+        mkdir -p $HOME/.config/arvados
+        cat >$HOME/.config/arvados/$ARVBOX_CONTAINER.conf <<EOF
+ARVADOS_API_HOST=$(getip):8000
+ARVADOS_API_TOKEN=
+ARVADOS_API_HOST_INSECURE=true
+EOF
+    fi
+}
+
 run() {
     if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
         echo "Container $ARVBOX_CONTAINER is already running, use stop, restart or reboot"
@@ -60,6 +78,7 @@ run() {
                --privileged \
                --volumes-from $ARVBOX_CONTAINER-data \
                arvados/arvbox-demo
+        updateconf
     else
         mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS"
 
@@ -91,12 +110,10 @@ run() {
                    arvados/arvbox-dev \
                    /usr/local/bin/runsvinit -svdir=/etc/test-service
 
-            while ! docker exec -ti \
+            docker exec -ti \
                     $ARVBOX_CONTAINER \
                     /usr/local/lib/arvbox/runsu.sh \
-                    psql -c'\du' ; do
-                sleep 1
-            done
+                    /usr/local/lib/arvbox/waitforpostgres.sh
 
             docker exec -ti \
                    $ARVBOX_CONTAINER \
@@ -132,6 +149,7 @@ run() {
                    "--volume=$PASSENGER:/var/lib/passenger:rw" \
                    "--volume=$GEMS:/var/lib/gems:rw" \
                    arvados/arvbox-dev
+            updateconf
             FF=/tmp/arvbox-fifo-$$
             mkfifo $FF
             docker logs -f $ARVBOX_CONTAINER > $FF &
@@ -144,6 +162,7 @@ run() {
             done < $FF
             rm $FF
             echo
+            echo "export ARVADOS_API_HOST="
             echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
         fi
     fi
@@ -215,11 +234,10 @@ case "$subcmd" in
         ;;
 
     ip|open)
-        IP=$(docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-)
         if test "$subcmd" = 'ip' ; then
-            echo $IP
+            echo $(getip)
         else
-            xdg-open http://$IP
+            xdg-open http://$(getip)
         fi
         ;;
 
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.base b/arvbox/lib/arvbox/docker/Dockerfile.base
index e9b90ac..23fca74 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -21,7 +21,8 @@ RUN cd /root && \
 ADD fuse.conf /etc/
 
 ADD crunch-setup.sh gitolite.rc \
-    keep-setup.sh common.sh createusers.sh logger runsu.sh \
+    keep-setup.sh common.sh createusers.sh \
+    logger runsu.sh waitforpostgres.sh \
     /usr/local/lib/arvbox/
 ADD service/ /usr/local/lib/arvbox/service
 RUN rmdir /etc/service && ln -sf /usr/local/lib/arvbox/service /etc
diff --git a/arvbox/lib/arvbox/docker/waitforpostgres.sh b/arvbox/lib/arvbox/docker/waitforpostgres.sh
new file mode 100755
index 0000000..a07fa8c
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/waitforpostgres.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+while ! psql -c\\du >/dev/null 2>/dev/null ; do
+    sleep 1
+done

commit 3f61293fb2c19f658e13c9e6b09b5d1c104acf48
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 12 13:13:31 2016 -0500

    8080: Add note about provenance of gitolite.rc

diff --git a/arvbox/lib/arvbox/docker/gitolite.rc b/arvbox/lib/arvbox/docker/gitolite.rc
index b735870..03c4b29 100644
--- a/arvbox/lib/arvbox/docker/gitolite.rc
+++ b/arvbox/lib/arvbox/docker/gitolite.rc
@@ -1,3 +1,7 @@
+# This is based on the default Gitolite configuration file with the following
+# changes applied as described here:
+# http://doc.arvados.org/install/install-arv-git-httpd.html
+
 # configuration variables for gitolite
 
 # This file is in perl syntax.  But you do NOT need to know perl to edit it --

commit 9d5fa0c0b0c63f4b370ca0bea8fdce1e43adab30
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 12 10:07:00 2016 -0500

    8080: Use runsvinit for tests too.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 8b3aa0b..2e23a98 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -89,7 +89,7 @@ run() {
                    "--volume=$PASSENGER:/var/lib/passenger:rw" \
                    "--volume=$GEMS:/var/lib/gems:rw" \
                    arvados/arvbox-dev \
-                   /usr/local/lib/arvbox/service/postgres/run
+                   /usr/local/bin/runsvinit -svdir=/etc/test-service
 
             while ! docker exec -ti \
                     $ARVBOX_CONTAINER \
@@ -282,19 +282,18 @@ case "$subcmd" in
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|destroy|run-tests|log|svrestart)"
+        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|destroy|log|svrestart)"
         echo
-        echo "build      build arvbox Docker image"
-        echo "start|run  start $ARVBOX_CONTAINER container "
+        echo "build <config>      build arvbox Docker image"
+        echo "start|run <config>  start $ARVBOX_CONTAINER container"
         echo "open       open arvbox workbench in a web browser"
         echo "shell      enter arvbox shell"
         echo "ip         print arvbox ip address"
         echo "stop       stop arvbox container"
-        echo "restart    stop, then run again"
-        echo "reboot     stop, build arvbox Docker image, run"
+        echo "restart <config>  stop, then run again"
+        echo "reboot  <config>  stop, build arvbox Docker image, run"
         echo "reset      delete arvbox arvados data (be careful!)"
         echo "destroy    delete all arvbox code and data (be careful!)"
-        echo "run-tests  run run-tests.sh inside $ARVBOX_CONTAINER container"
         echo "log       <service> tail log of specified service"
         echo "svrestart <service> restart specified service inside arvbox"
         echo "clone <from> <to>   clone an arvbox"
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.dev b/arvbox/lib/arvbox/docker/Dockerfile.dev
index 9883d37..408e2de 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.dev
+++ b/arvbox/lib/arvbox/docker/Dockerfile.dev
@@ -9,3 +9,5 @@ RUN set -e && \
  curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
  tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
  ln -s ../$PJS/bin/phantomjs /usr/local/bin/
+
+RUN mkdir /etc/test-service && ln -sf /usr/local/lib/arvbox/service/postgres /etc/test-service

commit 7ee4a20ed997c534abe00213c511c1ebd5d134b5
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 11 17:31:46 2016 -0500

    8080: Rails projects use explicit 'development' section instead of 'common' to
    avoid interfering with test configuration.

diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index ceccb72..508c411 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -38,23 +38,18 @@ test -s /var/lib/arvados/self-signed.key
 sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
 
 cat >config/application.yml <<EOF
-common:
+development:
+  uuid_prefix: $uuid_prefix
   secret_token: $secret_token
+  blob_signing_key: $blob_signing_key
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
   sso_provider_url: "https://$localip:${services[sso]}"
-  workbench_address: "http://$localip/"
   sso_insecure: true
-development:
-  uuid_prefix: $uuid_prefix
-  auto_admin_first_user: true
-  blob_signing_key: $blob_signing_key
+  workbench_address: "http://$localip/"
   git_repo_ssh_base: "git@$localip:"
   git_repo_https_base: "http://$localip:${services[arv-git-httpd]}/"
-test:
-  uuid_prefix: zzzzz
-  git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"
-  git_repo_https_base: "http://git.zzzzz.arvadosapi.com/"
+  auto_admin_first_user: true
 EOF
 
 if ! test -f /var/lib/arvados/api_database_pw ; then
@@ -67,24 +62,7 @@ if ! (psql -c "\du" | grep "^ arvados ") >/dev/null ; then
     psql -c "ALTER USER arvados CREATEDB;"
 fi
 
-cat >config/database.yml <<EOF
-development:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_development
-  username: arvados
-  password: $database_pw
-  host: localhost
-  template: template0
-test:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_test
-  username: arvados
-  password: $database_pw
-  host: localhost
-  template: template0
-EOF
+sed "s/password:.*/password: $database_pw/" <config/database.yml.example >config/database.yml
 
 if ! test -f /var/lib/arvados/api_database_setup ; then
    bundle exec rake db:setup
diff --git a/arvbox/lib/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
index d1c566a..8a96d47 100755
--- a/arvbox/lib/arvbox/docker/service/sso/run-service
+++ b/arvbox/lib/arvbox/docker/service/sso/run-service
@@ -32,7 +32,7 @@ if ! test -s /var/lib/arvados/self-signed.key ; then
 fi
 
 cat >config/application.yml <<EOF
-common:
+development:
   uuid_prefix: $uuid_prefix
   secret_token: $secret_token
   default_link_url: "http://$localip"
@@ -49,24 +49,7 @@ if ! (psql -c "\du" | grep "^ arvados_sso ") >/dev/null ; then
     psql -c "ALTER USER arvados_sso CREATEDB;"
 fi
 
-cat >config/database.yml <<EOF
-development:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_sso_development
-  username: arvados_sso
-  password: $database_pw
-  host: localhost
-  template: template0
-test:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_sso_test
-  username: arvados_sso
-  password: $database_pw
-  host: localhost
-  template: template0
-EOF
+sed "s/password:.*/password: $database_pw/" <config/database.yml.example >config/database.yml
 
 if ! test -f /var/lib/arvados/sso_database_setup ; then
    bundle exec rake db:setup
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run-service b/arvbox/lib/arvbox/docker/service/workbench/run-service
index a5f7195..7f0542f 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run-service
+++ b/arvbox/lib/arvbox/docker/service/workbench/run-service
@@ -27,7 +27,7 @@ if ! test -s self-signed.key ; then
 fi
 
 cat >config/application.yml <<EOF
-common:
+development:
   secret_token: $secret_token
   arvados_login_base: https://$localip:${services[api]}/login
   arvados_v1_base: https://$localip:${services[api]}/arvados/v1

commit a0d297b64c24caff3a85167fb9c179c2ae605098
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 11 16:42:08 2016 -0500

    Remove test image, use the same image for dev and test.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 0d34783..8b3aa0b 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -14,8 +14,12 @@ if test -z "$ARVBOX_CONTAINER" ; then
     ARVBOX_CONTAINER=arvbox
 fi
 
+if test -z "$ARVBOX_BASE" ; then
+    ARVBOX_BASE="$HOME/.arvbox"
+fi
+
 if test -z "$ARVBOX_DATA" ; then
-    ARVBOX_DATA="$HOME/.arvbox/$ARVBOX_CONTAINER"
+    ARVBOX_DATA="$ARVBOX_BASE/$ARVBOX_CONTAINER"
 fi
 
 if test -z "$ARVADOS_ROOT" ; then
@@ -84,28 +88,29 @@ run() {
                    "--volume=$VAR_DATA:/var/lib/arvados:rw" \
                    "--volume=$PASSENGER:/var/lib/passenger:rw" \
                    "--volume=$GEMS:/var/lib/gems:rw" \
-                   arvados/arvbox-test
+                   arvados/arvbox-dev \
+                   /usr/local/lib/arvbox/service/postgres/run
 
             while ! docker exec -ti \
                     $ARVBOX_CONTAINER \
-                    /etc/tests-service/runsu.sh \
+                    /usr/local/lib/arvbox/runsu.sh \
                     psql -c'\du' ; do
                 sleep 1
             done
 
             docker exec -ti \
                    $ARVBOX_CONTAINER \
-                   /etc/tests-service/runsu.sh \
-                   /etc/service/sso/run-service --only-setup
+                   /usr/local/lib/arvbox/runsu.sh \
+                   /usr/local/lib/arvbox/service/sso/run-service --only-setup
 
             docker exec -ti \
                    $ARVBOX_CONTAINER \
-                   /etc/tests-service/runsu.sh \
-                   /etc/service/api/run-service --only-setup
+                   /usr/local/lib/arvbox/runsu.sh \
+                   /usr/local/lib/arvbox/service/api/run-service --only-setup
 
             docker exec -ti \
                    $ARVBOX_CONTAINER \
-                   /etc/tests-service/runsu.sh \
+                   /usr/local/lib/arvbox/runsu.sh \
                    /usr/src/arvados-dev/jenkins/run-tests.sh \
                    --leave-temp \
                    WORKSPACE=/usr/src/arvados \
@@ -159,9 +164,10 @@ build() {
         exit 1
     fi
     docker build -t arvados/arvbox-base -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
-    docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
-    if test "$1" = demo -o "$1" = test ; then
-        docker build -t arvados/arvbox-$1 -f "$ARVBOX_DOCKER/Dockerfile.$1" "$ARVBOX_DOCKER"
+    if test "$1" = dev -o "$1" = test ; then
+        docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
+    else
+        docker build -t arvados/arvbox-demo -f "$ARVBOX_DOCKER/Dockerfile.demo" "$ARVBOX_DOCKER"
     fi
 }
 
@@ -173,7 +179,9 @@ check() {
 }
 
 subcmd="$1"
-shift
+if test -n "$subcmd" ; then
+    shift
+fi
 case "$subcmd" in
     build)
         check $@
@@ -195,7 +203,7 @@ case "$subcmd" in
 
     restart)
         check $@
-        stop $@
+        stop
         run $@
         ;;
 
@@ -247,10 +255,10 @@ case "$subcmd" in
     log|svrestart)
         if test -n "$1" ; then
             if test "$subcmd" = log ; then
-                docker exec -ti $ARVBOX_CONTAINER tail -n100 /etc/service/$1/log/main/current
+                docker exec -ti $ARVBOX_CONTAINER tail -n100 "/etc/service/$1/log/main/current"
             fi
             if test "$subcmd" = svrestart ; then
-                docker exec -ti $ARVBOX_CONTAINER sv restart $1
+                docker exec -ti $ARVBOX_CONTAINER sv restart "$1"
                 docker exec -ti $ARVBOX_CONTAINER sv restart ready
             fi
         else
@@ -262,11 +270,12 @@ case "$subcmd" in
 
     clone)
         if test -n "$2" ; then
-            cp -r $HOME/.arvbox/$1 $HOME/.arvbox/$2
+            cp -r "$ARVBOX_BASE/$1" "$ARVBOX_BASE/$2"
             echo "Created new arvbox $2"
             echo "export ARVBOX_CONTAINER=$2"
         else
             echo "clone <from> <to>   clone an arvbox"
+            echo "available arvboxes: $(ls $ARVBOX_BASE)"
         fi
         ;;
 
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.base b/arvbox/lib/arvbox/docker/Dockerfile.base
index e73ba92..e9b90ac 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -23,6 +23,8 @@ ADD fuse.conf /etc/
 ADD crunch-setup.sh gitolite.rc \
     keep-setup.sh common.sh createusers.sh logger runsu.sh \
     /usr/local/lib/arvbox/
+ADD service/ /usr/local/lib/arvbox/service
+RUN rmdir /etc/service && ln -sf /usr/local/lib/arvbox/service /etc
 
 # Start the supervisor.
 CMD ["/usr/local/bin/runsvinit"]
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
index 8a255f8..1085abc 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -1,4 +1,4 @@
-FROM arvados/arvbox-dev
+FROM arvados/arvbox-base
 
 RUN cd /usr/src && \
     git clone https://github.com/curoverse/arvados.git && \
@@ -6,9 +6,9 @@ RUN cd /usr/src && \
 
 RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
 
-RUN sudo -u arvbox /etc/service/sso/run-service --only-deps
-RUN sudo -u arvbox /etc/service/api/run-service --only-deps
-RUN sudo -u arvbox /etc/service/workbench/run-service --only-deps
-RUN sudo -u arvbox /etc/service/doc/run-service --only-deps
-RUN sudo -u arvbox /etc/service/vm/run-service --only-deps
-RUN sudo -u arvbox /etc/service/sdk/run-service
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/sso/run-service --only-deps
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/api/run-service --only-deps
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/workbench/run-service --only-deps
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/doc/run-service --only-deps
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/vm/run-service --only-deps
+RUN sudo -u arvbox /usr/local/lib/arvbox/service/sdk/run-service
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.dev b/arvbox/lib/arvbox/docker/Dockerfile.dev
index fc81bb6..9883d37 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.dev
+++ b/arvbox/lib/arvbox/docker/Dockerfile.dev
@@ -1,3 +1,11 @@
 FROM arvados/arvbox-base
 
-ADD service /etc/service
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
+    python-virtualenv python3-virtualenv linkchecker xvfb iceweasel
+
+RUN set -e && \
+ PJS=phantomjs-1.9.7-linux-x86_64 && \
+ curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
+ tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
+ ln -s ../$PJS/bin/phantomjs /usr/local/bin/
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.test b/arvbox/lib/arvbox/docker/Dockerfile.test
deleted file mode 100644
index c84dfb5..0000000
--- a/arvbox/lib/arvbox/docker/Dockerfile.test
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM arvados/arvbox-base
-
-RUN apt-get update && \
-    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
-    python-virtualenv python3-virtualenv linkchecker xvfb iceweasel
-
-RUN set -e && \
- PJS=phantomjs-1.9.7-linux-x86_64 && \
- curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
- tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
- ln -s ../$PJS/bin/phantomjs /usr/local/bin/
-
-ADD service/postgres /etc/service/
diff --git a/arvbox/lib/arvbox/docker/common.sh b/arvbox/lib/arvbox/docker/common.sh
index d508c5c..69869a7 100644
--- a/arvbox/lib/arvbox/docker/common.sh
+++ b/arvbox/lib/arvbox/docker/common.sh
@@ -17,7 +17,7 @@ services=(
   [doc]=8001
 )
 
-if test $(id arvbox -u) = 0 ; then
+if test "$(id arvbox -u 2>/dev/null)" = 0 ; then
     PGUSER=postgres
     PGGROUP=postgres
 else

commit f3b90d4c543dcfbcbd4ea8023cfec78a1e65690b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 11 15:26:34 2016 -0500

    8080: Better quoting.  Refactor git initialization.  Move runsh and logger
    scripts.  Rename some services.  Add -o pipefail.

diff --git a/arvbox/README b/arvbox/README
index f6b7267..bfc4ab4 100644
--- a/arvbox/README
+++ b/arvbox/README
@@ -4,7 +4,7 @@ Development and demonstration environment for Arvados.
 
 Usage:
 
-$ ./arvbox reboot
+$ ./bin/arvbox reboot
 
 Notes:
 
diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index af13ab9..0d34783 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -15,77 +15,117 @@ if test -z "$ARVBOX_CONTAINER" ; then
 fi
 
 if test -z "$ARVBOX_DATA" ; then
-    ARVBOX_DATA=$HOME/.arvbox/$ARVBOX_CONTAINER
+    ARVBOX_DATA="$HOME/.arvbox/$ARVBOX_CONTAINER"
 fi
 
 if test -z "$ARVADOS_ROOT" ; then
-    ARVADOS_ROOT=$ARVBOX_DATA/arvados
+    ARVADOS_ROOT="$ARVBOX_DATA/arvados"
 fi
 
 if test -z "$ARVADOS_DEV_ROOT" ; then
-    ARVADOS_DEV_ROOT=$ARVBOX_DATA/arvados-dev
+    ARVADOS_DEV_ROOT="$ARVBOX_DATA/arvados-dev"
 fi
 
 if test -z "$SSO_ROOT" ; then
-    SSO_ROOT=$ARVBOX_DATA/sso-devise-omniauth-provider
+    SSO_ROOT="$ARVBOX_DATA/sso-devise-omniauth-provider"
 fi
 
-PG_DATA=$ARVBOX_DATA/postgres
-VAR_DATA=$ARVBOX_DATA/var
-PASSENGER=$ARVBOX_DATA/passenger
-GEMS=$ARVBOX_DATA/gems
+PG_DATA="$ARVBOX_DATA/postgres"
+VAR_DATA="$ARVBOX_DATA/var"
+PASSENGER="$ARVBOX_DATA/passenger"
+GEMS="$ARVBOX_DATA/gems"
 
 run() {
+    if docker ps -a | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        echo "Container $ARVBOX_CONTAINER is already running, use stop, restart or reboot"
+        exit 0
+    fi
+
     if test "$1" = demo ; then
-        if test -d $ARVBOX_DATA ; then
+        if test -d "$ARVBOX_DATA" ; then
             echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
-            echo "Set ARVBOX_CONTAINER to something else"
+            echo "Set ARVBOX_CONTAINER to set the demo container name"
             exit 1
         fi
 
-        if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
-            docker start -a $ARVBOX_CONTAINER
-        else
-            docker run \
-                   --name=$ARVBOX_CONTAINER \
-                   --privileged \
-                   arvados/arvbox-demo
+        if ! (docker ps -a | grep -E "$ARVBOX_CONTAINER-data$" -q) ; then
+            docker create -v /var/lib/postgres -v /var/lib/arvados --name $ARVBOX_CONTAINER-data arvados/arvbox-demo /bin/true
         fi
+        docker run \
+               --name=$ARVBOX_CONTAINER \
+               --privileged \
+               --volumes-from $ARVBOX_CONTAINER-data \
+               arvados/arvbox-demo
     else
-        mkdir -p $PG_DATA $VAR_DATA $PASSENGER $GEMS
+        mkdir -p "$PG_DATA" "$VAR_DATA" "$PASSENGER" "$GEMS"
 
-        if ! test -d $ARVADOS_ROOT ; then
-            git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
+        if ! test -d "$ARVADOS_ROOT" ; then
+            git clone https://github.com/curoverse/arvados.git "$ARVADOS_ROOT"
         fi
-        if ! test -d $SSO_ROOT ; then
-            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
+        if ! test -d "$SSO_ROOT" ; then
+            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git "$SSO_ROOT"
         fi
 
-        if test "$1" = testing ; then
+        if test "$1" = test ; then
+            shift
+
+            if ! test -d "$ARVADOS_DEV_ROOT" ; then
+                git clone https://github.com/curoverse/arvados-dev.git "$ARVADOS_DEV_ROOT"
+            fi
+
             docker run \
                    --detach \
                    --name=$ARVBOX_CONTAINER \
                    --privileged \
-                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-                   --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
-                   --volume=$SSO_ROOT:/usr/src/sso:rw \
-                   --volume=$PG_DATA:/var/lib/postgresql:rw \
-                   --volume=$VAR_DATA:/var/lib/arvados:rw \
-                   --volume=$PASSENGER:/var/lib/passenger:rw \
-                   --volume=$GEMS:/var/lib/gems:rw \
-                   arvados/arvbox-dev \
-                   LD_PRELOAD=/lib/runit-docker.so exec runsvdir /etc/tests-service
+                   "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
+                   "--volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw" \
+                   "--volume=$SSO_ROOT:/usr/src/sso:rw" \
+                   "--volume=$PG_DATA:/var/lib/postgresql:rw" \
+                   "--volume=$VAR_DATA:/var/lib/arvados:rw" \
+                   "--volume=$PASSENGER:/var/lib/passenger:rw" \
+                   "--volume=$GEMS:/var/lib/gems:rw" \
+                   arvados/arvbox-test
+
+            while ! docker exec -ti \
+                    $ARVBOX_CONTAINER \
+                    /etc/tests-service/runsu.sh \
+                    psql -c'\du' ; do
+                sleep 1
+            done
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /etc/tests-service/runsu.sh \
+                   /etc/service/sso/run-service --only-setup
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /etc/tests-service/runsu.sh \
+                   /etc/service/api/run-service --only-setup
+
+            docker exec -ti \
+                   $ARVBOX_CONTAINER \
+                   /etc/tests-service/runsu.sh \
+                   /usr/src/arvados-dev/jenkins/run-tests.sh \
+                   --leave-temp \
+                   WORKSPACE=/usr/src/arvados \
+                   VENVDIR=/var/lib/arvados/tests-venv \
+                   VENV3DIR=/var/lib/arvados/tests-venv3 \
+                   GOPATH=/var/lib/arvados/tests-gostuff \
+                   GEMHOME=/var/lib/gems/ruby/2.1.0 \
+                   GEM_HOME=/var/lib/gems \
+                   "$@"
         else
             docker run \
                    --detach \
                    --name=$ARVBOX_CONTAINER \
                    --privileged \
-                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-                   --volume=$SSO_ROOT:/usr/src/sso:rw \
-                   --volume=$PG_DATA:/var/lib/postgresql:rw \
-                   --volume=$VAR_DATA:/var/lib/arvados:rw \
-                   --volume=$PASSENGER:/var/lib/passenger:rw \
-                   --volume=$GEMS:/var/lib/gems:rw \
+                   "--volume=$ARVADOS_ROOT:/usr/src/arvados:rw" \
+                   "--volume=$SSO_ROOT:/usr/src/sso:rw" \
+                   "--volume=$PG_DATA:/var/lib/postgresql:rw" \
+                   "--volume=$VAR_DATA:/var/lib/arvados:rw" \
+                   "--volume=$PASSENGER:/var/lib/passenger:rw" \
+                   "--volume=$GEMS:/var/lib/gems:rw" \
                    arvados/arvbox-dev
             FF=/tmp/arvbox-fifo-$$
             mkfifo $FF
@@ -105,32 +145,44 @@ run() {
 }
 
 stop() {
-    if docker ps -a --filter "status=running" |grep -E "$ARVBOX_CONTAINER$" -q ; then
+    if docker ps -a --filter "status=running" | grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker stop $ARVBOX_CONTAINER
     fi
-    if test "$1" != demo ; then
-        if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
-            docker rm --volumes=true $ARVBOX_CONTAINER
-        fi
+    if docker ps -a --filter "status=exited" | grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker rm --volumes=true $ARVBOX_CONTAINER
     fi
 }
 
 build() {
-    docker build -t arvados/arvbox-base -f $ARVBOX_DOCKER/Dockerfile.base $ARVBOX_DOCKER
-    if test "$1" = demo ; then
-        docker build -t arvados/arvbox-demo -f $ARVBOX_DOCKER/Dockerfile.demo $ARVBOX_DOCKER
-    else
-        docker build -t arvados/arvbox-dev -f $ARVBOX_DOCKER/Dockerfile.dev $ARVBOX_DOCKER
+    if ! test -f "$ARVBOX_DOCKER/Dockerfile.base" ;  then
+        echo "Could not find Dockerfile ($ARVBOX_DOCKER/Dockerfile.base)"
+        exit 1
+    fi
+    docker build -t arvados/arvbox-base -f "$ARVBOX_DOCKER/Dockerfile.base" "$ARVBOX_DOCKER"
+    docker build -t arvados/arvbox-dev -f "$ARVBOX_DOCKER/Dockerfile.dev" "$ARVBOX_DOCKER"
+    if test "$1" = demo -o "$1" = test ; then
+        docker build -t arvados/arvbox-$1 -f "$ARVBOX_DOCKER/Dockerfile.$1" "$ARVBOX_DOCKER"
     fi
 }
 
-case $1 in
+check() {
+    if test -z "$1" ; then
+        echo "Argument to $subcmd must be one of dev, test, demo"
+        exit 1
+    fi
+}
+
+subcmd="$1"
+shift
+case "$subcmd" in
     build)
-        build $2
+        check $@
+        build $@
         ;;
 
     start|run)
-        run $2
+        check $@
+        run $@
         ;;
 
     sh*)
@@ -138,111 +190,81 @@ case $1 in
         ;;
 
     stop)
-        stop $2
+        stop
         ;;
 
     restart)
-        stop
-        run
+        check $@
+        stop $@
+        run $@
         ;;
 
     reboot)
+        check $@
         stop
-        build
-        run
+        build $@
+        run $@
         ;;
 
     ip|open)
         IP=$(docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-)
-        if test $1 = 'ip' ; then
+        if test "$subcmd" = 'ip' ; then
             echo $IP
         else
             xdg-open http://$IP
         fi
         ;;
 
-    reset)
-        if test "$2" != -f ; then
-            echo "WARNING!  This will delete your database, git and keep files inside your arvbox ($ARVBOX_DATA).  Use reset -f if you really mean it."
-            exit 1
-        fi
+    reset|destroy)
         stop
-        rm -rf $ARVBOX_DATA/postgres
-        rm -rf $ARVBOX_DATA/var
-        ;;
-
-    destroy)
-        if test "$2" != -f ; then
-            echo "WARNING!  This will delete all code and data inside your arvbox ($ARVBOX_DATA).  Use destroy -f if you really mean it."
-            exit 1
+        if test -d "$ARVBOX_DATA" ; then
+            if test "$subcmd" = destroy ; then
+                if test "$1" != -f ; then
+                    echo "WARNING!  This will delete your entire arvbox ($ARVBOX_DATA)."
+                    echo "Use destroy -f if you really mean it."
+                    exit 1
+                fi
+                rm -rf "$ARVBOX_DATA"
+            else
+                if test "$1" != -f ; then
+                    echo "WARNING!  This will delete your arvbox data ($ARVBOX_DATA)."
+                    echo "Code and downloaded packages will be preserved."
+                    echo "Use reset -f if you really mean it."
+                    exit 1
+                fi
+                rm -rf "$ARVBOX_DATA/postgres"
+                rm -rf "$ARVBOX_DATA/var"
+            fi
+        else
+            if test "$1" != -f ; then
+                echo "WARNING!  This will delete your data container $ARVBOX_CONTAINER-data.  Use -f if you really mean it."
+                exit 1
+            fi
+            docker rm "$ARVBOX_CONTAINER-data"
         fi
-        stop
-        rm -rf $ARVBOX_DATA
         ;;
 
     log|svrestart)
-        if test -n "$2" ; then
-            if test "$1" = log ; then
-                docker exec -ti $ARVBOX_CONTAINER tail -n100 /etc/service/$2/log/main/current
+        if test -n "$1" ; then
+            if test "$subcmd" = log ; then
+                docker exec -ti $ARVBOX_CONTAINER tail -n100 /etc/service/$1/log/main/current
             fi
-            if test "$1" = svrestart ; then
-                docker exec -ti $ARVBOX_CONTAINER sv restart $2
+            if test "$subcmd" = svrestart ; then
+                docker exec -ti $ARVBOX_CONTAINER sv restart $1
                 docker exec -ti $ARVBOX_CONTAINER sv restart ready
             fi
         else
-            echo "Usage: $0 $1 <service>"
+            echo "Usage: $0 $subcmd <service>"
             echo "Available services:"
             docker exec -ti $ARVBOX_CONTAINER ls /etc/service
         fi
         ;;
 
-    run-tests)
-        stop
-
-        if ! test -d $ARVADOS_DEV_ROOT ; then
-            git clone https://github.com/curoverse/arvados-dev.git $ARVADOS_DEV_ROOT
-        fi
-
-        run testing
-
-        shift
-
-        while ! docker exec -ti \
-                $ARVBOX_CONTAINER \
-                /etc/tests-service/runsu.sh \
-                psql -c'\du' ; do
-            sleep 1
-        done
-
-        docker exec -ti \
-               $ARVBOX_CONTAINER \
-               /etc/tests-service/runsu.sh \
-               /etc/service/sso/run-service --only-setup
-
-        docker exec -ti \
-               $ARVBOX_CONTAINER \
-               /etc/tests-service/runsu.sh \
-               /etc/service/api/run-service --only-setup
-
-        docker exec -ti \
-               $ARVBOX_CONTAINER \
-               /etc/tests-service/runsu.sh \
-               /usr/src/arvados-dev/jenkins/run-tests.sh \
-               --leave-temp \
-               WORKSPACE=/usr/src/arvados \
-               VENVDIR=/var/lib/arvados/tests-venv \
-               VENV3DIR=/var/lib/arvados/tests-venv3 \
-               GOPATH=/var/lib/arvados/tests-gostuff \
-               GEMHOME=/var/lib/gems/ruby/2.1.0 \
-               GEM_HOME=/var/lib/gems/ruby/2.1.0 \
-               "$@"
-        ;;
-
     clone)
-        if test -n "$3" ; then
-            cp -r $HOME/.arvbox/$2 $HOME/.arvbox/$3
-            echo "Created new arvbox $3"
-            echo "export ARVBOX_CONTAINER=$3"
+        if test -n "$2" ; then
+            cp -r $HOME/.arvbox/$1 $HOME/.arvbox/$2
+            echo "Created new arvbox $2"
+            echo "export ARVBOX_CONTAINER=$2"
         else
             echo "clone <from> <to>   clone an arvbox"
         fi
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.base b/arvbox/lib/arvbox/docker/Dockerfile.base
index 274dceb..e73ba92 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -14,13 +14,15 @@ RUN apt-get update && \
 RUN curl -sSL https://get.docker.com/ | sh
 VOLUME /var/lib/docker
 
-ADD runit-docker /root/runit-docker
-
-RUN cd /root/runit-docker && \
-    make && \
-    make install
+RUN cd /root && \
+    GOPATH=$PWD go get github.com/curoverse/runsvinit && \
+    install bin/runsvinit /usr/local/bin
 
 ADD fuse.conf /etc/
 
+ADD crunch-setup.sh gitolite.rc \
+    keep-setup.sh common.sh createusers.sh logger runsu.sh \
+    /usr/local/lib/arvbox/
+
 # Start the supervisor.
-CMD LD_PRELOAD=/lib/runit-docker.so exec runsvdir /etc/service
+CMD ["/usr/local/bin/runsvinit"]
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
index 46ab21e..8a255f8 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -1,12 +1,9 @@
-FROM arvados/arvbox-base
+FROM arvados/arvbox-dev
 
 RUN cd /usr/src && \
     git clone https://github.com/curoverse/arvados.git && \
     git clone https://github.com/curoverse/sso-devise-omniauth-provider.git sso
 
-ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
-ADD service /etc/service
-
 RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
 
 RUN sudo -u arvbox /etc/service/sso/run-service --only-deps
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.dev b/arvbox/lib/arvbox/docker/Dockerfile.dev
index cb2873b..fc81bb6 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.dev
+++ b/arvbox/lib/arvbox/docker/Dockerfile.dev
@@ -1,15 +1,3 @@
 FROM arvados/arvbox-base
 
-RUN apt-get update && \
-    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
-    python-virtualenv python3-virtualenv linkchecker xvfb iceweasel
-
-RUN set -e && \
- PJS=phantomjs-1.9.7-linux-x86_64 && \
- curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
- tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
- ln -s ../$PJS/bin/phantomjs /usr/local/bin/
-
-ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
 ADD service /etc/service
-ADD tests-service /etc/tests-service
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.dev b/arvbox/lib/arvbox/docker/Dockerfile.test
similarity index 68%
copy from arvbox/lib/arvbox/docker/Dockerfile.dev
copy to arvbox/lib/arvbox/docker/Dockerfile.test
index cb2873b..c84dfb5 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.dev
+++ b/arvbox/lib/arvbox/docker/Dockerfile.test
@@ -10,6 +10,4 @@ RUN set -e && \
  tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
  ln -s ../$PJS/bin/phantomjs /usr/local/bin/
 
-ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
-ADD service /etc/service
-ADD tests-service /etc/tests-service
+ADD service/postgres /etc/service/
diff --git a/arvbox/lib/arvbox/docker/common.sh b/arvbox/lib/arvbox/docker/common.sh
index d58c39b..d508c5c 100644
--- a/arvbox/lib/arvbox/docker/common.sh
+++ b/arvbox/lib/arvbox/docker/common.sh
@@ -1,7 +1,22 @@
+
 localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
 export GEM_HOME=/var/lib/gems
 export GEM_PATH=/var/lib/gems
 
+declare -A services
+services=(
+  [workbench]=80
+  [api]=8000
+  [sso]=8900
+  [arv-git-httpd]=9001
+  [keep-web]=9002
+  [keepproxy]=25100
+  [keepstore0]=25107
+  [keepstore1]=25108
+  [ssh]=22
+  [doc]=8001
+)
+
 if test $(id arvbox -u) = 0 ; then
     PGUSER=postgres
     PGGROUP=postgres
diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index 680d9fe..a90992a 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -1,6 +1,6 @@
-#!/bin/sh
+#!/bin/bash
 
-set -e
+set -e -o pipefail
 
 if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
diff --git a/arvbox/lib/arvbox/docker/crunch-setup.sh b/arvbox/lib/arvbox/docker/crunch-setup.sh
index b6f5cda..178fec1 100755
--- a/arvbox/lib/arvbox/docker/crunch-setup.sh
+++ b/arvbox/lib/arvbox/docker/crunch-setup.sh
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -11,10 +11,10 @@ cd /var/lib/arvados/gostuff
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/crunchstat"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/crunchstat"
 install bin/crunchstat /usr/local/bin
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 export CRUNCH_JOB_BIN=/usr/src/arvados/sdk/cli/bin/crunch-job
diff --git a/arvbox/lib/arvbox/docker/gitolite-setup.sh b/arvbox/lib/arvbox/docker/gitolite-setup.sh
deleted file mode 100755
index 9a2ff9e..0000000
--- a/arvbox/lib/arvbox/docker/gitolite-setup.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-set -e
-
-cd ~
-
-gitolite setup -pk .ssh/id_rsa.pub
-
-if ! test -d gitolite-admin ; then
-    git clone git at localhost:gitolite-admin
-fi
-
-cd gitolite-admin
-git config user.email arvados
-git config user.name arvados
-git config push.default simple
-git push
-
-cp ~git/gitolite.rc ~git/.gitolite.rc
diff --git a/arvbox/lib/arvbox/docker/gitssh-setup.sh b/arvbox/lib/arvbox/docker/gitssh-setup.sh
deleted file mode 100755
index aad7dd4..0000000
--- a/arvbox/lib/arvbox/docker/gitssh-setup.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-cd ~
-
-if ! test -s .ssh/id_rsa ; then
-    mkdir -p .ssh
-    chmod -R 0700 .ssh
-    ssh-keygen -t rsa -P '' -f .ssh/id_rsa
-fi
-
-if test -s /var/lib/arvados/git/.ssh/known_hosts ; then
-    ssh-keygen -f "/var/lib/arvados/git/.ssh/known_hosts" -R localhost
-fi
-
-if ! test -s .ssh/authorized_keys ; then
-    cp .ssh/id_rsa.pub .ssh/authorized_keys
-    ssh -o stricthostkeychecking=no localhost
-    rm .ssh/authorized_keys
-else
-    ssh -o stricthostkeychecking=no localhost
-fi
diff --git a/arvbox/lib/arvbox/docker/keep-setup.sh b/arvbox/lib/arvbox/docker/keep-setup.sh
index 529360e..b66463f 100755
--- a/arvbox/lib/arvbox/docker/keep-setup.sh
+++ b/arvbox/lib/arvbox/docker/keep-setup.sh
@@ -2,7 +2,7 @@
 
 exec 2>&1
 sleep 2
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -12,29 +12,17 @@ cd /var/lib/arvados/gostuff
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keepstore"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keepstore"
 install bin/keepstore /usr/local/bin
 
-mkdir -p /var/lib/arvados/keep
+mkdir -p /var/lib/arvados/$1
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
-if test -s /var/lib/arvados/$1-uuid ; then
-    keep_uuid=$(cat /var/lib/arvados/$1-uuid)
-    set +e
-    read -rd $'\000' keepservice <<EOF
-{
- "service_host":"$localip",
- "service_port":$2
-}
-EOF
-    set -e
-    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
-else
-    set +e
-    read -rd $'\000' keepservice <<EOF
+set +e
+read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
  "service_port":$2,
@@ -42,14 +30,22 @@ else
  "service_type":"disk"
 }
 EOF
-    set -e
+set -e
+
+if test -s /var/lib/arvados/$1-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/$1-uuid)
+    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
+else
     UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
     echo $UUID > /var/lib/arvados/$1-uuid
 fi
 
+set +e
+killall -HUP keepproxy
+
 exec /usr/local/bin/keepstore \
      -listen=:$2 \
      -enforce-permissions=true \
      -blob-signing-key-file=/var/lib/arvados/blob_signing_key \
      -max-buffers=20 \
-     -volume=/var/lib/arvados/keep
+     -volume=/var/lib/arvados/$1
diff --git a/arvbox/lib/arvbox/docker/service/logger b/arvbox/lib/arvbox/docker/logger
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/logger
rename to arvbox/lib/arvbox/docker/logger
diff --git a/arvbox/lib/arvbox/docker/service/runsu.sh b/arvbox/lib/arvbox/docker/runsu.sh
similarity index 75%
rename from arvbox/lib/arvbox/docker/service/runsu.sh
rename to arvbox/lib/arvbox/docker/runsu.sh
index d702709..1557d09 100755
--- a/arvbox/lib/arvbox/docker/service/runsu.sh
+++ b/arvbox/lib/arvbox/docker/runsu.sh
@@ -8,7 +8,7 @@ flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 export HOME=/var/lib/arvados
 
 if test -z "$1" ; then
-    exec chpst -u arvbox:arvbox $0-service
+    exec chpst -u arvbox:arvbox:docker $0-service
 else
-    exec chpst -u arvbox:arvbox $@
+    exec chpst -u arvbox:arvbox:docker $@
 fi
diff --git a/arvbox/lib/arvbox/docker/service/api/log/run b/arvbox/lib/arvbox/docker/service/api/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/api/log/run
+++ b/arvbox/lib/arvbox/docker/service/api/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/api/run b/arvbox/lib/arvbox/docker/service/api/run
index ef446b5..a388c8b 120000
--- a/arvbox/lib/arvbox/docker/service/api/run
+++ b/arvbox/lib/arvbox/docker/service/api/run
@@ -1 +1 @@
-../runsu.sh
\ No newline at end of file
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 6615736..ceccb72 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -ex
+set -ex -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -42,7 +42,7 @@ common:
   secret_token: $secret_token
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
-  sso_provider_url: "https://$localip:3002"
+  sso_provider_url: "https://$localip:${services[sso]}"
   workbench_address: "http://$localip/"
   sso_insecure: true
 development:
@@ -50,7 +50,7 @@ development:
   auto_admin_first_user: true
   blob_signing_key: $blob_signing_key
   git_repo_ssh_base: "git@$localip:"
-  git_repo_https_base: "http://$localip:9001/"
+  git_repo_https_base: "http://$localip:${services[arv-git-httpd]}/"
 test:
   uuid_prefix: zzzzz
   git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"
@@ -104,7 +104,7 @@ if test "$1" = "--only-setup" ; then
     exit
 fi
 
-ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=3001 \
+ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=${services[api]} \
                   --runtime-dir=/var/lib/passenger \
                   --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
                   --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/lib/arvbox/docker/service/keepweb/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/keepweb/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run b/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/arv-git-httpd/run b/arvbox/lib/arvbox/docker/service/arv-git-httpd/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/arv-git-httpd/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/githttp/run-service b/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
similarity index 54%
rename from arvbox/lib/arvbox/docker/service/githttp/run-service
rename to arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
index 7e4f1de..7bd6a7c 100755
--- a/arvbox/lib/arvbox/docker/service/githttp/run-service
+++ b/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -11,13 +11,17 @@ cd /var/lib/arvados/gostuff
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
 install bin/arv-git-httpd /usr/local/bin
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export GITOLITE_HTTP_HOME=/var/lib/arvados/git
 export GL_BYPASS_ACCESS_CHECKS=1
 export PATH="$PATH:/var/lib/arvados/git/bin"
 cd ~git
-/usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
+
+exec /usr/local/bin/arv-git-httpd \
+     -address=:${services[arv-git-httpd]} \
+     -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell \
+     -repo-root=/var/lib/arvados/git/repositories
diff --git a/arvbox/lib/arvbox/docker/service/keep1/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/keep1/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch0/run-service b/arvbox/lib/arvbox/docker/service/crunch-dispatch0/run-service
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/crunch0/run-service
rename to arvbox/lib/arvbox/docker/service/crunch-dispatch0/run-service
diff --git a/arvbox/lib/arvbox/docker/service/keep0/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/keep0/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch1/run-service b/arvbox/lib/arvbox/docker/service/crunch-dispatch1/run-service
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/crunch1/run-service
rename to arvbox/lib/arvbox/docker/service/crunch-dispatch1/run-service
diff --git a/arvbox/lib/arvbox/docker/service/crunch0/log/run b/arvbox/lib/arvbox/docker/service/crunch0/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/crunch0/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch0/run b/arvbox/lib/arvbox/docker/service/crunch0/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/crunch0/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch1/log/run b/arvbox/lib/arvbox/docker/service/crunch1/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/crunch1/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/crunch1/run b/arvbox/lib/arvbox/docker/service/crunch1/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/crunch1/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/doc/log/run b/arvbox/lib/arvbox/docker/service/doc/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/doc/log/run
+++ b/arvbox/lib/arvbox/docker/service/doc/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/doc/run b/arvbox/lib/arvbox/docker/service/doc/run
index ef446b5..a388c8b 120000
--- a/arvbox/lib/arvbox/docker/service/doc/run
+++ b/arvbox/lib/arvbox/docker/service/doc/run
@@ -1 +1 @@
-../runsu.sh
\ No newline at end of file
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/doc/run-service b/arvbox/lib/arvbox/docker/service/doc/run-service
index a64d5c8..acbe21c 100755
--- a/arvbox/lib/arvbox/docker/service/doc/run-service
+++ b/arvbox/lib/arvbox/docker/service/doc/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -ex
+set -ex -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -14,6 +14,28 @@ fi
 
 set -u
 
-bundle exec rake generate baseurl=http://$localip:8000 arvados_api_host=$localip:3001 arvados_workbench_host=http://$localip
+cat <<EOF >/var/lib/arvados/doc-nginx.conf
+worker_processes auto;
+pid /var/lib/arvados/doc-nginx.pid;
+daemon off;
 
-exec bundle exec rake run
+events {
+	worker_connections 64;
+}
+
+http {
+     include /etc/nginx/mime.types;
+     default_type application/octet-stream;
+     server {
+            listen ${services[doc]} default_server;
+            listen [::]:${services[doc]} default_server;
+            root /usr/src/arvados/doc/.site;
+            index index.html;
+            server_name _;
+     }
+}
+EOF
+
+bundle exec rake generate baseurl=http://$localip:${services[doc]} arvados_api_host=$localip:${services[api]} arvados_workbench_host=http://$localip
+
+exec nginx -c /var/lib/arvados/doc-nginx.conf
diff --git a/arvbox/lib/arvbox/docker/service/docker/log/run b/arvbox/lib/arvbox/docker/service/docker/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/docker/log/run
+++ b/arvbox/lib/arvbox/docker/service/docker/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/git/log/run b/arvbox/lib/arvbox/docker/service/git/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/git/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/git/run b/arvbox/lib/arvbox/docker/service/git/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/git/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/githttp/log/run b/arvbox/lib/arvbox/docker/service/githttp/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/githttp/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/githttp/run b/arvbox/lib/arvbox/docker/service/githttp/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/githttp/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/githttp/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/githttp/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/gitolite/log/run b/arvbox/lib/arvbox/docker/service/gitolite/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/gitolite/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/gitolite/run b/arvbox/lib/arvbox/docker/service/gitolite/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/gitolite/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/git/run-service b/arvbox/lib/arvbox/docker/service/gitolite/run-service
similarity index 62%
rename from arvbox/lib/arvbox/docker/service/git/run-service
rename to arvbox/lib/arvbox/docker/service/gitolite/run-service
index 4f08040..2e5f1e9 100755
--- a/arvbox/lib/arvbox/docker/service/git/run-service
+++ b/arvbox/lib/arvbox/docker/service/gitolite/run-service
@@ -1,28 +1,59 @@
 #!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/git
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
+export USER=git
+export USERNAME=git
+export LOGNAME=git
+export HOME=/var/lib/arvados/git
+
+cd ~arvbox
+
+mkdir -p ~arvbox/.ssh ~git/.ssh
+chmod 0700 ~arvbox/.ssh ~git/.ssh
+
+if ! test -s ~arvbox/.ssh/id_rsa ; then
+    ssh-keygen -t rsa -P '' -f .ssh/id_rsa
+    cp ~arvbox/.ssh/id_rsa ~arvbox/.ssh/id_rsa.pub ~git/.ssh
+fi
+
+if test -s ~arvbox/.ssh/known_hosts ; then
+    ssh-keygen -f ".ssh/known_hosts" -R localhost
+fi
+
 if ! test -f /var/lib/arvados/gitolite-setup ; then
-   cp -r /usr/local/lib/arvbox/gitolite-setup.sh /usr/local/lib/arvbox/gitssh-setup.sh /usr/local/lib/arvbox/gitolite.rc /var/lib/arvados/git/
+    cd ~git
+
+    cp .ssh/id_rsa.pub .ssh/authorized_keys
+    ssh -o stricthostkeychecking=no git at localhost
+    rm .ssh/authorized_keys
+
+    cp -r /usr/local/lib/arvbox/gitolite.rc .
+
+    gitolite setup -pk .ssh/id_rsa.pub
 
-   chown -R git:git ~git
+    if ! test -d gitolite-admin ; then
+        git clone git at localhost:gitolite-admin
+    fi
 
-   su git -c "/var/lib/arvados/git/gitssh-setup.sh"
-   su git -c "/var/lib/arvados/git/gitolite-setup.sh"
+    cd gitolite-admin
+    git config user.email arvados
+    git config user.name arvados
+    git config push.default simple
+    git push
 
-   touch /var/lib/arvados/gitolite-setup
+    touch /var/lib/arvados/gitolite-setup
 else
-    chown -R git:git ~git
-    su git -c "/var/lib/arvados/git/gitssh-setup.sh"
+    ssh -o stricthostkeychecking=no git at localhost
 fi
 
 prefix=$(arv --format=uuid user current | cut -d- -f1)
@@ -66,7 +97,7 @@ cat > config/arvados-clients.yml <<EOF
 development:
   gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
   gitolite_tmp: /var/lib/arvados/git
-  arvados_api_host: $localip:3001
+  arvados_api_host: $localip:${services[api]}
   arvados_api_token: "$ARVADOS_API_TOKEN"
   arvados_api_host_insecure: true
   gitolite_arvados_git_user_key: "$git_user_key"
diff --git a/arvbox/lib/arvbox/docker/service/git/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keep-web/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/git/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keep-web/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/keep-web/log/run b/arvbox/lib/arvbox/docker/service/keep-web/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keep-web/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keep-web/run b/arvbox/lib/arvbox/docker/service/keep-web/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keep-web/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepweb/run-service b/arvbox/lib/arvbox/docker/service/keep-web/run-service
similarity index 59%
rename from arvbox/lib/arvbox/docker/service/keepweb/run-service
rename to arvbox/lib/arvbox/docker/service/keep-web/run-service
index 38c31f0..a2c6aa1 100755
--- a/arvbox/lib/arvbox/docker/service/keepweb/run-service
+++ b/arvbox/lib/arvbox/docker/service/keep-web/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -11,11 +11,11 @@ cd /var/lib/arvados/gostuff
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keep-web"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keep-web"
 install bin/keep-web /usr/local/bin
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
-exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
+exec /usr/local/bin/keep-web -trust-all-content -listen=:${services[keep-web]}
diff --git a/arvbox/lib/arvbox/docker/service/keep0/log/run b/arvbox/lib/arvbox/docker/service/keep0/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/keep0/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keep0/run b/arvbox/lib/arvbox/docker/service/keep0/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/keep0/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keep0/run-service b/arvbox/lib/arvbox/docker/service/keep0/run-service
deleted file mode 100755
index 7f87b88..0000000
--- a/arvbox/lib/arvbox/docker/service/keep0/run-service
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-exec /usr/local/lib/arvbox/keep-setup.sh keep0 25107
diff --git a/arvbox/lib/arvbox/docker/service/keep1/log/run b/arvbox/lib/arvbox/docker/service/keep1/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/keep1/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keep1/run b/arvbox/lib/arvbox/docker/service/keep1/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/keep1/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keep1/run-service b/arvbox/lib/arvbox/docker/service/keep1/run-service
deleted file mode 100755
index a44c8dd..0000000
--- a/arvbox/lib/arvbox/docker/service/keep1/run-service
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-sleep 1
-exec /usr/local/lib/arvbox/keep-setup.sh keep1 25108
diff --git a/arvbox/lib/arvbox/docker/service/keepproxy/log/run b/arvbox/lib/arvbox/docker/service/keepproxy/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/keepproxy/log/run
+++ b/arvbox/lib/arvbox/docker/service/keepproxy/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepproxy/run b/arvbox/lib/arvbox/docker/service/keepproxy/run
index ef446b5..a388c8b 120000
--- a/arvbox/lib/arvbox/docker/service/keepproxy/run
+++ b/arvbox/lib/arvbox/docker/service/keepproxy/run
@@ -1 +1 @@
-../runsu.sh
\ No newline at end of file
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepproxy/run-service b/arvbox/lib/arvbox/docker/service/keepproxy/run-service
index 87b46d9..413a67e 100755
--- a/arvbox/lib/arvbox/docker/service/keepproxy/run-service
+++ b/arvbox/lib/arvbox/docker/service/keepproxy/run-service
@@ -2,7 +2,7 @@
 
 exec 2>&1
 sleep 2
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -12,38 +12,30 @@ cd /var/lib/arvados/gostuff
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keepproxy"
+flock /var/lib/arvados/gostuff.lock go get -t "git.curoverse.com/arvados.git/services/keepproxy"
 install bin/keepproxy /usr/local/bin
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
-if test -s /var/lib/arvados/keepproxy-uuid ; then
-    keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
-    set +e
-    read -rd $'\000' keepservice <<EOF
-{
- "service_host":"$localip",
- "service_port":25100,
- "service_type":"proxy"
-}
-EOF
-   set -e
-   arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
-else
-    set +e
-    read -rd $'\000' keepservice <<EOF
+set +e
+read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
- "service_port":25100,
+ "service_port":${services[keepproxy]},
  "service_ssl_flag":false,
  "service_type":"proxy"
 }
 EOF
-    set -e
+set -e
+
+if test -s /var/lib/arvados/keepproxy-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
+    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
+else
     UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
     echo $UUID > /var/lib/arvados/keepproxy-uuid
 fi
 
-exec /usr/local/bin/keepproxy -listen=":25100"
+exec /usr/local/bin/keepproxy -listen=:${services[keepproxy]}
diff --git a/arvbox/lib/arvbox/docker/service/crunch1/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keepstore0/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/crunch1/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keepstore0/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/keepstore0/log/run b/arvbox/lib/arvbox/docker/service/keepstore0/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore0/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepstore0/run b/arvbox/lib/arvbox/docker/service/keepstore0/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore0/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepstore0/run-service b/arvbox/lib/arvbox/docker/service/keepstore0/run-service
new file mode 100755
index 0000000..cf411e4
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore0/run-service
@@ -0,0 +1,3 @@
+#!/bin/bash
+. /usr/local/lib/arvbox/common.sh
+exec /usr/local/lib/arvbox/keep-setup.sh keep0 ${services[keepstore0]}
diff --git a/arvbox/lib/arvbox/docker/service/crunch0/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keepstore1/log/main/.gitstub
similarity index 100%
rename from arvbox/lib/arvbox/docker/service/crunch0/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keepstore1/log/main/.gitstub
diff --git a/arvbox/lib/arvbox/docker/service/keepstore1/log/run b/arvbox/lib/arvbox/docker/service/keepstore1/log/run
new file mode 120000
index 0000000..d6aef4a
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore1/log/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepstore1/run b/arvbox/lib/arvbox/docker/service/keepstore1/run
new file mode 120000
index 0000000..a388c8b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore1/run
@@ -0,0 +1 @@
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepstore1/run-service b/arvbox/lib/arvbox/docker/service/keepstore1/run-service
new file mode 100755
index 0000000..8d34d06
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/keepstore1/run-service
@@ -0,0 +1,3 @@
+#!/bin/bash
+. /usr/local/lib/arvbox/common.sh
+exec /usr/local/lib/arvbox/keep-setup.sh keep1 ${services[keepstore1]}
diff --git a/arvbox/lib/arvbox/docker/service/keepweb/log/run b/arvbox/lib/arvbox/docker/service/keepweb/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/lib/arvbox/docker/service/keepweb/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/keepweb/run b/arvbox/lib/arvbox/docker/service/keepweb/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/keepweb/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/postgres/log/run b/arvbox/lib/arvbox/docker/service/postgres/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/postgres/log/run
+++ b/arvbox/lib/arvbox/docker/service/postgres/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/postgres/run b/arvbox/lib/arvbox/docker/service/postgres/run
index ad9cc37..4918bd7 100755
--- a/arvbox/lib/arvbox/docker/service/postgres/run
+++ b/arvbox/lib/arvbox/docker/service/postgres/run
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 
diff --git a/arvbox/lib/arvbox/docker/service/postgres/run-service b/arvbox/lib/arvbox/docker/service/postgres/run-service
index 0a67d6c..dd2eb1a 100755
--- a/arvbox/lib/arvbox/docker/service/postgres/run-service
+++ b/arvbox/lib/arvbox/docker/service/postgres/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 if ! test -d /var/lib/postgresql/9.4/main ; then
     /usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main
diff --git a/arvbox/lib/arvbox/docker/service/ready/run b/arvbox/lib/arvbox/docker/service/ready/run
index ef446b5..a388c8b 120000
--- a/arvbox/lib/arvbox/docker/service/ready/run
+++ b/arvbox/lib/arvbox/docker/service/ready/run
@@ -1 +1 @@
-../runsu.sh
\ No newline at end of file
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/ready/run-service b/arvbox/lib/arvbox/docker/service/ready/run-service
index a26d47c..52c0adc 100755
--- a/arvbox/lib/arvbox/docker/service/ready/run-service
+++ b/arvbox/lib/arvbox/docker/service/ready/run-service
@@ -2,7 +2,7 @@
 
 . /usr/local/lib/arvbox/common.sh
 
-set -eu
+set -eu -o pipefail
 
 if ! [[ -d /tmp/arvbox-ready ]] ; then
    echo
@@ -16,24 +16,8 @@ fi
 
 sleep 3
 
-declare -A services
-services=(
-  [workbench]=80
-  [api]=3001
-  [sso]=3002
-  [githttp]=9001
-  [keepweb]=25099
-  [keepproxy]=25100
-  [keep0]=25107
-  [keep1]=25108
-  [ssh]=22
-  [doc]=8000
-)
-
 waiting=""
 
-. /usr/local/lib/arvbox/common.sh
-
 for s in "${!services[@]}"
 do
   if ! [[ -f /tmp/arvbox-ready/$s ]] ; then
diff --git a/arvbox/lib/arvbox/docker/service/sdk/log/run b/arvbox/lib/arvbox/docker/service/sdk/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/sdk/log/run
+++ b/arvbox/lib/arvbox/docker/service/sdk/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/sdk/run b/arvbox/lib/arvbox/docker/service/sdk/run
index e6c844d..816b166 100755
--- a/arvbox/lib/arvbox/docker/service/sdk/run
+++ b/arvbox/lib/arvbox/docker/service/sdk/run
@@ -1,5 +1,5 @@
 #!/bin/sh
 set -e
 
-/etc/service/runsu.sh $0-service
+/usr/local/lib/arvbox/runsu.sh $0-service
 sv stop sdk
diff --git a/arvbox/lib/arvbox/docker/service/sdk/run-service b/arvbox/lib/arvbox/docker/service/sdk/run-service
index fd376ba..b51f0fc 100755
--- a/arvbox/lib/arvbox/docker/service/sdk/run-service
+++ b/arvbox/lib/arvbox/docker/service/sdk/run-service
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
diff --git a/arvbox/lib/arvbox/docker/service/ssh/log/run b/arvbox/lib/arvbox/docker/service/ssh/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/ssh/log/run
+++ b/arvbox/lib/arvbox/docker/service/ssh/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/ssh/run b/arvbox/lib/arvbox/docker/service/ssh/run
index 0db3ffa..0f23542 100755
--- a/arvbox/lib/arvbox/docker/service/ssh/run
+++ b/arvbox/lib/arvbox/docker/service/ssh/run
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -eux
+set -eux -o pipefail
 
 if ! test -d /var/run/sshd ; then
    mkdir /var/run/sshd
diff --git a/arvbox/lib/arvbox/docker/service/sso/log/run b/arvbox/lib/arvbox/docker/service/sso/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/sso/log/run
+++ b/arvbox/lib/arvbox/docker/service/sso/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/sso/run b/arvbox/lib/arvbox/docker/service/sso/run
index ef446b5..a388c8b 120000
--- a/arvbox/lib/arvbox/docker/service/sso/run
+++ b/arvbox/lib/arvbox/docker/service/sso/run
@@ -1 +1 @@
-../runsu.sh
\ No newline at end of file
+/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
index 29951f5..d1c566a 100755
--- a/arvbox/lib/arvbox/docker/service/sso/run-service
+++ b/arvbox/lib/arvbox/docker/service/sso/run-service
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -ex
+set -ex -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -96,7 +96,7 @@ if test "$1" = "--only-setup" ; then
     exit
 fi
 
-exec bundle exec passenger start --port=3002 \
+exec bundle exec passenger start --port=${services[sso]} \
      --runtime-dir=/var/lib/passenger \
      --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
      --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/lib/arvbox/docker/service/vm/log/run b/arvbox/lib/arvbox/docker/service/vm/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/vm/log/run
+++ b/arvbox/lib/arvbox/docker/service/vm/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/vm/run b/arvbox/lib/arvbox/docker/service/vm/run
deleted file mode 120000
index ef446b5..0000000
--- a/arvbox/lib/arvbox/docker/service/vm/run
+++ /dev/null
@@ -1 +0,0 @@
-../runsu.sh
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/vm/run b/arvbox/lib/arvbox/docker/service/vm/run
new file mode 100755
index 0000000..b7fb9cc
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/service/vm/run
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+. /usr/local/lib/arvbox/common.sh
+
+git config --system "credential.http://$localip:${services[arv-git-httpd]}/.username" none
+git config --system "credential.http://$localip:${services[arv-git-httpd]}/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
+
+exec /usr/local/lib/arvbox/runsu.sh $0-service
diff --git a/arvbox/lib/arvbox/docker/service/vm/run-service b/arvbox/lib/arvbox/docker/service/vm/run-service
index 9b894ff..a845e44 100755
--- a/arvbox/lib/arvbox/docker/service/vm/run-service
+++ b/arvbox/lib/arvbox/docker/service/vm/run-service
@@ -2,7 +2,7 @@
 
 exec 2>&1
 sleep 2
-set -eux
+set -ex -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -13,31 +13,24 @@ if test "$1" = "--only-deps" ; then
     exit
 fi
 
-git config --system "credential.http://$localip:9001/.username" none
-git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
+set -u
 
-export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST=$localip:${services[api]}
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
-if test -s /var/lib/arvados/vm-uuid ; then
-    ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
-    set +e
-    read -rd $'\000' vm <<EOF
+set +e
+read -rd $'\000' vm <<EOF
 {
  "hostname":"$localip"
 }
 EOF
-    set -e
+set -e
+
+if test -s /var/lib/arvados/vm-uuid ; then
+    ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
     arv virtual_machine update --uuid $ARVADOS_VIRTUAL_MACHINE_UUID --virtual-machine "$vm"
 else
-    set +e
-    read -rd $'\000' vm <<EOF
-{
- "hostname":"$localip"
-}
-EOF
-    set -e
     ARVADOS_VIRTUAL_MACHINE_UUID=$(arv --format=uuid virtual_machine create --virtual-machine "$vm")
     echo $ARVADOS_VIRTUAL_MACHINE_UUID > /var/lib/arvados/vm-uuid
 fi
diff --git a/arvbox/lib/arvbox/docker/service/workbench/log/run b/arvbox/lib/arvbox/docker/service/workbench/log/run
index f99cc1d..d6aef4a 120000
--- a/arvbox/lib/arvbox/docker/service/workbench/log/run
+++ b/arvbox/lib/arvbox/docker/service/workbench/log/run
@@ -1 +1 @@
-../../logger
\ No newline at end of file
+/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run b/arvbox/lib/arvbox/docker/service/workbench/run
index f121ce7..6ac0476 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run
+++ b/arvbox/lib/arvbox/docker/service/workbench/run
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-/etc/service/runsu.sh $0-service $1
+/usr/local/lib/arvbox/runsu.sh $0-service $1
 
 cd /usr/src/arvados/apps/workbench
 
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run-service b/arvbox/lib/arvbox/docker/service/workbench/run-service
index dadd989..a5f7195 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run-service
+++ b/arvbox/lib/arvbox/docker/service/workbench/run-service
@@ -1,13 +1,12 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
-set -ex
+set -ex -o pipefail
 
 .  /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development
-export GEM_HOME=/var/lib/gems
 
 run_bundler --without=development
 bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
@@ -30,10 +29,10 @@ fi
 cat >config/application.yml <<EOF
 common:
   secret_token: $secret_token
-  arvados_login_base: https://$localip:3001/login
-  arvados_v1_base: https://$localip:3001/arvados/v1
+  arvados_login_base: https://$localip:${services[api]}/login
+  arvados_v1_base: https://$localip:${services[api]}/arvados/v1
   arvados_insecure_https: true
-  keep_web_download_url: http://$localip:25099/c=%{uuid_or_pdh}
-  keep_web_url: http://$localip:25099/c=%{uuid_or_pdh}
-  arvados_docsite: http://$localip:8000/
+  keep_web_download_url: http://$localip:${services[keep-web]}/c=%{uuid_or_pdh}
+  keep_web_url: http://$localip:${services[keep-web]}/c=%{uuid_or_pdh}
+  arvados_docsite: http://$localip:${services[doc]}/
 EOF

commit ba698c52e9be47f547dd743f4e13d7992db80743
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Fri Jan 8 16:24:43 2016 -0500

    Working on getting demo image to work.  Not quite there yet.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 57c0905..af13ab9 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -35,12 +35,16 @@ VAR_DATA=$ARVBOX_DATA/var
 PASSENGER=$ARVBOX_DATA/passenger
 GEMS=$ARVBOX_DATA/gems
 
-mkdir -p $PG_DATA $VAR_DATA $PASSENGER $GEMS
-
 run() {
     if test "$1" = demo ; then
+        if test -d $ARVBOX_DATA ; then
+            echo "It looks like you already have a development container named $ARVBOX_CONTAINER."
+            echo "Set ARVBOX_CONTAINER to something else"
+            exit 1
+        fi
+
         if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
-            docker start $ARVBOX_CONTAINER
+            docker start -a $ARVBOX_CONTAINER
         else
             docker run \
                    --name=$ARVBOX_CONTAINER \
@@ -48,6 +52,8 @@ run() {
                    arvados/arvbox-demo
         fi
     else
+        mkdir -p $PG_DATA $VAR_DATA $PASSENGER $GEMS
+
         if ! test -d $ARVADOS_ROOT ; then
             git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
         fi
@@ -68,7 +74,7 @@ run() {
                    --volume=$PASSENGER:/var/lib/passenger:rw \
                    --volume=$GEMS:/var/lib/gems:rw \
                    arvados/arvbox-dev \
-                   runsvdir /etc/tests-service
+                   LD_PRELOAD=/lib/runit-docker.so exec runsvdir /etc/tests-service
         else
             docker run \
                    --detach \
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.base b/arvbox/lib/arvbox/docker/Dockerfile.base
index 9308d9c..274dceb 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.base
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -20,9 +20,7 @@ RUN cd /root/runit-docker && \
     make && \
     make install
 
-ENV LD_PRELOAD /lib/runit-docker.so
-
 ADD fuse.conf /etc/
 
 # Start the supervisor.
-CMD ["runsvdir", "/etc/service"]
+CMD LD_PRELOAD=/lib/runit-docker.so exec runsvdir /etc/service
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
index 2ab772e..46ab21e 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile.demo
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -7,10 +7,11 @@ RUN cd /usr/src && \
 ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
 ADD service /etc/service
 
-RUN mkdir -p /var/lib/arvados
-RUN /etc/service/sso/run-service --only-deps
-RUN /etc/service/api/run-service --only-deps
-RUN /etc/service/workbench/run-service --only-deps
-RUN /etc/service/doc/run-service --only-deps
-RUN /etc/service/vm/run-service --only-deps
-RUN /etc/service/sdk/run-service
+RUN chown -R 1000:1000 /usr/src && /usr/local/lib/arvbox/createusers.sh
+
+RUN sudo -u arvbox /etc/service/sso/run-service --only-deps
+RUN sudo -u arvbox /etc/service/api/run-service --only-deps
+RUN sudo -u arvbox /etc/service/workbench/run-service --only-deps
+RUN sudo -u arvbox /etc/service/doc/run-service --only-deps
+RUN sudo -u arvbox /etc/service/vm/run-service --only-deps
+RUN sudo -u arvbox /etc/service/sdk/run-service
diff --git a/arvbox/lib/arvbox/docker/common.sh b/arvbox/lib/arvbox/docker/common.sh
index bc328f7..d58c39b 100644
--- a/arvbox/lib/arvbox/docker/common.sh
+++ b/arvbox/lib/arvbox/docker/common.sh
@@ -2,6 +2,14 @@ localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
 export GEM_HOME=/var/lib/gems
 export GEM_PATH=/var/lib/gems
 
+if test $(id arvbox -u) = 0 ; then
+    PGUSER=postgres
+    PGGROUP=postgres
+else
+    PGUSER=arvbox
+    PGGROUP=arvbox
+fi
+
 run_bundler() {
     if test -f Gemfile.lock ; then
         frozen=--frozen
diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index 8d27990..680d9fe 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -7,7 +7,8 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
     FUSEGID=$(ls -nd /dev/fuse | sed 's/ */ /' | cut -d' ' -f5)
 
-    mkdir -p /var/lib/arvados/git
+    mkdir -p /var/lib/arvados/git /var/lib/gems /var/lib/passenger
+
     groupadd --gid $HOSTGID --non-unique arvbox
     groupadd --gid $FUSEGID --non-unique fuse
     groupadd --gid $HOSTGID --non-unique git
@@ -19,7 +20,7 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
     useradd --groups docker,fuse crunch
 
-    chown arvbox:arvbox -R /usr/local
+    chown arvbox:arvbox -R /usr/local /var/lib/arvados /var/lib/gems /var/lib/passenger
 
     mkdir -p /var/lib/gems/ruby/2.1.0
     chown arvbox:arvbox -R /var/lib/gems/ruby/2.1.0
diff --git a/arvbox/lib/arvbox/docker/service/postgres/run b/arvbox/lib/arvbox/docker/service/postgres/run
index 55d11a0..ad9cc37 100755
--- a/arvbox/lib/arvbox/docker/service/postgres/run
+++ b/arvbox/lib/arvbox/docker/service/postgres/run
@@ -2,17 +2,11 @@
 
 flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 
-if test $(id arvbox -u) = 0 ; then
-    USER=postgres
-    GROUP=postgres
-else
-    USER=arvbox
-    GROUP=arvbox
-fi
+. /usr/local/lib/arvbox/common.sh
 
-chown -R $USER:$GROUP /var/lib/postgresql
-chown -R $USER:$GROUP /var/run/postgresql
-chown -R $USER:$GROUP /etc/postgresql
-chown -R $USER:$GROUP /etc/ssl/private
+chown -R $PGUSER:$PGGROUP /var/lib/postgresql
+chown -R $PGUSER:$PGGROUP /var/run/postgresql
+chown -R $PGUSER:$PGGROUP /etc/postgresql
+chown -R $PGUSER:$PGGROUP /etc/ssl/private
 
-exec su $USER $0-service
+exec chpst -u $PGUSER:$PGGROUP $0-service
diff --git a/arvbox/lib/arvbox/docker/service/runsu.sh b/arvbox/lib/arvbox/docker/service/runsu.sh
index 32eccdf..d702709 100755
--- a/arvbox/lib/arvbox/docker/service/runsu.sh
+++ b/arvbox/lib/arvbox/docker/service/runsu.sh
@@ -8,7 +8,7 @@ flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 export HOME=/var/lib/arvados
 
 if test -z "$1" ; then
-    exec sudo -u arvbox -- $0-service
+    exec chpst -u arvbox:arvbox $0-service
 else
-    exec sudo -u arvbox -- $@
+    exec chpst -u arvbox:arvbox $@
 fi

commit b6bd09f01642d2ad2dc33259a2382615daadf059
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Thu Jan 7 14:03:01 2016 -0500

    8080: Refactor to base image and dev or demo images.  Working on support for a
    "demo" image that comes with everything packed in.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index e90dce1..57c0905 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -38,54 +38,63 @@ GEMS=$ARVBOX_DATA/gems
 mkdir -p $PG_DATA $VAR_DATA $PASSENGER $GEMS
 
 run() {
-    if ! test -d $ARVADOS_ROOT ; then
-        git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
-    fi
-    if ! test -d $SSO_ROOT ; then
-        git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
-    fi
-
-    if test "$1" = testing ; then
-        docker run \
-               --detach \
-               --name=$ARVBOX_CONTAINER \
-               --privileged \
-               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-               --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
-               --volume=$SSO_ROOT:/usr/src/sso:rw \
-               --volume=$PG_DATA:/var/lib/postgresql:rw \
-               --volume=$VAR_DATA:/var/lib/arvados:rw \
-               --volume=$PASSENGER:/var/lib/passenger:rw \
-               --volume=$GEMS:/var/lib/gems:rw \
-               --volume=/var/lib/docker \
-               arvados/arvbox \
-               runsvdir /etc/tests-service
+    if test "$1" = demo ; then
+        if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
+            docker start $ARVBOX_CONTAINER
+        else
+            docker run \
+                   --name=$ARVBOX_CONTAINER \
+                   --privileged \
+                   arvados/arvbox-demo
+        fi
     else
-        docker run \
-               --detach \
-               --name=$ARVBOX_CONTAINER \
-               --privileged \
-               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-               --volume=$SSO_ROOT:/usr/src/sso:rw \
-               --volume=$PG_DATA:/var/lib/postgresql:rw \
-               --volume=$VAR_DATA:/var/lib/arvados:rw \
-               --volume=$PASSENGER:/var/lib/passenger:rw \
-               --volume=$GEMS:/var/lib/gems:rw \
-               --volume=/var/lib/docker \
-               arvados/arvbox
-        FF=/tmp/arvbox-fifo-$$
-        mkfifo $FF
-        docker logs -f $ARVBOX_CONTAINER > $FF &
-        LOGPID=$!
-        while read line ; do
-            echo $line
-            if echo $line | grep "Workbench is running at" >/dev/null ; then
-                kill $LOGPID
-            fi
-        done < $FF
-        rm $FF
-        echo
-        echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
+        if ! test -d $ARVADOS_ROOT ; then
+            git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
+        fi
+        if ! test -d $SSO_ROOT ; then
+            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
+        fi
+
+        if test "$1" = testing ; then
+            docker run \
+                   --detach \
+                   --name=$ARVBOX_CONTAINER \
+                   --privileged \
+                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+                   --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
+                   --volume=$SSO_ROOT:/usr/src/sso:rw \
+                   --volume=$PG_DATA:/var/lib/postgresql:rw \
+                   --volume=$VAR_DATA:/var/lib/arvados:rw \
+                   --volume=$PASSENGER:/var/lib/passenger:rw \
+                   --volume=$GEMS:/var/lib/gems:rw \
+                   arvados/arvbox-dev \
+                   runsvdir /etc/tests-service
+        else
+            docker run \
+                   --detach \
+                   --name=$ARVBOX_CONTAINER \
+                   --privileged \
+                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+                   --volume=$SSO_ROOT:/usr/src/sso:rw \
+                   --volume=$PG_DATA:/var/lib/postgresql:rw \
+                   --volume=$VAR_DATA:/var/lib/arvados:rw \
+                   --volume=$PASSENGER:/var/lib/passenger:rw \
+                   --volume=$GEMS:/var/lib/gems:rw \
+                   arvados/arvbox-dev
+            FF=/tmp/arvbox-fifo-$$
+            mkfifo $FF
+            docker logs -f $ARVBOX_CONTAINER > $FF &
+            LOGPID=$!
+            while read line ; do
+                echo $line
+                if echo $line | grep "Workbench is running at" >/dev/null ; then
+                    kill $LOGPID
+                fi
+            done < $FF
+            rm $FF
+            echo
+            echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
+        fi
     fi
 }
 
@@ -93,18 +102,29 @@ stop() {
     if docker ps -a --filter "status=running" |grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker stop $ARVBOX_CONTAINER
     fi
-    if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
-        docker rm --volumes=true $ARVBOX_CONTAINER
+    if test "$1" != demo ; then
+        if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
+            docker rm --volumes=true $ARVBOX_CONTAINER
+        fi
+    fi
+}
+
+build() {
+    docker build -t arvados/arvbox-base -f $ARVBOX_DOCKER/Dockerfile.base $ARVBOX_DOCKER
+    if test "$1" = demo ; then
+        docker build -t arvados/arvbox-demo -f $ARVBOX_DOCKER/Dockerfile.demo $ARVBOX_DOCKER
+    else
+        docker build -t arvados/arvbox-dev -f $ARVBOX_DOCKER/Dockerfile.dev $ARVBOX_DOCKER
     fi
 }
 
 case $1 in
     build)
-        docker build -t arvados/arvbox $ARVBOX_DOCKER
+        build $2
         ;;
 
     start|run)
-        run
+        run $2
         ;;
 
     sh*)
@@ -112,7 +132,7 @@ case $1 in
         ;;
 
     stop)
-        stop
+        stop $2
         ;;
 
     restart)
@@ -122,7 +142,7 @@ case $1 in
 
     reboot)
         stop
-        docker build -t arvados/arvbox $ARVBOX_DOCKER
+        build
         run
         ;;
 
@@ -180,6 +200,24 @@ case $1 in
         run testing
 
         shift
+
+        while ! docker exec -ti \
+                $ARVBOX_CONTAINER \
+                /etc/tests-service/runsu.sh \
+                psql -c'\du' ; do
+            sleep 1
+        done
+
+        docker exec -ti \
+               $ARVBOX_CONTAINER \
+               /etc/tests-service/runsu.sh \
+               /etc/service/sso/run-service --only-setup
+
+        docker exec -ti \
+               $ARVBOX_CONTAINER \
+               /etc/tests-service/runsu.sh \
+               /etc/service/api/run-service --only-setup
+
         docker exec -ti \
                $ARVBOX_CONTAINER \
                /etc/tests-service/runsu.sh \
diff --git a/arvbox/lib/arvbox/docker/Dockerfile b/arvbox/lib/arvbox/docker/Dockerfile.base
similarity index 56%
rename from arvbox/lib/arvbox/docker/Dockerfile
rename to arvbox/lib/arvbox/docker/Dockerfile.base
index 22ad5d2..9308d9c 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile
+++ b/arvbox/lib/arvbox/docker/Dockerfile.base
@@ -9,16 +9,10 @@ RUN apt-get update && \
     libpython-dev fuse libfuse-dev python-pip \
     pkg-config libattr1-dev python-llfuse python-pycurl \
     libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
-    libjson-perl python-virtualenv python3-virtualenv nginx \
-    gitolite3 lsof python-epydoc linkchecker xvfb iceweasel graphviz
+    libjson-perl nginx gitolite3 lsof python-epydoc graphviz
 
 RUN curl -sSL https://get.docker.com/ | sh
-
-RUN set -e && \
- PJS=phantomjs-1.9.7-linux-x86_64 && \
- curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
- tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
- ln -s ../$PJS/bin/phantomjs /usr/local/bin/
+VOLUME /var/lib/docker
 
 ADD runit-docker /root/runit-docker
 
@@ -30,9 +24,5 @@ ENV LD_PRELOAD /lib/runit-docker.so
 
 ADD fuse.conf /etc/
 
-ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
-ADD service /etc/service
-ADD tests-service /etc/tests-service
-
 # Start the supervisor.
 CMD ["runsvdir", "/etc/service"]
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.demo b/arvbox/lib/arvbox/docker/Dockerfile.demo
new file mode 100644
index 0000000..2ab772e
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/Dockerfile.demo
@@ -0,0 +1,16 @@
+FROM arvados/arvbox-base
+
+RUN cd /usr/src && \
+    git clone https://github.com/curoverse/arvados.git && \
+    git clone https://github.com/curoverse/sso-devise-omniauth-provider.git sso
+
+ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
+ADD service /etc/service
+
+RUN mkdir -p /var/lib/arvados
+RUN /etc/service/sso/run-service --only-deps
+RUN /etc/service/api/run-service --only-deps
+RUN /etc/service/workbench/run-service --only-deps
+RUN /etc/service/doc/run-service --only-deps
+RUN /etc/service/vm/run-service --only-deps
+RUN /etc/service/sdk/run-service
diff --git a/arvbox/lib/arvbox/docker/Dockerfile.dev b/arvbox/lib/arvbox/docker/Dockerfile.dev
new file mode 100644
index 0000000..cb2873b
--- /dev/null
+++ b/arvbox/lib/arvbox/docker/Dockerfile.dev
@@ -0,0 +1,15 @@
+FROM arvados/arvbox-base
+
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
+    python-virtualenv python3-virtualenv linkchecker xvfb iceweasel
+
+RUN set -e && \
+ PJS=phantomjs-1.9.7-linux-x86_64 && \
+ curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
+ tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
+ ln -s ../$PJS/bin/phantomjs /usr/local/bin/
+
+ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
+ADD service /etc/service
+ADD tests-service /etc/tests-service
diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index 25b1dd7..8d27990 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -33,4 +33,7 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     rm -r /var/log/nginx
     mkdir -p /var/log/nginx
     chown arvbox:arvbox -R /var/log/nginx
+
+    mkdir -p /tmp/crunch0 /tmp/crunch1
+    chown crunch:crunch -R /tmp/crunch0 /tmp/crunch1
 fi
diff --git a/arvbox/lib/arvbox/docker/crunch-setup.sh b/arvbox/lib/arvbox/docker/crunch-setup.sh
index c52b034..b6f5cda 100755
--- a/arvbox/lib/arvbox/docker/crunch-setup.sh
+++ b/arvbox/lib/arvbox/docker/crunch-setup.sh
@@ -14,9 +14,6 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/crunchstat"
 install bin/crunchstat /usr/local/bin
 
-mkdir -p /tmp/$1
-chown crunch:crunch -R /tmp/$1
-
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index b4ab386..6615736 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 exec 2>&1
-set -eux
+set -ex
 
 . /usr/local/lib/arvbox/common.sh
 
@@ -9,6 +9,13 @@ cd /usr/src/arvados/services/api
 export RAILS_ENV=development
 
 run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
 
 if ! test -s /var/lib/arvados/api_uuid_prefix ; then
     ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
@@ -92,7 +99,12 @@ rm -rf tmp
 
 bundle exec rake db:migrate
 
+set +u
+if test "$1" = "--only-setup" ; then
+    exit
+fi
+
 ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=3001 \
-    --runtime-dir=/var/lib/passenger \
-    --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
-    --ssl-certificate-key=/var/lib/arvados/self-signed.key
+                  --runtime-dir=/var/lib/passenger \
+                  --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+                  --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/lib/arvbox/docker/service/doc/run-service b/arvbox/lib/arvbox/docker/service/doc/run-service
index bb0f0e4..a64d5c8 100755
--- a/arvbox/lib/arvbox/docker/service/doc/run-service
+++ b/arvbox/lib/arvbox/docker/service/doc/run-service
@@ -1,11 +1,19 @@
 #!/bin/sh
 
 exec 2>&1
-set -eux
+set -ex
 
 . /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/arvados/doc
 run_bundler --without=development
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
+
 bundle exec rake generate baseurl=http://$localip:8000 arvados_api_host=$localip:3001 arvados_workbench_host=http://$localip
+
 exec bundle exec rake run
diff --git a/arvbox/lib/arvbox/docker/service/postgres/run b/arvbox/lib/arvbox/docker/service/postgres/run
index cf8250c..55d11a0 100755
--- a/arvbox/lib/arvbox/docker/service/postgres/run
+++ b/arvbox/lib/arvbox/docker/service/postgres/run
@@ -1,11 +1,18 @@
 #!/bin/sh
 
-HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
-HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
+flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 
-chown -R $HOSTUID:$HOSTGID /var/lib/postgresql
-chown -R $HOSTUID:$HOSTGID /var/run/postgresql
-chown -R $HOSTUID:$HOSTGID /etc/postgresql
-chown -R $HOSTUID:$HOSTGID /etc/ssl/private
+if test $(id arvbox -u) = 0 ; then
+    USER=postgres
+    GROUP=postgres
+else
+    USER=arvbox
+    GROUP=arvbox
+fi
 
-exec chpst -u:$HOSTUID:$HOSTGID $0-service
+chown -R $USER:$GROUP /var/lib/postgresql
+chown -R $USER:$GROUP /var/run/postgresql
+chown -R $USER:$GROUP /etc/postgresql
+chown -R $USER:$GROUP /etc/ssl/private
+
+exec su $USER $0-service
diff --git a/arvbox/lib/arvbox/docker/service/postgres/run-service b/arvbox/lib/arvbox/docker/service/postgres/run-service
index 6f72ba6..0a67d6c 100755
--- a/arvbox/lib/arvbox/docker/service/postgres/run-service
+++ b/arvbox/lib/arvbox/docker/service/postgres/run-service
@@ -5,7 +5,7 @@ set -eux
 
 if ! test -d /var/lib/postgresql/9.4/main ; then
     /usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main
-    sh -c "while ! createdb ; do sleep 1 ; done" &
+    sh -c "while ! psql -c'\du' >/dev/null 2>/dev/null ; do createdb ; sleep 1 ; done" &
 fi
 mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
 
diff --git a/arvbox/lib/arvbox/docker/service/ready/run-service b/arvbox/lib/arvbox/docker/service/ready/run-service
index e99570b..a26d47c 100755
--- a/arvbox/lib/arvbox/docker/service/ready/run-service
+++ b/arvbox/lib/arvbox/docker/service/ready/run-service
@@ -8,13 +8,13 @@ if ! [[ -d /tmp/arvbox-ready ]] ; then
    echo
    echo "Arvados-in-a-box starting"
    echo
-   echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes to download and"
+   echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes (or longer) to download and"
    echo "install dependencies.  Use \"arvbox log\" to monitor the progress of specific services."
    echo
    mkdir -p /tmp/arvbox-ready
 fi
 
-sleep 2
+sleep 3
 
 declare -A services
 services=(
@@ -56,6 +56,10 @@ elif ! which arv-get >/dev/null ; then
   waiting="$waiting sdk"
 fi
 
+if ! (ps x | grep -v grep | grep "crunch-dispatch") > /dev/null ; then
+    waiting="$waiting crunch-dispatch"
+fi
+
 if ! [[ -z "$waiting" ]] ; then
     if ps x | grep -v grep | grep "bundle install" > /dev/null; then
         gemcount=$(ls /var/lib/gems/ruby/2.1.0/gems 2>/dev/null | wc -l)
diff --git a/arvbox/lib/arvbox/docker/service/runsu.sh b/arvbox/lib/arvbox/docker/service/runsu.sh
index 329067f..32eccdf 100755
--- a/arvbox/lib/arvbox/docker/service/runsu.sh
+++ b/arvbox/lib/arvbox/docker/service/runsu.sh
@@ -8,7 +8,7 @@ flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
 export HOME=/var/lib/arvados
 
 if test -z "$1" ; then
-    exec su arvbox -c -- "exec $0-service"
+    exec sudo -u arvbox -- $0-service
 else
-    exec su arvbox --shell /bin/bash -- $@
+    exec sudo -u arvbox -- $@
 fi
diff --git a/arvbox/lib/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
index 26defa7..29951f5 100755
--- a/arvbox/lib/arvbox/docker/service/sso/run-service
+++ b/arvbox/lib/arvbox/docker/service/sso/run-service
@@ -1,15 +1,21 @@
 #!/bin/sh
 
 exec 2>&1
-set -eux
+set -ex
 
 . /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/sso
 export RAILS_ENV=development
-export GEM_HOME=/var/lib/gems
 
 run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
 
 if ! test -s /var/lib/arvados/sso_uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/sso_uuid_prefix
@@ -84,7 +90,12 @@ fi
 rm -rf tmp
 
 bundle exec rake db:migrate
-export HOME=/var/lib/passenger
+
+set +u
+if test "$1" = "--only-setup" ; then
+    exit
+fi
+
 exec bundle exec passenger start --port=3002 \
      --runtime-dir=/var/lib/passenger \
      --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
diff --git a/arvbox/lib/arvbox/docker/service/vm/run-service b/arvbox/lib/arvbox/docker/service/vm/run-service
index f6844d8..9b894ff 100755
--- a/arvbox/lib/arvbox/docker/service/vm/run-service
+++ b/arvbox/lib/arvbox/docker/service/vm/run-service
@@ -6,6 +6,13 @@ set -eux
 
 . /usr/local/lib/arvbox/common.sh
 
+cd /usr/src/arvados/services/login-sync
+run_bundler
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
 git config --system "credential.http://$localip:9001/.username" none
 git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
 
@@ -37,9 +44,6 @@ fi
 
 export ARVADOS_VIRTUAL_MACHINE_UUID
 
-cd /usr/src/arvados/services/login-sync
-run_bundler
-
 while true ; do
       bundle exec arvados-login-sync
       sleep 120
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run b/arvbox/lib/arvbox/docker/service/workbench/run
index 665d848..f121ce7 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run
+++ b/arvbox/lib/arvbox/docker/service/workbench/run
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-/etc/service/runsu.sh $0-service
+/etc/service/runsu.sh $0-service $1
 
 cd /usr/src/arvados/apps/workbench
 
@@ -9,5 +9,7 @@ rm -rf tmp
 mkdir tmp
 chown arvbox:arvbox tmp
 
-exec bundle exec passenger start --port 80 \
-     --user arvbox --runtime-dir=/var/lib/passenger
+if test "$1" != "--only-deps" ; then
+    exec bundle exec passenger start --port 80 \
+         --user arvbox --runtime-dir=/var/lib/passenger
+fi
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run-service b/arvbox/lib/arvbox/docker/service/workbench/run-service
index 359f1e9..dadd989 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run-service
+++ b/arvbox/lib/arvbox/docker/service/workbench/run-service
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 exec 2>&1
-set -eux
+set -ex
 
 .  /usr/local/lib/arvbox/common.sh
 
@@ -10,6 +10,13 @@ export RAILS_ENV=development
 export GEM_HOME=/var/lib/gems
 
 run_bundler --without=development
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger
+
+if test "$1" = "--only-deps" ; then
+    exit
+fi
+
+set -u
 
 if ! test -s /var/lib/arvados/workbench_secret_token ; then
   ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/workbench_secret_token
@@ -30,7 +37,3 @@ common:
   keep_web_url: http://$localip:25099/c=%{uuid_or_pdh}
   arvados_docsite: http://$localip:8000/
 EOF
-
-#bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
-
-bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger

commit dbd9987ed8c1514938216504d8ee1f13282f82c3
Merge: 514df69 91b7d5d
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Thu Jan 7 10:26:54 2016 -0500

    Merge branch '8080-arvbox' of git.curoverse.com:arvados-dev into 8080-arvbox
    
    Conflicts:
    	arvbox/lib/arvbox/docker/service/api/run-service

diff --cc arvbox/lib/arvbox/docker/createusers.sh
index 65c5463,ec02811..25b1dd7
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@@ -5,29 -5,17 +5,31 @@@ set -
  if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
      HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
      HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
++    FUSEGID=$(ls -nd /dev/fuse | sed 's/ */ /' | cut -d' ' -f5)
  
      mkdir -p /var/lib/arvados/git
 -    groupadd --gid $HOSTGID arvbox
 +    groupadd --gid $HOSTGID --non-unique arvbox
++    groupadd --gid $FUSEGID --non-unique fuse
      groupadd --gid $HOSTGID --non-unique git
      useradd --home-dir /var/lib/arvados \
              --uid $HOSTUID --gid $HOSTGID \
 -            --groups docker arvbox
 +            --non-unique \
-             --groups docker \
++            --groups docker,fuse \
 +            arvbox
      useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
--    useradd --groups docker crunch
++    useradd --groups docker,fuse crunch
 +
      chown arvbox:arvbox -R /usr/local
 +
 +    mkdir -p /var/lib/gems/ruby/2.1.0
 +    chown arvbox:arvbox -R /var/lib/gems/ruby/2.1.0
 +
      chown arvbox:arvbox -R /var/lib/nginx
 +
 +    # There's something weird about /var/log/nginx that prevents a non-root
 +    # arvbox user from writing to it, even after the ownership has been
 +    # changed.  As a workaround, delete it and recreate it.
 +
      rm -r /var/log/nginx
      mkdir -p /var/log/nginx
      chown arvbox:arvbox -R /var/log/nginx
diff --cc arvbox/lib/arvbox/docker/service/api/run-service
index ae7acd9,5b5bb1c..b4ab386
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@@ -55,9 -55,10 +55,9 @@@ if ! test -f /var/lib/arvados/api_datab
  fi
  database_pw=$(cat /var/lib/arvados/api_database_pw)
  
 -if ! test -f /var/lib/arvados/api_user_setup ; then
 +if ! (psql -c "\du" | grep "^ arvados ") >/dev/null ; then
-    psql -c "create user arvados with password '$database_pw'"
-    psql -c "ALTER USER arvados CREATEDB;"
+     psql -c "create user arvados with password '$database_pw'"
+     psql -c "ALTER USER arvados CREATEDB;"
 -    touch /var/lib/arvados/api_user_setup
  fi
  
  cat >config/database.yml <<EOF

commit 514df6920bdb330af068a20e1c88d971179cf124
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Thu Jan 7 10:05:29 2016 -0500

    8080: Set up ruby gems directory as part of createusers.sh.  Detect if database
    user already exists.  Try Docker overlay driver first before using default.

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 8e01457..e90dce1 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -127,7 +127,7 @@ case $1 in
         ;;
 
     ip|open)
-        IP=$(docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
+        IP=$(docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | head -n1 | tr -d ' ":,\n' | cut -c10-)
         if test $1 = 'ip' ; then
             echo $IP
         else
diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index ec02811..65c5463 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -7,15 +7,27 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
 
     mkdir -p /var/lib/arvados/git
-    groupadd --gid $HOSTGID arvbox
+    groupadd --gid $HOSTGID --non-unique arvbox
     groupadd --gid $HOSTGID --non-unique git
     useradd --home-dir /var/lib/arvados \
             --uid $HOSTUID --gid $HOSTGID \
-            --groups docker arvbox
+            --non-unique \
+            --groups docker \
+            arvbox
     useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
     useradd --groups docker crunch
+
     chown arvbox:arvbox -R /usr/local
+
+    mkdir -p /var/lib/gems/ruby/2.1.0
+    chown arvbox:arvbox -R /var/lib/gems/ruby/2.1.0
+
     chown arvbox:arvbox -R /var/lib/nginx
+
+    # There's something weird about /var/log/nginx that prevents a non-root
+    # arvbox user from writing to it, even after the ownership has been
+    # changed.  As a workaround, delete it and recreate it.
+
     rm -r /var/log/nginx
     mkdir -p /var/log/nginx
     chown arvbox:arvbox -R /var/log/nginx
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 56988fe..ae7acd9 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -55,10 +55,9 @@ if ! test -f /var/lib/arvados/api_database_pw ; then
 fi
 database_pw=$(cat /var/lib/arvados/api_database_pw)
 
-if ! test -f /var/lib/arvados/api_user_setup ; then
+if ! (psql -c "\du" | grep "^ arvados ") >/dev/null ; then
    psql -c "create user arvados with password '$database_pw'"
    psql -c "ALTER USER arvados CREATEDB;"
-   touch /var/lib/arvados/api_user_setup
 fi
 
 cat >config/database.yml <<EOF
diff --git a/arvbox/lib/arvbox/docker/service/docker/run b/arvbox/lib/arvbox/docker/service/docker/run
index 93834ee..83537d3 100755
--- a/arvbox/lib/arvbox/docker/service/docker/run
+++ b/arvbox/lib/arvbox/docker/service/docker/run
@@ -95,6 +95,6 @@ rm -rf /var/run/docker.pid
 read pid cmd state ppid pgrp session tty_nr tpgid rest < /proc/self/stat
 trap "kill -TERM -$pgrp; exit" EXIT TERM KILL SIGKILL SIGTERM SIGQUIT
 
-if ! docker daemon $DOCKER_DAEMON_ARGS ; then
-    docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
+if ! docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS ; then
+    docker daemon $DOCKER_DAEMON_ARGS
 fi
diff --git a/arvbox/lib/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
index ac19760..26defa7 100755
--- a/arvbox/lib/arvbox/docker/service/sso/run-service
+++ b/arvbox/lib/arvbox/docker/service/sso/run-service
@@ -38,10 +38,9 @@ if ! test -f /var/lib/arvados/sso_database_pw ; then
 fi
 database_pw=$(cat /var/lib/arvados/sso_database_pw)
 
-if ! test -f /var/lib/arvados/sso_user_setup ; then
+if ! (psql -c "\du" | grep "^ arvados_sso ") >/dev/null ; then
     psql -c "create user arvados_sso with password '$database_pw'"
     psql -c "ALTER USER arvados_sso CREATEDB;"
-    touch /var/lib/arvados/sso_user_setup
 fi
 
 cat >config/database.yml <<EOF

commit 91b7d5d373a317e341054a256aed46800c1eb918
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jan 6 20:56:41 2016 -0500

    8080: Fix up whitespace.

diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 6c09e4b..5b5bb1c 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -11,17 +11,17 @@ export RAILS_ENV=development
 run_bundler --without=development
 
 if ! test -s /var/lib/arvados/api_uuid_prefix ; then
-  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
+    ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
 fi
 uuid_prefix=$(cat /var/lib/arvados/api_uuid_prefix)
 
 if ! test -s /var/lib/arvados/api_secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/api_secret_token
+    ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/api_secret_token
 fi
 secret_token=$(cat /var/lib/arvados/api_secret_token)
 
 if ! test -s /var/lib/arvados/blob_signing_key ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/blob_signing_key
+    ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/blob_signing_key
 fi
 blob_signing_key=$(cat /var/lib/arvados/blob_signing_key)
 
@@ -56,9 +56,9 @@ fi
 database_pw=$(cat /var/lib/arvados/api_database_pw)
 
 if ! test -f /var/lib/arvados/api_user_setup ; then
-   psql -c "create user arvados with password '$database_pw'"
-   psql -c "ALTER USER arvados CREATEDB;"
-   touch /var/lib/arvados/api_user_setup
+    psql -c "create user arvados with password '$database_pw'"
+    psql -c "ALTER USER arvados CREATEDB;"
+    touch /var/lib/arvados/api_user_setup
 fi
 
 cat >config/database.yml <<EOF
@@ -94,6 +94,6 @@ rm -rf tmp
 bundle exec rake db:migrate
 
 ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=3001 \
-                  --runtime-dir=/var/lib/passenger \
-                  --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
-                  --ssl-certificate-key=/var/lib/arvados/self-signed.key
+    --runtime-dir=/var/lib/passenger \
+    --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+    --ssl-certificate-key=/var/lib/arvados/self-signed.key

commit d1fc5ede6afc84fca17b2125a9f6a775538a1cd4
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jan 6 20:56:03 2016 -0500

    8080: Do not override blob_signing_key (or git_repo_*) in test config.

diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 56988fe..6c09e4b 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -33,17 +33,17 @@ sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
 cat >config/application.yml <<EOF
 common:
   secret_token: $secret_token
-  blob_signing_key: $blob_signing_key
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
   sso_provider_url: "https://$localip:3002"
   workbench_address: "http://$localip/"
   sso_insecure: true
+development:
+  uuid_prefix: $uuid_prefix
   auto_admin_first_user: true
+  blob_signing_key: $blob_signing_key
   git_repo_ssh_base: "git@$localip:"
   git_repo_https_base: "http://$localip:9001/"
-development:
-  uuid_prefix: $uuid_prefix
 test:
   uuid_prefix: zzzzz
   git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"

commit 0accddbffff3a0e438c2a418ba2f82522e882f30
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jan 6 18:49:28 2016 -0500

    8080: Add fuse group, needed by services/login-sync tests.

diff --git a/arvbox/lib/arvbox/docker/Dockerfile b/arvbox/lib/arvbox/docker/Dockerfile
index 22ad5d2..0fc0247 100644
--- a/arvbox/lib/arvbox/docker/Dockerfile
+++ b/arvbox/lib/arvbox/docker/Dockerfile
@@ -29,6 +29,7 @@ RUN cd /root/runit-docker && \
 ENV LD_PRELOAD /lib/runit-docker.so
 
 ADD fuse.conf /etc/
+RUN addgroup --gid 888 fuse
 
 ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
 ADD service /etc/service

commit f794ddd776cc76fcfa918d78c94287158be3866e
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Jan 6 12:10:44 2016 -0500

    8080: Check container state before stop/rm

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index a7cc884..8e01457 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -90,9 +90,11 @@ run() {
 }
 
 stop() {
-    if docker ps -a |grep -E "\b$ARVBOX_CONTAINER\b" -q ; then
+    if docker ps -a --filter "status=running" |grep -E "$ARVBOX_CONTAINER$" -q ; then
         docker stop $ARVBOX_CONTAINER
-        docker rm -v $ARVBOX_CONTAINER
+    fi
+    if docker ps -a --filter "status=exited" |grep -E "$ARVBOX_CONTAINER$" -q ; then
+        docker rm --volumes=true $ARVBOX_CONTAINER
     fi
 }
 

commit f82f8be7ab3c1cde23f629cdff5cf71205483d04
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 16:16:58 2016 -0500

    8080: Fix greadlink test

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index 62bae22..a7cc884 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -3,7 +3,7 @@
 set -e
 
 if test -z "$ARVBOX_DOCKER" ; then
-    if $(which greadlink) >/dev/null ; then
+    if which greadlink >/dev/null ; then
         ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker)
     else
         ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker)

commit c6a7ecdcb77ae6808a62e42b8e42fdc8d9af52ad
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 16:03:39 2016 -0500

    8080: Touch api_user_setup

diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index da2dc16..56988fe 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -55,9 +55,10 @@ if ! test -f /var/lib/arvados/api_database_pw ; then
 fi
 database_pw=$(cat /var/lib/arvados/api_database_pw)
 
-if ! test -f /var/lib/arvados/api_database_setup ; then
+if ! test -f /var/lib/arvados/api_user_setup ; then
    psql -c "create user arvados with password '$database_pw'"
    psql -c "ALTER USER arvados CREATEDB;"
+   touch /var/lib/arvados/api_user_setup
 fi
 
 cat >config/database.yml <<EOF

commit 859f5ca6abf7198d2fa3c51f16ecd372cd30fc69
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 14:13:02 2016 -0500

    8080: Change git repo https base for testing.

diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 78e709c..da2dc16 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -47,6 +47,7 @@ development:
 test:
   uuid_prefix: zzzzz
   git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"
+  git_repo_https_base: "http://git.zzzzz.arvadosapi.com/"
 EOF
 
 if ! test -f /var/lib/arvados/api_database_pw ; then

commit 589d24cf1dde32ecdd2ccdc003bee7af809ed976
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 14:07:41 2016 -0500

    8080: Add back in passenger binary caching.  Separate "reset" and "destroy"
    commands.  Add "clone".

diff --git a/arvbox/bin/arvbox b/arvbox/bin/arvbox
index ff93ac7..62bae22 100755
--- a/arvbox/bin/arvbox
+++ b/arvbox/bin/arvbox
@@ -32,9 +32,10 @@ fi
 
 PG_DATA=$ARVBOX_DATA/postgres
 VAR_DATA=$ARVBOX_DATA/var
+PASSENGER=$ARVBOX_DATA/passenger
 GEMS=$ARVBOX_DATA/gems
 
-mkdir -p $PG_DATA $VAR_DATA $GEMS
+mkdir -p $PG_DATA $VAR_DATA $PASSENGER $GEMS
 
 run() {
     if ! test -d $ARVADOS_ROOT ; then
@@ -54,6 +55,7 @@ run() {
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
+               --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox \
@@ -67,6 +69,7 @@ run() {
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
+               --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox
@@ -122,7 +125,7 @@ case $1 in
         ;;
 
     ip|open)
-        IP=$(docker inspect arvbox | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
+        IP=$(docker inspect $ARVBOX_CONTAINER | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
         if test $1 = 'ip' ; then
             echo $IP
         else
@@ -132,7 +135,17 @@ case $1 in
 
     reset)
         if test "$2" != -f ; then
-            echo "WARNING!  This will delete all code and data inside your arvbox ($ARVBOX_DATA).  Use reset -f if you really mean it."
+            echo "WARNING!  This will delete your database, git and keep files inside your arvbox ($ARVBOX_DATA).  Use reset -f if you really mean it."
+            exit 1
+        fi
+        stop
+        rm -rf $ARVBOX_DATA/postgres
+        rm -rf $ARVBOX_DATA/var
+        ;;
+
+    destroy)
+        if test "$2" != -f ; then
+            echo "WARNING!  This will delete all code and data inside your arvbox ($ARVBOX_DATA).  Use destroy -f if you really mean it."
             exit 1
         fi
         stop
@@ -179,10 +192,20 @@ case $1 in
                "$@"
         ;;
 
+    clone)
+        if test -n "$3" ; then
+            cp -r $HOME/.arvbox/$2 $HOME/.arvbox/$3
+            echo "Created new arvbox $3"
+            echo "export ARVBOX_CONTAINER=$3"
+        else
+            echo "clone <from> <to>   clone an arvbox"
+        fi
+        ;;
+
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|run-tests|log|svrestart)"
+        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|destroy|run-tests|log|svrestart)"
         echo
         echo "build      build arvbox Docker image"
         echo "start|run  start $ARVBOX_CONTAINER container "
@@ -192,9 +215,11 @@ case $1 in
         echo "stop       stop arvbox container"
         echo "restart    stop, then run again"
         echo "reboot     stop, build arvbox Docker image, run"
-        echo "reset      delete all persistent data (be careful!)"
+        echo "reset      delete arvbox arvados data (be careful!)"
+        echo "destroy    delete all arvbox code and data (be careful!)"
         echo "run-tests  run run-tests.sh inside $ARVBOX_CONTAINER container"
         echo "log       <service> tail log of specified service"
         echo "svrestart <service> restart specified service inside arvbox"
+        echo "clone <from> <to>   clone an arvbox"
         ;;
 esac
diff --git a/arvbox/lib/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
index 5bd8621..78e709c 100755
--- a/arvbox/lib/arvbox/docker/service/api/run-service
+++ b/arvbox/lib/arvbox/docker/service/api/run-service
@@ -91,4 +91,7 @@ rm -rf tmp
 
 bundle exec rake db:migrate
 
-ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start -p3001 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
+ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start --port=3001 \
+                  --runtime-dir=/var/lib/passenger \
+                  --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+                  --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/lib/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
index 085b79a..ac19760 100755
--- a/arvbox/lib/arvbox/docker/service/sso/run-service
+++ b/arvbox/lib/arvbox/docker/service/sso/run-service
@@ -38,9 +38,10 @@ if ! test -f /var/lib/arvados/sso_database_pw ; then
 fi
 database_pw=$(cat /var/lib/arvados/sso_database_pw)
 
-if ! test -f /var/lib/arvados/sso_database_setup ; then
+if ! test -f /var/lib/arvados/sso_user_setup ; then
     psql -c "create user arvados_sso with password '$database_pw'"
     psql -c "ALTER USER arvados_sso CREATEDB;"
+    touch /var/lib/arvados/sso_user_setup
 fi
 
 cat >config/database.yml <<EOF
@@ -85,4 +86,7 @@ rm -rf tmp
 
 bundle exec rake db:migrate
 export HOME=/var/lib/passenger
-exec bundle exec passenger start -p3002 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
+exec bundle exec passenger start --port=3002 \
+     --runtime-dir=/var/lib/passenger \
+     --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem \
+     --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run b/arvbox/lib/arvbox/docker/service/workbench/run
index 7838220..665d848 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run
+++ b/arvbox/lib/arvbox/docker/service/workbench/run
@@ -9,4 +9,5 @@ rm -rf tmp
 mkdir tmp
 chown arvbox:arvbox tmp
 
-exec bundle exec passenger start --port 80 --user arvbox
+exec bundle exec passenger start --port 80 \
+     --user arvbox --runtime-dir=/var/lib/passenger
diff --git a/arvbox/lib/arvbox/docker/service/workbench/run-service b/arvbox/lib/arvbox/docker/service/workbench/run-service
index 09f929f..359f1e9 100755
--- a/arvbox/lib/arvbox/docker/service/workbench/run-service
+++ b/arvbox/lib/arvbox/docker/service/workbench/run-service
@@ -33,4 +33,4 @@ EOF
 
 #bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
 
-bundle exec passenger start --runtime-check-only
+bundle exec passenger start --runtime-check-only --runtime-dir=/var/lib/passenger

commit 0d421679b68f9916b83393f0bb2ef777c1b42d2d
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 13:28:42 2016 -0500

    8080: Workaround for weird nginx permission problem on /var/log/nginx.

diff --git a/arvbox/lib/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
index eda4161..ec02811 100755
--- a/arvbox/lib/arvbox/docker/createusers.sh
+++ b/arvbox/lib/arvbox/docker/createusers.sh
@@ -15,4 +15,8 @@ if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
     useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
     useradd --groups docker crunch
     chown arvbox:arvbox -R /usr/local
+    chown arvbox:arvbox -R /var/lib/nginx
+    rm -r /var/log/nginx
+    mkdir -p /var/log/nginx
+    chown arvbox:arvbox -R /var/log/nginx
 fi

commit 88b19c236a525a8e1532f967674580282319f58e
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 13:03:42 2016 -0500

    8080: Rearrange directory structure to be more suitable for packaging.

diff --git a/arvbox/arvbox b/arvbox/bin/arvbox
similarity index 93%
rename from arvbox/arvbox
rename to arvbox/bin/arvbox
index 596d20b..ff93ac7 100755
--- a/arvbox/arvbox
+++ b/arvbox/bin/arvbox
@@ -2,7 +2,13 @@
 
 set -e
 
-ARVBOX_SCRIPT=$(readlink -f $(dirname $0))
+if test -z "$ARVBOX_DOCKER" ; then
+    if $(which greadlink) >/dev/null ; then
+        ARVBOX_DOCKER=$(greadlink -f $(dirname $0)/../lib/arvbox/docker)
+    else
+        ARVBOX_DOCKER=$(readlink -f $(dirname $0)/../lib/arvbox/docker)
+    fi
+fi
 
 if test -z "$ARVBOX_CONTAINER" ; then
     ARVBOX_CONTAINER=arvbox
@@ -89,8 +95,7 @@ stop() {
 
 case $1 in
     build)
-        cd $ARVBOX_SCRIPT
-        docker build -t arvados/arvbox docker
+        docker build -t arvados/arvbox $ARVBOX_DOCKER
         ;;
 
     start|run)
@@ -112,8 +117,7 @@ case $1 in
 
     reboot)
         stop
-        cd $ARVBOX_SCRIPT
-        docker build -t arvados/arvbox docker
+        docker build -t arvados/arvbox $ARVBOX_DOCKER
         run
         ;;
 
diff --git a/arvbox/docker/Dockerfile b/arvbox/lib/arvbox/docker/Dockerfile
similarity index 100%
rename from arvbox/docker/Dockerfile
rename to arvbox/lib/arvbox/docker/Dockerfile
diff --git a/arvbox/docker/common.sh b/arvbox/lib/arvbox/docker/common.sh
similarity index 100%
rename from arvbox/docker/common.sh
rename to arvbox/lib/arvbox/docker/common.sh
diff --git a/arvbox/docker/createusers.sh b/arvbox/lib/arvbox/docker/createusers.sh
similarity index 100%
rename from arvbox/docker/createusers.sh
rename to arvbox/lib/arvbox/docker/createusers.sh
diff --git a/arvbox/docker/crunch-setup.sh b/arvbox/lib/arvbox/docker/crunch-setup.sh
similarity index 100%
rename from arvbox/docker/crunch-setup.sh
rename to arvbox/lib/arvbox/docker/crunch-setup.sh
diff --git a/arvbox/docker/fuse.conf b/arvbox/lib/arvbox/docker/fuse.conf
similarity index 100%
rename from arvbox/docker/fuse.conf
rename to arvbox/lib/arvbox/docker/fuse.conf
diff --git a/arvbox/docker/gitolite-setup.sh b/arvbox/lib/arvbox/docker/gitolite-setup.sh
similarity index 100%
rename from arvbox/docker/gitolite-setup.sh
rename to arvbox/lib/arvbox/docker/gitolite-setup.sh
diff --git a/arvbox/docker/gitolite.rc b/arvbox/lib/arvbox/docker/gitolite.rc
similarity index 100%
rename from arvbox/docker/gitolite.rc
rename to arvbox/lib/arvbox/docker/gitolite.rc
diff --git a/arvbox/docker/gitssh-setup.sh b/arvbox/lib/arvbox/docker/gitssh-setup.sh
similarity index 100%
rename from arvbox/docker/gitssh-setup.sh
rename to arvbox/lib/arvbox/docker/gitssh-setup.sh
diff --git a/arvbox/docker/keep-setup.sh b/arvbox/lib/arvbox/docker/keep-setup.sh
similarity index 100%
rename from arvbox/docker/keep-setup.sh
rename to arvbox/lib/arvbox/docker/keep-setup.sh
diff --git a/arvbox/docker/runit-docker/.gitignore b/arvbox/lib/arvbox/docker/runit-docker/.gitignore
similarity index 100%
rename from arvbox/docker/runit-docker/.gitignore
rename to arvbox/lib/arvbox/docker/runit-docker/.gitignore
diff --git a/arvbox/docker/runit-docker/LICENSE b/arvbox/lib/arvbox/docker/runit-docker/LICENSE
similarity index 100%
rename from arvbox/docker/runit-docker/LICENSE
rename to arvbox/lib/arvbox/docker/runit-docker/LICENSE
diff --git a/arvbox/docker/runit-docker/Makefile b/arvbox/lib/arvbox/docker/runit-docker/Makefile
similarity index 100%
rename from arvbox/docker/runit-docker/Makefile
rename to arvbox/lib/arvbox/docker/runit-docker/Makefile
diff --git a/arvbox/docker/runit-docker/README.md b/arvbox/lib/arvbox/docker/runit-docker/README.md
similarity index 100%
rename from arvbox/docker/runit-docker/README.md
rename to arvbox/lib/arvbox/docker/runit-docker/README.md
diff --git a/arvbox/docker/runit-docker/debian/changelog b/arvbox/lib/arvbox/docker/runit-docker/debian/changelog
similarity index 100%
rename from arvbox/docker/runit-docker/debian/changelog
rename to arvbox/lib/arvbox/docker/runit-docker/debian/changelog
diff --git a/arvbox/docker/runit-docker/debian/compat b/arvbox/lib/arvbox/docker/runit-docker/debian/compat
similarity index 100%
rename from arvbox/docker/runit-docker/debian/compat
rename to arvbox/lib/arvbox/docker/runit-docker/debian/compat
diff --git a/arvbox/docker/runit-docker/debian/control b/arvbox/lib/arvbox/docker/runit-docker/debian/control
similarity index 100%
rename from arvbox/docker/runit-docker/debian/control
rename to arvbox/lib/arvbox/docker/runit-docker/debian/control
diff --git a/arvbox/docker/runit-docker/debian/copyright b/arvbox/lib/arvbox/docker/runit-docker/debian/copyright
similarity index 100%
rename from arvbox/docker/runit-docker/debian/copyright
rename to arvbox/lib/arvbox/docker/runit-docker/debian/copyright
diff --git a/arvbox/docker/runit-docker/debian/docs b/arvbox/lib/arvbox/docker/runit-docker/debian/docs
similarity index 100%
rename from arvbox/docker/runit-docker/debian/docs
rename to arvbox/lib/arvbox/docker/runit-docker/debian/docs
diff --git a/arvbox/docker/runit-docker/debian/rules b/arvbox/lib/arvbox/docker/runit-docker/debian/rules
similarity index 100%
rename from arvbox/docker/runit-docker/debian/rules
rename to arvbox/lib/arvbox/docker/runit-docker/debian/rules
diff --git a/arvbox/docker/runit-docker/debian/source/format b/arvbox/lib/arvbox/docker/runit-docker/debian/source/format
similarity index 100%
rename from arvbox/docker/runit-docker/debian/source/format
rename to arvbox/lib/arvbox/docker/runit-docker/debian/source/format
diff --git a/arvbox/docker/runit-docker/runit-docker b/arvbox/lib/arvbox/docker/runit-docker/runit-docker
similarity index 100%
rename from arvbox/docker/runit-docker/runit-docker
rename to arvbox/lib/arvbox/docker/runit-docker/runit-docker
diff --git a/arvbox/docker/runit-docker/runit-docker.c b/arvbox/lib/arvbox/docker/runit-docker/runit-docker.c
similarity index 100%
rename from arvbox/docker/runit-docker/runit-docker.c
rename to arvbox/lib/arvbox/docker/runit-docker/runit-docker.c
diff --git a/arvbox/docker/service/workbench/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/api/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/workbench/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/api/log/main/.gitstub
diff --git a/arvbox/docker/service/workbench/log/run b/arvbox/lib/arvbox/docker/service/api/log/run
similarity index 100%
rename from arvbox/docker/service/workbench/log/run
rename to arvbox/lib/arvbox/docker/service/api/log/run
diff --git a/arvbox/docker/service/vm/run b/arvbox/lib/arvbox/docker/service/api/run
similarity index 100%
rename from arvbox/docker/service/vm/run
rename to arvbox/lib/arvbox/docker/service/api/run
diff --git a/arvbox/docker/service/api/run-service b/arvbox/lib/arvbox/docker/service/api/run-service
similarity index 100%
rename from arvbox/docker/service/api/run-service
rename to arvbox/lib/arvbox/docker/service/api/run-service
diff --git a/arvbox/docker/service/vm/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/crunch0/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/vm/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/crunch0/log/main/.gitstub
diff --git a/arvbox/docker/service/vm/log/run b/arvbox/lib/arvbox/docker/service/crunch0/log/run
similarity index 100%
rename from arvbox/docker/service/vm/log/run
rename to arvbox/lib/arvbox/docker/service/crunch0/log/run
diff --git a/arvbox/docker/service/sso/run b/arvbox/lib/arvbox/docker/service/crunch0/run
similarity index 100%
rename from arvbox/docker/service/sso/run
rename to arvbox/lib/arvbox/docker/service/crunch0/run
diff --git a/arvbox/docker/service/crunch0/run-service b/arvbox/lib/arvbox/docker/service/crunch0/run-service
similarity index 100%
rename from arvbox/docker/service/crunch0/run-service
rename to arvbox/lib/arvbox/docker/service/crunch0/run-service
diff --git a/arvbox/docker/service/sso/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/crunch1/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/sso/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/crunch1/log/main/.gitstub
diff --git a/arvbox/docker/service/sso/log/run b/arvbox/lib/arvbox/docker/service/crunch1/log/run
similarity index 100%
rename from arvbox/docker/service/sso/log/run
rename to arvbox/lib/arvbox/docker/service/crunch1/log/run
diff --git a/arvbox/docker/service/ready/run b/arvbox/lib/arvbox/docker/service/crunch1/run
similarity index 100%
rename from arvbox/docker/service/ready/run
rename to arvbox/lib/arvbox/docker/service/crunch1/run
diff --git a/arvbox/docker/service/crunch1/run-service b/arvbox/lib/arvbox/docker/service/crunch1/run-service
similarity index 100%
rename from arvbox/docker/service/crunch1/run-service
rename to arvbox/lib/arvbox/docker/service/crunch1/run-service
diff --git a/arvbox/docker/service/ssh/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/doc/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/ssh/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/doc/log/main/.gitstub
diff --git a/arvbox/docker/service/ssh/log/run b/arvbox/lib/arvbox/docker/service/doc/log/run
similarity index 100%
rename from arvbox/docker/service/ssh/log/run
rename to arvbox/lib/arvbox/docker/service/doc/log/run
diff --git a/arvbox/docker/service/keepweb/run b/arvbox/lib/arvbox/docker/service/doc/run
similarity index 100%
rename from arvbox/docker/service/keepweb/run
rename to arvbox/lib/arvbox/docker/service/doc/run
diff --git a/arvbox/docker/service/doc/run-service b/arvbox/lib/arvbox/docker/service/doc/run-service
similarity index 100%
rename from arvbox/docker/service/doc/run-service
rename to arvbox/lib/arvbox/docker/service/doc/run-service
diff --git a/arvbox/docker/service/sdk/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/docker/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/sdk/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/docker/log/main/.gitstub
diff --git a/arvbox/docker/service/sdk/log/run b/arvbox/lib/arvbox/docker/service/docker/log/run
similarity index 100%
rename from arvbox/docker/service/sdk/log/run
rename to arvbox/lib/arvbox/docker/service/docker/log/run
diff --git a/arvbox/docker/service/docker/run b/arvbox/lib/arvbox/docker/service/docker/run
similarity index 100%
rename from arvbox/docker/service/docker/run
rename to arvbox/lib/arvbox/docker/service/docker/run
diff --git a/arvbox/docker/service/postgres/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/git/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/postgres/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/git/log/main/.gitstub
diff --git a/arvbox/docker/service/postgres/log/run b/arvbox/lib/arvbox/docker/service/git/log/run
similarity index 100%
rename from arvbox/docker/service/postgres/log/run
rename to arvbox/lib/arvbox/docker/service/git/log/run
diff --git a/arvbox/docker/service/keepproxy/run b/arvbox/lib/arvbox/docker/service/git/run
similarity index 100%
rename from arvbox/docker/service/keepproxy/run
rename to arvbox/lib/arvbox/docker/service/git/run
diff --git a/arvbox/docker/service/git/run-service b/arvbox/lib/arvbox/docker/service/git/run-service
similarity index 100%
rename from arvbox/docker/service/git/run-service
rename to arvbox/lib/arvbox/docker/service/git/run-service
diff --git a/arvbox/docker/service/keepweb/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/githttp/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/keepweb/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/githttp/log/main/.gitstub
diff --git a/arvbox/docker/service/keepweb/log/run b/arvbox/lib/arvbox/docker/service/githttp/log/run
similarity index 100%
rename from arvbox/docker/service/keepweb/log/run
rename to arvbox/lib/arvbox/docker/service/githttp/log/run
diff --git a/arvbox/docker/service/keep1/run b/arvbox/lib/arvbox/docker/service/githttp/run
similarity index 100%
rename from arvbox/docker/service/keep1/run
rename to arvbox/lib/arvbox/docker/service/githttp/run
diff --git a/arvbox/docker/service/githttp/run-service b/arvbox/lib/arvbox/docker/service/githttp/run-service
similarity index 100%
rename from arvbox/docker/service/githttp/run-service
rename to arvbox/lib/arvbox/docker/service/githttp/run-service
diff --git a/arvbox/docker/service/keepproxy/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keep0/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/keepproxy/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keep0/log/main/.gitstub
diff --git a/arvbox/docker/service/keepproxy/log/run b/arvbox/lib/arvbox/docker/service/keep0/log/run
similarity index 100%
rename from arvbox/docker/service/keepproxy/log/run
rename to arvbox/lib/arvbox/docker/service/keep0/log/run
diff --git a/arvbox/docker/service/keep0/run b/arvbox/lib/arvbox/docker/service/keep0/run
similarity index 100%
rename from arvbox/docker/service/keep0/run
rename to arvbox/lib/arvbox/docker/service/keep0/run
diff --git a/arvbox/docker/service/keep0/run-service b/arvbox/lib/arvbox/docker/service/keep0/run-service
similarity index 100%
rename from arvbox/docker/service/keep0/run-service
rename to arvbox/lib/arvbox/docker/service/keep0/run-service
diff --git a/arvbox/docker/service/keep1/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keep1/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/keep1/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keep1/log/main/.gitstub
diff --git a/arvbox/docker/service/keep1/log/run b/arvbox/lib/arvbox/docker/service/keep1/log/run
similarity index 100%
rename from arvbox/docker/service/keep1/log/run
rename to arvbox/lib/arvbox/docker/service/keep1/log/run
diff --git a/arvbox/docker/service/githttp/run b/arvbox/lib/arvbox/docker/service/keep1/run
similarity index 100%
rename from arvbox/docker/service/githttp/run
rename to arvbox/lib/arvbox/docker/service/keep1/run
diff --git a/arvbox/docker/service/keep1/run-service b/arvbox/lib/arvbox/docker/service/keep1/run-service
similarity index 100%
rename from arvbox/docker/service/keep1/run-service
rename to arvbox/lib/arvbox/docker/service/keep1/run-service
diff --git a/arvbox/docker/service/keep0/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keepproxy/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/keep0/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keepproxy/log/main/.gitstub
diff --git a/arvbox/docker/service/keep0/log/run b/arvbox/lib/arvbox/docker/service/keepproxy/log/run
similarity index 100%
rename from arvbox/docker/service/keep0/log/run
rename to arvbox/lib/arvbox/docker/service/keepproxy/log/run
diff --git a/arvbox/docker/service/git/run b/arvbox/lib/arvbox/docker/service/keepproxy/run
similarity index 100%
rename from arvbox/docker/service/git/run
rename to arvbox/lib/arvbox/docker/service/keepproxy/run
diff --git a/arvbox/docker/service/keepproxy/run-service b/arvbox/lib/arvbox/docker/service/keepproxy/run-service
similarity index 100%
rename from arvbox/docker/service/keepproxy/run-service
rename to arvbox/lib/arvbox/docker/service/keepproxy/run-service
diff --git a/arvbox/docker/service/githttp/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/keepweb/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/githttp/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/keepweb/log/main/.gitstub
diff --git a/arvbox/docker/service/githttp/log/run b/arvbox/lib/arvbox/docker/service/keepweb/log/run
similarity index 100%
rename from arvbox/docker/service/githttp/log/run
rename to arvbox/lib/arvbox/docker/service/keepweb/log/run
diff --git a/arvbox/docker/service/doc/run b/arvbox/lib/arvbox/docker/service/keepweb/run
similarity index 100%
rename from arvbox/docker/service/doc/run
rename to arvbox/lib/arvbox/docker/service/keepweb/run
diff --git a/arvbox/docker/service/keepweb/run-service b/arvbox/lib/arvbox/docker/service/keepweb/run-service
similarity index 100%
rename from arvbox/docker/service/keepweb/run-service
rename to arvbox/lib/arvbox/docker/service/keepweb/run-service
diff --git a/arvbox/docker/service/logger b/arvbox/lib/arvbox/docker/service/logger
similarity index 100%
rename from arvbox/docker/service/logger
rename to arvbox/lib/arvbox/docker/service/logger
diff --git a/arvbox/docker/service/git/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/postgres/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/git/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/postgres/log/main/.gitstub
diff --git a/arvbox/docker/service/git/log/run b/arvbox/lib/arvbox/docker/service/postgres/log/run
similarity index 100%
rename from arvbox/docker/service/git/log/run
rename to arvbox/lib/arvbox/docker/service/postgres/log/run
diff --git a/arvbox/docker/service/postgres/run b/arvbox/lib/arvbox/docker/service/postgres/run
similarity index 100%
rename from arvbox/docker/service/postgres/run
rename to arvbox/lib/arvbox/docker/service/postgres/run
diff --git a/arvbox/docker/service/postgres/run-service b/arvbox/lib/arvbox/docker/service/postgres/run-service
similarity index 100%
rename from arvbox/docker/service/postgres/run-service
rename to arvbox/lib/arvbox/docker/service/postgres/run-service
diff --git a/arvbox/docker/service/crunch1/run b/arvbox/lib/arvbox/docker/service/ready/run
similarity index 100%
rename from arvbox/docker/service/crunch1/run
rename to arvbox/lib/arvbox/docker/service/ready/run
diff --git a/arvbox/docker/service/ready/run-service b/arvbox/lib/arvbox/docker/service/ready/run-service
similarity index 100%
rename from arvbox/docker/service/ready/run-service
rename to arvbox/lib/arvbox/docker/service/ready/run-service
diff --git a/arvbox/docker/service/runsu.sh b/arvbox/lib/arvbox/docker/service/runsu.sh
similarity index 100%
rename from arvbox/docker/service/runsu.sh
rename to arvbox/lib/arvbox/docker/service/runsu.sh
diff --git a/arvbox/docker/service/docker/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/sdk/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/docker/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/sdk/log/main/.gitstub
diff --git a/arvbox/docker/service/docker/log/run b/arvbox/lib/arvbox/docker/service/sdk/log/run
similarity index 100%
rename from arvbox/docker/service/docker/log/run
rename to arvbox/lib/arvbox/docker/service/sdk/log/run
diff --git a/arvbox/docker/service/sdk/run b/arvbox/lib/arvbox/docker/service/sdk/run
similarity index 100%
rename from arvbox/docker/service/sdk/run
rename to arvbox/lib/arvbox/docker/service/sdk/run
diff --git a/arvbox/docker/service/sdk/run-service b/arvbox/lib/arvbox/docker/service/sdk/run-service
similarity index 100%
rename from arvbox/docker/service/sdk/run-service
rename to arvbox/lib/arvbox/docker/service/sdk/run-service
diff --git a/arvbox/docker/service/doc/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/ssh/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/doc/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/ssh/log/main/.gitstub
diff --git a/arvbox/docker/service/doc/log/run b/arvbox/lib/arvbox/docker/service/ssh/log/run
similarity index 100%
rename from arvbox/docker/service/doc/log/run
rename to arvbox/lib/arvbox/docker/service/ssh/log/run
diff --git a/arvbox/docker/service/ssh/run b/arvbox/lib/arvbox/docker/service/ssh/run
similarity index 100%
rename from arvbox/docker/service/ssh/run
rename to arvbox/lib/arvbox/docker/service/ssh/run
diff --git a/arvbox/docker/service/crunch1/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/sso/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/crunch1/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/sso/log/main/.gitstub
diff --git a/arvbox/docker/service/crunch1/log/run b/arvbox/lib/arvbox/docker/service/sso/log/run
similarity index 100%
rename from arvbox/docker/service/crunch1/log/run
rename to arvbox/lib/arvbox/docker/service/sso/log/run
diff --git a/arvbox/docker/service/crunch0/run b/arvbox/lib/arvbox/docker/service/sso/run
similarity index 100%
rename from arvbox/docker/service/crunch0/run
rename to arvbox/lib/arvbox/docker/service/sso/run
diff --git a/arvbox/docker/service/sso/run-service b/arvbox/lib/arvbox/docker/service/sso/run-service
similarity index 100%
rename from arvbox/docker/service/sso/run-service
rename to arvbox/lib/arvbox/docker/service/sso/run-service
diff --git a/arvbox/docker/service/crunch0/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/vm/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/crunch0/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/vm/log/main/.gitstub
diff --git a/arvbox/docker/service/crunch0/log/run b/arvbox/lib/arvbox/docker/service/vm/log/run
similarity index 100%
rename from arvbox/docker/service/crunch0/log/run
rename to arvbox/lib/arvbox/docker/service/vm/log/run
diff --git a/arvbox/docker/service/api/run b/arvbox/lib/arvbox/docker/service/vm/run
similarity index 100%
rename from arvbox/docker/service/api/run
rename to arvbox/lib/arvbox/docker/service/vm/run
diff --git a/arvbox/docker/service/vm/run-service b/arvbox/lib/arvbox/docker/service/vm/run-service
similarity index 100%
rename from arvbox/docker/service/vm/run-service
rename to arvbox/lib/arvbox/docker/service/vm/run-service
diff --git a/arvbox/docker/service/api/log/main/.gitstub b/arvbox/lib/arvbox/docker/service/workbench/log/main/.gitstub
similarity index 100%
rename from arvbox/docker/service/api/log/main/.gitstub
rename to arvbox/lib/arvbox/docker/service/workbench/log/main/.gitstub
diff --git a/arvbox/docker/service/api/log/run b/arvbox/lib/arvbox/docker/service/workbench/log/run
similarity index 100%
rename from arvbox/docker/service/api/log/run
rename to arvbox/lib/arvbox/docker/service/workbench/log/run
diff --git a/arvbox/docker/service/workbench/run b/arvbox/lib/arvbox/docker/service/workbench/run
similarity index 100%
rename from arvbox/docker/service/workbench/run
rename to arvbox/lib/arvbox/docker/service/workbench/run
diff --git a/arvbox/docker/service/workbench/run-service b/arvbox/lib/arvbox/docker/service/workbench/run-service
similarity index 100%
rename from arvbox/docker/service/workbench/run-service
rename to arvbox/lib/arvbox/docker/service/workbench/run-service
diff --git a/arvbox/docker/tests-service/docker b/arvbox/lib/arvbox/docker/tests-service/docker
similarity index 100%
rename from arvbox/docker/tests-service/docker
rename to arvbox/lib/arvbox/docker/tests-service/docker
diff --git a/arvbox/docker/tests-service/logger b/arvbox/lib/arvbox/docker/tests-service/logger
similarity index 100%
rename from arvbox/docker/tests-service/logger
rename to arvbox/lib/arvbox/docker/tests-service/logger
diff --git a/arvbox/docker/tests-service/postgres b/arvbox/lib/arvbox/docker/tests-service/postgres
similarity index 100%
rename from arvbox/docker/tests-service/postgres
rename to arvbox/lib/arvbox/docker/tests-service/postgres
diff --git a/arvbox/docker/tests-service/runsu.sh b/arvbox/lib/arvbox/docker/tests-service/runsu.sh
similarity index 100%
rename from arvbox/docker/tests-service/runsu.sh
rename to arvbox/lib/arvbox/docker/tests-service/runsu.sh

commit 9b59c468d0776e6a7c35b19ed5d0b8f877572c7c
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Jan 5 12:04:01 2016 -0500

    8080: Now runs most services as regular use instead of root.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index f6e95cb..596d20b 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -24,12 +24,11 @@ if test -z "$SSO_ROOT" ; then
     SSO_ROOT=$ARVBOX_DATA/sso-devise-omniauth-provider
 fi
 
-PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
 VAR_DATA=$ARVBOX_DATA/var
 GEMS=$ARVBOX_DATA/gems
 
-mkdir -p $PASSENGER $PG_DATA $VAR_DATA $GEMS
+mkdir -p $PG_DATA $VAR_DATA $GEMS
 
 run() {
     if ! test -d $ARVADOS_ROOT ; then
@@ -49,7 +48,6 @@ run() {
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
-               --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox \
@@ -63,7 +61,6 @@ run() {
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
-               --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox
@@ -86,7 +83,7 @@ run() {
 stop() {
     if docker ps -a |grep -E "\b$ARVBOX_CONTAINER\b" -q ; then
         docker stop $ARVBOX_CONTAINER
-        docker rm $ARVBOX_CONTAINER
+        docker rm -v $ARVBOX_CONTAINER
     fi
 }
 
@@ -135,13 +132,13 @@ case $1 in
             exit 1
         fi
         stop
-        sudo rm -rf $ARVBOX_DATA
+        rm -rf $ARVBOX_DATA
         ;;
 
     log|svrestart)
         if test -n "$2" ; then
             if test "$1" = log ; then
-                docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
+                docker exec -ti $ARVBOX_CONTAINER tail -n100 /etc/service/$2/log/main/current
             fi
             if test "$1" = svrestart ; then
                 docker exec -ti $ARVBOX_CONTAINER sv restart $2
@@ -164,14 +161,17 @@ case $1 in
         run testing
 
         shift
-        docker exec -ti $ARVBOX_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
+        docker exec -ti \
+               $ARVBOX_CONTAINER \
+               /etc/tests-service/runsu.sh \
+               /usr/src/arvados-dev/jenkins/run-tests.sh \
                --leave-temp \
                WORKSPACE=/usr/src/arvados \
                VENVDIR=/var/lib/arvados/tests-venv \
                VENV3DIR=/var/lib/arvados/tests-venv3 \
                GOPATH=/var/lib/arvados/tests-gostuff \
-               GEMHOME=/var/lib/gems \
-               GEM_HOME=/var/lib/gems \
+               GEMHOME=/var/lib/gems/ruby/2.1.0 \
+               GEM_HOME=/var/lib/gems/ruby/2.1.0 \
                "$@"
         ;;
 
diff --git a/arvbox/docker/Dockerfile b/arvbox/docker/Dockerfile
index a9c3321..22ad5d2 100644
--- a/arvbox/docker/Dockerfile
+++ b/arvbox/docker/Dockerfile
@@ -29,12 +29,8 @@ RUN cd /root/runit-docker && \
 ENV LD_PRELOAD /lib/runit-docker.so
 
 ADD fuse.conf /etc/
-RUN useradd crunch && \
-    addgroup crunch docker && \
-    mkdir -p /var/lib/arvados/git && \
-    useradd --home-dir /var/lib/arvados/git git
 
-ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh /root/
+ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh createusers.sh /usr/local/lib/arvbox/
 ADD service /etc/service
 ADD tests-service /etc/tests-service
 
diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index 3bf3293..bc328f7 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -8,8 +8,8 @@ run_bundler() {
     else
         frozen=""
     fi
-    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment $frozen "$@" ; then
-        flock /var/lib/arvados/gems.lock bundle install --no-deployment $frozen "$@"
+    if ! flock /var/lib/arvados/gems.lock bundle install --path $GEM_HOME --local --no-deployment $frozen "$@" ; then
+        flock /var/lib/arvados/gems.lock bundle install --path $GEM_HOME --no-deployment $frozen "$@"
     fi
 }
 
diff --git a/arvbox/docker/createusers.sh b/arvbox/docker/createusers.sh
new file mode 100755
index 0000000..eda4161
--- /dev/null
+++ b/arvbox/docker/createusers.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+if ! grep "^arvbox:" /etc/passwd >/dev/null 2>/dev/null ; then
+    HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
+    HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
+
+    mkdir -p /var/lib/arvados/git
+    groupadd --gid $HOSTGID arvbox
+    groupadd --gid $HOSTGID --non-unique git
+    useradd --home-dir /var/lib/arvados \
+            --uid $HOSTUID --gid $HOSTGID \
+            --groups docker arvbox
+    useradd --home-dir /var/lib/arvados/git --uid $HOSTUID --gid $HOSTGID --non-unique git
+    useradd --groups docker crunch
+    chown arvbox:arvbox -R /usr/local
+fi
diff --git a/arvbox/docker/crunch-setup.sh b/arvbox/docker/crunch-setup.sh
index 4b70250..c52b034 100755
--- a/arvbox/docker/crunch-setup.sh
+++ b/arvbox/docker/crunch-setup.sh
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
diff --git a/arvbox/docker/keep-setup.sh b/arvbox/docker/keep-setup.sh
index a4f8832..529360e 100755
--- a/arvbox/docker/keep-setup.sh
+++ b/arvbox/docker/keep-setup.sh
@@ -4,7 +4,7 @@ exec 2>&1
 sleep 2
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
deleted file mode 100755
index 68019e7..0000000
--- a/arvbox/docker/service/api/run
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-cd /usr/src/arvados/services/api
-export RAILS_ENV=development
-
-run_bundler --without=development
-
-if ! test -s /var/lib/arvados/api_uuid_prefix ; then
-  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
-fi
-uuid_prefix=$(cat /var/lib/arvados/api_uuid_prefix)
-
-if ! test -s /var/lib/arvados/api_secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/api_secret_token
-fi
-secret_token=$(cat /var/lib/arvados/api_secret_token)
-
-if ! test -s /var/lib/arvados/blob_signing_key ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/blob_signing_key
-fi
-blob_signing_key=$(cat /var/lib/arvados/blob_signing_key)
-
-# self signed key will be created by SSO server script.
-test -s /var/lib/arvados/self-signed.key
-
-sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
-
-cat >config/application.yml <<EOF
-common:
-  secret_token: $secret_token
-  blob_signing_key: $blob_signing_key
-  sso_app_secret: $sso_app_secret
-  sso_app_id: arvados-server
-  sso_provider_url: "https://$localip:3002"
-  workbench_address: "http://$localip/"
-  sso_insecure: true
-  auto_admin_first_user: true
-  git_repo_ssh_base: "git@$localip:"
-  git_repo_https_base: "http://$localip:9001/"
-development:
-  uuid_prefix: $uuid_prefix
-test:
-  uuid_prefix: zzzzz
-  git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"
-EOF
-
-if ! test -f /var/lib/arvados/api_database_pw ; then
-    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/api_database_pw
-fi
-database_pw=$(cat /var/lib/arvados/api_database_pw)
-
-if ! test -f /var/lib/arvados/api_database_setup ; then
-   su postgres -c "psql -c \"create user arvados with password '$database_pw'\""
-   su postgres -c "psql -c \"ALTER USER arvados CREATEDB;\""
-fi
-
-cat >config/database.yml <<EOF
-development:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_development
-  username: arvados
-  password: $database_pw
-  host: localhost
-  template: template0
-test:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_test
-  username: arvados
-  password: $database_pw
-  host: localhost
-  template: template0
-EOF
-
-if ! test -f /var/lib/arvados/api_database_setup ; then
-   bundle exec rake db:setup
-   touch /var/lib/arvados/api_database_setup
-fi
-
-if ! test -s /var/lib/arvados/superuser_token ; then
-    bundle exec ./script/create_superuser_token.rb > /var/lib/arvados/superuser_token
-fi
-
-rm -rf tmp
-
-bundle exec rake db:migrate
-ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/api/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run-service
similarity index 87%
copy from arvbox/docker/service/api/run
copy to arvbox/docker/service/api/run-service
index 68019e7..5bd8621 100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/arvados/services/api
 export RAILS_ENV=development
@@ -55,8 +55,8 @@ fi
 database_pw=$(cat /var/lib/arvados/api_database_pw)
 
 if ! test -f /var/lib/arvados/api_database_setup ; then
-   su postgres -c "psql -c \"create user arvados with password '$database_pw'\""
-   su postgres -c "psql -c \"ALTER USER arvados CREATEDB;\""
+   psql -c "create user arvados with password '$database_pw'"
+   psql -c "ALTER USER arvados CREATEDB;"
 fi
 
 cat >config/database.yml <<EOF
@@ -90,4 +90,5 @@ fi
 rm -rf tmp
 
 bundle exec rake db:migrate
-ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
+
+ARVADOS_WEBSOCKETS=1 exec bundle exec passenger start -p3001 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/docker/service/crunch0/run b/arvbox/docker/service/crunch0/run
deleted file mode 100755
index dd864a0..0000000
--- a/arvbox/docker/service/crunch0/run
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-exec /root/crunch-setup.sh crunch0
diff --git a/arvbox/docker/service/crunch0/run b/arvbox/docker/service/crunch0/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/crunch0/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/crunch0/run-service b/arvbox/docker/service/crunch0/run-service
new file mode 100755
index 0000000..fa3a73a
--- /dev/null
+++ b/arvbox/docker/service/crunch0/run-service
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/local/lib/arvbox/crunch-setup.sh crunch0
diff --git a/arvbox/docker/service/crunch1/run b/arvbox/docker/service/crunch1/run
deleted file mode 100755
index d7583e5..0000000
--- a/arvbox/docker/service/crunch1/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-sleep 1
-exec /root/crunch-setup.sh crunch1
diff --git a/arvbox/docker/service/crunch1/run b/arvbox/docker/service/crunch1/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/crunch1/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/crunch1/run-service b/arvbox/docker/service/crunch1/run-service
new file mode 100755
index 0000000..6430e9c
--- /dev/null
+++ b/arvbox/docker/service/crunch1/run-service
@@ -0,0 +1,3 @@
+#!/bin/sh
+sleep 1
+exec /usr/local/lib/arvbox/crunch-setup.sh crunch1
diff --git a/arvbox/docker/service/doc/run b/arvbox/docker/service/doc/run
deleted file mode 100755
index e12fdb4..0000000
--- a/arvbox/docker/service/doc/run
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-cd /usr/src/arvados/doc
-run_bundler --without=development
-bundle exec rake generate baseurl=http://$localip:8000 arvados_api_host=$localip:3001 arvados_workbench_host=http://$localip
-exec bundle exec rake run
diff --git a/arvbox/docker/service/doc/run b/arvbox/docker/service/doc/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/doc/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/doc/run b/arvbox/docker/service/doc/run-service
similarity index 87%
copy from arvbox/docker/service/doc/run
copy to arvbox/docker/service/doc/run-service
index e12fdb4..bb0f0e4 100755
--- a/arvbox/docker/service/doc/run
+++ b/arvbox/docker/service/doc/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/arvados/doc
 run_bundler --without=development
diff --git a/arvbox/docker/service/git/run b/arvbox/docker/service/git/run
deleted file mode 100755
index c87c25b..0000000
--- a/arvbox/docker/service/git/run
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-mkdir -p /var/lib/arvados/git
-
-export ARVADOS_API_HOST=$localip:3001
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
-
-if ! test -f /var/lib/arvados/gitolite-setup ; then
-   cp -r /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
-
-   chown -R git:git ~git
-
-   su git -c "/var/lib/arvados/git/gitssh-setup.sh"
-   su git -c "/var/lib/arvados/git/gitolite-setup.sh"
-
-   touch /var/lib/arvados/gitolite-setup
-else
-    chown -R git:git ~git
-    su git -c "/var/lib/arvados/git/gitssh-setup.sh"
-fi
-
-prefix=$(arv --format=uuid user current | cut -d- -f1)
-
-if ! test -s /var/lib/arvados/arvados-git-uuid ; then
-    repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
-    echo $repo_uuid > /var/lib/arvados/arvados-git-uuid
-fi
-
-repo_uuid=$(cat /var/lib/arvados/arvados-git-uuid)
-
-if ! test -s /var/lib/arvados/arvados-git-link-uuid ; then
-    all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
-
-    set +e
-    read -rd $'\000' newlink <<EOF
-{
- "tail_uuid":"$all_users_group_uuid",
- "head_uuid":"$repo_uuid",
- "link_class":"permission",
- "name":"can_read"
-}
-EOF
-    set -e
-    link_uuid=$(arv --format=uuid link create --link "$newlink")
-    echo $link_uuid > /var/lib/arvados/arvados-git-link-uuid
-fi
-
-if ! test -d /var/lib/arvados/git/repositories/$repo_uuid.git ; then
-    git clone --bare /usr/src/arvados /var/lib/arvados/git/repositories/$repo_uuid.git
-else
-    git --git-dir=/var/lib/arvados/git/repositories/$repo_uuid.git fetch -f /usr/src/arvados master:master
-fi
-
-cd /usr/src/arvados/services/api
-export RAILS_ENV=development
-
-git_user_key=$(cat ~git/.ssh/id_rsa.pub)
-
-cat > config/arvados-clients.yml <<EOF
-development:
-  gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
-  gitolite_tmp: /var/lib/arvados/git
-  arvados_api_host: $localip:3001
-  arvados_api_token: "$ARVADOS_API_TOKEN"
-  arvados_api_host_insecure: true
-  gitolite_arvados_git_user_key: "$git_user_key"
-EOF
-
-while true ; do
-    su git -c "bundle exec script/arvados-git-sync.rb development"
-    sleep 120
-done
diff --git a/arvbox/docker/service/git/run b/arvbox/docker/service/git/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/git/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/git/run b/arvbox/docker/service/git/run-service
similarity index 89%
copy from arvbox/docker/service/git/run
copy to arvbox/docker/service/git/run-service
index c87c25b..4f08040 100755
--- a/arvbox/docker/service/git/run
+++ b/arvbox/docker/service/git/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/git
 
@@ -12,7 +12,7 @@ export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 if ! test -f /var/lib/arvados/gitolite-setup ; then
-   cp -r /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
+   cp -r /usr/local/lib/arvbox/gitolite-setup.sh /usr/local/lib/arvbox/gitssh-setup.sh /usr/local/lib/arvbox/gitolite.rc /var/lib/arvados/git/
 
    chown -R git:git ~git
 
@@ -73,6 +73,6 @@ development:
 EOF
 
 while true ; do
-    su git -c "bundle exec script/arvados-git-sync.rb development"
+    bundle exec script/arvados-git-sync.rb development
     sleep 120
 done
diff --git a/arvbox/docker/service/githttp/run b/arvbox/docker/service/githttp/run
deleted file mode 100755
index 390727d..0000000
--- a/arvbox/docker/service/githttp/run
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-mkdir -p /var/lib/arvados/gostuff
-cd /var/lib/arvados/gostuff
-
-export GOPATH=$PWD
-mkdir -p "$GOPATH/src/git.curoverse.com"
-ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
-install bin/arv-git-httpd /usr/local/bin
-
-export ARVADOS_API_HOST=$localip:3001
-export ARVADOS_API_HOST_INSECURE=1
-export GITOLITE_HTTP_HOME=/var/lib/arvados/git
-export GL_BYPASS_ACCESS_CHECKS=1
-export PATH="$PATH:/var/lib/arvados/git/bin"
-cd ~git
-exec chpst -u git:git /usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
diff --git a/arvbox/docker/service/githttp/run b/arvbox/docker/service/githttp/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/githttp/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/githttp/run b/arvbox/docker/service/githttp/run-service
similarity index 73%
copy from arvbox/docker/service/githttp/run
copy to arvbox/docker/service/githttp/run-service
index 390727d..7e4f1de 100755
--- a/arvbox/docker/service/githttp/run
+++ b/arvbox/docker/service/githttp/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
@@ -20,4 +20,4 @@ export GITOLITE_HTTP_HOME=/var/lib/arvados/git
 export GL_BYPASS_ACCESS_CHECKS=1
 export PATH="$PATH:/var/lib/arvados/git/bin"
 cd ~git
-exec chpst -u git:git /usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
+/usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
diff --git a/arvbox/docker/service/keep0/run b/arvbox/docker/service/keep0/run
deleted file mode 100755
index aa5b69c..0000000
--- a/arvbox/docker/service/keep0/run
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-exec /root/keep-setup.sh keep0 25107
\ No newline at end of file
diff --git a/arvbox/docker/service/keep0/run b/arvbox/docker/service/keep0/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/keep0/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/keep0/run-service b/arvbox/docker/service/keep0/run-service
new file mode 100755
index 0000000..7f87b88
--- /dev/null
+++ b/arvbox/docker/service/keep0/run-service
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/local/lib/arvbox/keep-setup.sh keep0 25107
diff --git a/arvbox/docker/service/keep1/run b/arvbox/docker/service/keep1/run
deleted file mode 100755
index 8b0d907..0000000
--- a/arvbox/docker/service/keep1/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-sleep 1
-exec /root/keep-setup.sh keep1 25108
diff --git a/arvbox/docker/service/keep1/run b/arvbox/docker/service/keep1/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/keep1/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/keep1/run-service b/arvbox/docker/service/keep1/run-service
new file mode 100755
index 0000000..a44c8dd
--- /dev/null
+++ b/arvbox/docker/service/keep1/run-service
@@ -0,0 +1,3 @@
+#!/bin/sh
+sleep 1
+exec /usr/local/lib/arvbox/keep-setup.sh keep1 25108
diff --git a/arvbox/docker/service/keepproxy/run b/arvbox/docker/service/keepproxy/run
deleted file mode 100755
index fe52d6d..0000000
--- a/arvbox/docker/service/keepproxy/run
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-exec 2>&1
-sleep 2
-set -eux
-
-. /root/common.sh
-
-mkdir -p /var/lib/arvados/gostuff
-cd /var/lib/arvados/gostuff
-
-export GOPATH=$PWD
-mkdir -p "$GOPATH/src/git.curoverse.com"
-ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keepproxy"
-install bin/keepproxy /usr/local/bin
-
-export ARVADOS_API_HOST=$localip:3001
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
-
-if test -s /var/lib/arvados/keepproxy-uuid ; then
-    keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
-    set +e
-    read -rd $'\000' keepservice <<EOF
-{
- "service_host":"$localip",
- "service_port":25100,
- "service_type":"proxy"
-}
-EOF
-   set -e
-   arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
-else
-    set +e
-    read -rd $'\000' keepservice <<EOF
-{
- "service_host":"$localip",
- "service_port":25100,
- "service_ssl_flag":false,
- "service_type":"proxy"
-}
-EOF
-    set -e
-    UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
-    echo $UUID > /var/lib/arvados/keepproxy-uuid
-fi
-
-exec /usr/local/bin/keepproxy -listen=":25100"
diff --git a/arvbox/docker/service/keepproxy/run b/arvbox/docker/service/keepproxy/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/keepproxy/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/keepproxy/run b/arvbox/docker/service/keepproxy/run-service
similarity index 97%
copy from arvbox/docker/service/keepproxy/run
copy to arvbox/docker/service/keepproxy/run-service
index fe52d6d..87b46d9 100755
--- a/arvbox/docker/service/keepproxy/run
+++ b/arvbox/docker/service/keepproxy/run-service
@@ -4,7 +4,7 @@ exec 2>&1
 sleep 2
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
diff --git a/arvbox/docker/service/keepweb/run b/arvbox/docker/service/keepweb/run
deleted file mode 100755
index ad808fe..0000000
--- a/arvbox/docker/service/keepweb/run
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-mkdir -p /var/lib/arvados/gostuff
-cd /var/lib/arvados/gostuff
-
-export GOPATH=$PWD
-mkdir -p "$GOPATH/src/git.curoverse.com"
-ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keep-web"
-install bin/keep-web /usr/local/bin
-
-export ARVADOS_API_HOST=$localip:3001
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
-
-exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
diff --git a/arvbox/docker/service/keepweb/run b/arvbox/docker/service/keepweb/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/keepweb/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/keepweb/run b/arvbox/docker/service/keepweb/run-service
similarity index 93%
copy from arvbox/docker/service/keepweb/run
copy to arvbox/docker/service/keepweb/run-service
index ad808fe..38c31f0 100755
--- a/arvbox/docker/service/keepweb/run
+++ b/arvbox/docker/service/keepweb/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
diff --git a/arvbox/docker/service/postgres/run b/arvbox/docker/service/postgres/run
index 2b7c497..cf8250c 100755
--- a/arvbox/docker/service/postgres/run
+++ b/arvbox/docker/service/postgres/run
@@ -1,13 +1,11 @@
 #!/bin/sh
 
-exec 2>&1
-set -eux
+HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
+HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
 
-chown postgres:postgres -R /var/lib/postgresql
-if ! test -d /var/lib/postgresql/9.4/main ; then
-   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
-fi
-mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
-chown postgres:postgres -R /var/run/postgresql
+chown -R $HOSTUID:$HOSTGID /var/lib/postgresql
+chown -R $HOSTUID:$HOSTGID /var/run/postgresql
+chown -R $HOSTUID:$HOSTGID /etc/postgresql
+chown -R $HOSTUID:$HOSTGID /etc/ssl/private
 
-exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
+exec chpst -u:$HOSTUID:$HOSTGID $0-service
diff --git a/arvbox/docker/service/postgres/run-service b/arvbox/docker/service/postgres/run-service
new file mode 100755
index 0000000..6f72ba6
--- /dev/null
+++ b/arvbox/docker/service/postgres/run-service
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+exec 2>&1
+set -eux
+
+if ! test -d /var/lib/postgresql/9.4/main ; then
+    /usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main
+    sh -c "while ! createdb ; do sleep 1 ; done" &
+fi
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+
+rm -f /var/lib/postgresql/9.4/main/postmaster.pid
+
+exec /usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
deleted file mode 100755
index 41732fd..0000000
--- a/arvbox/docker/service/ready/run
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/bin/bash
-
-. /root/common.sh
-
-set -eu
-
-if ! [[ -d /root/ready ]] ; then
-   echo
-   echo "Arvados-in-a-box starting"
-   echo
-   echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes to download and"
-   echo "install dependencies.  Use \"arvbox log\" to monitor the progress of specific services."
-   echo
-   mkdir -p /root/ready
-fi
-
-sleep 2
-
-declare -A services
-services=(
-  [workbench]=80
-  [api]=3001
-  [sso]=3002
-  [githttp]=9001
-  [keepweb]=25099
-  [keepproxy]=25100
-  [keep0]=25107
-  [keep1]=25108
-  [ssh]=22
-  [doc]=8000
-)
-
-waiting=""
-
-. /root/common.sh
-
-for s in "${!services[@]}"
-do
-  if ! [[ -f /root/ready/$s ]] ; then
-    if nc -z $localip ${services[$s]} ; then
-      echo "$s is ready at $localip:${services[$s]}"
-      touch /root/ready/$s
-    else
-      waiting="$waiting $s"
-    fi
-  fi
-done
-
-if ! docker version >/dev/null 2>/dev/null ; then
-  waiting="$waiting docker"
-fi
-
-if ! which arv >/dev/null ; then
-  waiting="$waiting ruby_sdk"
-fi
-
-if ! which arv-get >/dev/null ; then
-  waiting="$waiting python_sdk"
-fi
-
-if ! [[ -z "$waiting" ]] ; then
-    if ps x | grep -v grep | grep "bundle install" > /dev/null; then
-        gemcount=$(ls /var/lib/gems/gems 2>/dev/null | wc -l)
-
-        gemlockcount=0
-        for l in /usr/src/arvados/services/api/Gemfile.lock \
-                     /usr/src/arvados/apps/workbench/Gemfile.lock \
-                     /usr/src/sso/Gemfile.lock ; do
-            gc=$(cat $l \
-                        | grep -vE "(GEM|PLATFORMS|DEPENDENCIES|$^|remote:|specs:)" \
-                        | sed 's/^ *//' | sed 's/(.*)//' | sed 's/ *$//' | sort | uniq | wc -l)
-            gemlockcount=$(($gemlockcount + $gc))
-        done
-        waiting="$waiting (installing ruby gems $gemcount/$gemlockcount)"
-    fi
-
-    if ps x | grep -v grep | grep "c++.*/var/lib/passenger" > /dev/null ; then
-        waiting="$waiting (compiling passenger)"
-    fi
-
-    if ps x | grep -v grep | grep "pip install" > /dev/null; then
-        waiting="$waiting (installing python packages)"
-    fi
-    echo "    Waiting for$waiting ..."
-    exit 1
-fi
-
-echo
-echo "Your Arvados-in-a-box is ready!"
-echo "Workbench is running at http://$localip"
-
-rm -r /root/ready
-
-sv stop ready >/dev/null
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/ready/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run-service
similarity index 82%
copy from arvbox/docker/service/ready/run
copy to arvbox/docker/service/ready/run-service
index 41732fd..e99570b 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run-service
@@ -1,17 +1,17 @@
 #!/bin/bash
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 set -eu
 
-if ! [[ -d /root/ready ]] ; then
+if ! [[ -d /tmp/arvbox-ready ]] ; then
    echo
    echo "Arvados-in-a-box starting"
    echo
    echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes to download and"
    echo "install dependencies.  Use \"arvbox log\" to monitor the progress of specific services."
    echo
-   mkdir -p /root/ready
+   mkdir -p /tmp/arvbox-ready
 fi
 
 sleep 2
@@ -32,14 +32,14 @@ services=(
 
 waiting=""
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 for s in "${!services[@]}"
 do
-  if ! [[ -f /root/ready/$s ]] ; then
+  if ! [[ -f /tmp/arvbox-ready/$s ]] ; then
     if nc -z $localip ${services[$s]} ; then
       echo "$s is ready at $localip:${services[$s]}"
-      touch /root/ready/$s
+      touch /tmp/arvbox-ready/$s
     else
       waiting="$waiting $s"
     fi
@@ -51,16 +51,14 @@ if ! docker version >/dev/null 2>/dev/null ; then
 fi
 
 if ! which arv >/dev/null ; then
-  waiting="$waiting ruby_sdk"
-fi
-
-if ! which arv-get >/dev/null ; then
-  waiting="$waiting python_sdk"
+  waiting="$waiting sdk"
+elif ! which arv-get >/dev/null ; then
+  waiting="$waiting sdk"
 fi
 
 if ! [[ -z "$waiting" ]] ; then
     if ps x | grep -v grep | grep "bundle install" > /dev/null; then
-        gemcount=$(ls /var/lib/gems/gems 2>/dev/null | wc -l)
+        gemcount=$(ls /var/lib/gems/ruby/2.1.0/gems 2>/dev/null | wc -l)
 
         gemlockcount=0
         for l in /usr/src/arvados/services/api/Gemfile.lock \
@@ -89,6 +87,6 @@ echo
 echo "Your Arvados-in-a-box is ready!"
 echo "Workbench is running at http://$localip"
 
-rm -r /root/ready
+rm -r /tmp/arvbox-ready
 
 sv stop ready >/dev/null
diff --git a/arvbox/docker/service/runsu.sh b/arvbox/docker/service/runsu.sh
new file mode 100755
index 0000000..329067f
--- /dev/null
+++ b/arvbox/docker/service/runsu.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+HOSTUID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f4)
+HOSTGID=$(ls -nd /usr/src/arvados | sed 's/ */ /' | cut -d' ' -f5)
+
+flock /var/lib/arvados/createusers.lock /usr/local/lib/arvbox/createusers.sh
+
+export HOME=/var/lib/arvados
+
+if test -z "$1" ; then
+    exec su arvbox -c -- "exec $0-service"
+else
+    exec su arvbox --shell /bin/bash -- $@
+fi
diff --git a/arvbox/docker/service/sdk/run b/arvbox/docker/service/sdk/run
index e5e8880..e6c844d 100755
--- a/arvbox/docker/service/sdk/run
+++ b/arvbox/docker/service/sdk/run
@@ -1,27 +1,5 @@
-#!/bin/bash
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-mkdir -p ~/.pip /var/lib/arvados/pip
-cat > ~/.pip/pip.conf <<EOF
-[global]
-download_cache = /var/lib/arvados/pip
-EOF
-
-cd /usr/src/arvados/sdk/cli
-run_bundler --binstubs=$PWD/binstubs
-ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/bin/arv
-
-
-cd /usr/src/arvados/sdk/python
-python setup.py sdist
-pip_install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
-
-cd /usr/src/arvados/services/fuse
-python setup.py sdist
-pip_install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
+#!/bin/sh
+set -e
 
+/etc/service/runsu.sh $0-service
 sv stop sdk
diff --git a/arvbox/docker/service/sdk/run b/arvbox/docker/service/sdk/run-service
similarity index 82%
copy from arvbox/docker/service/sdk/run
copy to arvbox/docker/service/sdk/run-service
index e5e8880..fd376ba 100755
--- a/arvbox/docker/service/sdk/run
+++ b/arvbox/docker/service/sdk/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 mkdir -p ~/.pip /var/lib/arvados/pip
 cat > ~/.pip/pip.conf <<EOF
@@ -13,8 +13,7 @@ EOF
 
 cd /usr/src/arvados/sdk/cli
 run_bundler --binstubs=$PWD/binstubs
-ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/bin/arv
-
+ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/local/bin/arv
 
 cd /usr/src/arvados/sdk/python
 python setup.py sdist
@@ -23,5 +22,3 @@ pip_install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
 cd /usr/src/arvados/services/fuse
 python setup.py sdist
 pip_install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
-
-sv stop sdk
diff --git a/arvbox/docker/service/sso/run b/arvbox/docker/service/sso/run
deleted file mode 100755
index 73f6df5..0000000
--- a/arvbox/docker/service/sso/run
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-. /root/common.sh
-
-cd /usr/src/sso
-export RAILS_ENV=development
-export GEM_HOME=/var/lib/gems
-
-run_bundler --without=development
-
-if ! test -s /var/lib/arvados/sso_uuid_prefix ; then
-  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/sso_uuid_prefix
-fi
-uuid_prefix=$(cat /var/lib/arvados/sso_uuid_prefix)
-
-if ! test -s /var/lib/arvados/sso_secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_secret_token
-fi
-secret_token=$(cat /var/lib/arvados/sso_secret_token)
-
-if ! test -s /var/lib/arvados/self-signed.key ; then
-  openssl req -new -x509 -nodes -out /var/lib/arvados/self-signed.pem -keyout /var/lib/arvados/self-signed.key -days 365 -subj '/CN=localhost'
-fi
-
-cat >config/application.yml <<EOF
-common:
-  uuid_prefix: $uuid_prefix
-  secret_token: $secret_token
-  default_link_url: "http://$localip"
-  allow_account_registration: true
-EOF
-
-if ! test -f /var/lib/arvados/sso_database_pw ; then
-    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/sso_database_pw
-fi
-database_pw=$(cat /var/lib/arvados/sso_database_pw)
-
-if ! test -f /var/lib/arvados/sso_database_setup ; then
-    su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
-    su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
-fi
-
-cat >config/database.yml <<EOF
-development:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_sso_development
-  username: arvados_sso
-  password: $database_pw
-  host: localhost
-  template: template0
-test:
-  adapter: postgresql
-  encoding: utf8
-  database: arvados_sso_test
-  username: arvados_sso
-  password: $database_pw
-  host: localhost
-  template: template0
-EOF
-
-if ! test -f /var/lib/arvados/sso_database_setup ; then
-   bundle exec rake db:setup
-
-   if ! test -s /var/lib/arvados/sso_app_secret ; then
-       ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_app_secret
-   fi
-   app_secret=$(cat /var/lib/arvados/sso_app_secret)
-
-   bundle exec rails console <<EOF
-c = Client.new
-c.name = "joshid"
-c.app_id = "arvados-server"
-c.app_secret = "$app_secret"
-c.save!
-EOF
-
-   touch /var/lib/arvados/sso_database_setup
-fi
-
-rm -rf tmp
-
-bundle exec rake db:migrate
-bundle exec passenger start -p3002 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/docker/service/sso/run b/arvbox/docker/service/sso/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/sso/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/sso/run b/arvbox/docker/service/sso/run-service
similarity index 85%
copy from arvbox/docker/service/sso/run
copy to arvbox/docker/service/sso/run-service
index 73f6df5..085b79a 100755
--- a/arvbox/docker/service/sso/run
+++ b/arvbox/docker/service/sso/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/sso
 export RAILS_ENV=development
@@ -39,8 +39,8 @@ fi
 database_pw=$(cat /var/lib/arvados/sso_database_pw)
 
 if ! test -f /var/lib/arvados/sso_database_setup ; then
-    su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
-    su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
+    psql -c "create user arvados_sso with password '$database_pw'"
+    psql -c "ALTER USER arvados_sso CREATEDB;"
 fi
 
 cat >config/database.yml <<EOF
@@ -84,4 +84,5 @@ fi
 rm -rf tmp
 
 bundle exec rake db:migrate
-bundle exec passenger start -p3002 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
+export HOME=/var/lib/passenger
+exec bundle exec passenger start -p3002 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/arvbox/docker/service/vm/run b/arvbox/docker/service/vm/run
deleted file mode 100755
index 807d976..0000000
--- a/arvbox/docker/service/vm/run
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-
-exec 2>&1
-sleep 2
-set -eux
-
-. /root/common.sh
-
-git config --system "credential.http://$localip:9001/.username" none
-git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
-
-export ARVADOS_API_HOST=$localip:3001
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
-
-if test -s /var/lib/arvados/vm-uuid ; then
-    ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
-    set +e
-    read -rd $'\000' vm <<EOF
-{
- "hostname":"$localip"
-}
-EOF
-    set -e
-    arv virtual_machine update --uuid $ARVADOS_VIRTUAL_MACHINE_UUID --virtual-machine "$vm"
-else
-    set +e
-    read -rd $'\000' vm <<EOF
-{
- "hostname":"$localip"
-}
-EOF
-    set -e
-    ARVADOS_VIRTUAL_MACHINE_UUID=$(arv --format=uuid virtual_machine create --virtual-machine "$vm")
-    echo $ARVADOS_VIRTUAL_MACHINE_UUID > /var/lib/arvados/vm-uuid
-fi
-
-export ARVADOS_VIRTUAL_MACHINE_UUID
-
-cd /usr/src/arvados/services/login-sync
-run_bundler
-
-while true ; do
-      bundle exec arvados-login-sync
-      sleep 120
-done
diff --git a/arvbox/docker/service/vm/run b/arvbox/docker/service/vm/run
new file mode 120000
index 0000000..ef446b5
--- /dev/null
+++ b/arvbox/docker/service/vm/run
@@ -0,0 +1 @@
+../runsu.sh
\ No newline at end of file
diff --git a/arvbox/docker/service/vm/run b/arvbox/docker/service/vm/run-service
similarity index 97%
copy from arvbox/docker/service/vm/run
copy to arvbox/docker/service/vm/run-service
index 807d976..f6844d8 100755
--- a/arvbox/docker/service/vm/run
+++ b/arvbox/docker/service/vm/run-service
@@ -4,7 +4,7 @@ exec 2>&1
 sleep 2
 set -eux
 
-. /root/common.sh
+. /usr/local/lib/arvbox/common.sh
 
 git config --system "credential.http://$localip:9001/.username" none
 git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run
index ccdbc54..7838220 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
@@ -1,37 +1,12 @@
 #!/bin/sh
+set -e
 
-exec 2>&1
-set -eux
-
-. /root/common.sh
+/etc/service/runsu.sh $0-service
 
 cd /usr/src/arvados/apps/workbench
-export RAILS_ENV=development
-export GEM_HOME=/var/lib/gems
-
-run_bundler --without=development
-
-if ! test -s /var/lib/arvados/workbench_secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/workbench_secret_token
-fi
-secret_token=$(cat /var/lib/arvados/workbench_secret_token)
-
-if ! test -s self-signed.key ; then
-  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
-fi
-
-cat >config/application.yml <<EOF
-common:
-  secret_token: $secret_token
-  arvados_login_base: https://$localip:3001/login
-  arvados_v1_base: https://$localip:3001/arvados/v1
-  arvados_insecure_https: true
-  keep_web_download_url: http://$localip:25099/c=%{uuid_or_pdh}
-  keep_web_url: http://$localip:25099/c=%{uuid_or_pdh}
-  arvados_docsite: http://$localip:8000/
-EOF
 
 rm -rf tmp
+mkdir tmp
+chown arvbox:arvbox tmp
 
-#bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
-bundle exec passenger start -p80
+exec bundle exec passenger start --port 80 --user arvbox
diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run-service
similarity index 92%
copy from arvbox/docker/service/workbench/run
copy to arvbox/docker/service/workbench/run-service
index ccdbc54..09f929f 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run-service
@@ -3,7 +3,7 @@
 exec 2>&1
 set -eux
 
-. /root/common.sh
+.  /usr/local/lib/arvbox/common.sh
 
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development
@@ -31,7 +31,6 @@ common:
   arvados_docsite: http://$localip:8000/
 EOF
 
-rm -rf tmp
-
 #bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
-bundle exec passenger start -p80
+
+bundle exec passenger start --runtime-check-only
diff --git a/arvbox/docker/tests-service/docker b/arvbox/docker/tests-service/docker
new file mode 120000
index 0000000..9374f25
--- /dev/null
+++ b/arvbox/docker/tests-service/docker
@@ -0,0 +1 @@
+../service/docker
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/docker/log/run b/arvbox/docker/tests-service/docker/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/docker/tests-service/docker/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/docker/run b/arvbox/docker/tests-service/docker/run
deleted file mode 100755
index 99540e6..0000000
--- a/arvbox/docker/tests-service/docker/run
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/bin/bash
-
-# Taken from https://github.com/jpetazzo/dind
-
-exec 2>&1
-
-#!/bin/bash
-
-# Ensure that all nodes in /dev/mapper correspond to mapped devices currently loaded by the device-mapper kernel driver
-dmsetup mknodes
-
-# First, make sure that cgroups are mounted correctly.
-CGROUP=/sys/fs/cgroup
-: {LOG:=stdio}
-
-[ -d $CGROUP ] ||
-	mkdir $CGROUP
-
-mountpoint -q $CGROUP ||
-	mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
-		echo "Could not make a tmpfs mount. Did you use --privileged?"
-		exit 1
-	}
-
-if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
-then
-    mount -t securityfs none /sys/kernel/security || {
-        echo "Could not mount /sys/kernel/security."
-        echo "AppArmor detection and --privileged mode might break."
-    }
-fi
-
-# Mount the cgroup hierarchies exactly as they are in the parent system.
-for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
-do
-        [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
-        mountpoint -q $CGROUP/$SUBSYS ||
-                mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
-
-        # The two following sections address a bug which manifests itself
-        # by a cryptic "lxc-start: no ns_cgroup option specified" when
-        # trying to start containers withina container.
-        # The bug seems to appear when the cgroup hierarchies are not
-        # mounted on the exact same directories in the host, and in the
-        # container.
-
-        # Named, control-less cgroups are mounted with "-o name=foo"
-        # (and appear as such under /proc/<pid>/cgroup) but are usually
-        # mounted on a directory named "foo" (without the "name=" prefix).
-        # Systemd and OpenRC (and possibly others) both create such a
-        # cgroup. To avoid the aforementioned bug, we symlink "foo" to
-        # "name=foo". This shouldn't have any adverse effect.
-        echo $SUBSYS | grep -q ^name= && {
-                NAME=$(echo $SUBSYS | sed s/^name=//)
-                ln -s $SUBSYS $CGROUP/$NAME
-        }
-
-        # Likewise, on at least one system, it has been reported that
-        # systemd would mount the CPU and CPU accounting controllers
-        # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
-        # but on a directory called "cpu,cpuacct" (note the inversion
-        # in the order of the groups). This tries to work around it.
-        [ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
-done
-
-# Note: as I write those lines, the LXC userland tools cannot setup
-# a "sub-container" properly if the "devices" cgroup is not in its
-# own hierarchy. Let's detect this and issue a warning.
-grep -q :devices: /proc/1/cgroup ||
-	echo "WARNING: the 'devices' cgroup should be in its own hierarchy."
-grep -qw devices /proc/1/cgroup ||
-	echo "WARNING: it looks like the 'devices' cgroup is not mounted."
-
-# Now, close extraneous file descriptors.
-pushd /proc/self/fd >/dev/null
-for FD in *
-do
-	case "$FD" in
-	# Keep stdin/stdout/stderr
-	[012])
-		;;
-	# Nuke everything else
-	*)
-		eval exec "$FD>&-"
-		;;
-	esac
-done
-popd >/dev/null
-
-
-# If a pidfile is still around (for example after a container restart),
-# delete it so that docker can start.
-rm -rf /var/run/docker.pid
-
-exec docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
diff --git a/arvbox/docker/tests-service/logger b/arvbox/docker/tests-service/logger
deleted file mode 100755
index a79a518..0000000
--- a/arvbox/docker/tests-service/logger
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-exec svlogd -tt ./main
diff --git a/arvbox/docker/tests-service/logger b/arvbox/docker/tests-service/logger
new file mode 120000
index 0000000..b30194b
--- /dev/null
+++ b/arvbox/docker/tests-service/logger
@@ -0,0 +1 @@
+../service/logger
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/postgres b/arvbox/docker/tests-service/postgres
new file mode 120000
index 0000000..9b2d8a0
--- /dev/null
+++ b/arvbox/docker/tests-service/postgres
@@ -0,0 +1 @@
+../service/postgres
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/postgres/log/main/.gitstub b/arvbox/docker/tests-service/postgres/log/main/.gitstub
deleted file mode 100644
index e69de29..0000000
diff --git a/arvbox/docker/tests-service/postgres/log/run b/arvbox/docker/tests-service/postgres/log/run
deleted file mode 120000
index f99cc1d..0000000
--- a/arvbox/docker/tests-service/postgres/log/run
+++ /dev/null
@@ -1 +0,0 @@
-../../logger
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/postgres/run b/arvbox/docker/tests-service/postgres/run
deleted file mode 100755
index 2b7c497..0000000
--- a/arvbox/docker/tests-service/postgres/run
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-exec 2>&1
-set -eux
-
-chown postgres:postgres -R /var/lib/postgresql
-if ! test -d /var/lib/postgresql/9.4/main ; then
-   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
-fi
-mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
-chown postgres:postgres -R /var/run/postgresql
-
-exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
diff --git a/arvbox/docker/tests-service/runsu.sh b/arvbox/docker/tests-service/runsu.sh
new file mode 120000
index 0000000..22030b0
--- /dev/null
+++ b/arvbox/docker/tests-service/runsu.sh
@@ -0,0 +1 @@
+../service/runsu.sh
\ No newline at end of file

commit 7dae80945a055453fea5ec85a3c4b1ca160b5c17
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 16:18:39 2016 -0500

    8080: Fix arvados_docsite in workbench to point to local docsite.

diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run
index 1bbb5e7..ccdbc54 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
@@ -28,6 +28,7 @@ common:
   arvados_insecure_https: true
   keep_web_download_url: http://$localip:25099/c=%{uuid_or_pdh}
   keep_web_url: http://$localip:25099/c=%{uuid_or_pdh}
+  arvados_docsite: http://$localip:8000/
 EOF
 
 rm -rf tmp

commit f11b245d56c69fd127cf9340bbb5392ed6e48bf4
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 16:13:48 2016 -0500

    8080: Add Arvados documentation server.

diff --git a/arvbox/docker/service/doc/log/main/.gitstub b/arvbox/docker/service/doc/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/arvbox/docker/service/doc/log/run b/arvbox/docker/service/doc/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/arvbox/docker/service/doc/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/arvbox/docker/service/doc/run b/arvbox/docker/service/doc/run
new file mode 100755
index 0000000..e12fdb4
--- /dev/null
+++ b/arvbox/docker/service/doc/run
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+exec 2>&1
+set -eux
+
+. /root/common.sh
+
+cd /usr/src/arvados/doc
+run_bundler --without=development
+bundle exec rake generate baseurl=http://$localip:8000 arvados_api_host=$localip:3001 arvados_workbench_host=http://$localip
+exec bundle exec rake run
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
index 0908f01..41732fd 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
@@ -27,6 +27,7 @@ services=(
   [keep0]=25107
   [keep1]=25108
   [ssh]=22
+  [doc]=8000
 )
 
 waiting=""

commit 3932ff785ee96479493d07c9949ed9562242a5f2
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 15:59:49 2016 -0500

    8080: Install xvfb, iceweasel & phantomjs to be able to run workbench tests.

diff --git a/arvbox/docker/Dockerfile b/arvbox/docker/Dockerfile
index 85acd0f..a9c3321 100644
--- a/arvbox/docker/Dockerfile
+++ b/arvbox/docker/Dockerfile
@@ -10,10 +10,16 @@ RUN apt-get update && \
     pkg-config libattr1-dev python-llfuse python-pycurl \
     libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
     libjson-perl python-virtualenv python3-virtualenv nginx \
-    gitolite3 lsof
+    gitolite3 lsof python-epydoc linkchecker xvfb iceweasel graphviz
 
 RUN curl -sSL https://get.docker.com/ | sh
 
+RUN set -e && \
+ PJS=phantomjs-1.9.7-linux-x86_64 && \
+ curl -L -o/tmp/$PJS.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/$PJS.tar.bz2 && \
+ tar -C /usr/local -xjf /tmp/$PJS.tar.bz2 && \
+ ln -s ../$PJS/bin/phantomjs /usr/local/bin/
+
 ADD runit-docker /root/runit-docker
 
 RUN cd /root/runit-docker && \

commit 80da6be45102cbd2a312dff4f5203964ef0f55b0
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 15:04:45 2016 -0500

    8080: Fixes api server tests.

diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
index 419235d..68019e7 100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run
@@ -37,7 +37,7 @@ common:
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
   sso_provider_url: "https://$localip:3002"
-  workbench_address: "http://$localip"
+  workbench_address: "http://$localip/"
   sso_insecure: true
   auto_admin_first_user: true
   git_repo_ssh_base: "git@$localip:"

commit 91cc6e31cf0ad3cc43a45eb2dc7d30c2ae6158d7
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 14:26:30 2016 -0500

    8080: Test configuration uses "zzzzz" prefix, which fixes almost all the failing
    tests.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index ac1e604..f6e95cb 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -79,9 +79,7 @@ run() {
         done < $FF
         rm $FF
         echo
-        echo "You the Arvados source code is checked out at $ARVADOS_ROOT"
-        echo "Use '$0 reboot' to restart the container"
-        echo "or '$0 svrestart' to recompile/reconfigure/restart individual services."
+        echo "The Arvados source code is checked out at: $ARVADOS_ROOT"
     fi
 }
 
@@ -110,6 +108,11 @@ case $1 in
         stop
         ;;
 
+    restart)
+        stop
+        run
+        ;;
+
     reboot)
         stop
         cd $ARVBOX_SCRIPT
@@ -182,9 +185,10 @@ case $1 in
         echo "open       open arvbox workbench in a web browser"
         echo "shell      enter arvbox shell"
         echo "ip         print arvbox ip address"
-        echo "stop       stop arvbox"
-        echo "reboot     stop arvbox, build image, run"
-        echo "reset      delete persistent data (be careful!)"
+        echo "stop       stop arvbox container"
+        echo "restart    stop, then run again"
+        echo "reboot     stop, build arvbox Docker image, run"
+        echo "reset      delete all persistent data (be careful!)"
         echo "run-tests  run run-tests.sh inside $ARVBOX_CONTAINER container"
         echo "log       <service> tail log of specified service"
         echo "svrestart <service> restart specified service inside arvbox"
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
index 5c80964..419235d 100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run
@@ -32,7 +32,6 @@ sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
 
 cat >config/application.yml <<EOF
 common:
-  uuid_prefix: $uuid_prefix
   secret_token: $secret_token
   blob_signing_key: $blob_signing_key
   sso_app_secret: $sso_app_secret
@@ -43,6 +42,11 @@ common:
   auto_admin_first_user: true
   git_repo_ssh_base: "git@$localip:"
   git_repo_https_base: "http://$localip:9001/"
+development:
+  uuid_prefix: $uuid_prefix
+test:
+  uuid_prefix: zzzzz
+  git_repo_ssh_base: "git at git.zzzzz.arvadosapi.com:"
 EOF
 
 if ! test -f /var/lib/arvados/api_database_pw ; then

commit bfa7362c90e3bf5f42ac30612c0ec04ed9deb0d6
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 13:58:52 2016 -0500

    8080: Fix GEM_HOME

diff --git a/arvbox/arvbox b/arvbox/arvbox
index b5f1e5a..ac1e604 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -103,7 +103,7 @@ case $1 in
         ;;
 
     sh*)
-        docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM GEM_HOME=/var/lib/arvados/gems /bin/bash
+        docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM GEM_HOME=/var/lib/gems /bin/bash
         ;;
 
     stop)
@@ -167,8 +167,8 @@ case $1 in
                VENVDIR=/var/lib/arvados/tests-venv \
                VENV3DIR=/var/lib/arvados/tests-venv3 \
                GOPATH=/var/lib/arvados/tests-gostuff \
-               GEMHOME=/var/lib/arvados/gems \
-               GEM_HOME=/var/lib/arvados/gems \
+               GEMHOME=/var/lib/gems \
+               GEM_HOME=/var/lib/gems \
                "$@"
         ;;
 

commit 434c954c45ce545a92fc5326ae1f3d91b117e869
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 13:43:58 2016 -0500

    8080: Add note about where arvados git is checked out.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index c06dbcd..b5f1e5a 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -78,6 +78,10 @@ run() {
             fi
         done < $FF
         rm $FF
+        echo
+        echo "You the Arvados source code is checked out at $ARVADOS_ROOT"
+        echo "Use '$0 reboot' to restart the container"
+        echo "or '$0 svrestart' to recompile/reconfigure/restart individual services."
     fi
 }
 

commit e9e9a304e6bde8db0348a4cc6190d79d11241a1a
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 13:33:22 2016 -0500

    8080: log and svrestart print out available services

diff --git a/arvbox/arvbox b/arvbox/arvbox
index f953bc1..c06dbcd 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -113,11 +113,6 @@ case $1 in
         run
         ;;
 
-    svrestart)
-        docker exec -ti $ARVBOX_CONTAINER sv restart $2
-        docker exec -ti $ARVBOX_CONTAINER sv restart ready
-        ;;
-
     ip|open)
         IP=$(docker inspect arvbox | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
         if test $1 = 'ip' ; then
@@ -136,8 +131,20 @@ case $1 in
         sudo rm -rf $ARVBOX_DATA
         ;;
 
-    log)
-        docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
+    log|svrestart)
+        if test -n "$2" ; then
+            if test "$1" = log ; then
+                docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
+            fi
+            if test "$1" = svrestart ; then
+                docker exec -ti $ARVBOX_CONTAINER sv restart $2
+                docker exec -ti $ARVBOX_CONTAINER sv restart ready
+            fi
+        else
+            echo "Usage: $0 $1 <service>"
+            echo "Available services:"
+            docker exec -ti $ARVBOX_CONTAINER ls /etc/service
+        fi
         ;;
 
     run-tests)
@@ -164,7 +171,7 @@ case $1 in
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|log|svrestart|tests)"
+        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|run-tests|log|svrestart)"
         echo
         echo "build      build arvbox Docker image"
         echo "start|run  start $ARVBOX_CONTAINER container "
@@ -173,9 +180,9 @@ case $1 in
         echo "ip         print arvbox ip address"
         echo "stop       stop arvbox"
         echo "reboot     stop arvbox, build image, run"
-        echo "reset      delete persistent data (careful!)"
-        echo "log <component>       tail log of specified service"
-        echo "svrestart <component> restart specified service inside arvbox"
+        echo "reset      delete persistent data (be careful!)"
         echo "run-tests  run run-tests.sh inside $ARVBOX_CONTAINER container"
+        echo "log       <service> tail log of specified service"
+        echo "svrestart <service> restart specified service inside arvbox"
         ;;
 esac

commit c66016db77c59b3e5e5d459bb80d670e8ab075e4
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 10:42:10 2016 -0500

    8080: Count gems for status reporting.  Store gems in a different place.  Store
    database password and rewrite database.yml on startup.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index ee8910c..f953bc1 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -27,8 +27,9 @@ fi
 PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
 VAR_DATA=$ARVBOX_DATA/var
+GEMS=$ARVBOX_DATA/gems
 
-mkdir -p $PASSENGER $PG_DATA $VAR_DATA
+mkdir -p $PASSENGER $PG_DATA $VAR_DATA $GEMS
 
 run() {
     if ! test -d $ARVADOS_ROOT ; then
@@ -49,6 +50,7 @@ run() {
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
                --volume=$PASSENGER:/var/lib/passenger:rw \
+               --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox \
                runsvdir /etc/tests-service
@@ -62,6 +64,7 @@ run() {
                --volume=$PG_DATA:/var/lib/postgresql:rw \
                --volume=$VAR_DATA:/var/lib/arvados:rw \
                --volume=$PASSENGER:/var/lib/passenger:rw \
+               --volume=$GEMS:/var/lib/gems:rw \
                --volume=/var/lib/docker \
                arvados/arvbox
         FF=/tmp/arvbox-fifo-$$
@@ -105,7 +108,7 @@ case $1 in
 
     reboot)
         stop
-        cd $ARVBOX
+        cd $ARVBOX_SCRIPT
         docker build -t arvados/arvbox docker
         run
         ;;
diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index ffed7fd..3bf3293 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -1,6 +1,6 @@
 localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
-export GEM_HOME=/var/lib/arvados/gems
-export GEM_PATH=/var/lib/arvados/gems
+export GEM_HOME=/var/lib/gems
+export GEM_PATH=/var/lib/gems
 
 run_bundler() {
     if test -f Gemfile.lock ; then
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
index d290420..5c80964 100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run
@@ -45,11 +45,17 @@ common:
   git_repo_https_base: "http://$localip:9001/"
 EOF
 
+if ! test -f /var/lib/arvados/api_database_pw ; then
+    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/api_database_pw
+fi
+database_pw=$(cat /var/lib/arvados/api_database_pw)
+
 if ! test -f /var/lib/arvados/api_database_setup ; then
-   database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
    su postgres -c "psql -c \"create user arvados with password '$database_pw'\""
    su postgres -c "psql -c \"ALTER USER arvados CREATEDB;\""
-   cat >config/database.yml <<EOF
+fi
+
+cat >config/database.yml <<EOF
 development:
   adapter: postgresql
   encoding: utf8
@@ -67,6 +73,8 @@ test:
   host: localhost
   template: template0
 EOF
+
+if ! test -f /var/lib/arvados/api_database_setup ; then
    bundle exec rake db:setup
    touch /var/lib/arvados/api_database_setup
 fi
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
index 2b117b9..0908f01 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
@@ -59,8 +59,24 @@ fi
 
 if ! [[ -z "$waiting" ]] ; then
     if ps x | grep -v grep | grep "bundle install" > /dev/null; then
-        waiting="$waiting (installing ruby gems)"
+        gemcount=$(ls /var/lib/gems/gems 2>/dev/null | wc -l)
+
+        gemlockcount=0
+        for l in /usr/src/arvados/services/api/Gemfile.lock \
+                     /usr/src/arvados/apps/workbench/Gemfile.lock \
+                     /usr/src/sso/Gemfile.lock ; do
+            gc=$(cat $l \
+                        | grep -vE "(GEM|PLATFORMS|DEPENDENCIES|$^|remote:|specs:)" \
+                        | sed 's/^ *//' | sed 's/(.*)//' | sed 's/ *$//' | sort | uniq | wc -l)
+            gemlockcount=$(($gemlockcount + $gc))
+        done
+        waiting="$waiting (installing ruby gems $gemcount/$gemlockcount)"
+    fi
+
+    if ps x | grep -v grep | grep "c++.*/var/lib/passenger" > /dev/null ; then
+        waiting="$waiting (compiling passenger)"
     fi
+
     if ps x | grep -v grep | grep "pip install" > /dev/null; then
         waiting="$waiting (installing python packages)"
     fi
diff --git a/arvbox/docker/service/sso/run b/arvbox/docker/service/sso/run
index 718c526..73f6df5 100755
--- a/arvbox/docker/service/sso/run
+++ b/arvbox/docker/service/sso/run
@@ -7,7 +7,7 @@ set -eux
 
 cd /usr/src/sso
 export RAILS_ENV=development
-export GEM_HOME=/var/lib/arvados/gems
+export GEM_HOME=/var/lib/gems
 
 run_bundler --without=development
 
@@ -33,11 +33,17 @@ common:
   allow_account_registration: true
 EOF
 
+if ! test -f /var/lib/arvados/sso_database_pw ; then
+    ruby -e 'puts rand(2**128).to_s(36)' > /var/lib/arvados/sso_database_pw
+fi
+database_pw=$(cat /var/lib/arvados/sso_database_pw)
+
 if ! test -f /var/lib/arvados/sso_database_setup ; then
-   database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
-   su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
-   su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
-   cat >config/database.yml <<EOF
+    su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
+    su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
+fi
+
+cat >config/database.yml <<EOF
 development:
   adapter: postgresql
   encoding: utf8
@@ -55,6 +61,8 @@ test:
   host: localhost
   template: template0
 EOF
+
+if ! test -f /var/lib/arvados/sso_database_setup ; then
    bundle exec rake db:setup
 
    if ! test -s /var/lib/arvados/sso_app_secret ; then
diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run
index ea7d242..1bbb5e7 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
@@ -7,7 +7,7 @@ set -eux
 
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development
-export GEM_HOME=/var/lib/arvados/gems
+export GEM_HOME=/var/lib/gems
 
 run_bundler --without=development
 
diff --git a/arvbox/docker/tests-service/docker/log/run b/arvbox/docker/tests-service/docker/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/arvbox/docker/tests-service/docker/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/arvbox/docker/tests-service/docker/run b/arvbox/docker/tests-service/docker/run
new file mode 100755
index 0000000..99540e6
--- /dev/null
+++ b/arvbox/docker/tests-service/docker/run
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+# Taken from https://github.com/jpetazzo/dind
+
+exec 2>&1
+
+#!/bin/bash
+
+# Ensure that all nodes in /dev/mapper correspond to mapped devices currently loaded by the device-mapper kernel driver
+dmsetup mknodes
+
+# First, make sure that cgroups are mounted correctly.
+CGROUP=/sys/fs/cgroup
+: {LOG:=stdio}
+
+[ -d $CGROUP ] ||
+	mkdir $CGROUP
+
+mountpoint -q $CGROUP ||
+	mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
+		echo "Could not make a tmpfs mount. Did you use --privileged?"
+		exit 1
+	}
+
+if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
+then
+    mount -t securityfs none /sys/kernel/security || {
+        echo "Could not mount /sys/kernel/security."
+        echo "AppArmor detection and --privileged mode might break."
+    }
+fi
+
+# Mount the cgroup hierarchies exactly as they are in the parent system.
+for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
+do
+        [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
+        mountpoint -q $CGROUP/$SUBSYS ||
+                mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
+
+        # The two following sections address a bug which manifests itself
+        # by a cryptic "lxc-start: no ns_cgroup option specified" when
+        # trying to start containers withina container.
+        # The bug seems to appear when the cgroup hierarchies are not
+        # mounted on the exact same directories in the host, and in the
+        # container.
+
+        # Named, control-less cgroups are mounted with "-o name=foo"
+        # (and appear as such under /proc/<pid>/cgroup) but are usually
+        # mounted on a directory named "foo" (without the "name=" prefix).
+        # Systemd and OpenRC (and possibly others) both create such a
+        # cgroup. To avoid the aforementioned bug, we symlink "foo" to
+        # "name=foo". This shouldn't have any adverse effect.
+        echo $SUBSYS | grep -q ^name= && {
+                NAME=$(echo $SUBSYS | sed s/^name=//)
+                ln -s $SUBSYS $CGROUP/$NAME
+        }
+
+        # Likewise, on at least one system, it has been reported that
+        # systemd would mount the CPU and CPU accounting controllers
+        # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
+        # but on a directory called "cpu,cpuacct" (note the inversion
+        # in the order of the groups). This tries to work around it.
+        [ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
+done
+
+# Note: as I write those lines, the LXC userland tools cannot setup
+# a "sub-container" properly if the "devices" cgroup is not in its
+# own hierarchy. Let's detect this and issue a warning.
+grep -q :devices: /proc/1/cgroup ||
+	echo "WARNING: the 'devices' cgroup should be in its own hierarchy."
+grep -qw devices /proc/1/cgroup ||
+	echo "WARNING: it looks like the 'devices' cgroup is not mounted."
+
+# Now, close extraneous file descriptors.
+pushd /proc/self/fd >/dev/null
+for FD in *
+do
+	case "$FD" in
+	# Keep stdin/stdout/stderr
+	[012])
+		;;
+	# Nuke everything else
+	*)
+		eval exec "$FD>&-"
+		;;
+	esac
+done
+popd >/dev/null
+
+
+# If a pidfile is still around (for example after a container restart),
+# delete it so that docker can start.
+rm -rf /var/run/docker.pid
+
+exec docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
diff --git a/arvbox/docker/tests-service/logger b/arvbox/docker/tests-service/logger
new file mode 100755
index 0000000..a79a518
--- /dev/null
+++ b/arvbox/docker/tests-service/logger
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec svlogd -tt ./main

commit 1f01b5bfd46cf5eb6ff06d84fd3515c9d8c5d6ef
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Jan 4 09:45:05 2016 -0500

    8080: Move arvados and arvados-dev checkouts to ~/.arvbox.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index b0c1f52..ee8910c 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -2,33 +2,33 @@
 
 set -e
 
-ARVBOX=$(readlink -f $(dirname $0))
+ARVBOX_SCRIPT=$(readlink -f $(dirname $0))
 
-if test -z "$ARVADOS_ROOT" ; then
-    ARVADOS_ROOT=$ARVBOX/arvados
+if test -z "$ARVBOX_CONTAINER" ; then
+    ARVBOX_CONTAINER=arvbox
 fi
 
-if test -z "$ARVADOS_DEV_ROOT" ; then
-    ARVADOS_DEV_ROOT=$ARVBOX/arvados-dev
+if test -z "$ARVBOX_DATA" ; then
+    ARVBOX_DATA=$HOME/.arvbox/$ARVBOX_CONTAINER
 fi
 
-if test -z "$SSO_ROOT" ; then
-    SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
+if test -z "$ARVADOS_ROOT" ; then
+    ARVADOS_ROOT=$ARVBOX_DATA/arvados
 fi
 
-if test -z "$ARVBOX_CONTAINER" ; then
-    ARVBOX_CONTAINER=arvbox
+if test -z "$ARVADOS_DEV_ROOT" ; then
+    ARVADOS_DEV_ROOT=$ARVBOX_DATA/arvados-dev
 fi
 
-if test -z "$ARVBOX_DATA" ; then
-    ARVBOX_DATA=$HOME/.arvbox/$ARVBOX_CONTAINER
+if test -z "$SSO_ROOT" ; then
+    SSO_ROOT=$ARVBOX_DATA/sso-devise-omniauth-provider
 fi
 
 PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
-ARV_DATA=$ARVBOX_DATA/var
+VAR_DATA=$ARVBOX_DATA/var
 
-mkdir -p $PASSENGER $PG_DATA $ARV_DATA
+mkdir -p $PASSENGER $PG_DATA $VAR_DATA
 
 run() {
     if ! test -d $ARVADOS_ROOT ; then
@@ -47,7 +47,7 @@ run() {
                --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
-               --volume=$ARV_DATA:/var/lib/arvados:rw \
+               --volume=$VAR_DATA:/var/lib/arvados:rw \
                --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=/var/lib/docker \
                arvados/arvbox \
@@ -60,7 +60,7 @@ run() {
                --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
                --volume=$SSO_ROOT:/usr/src/sso:rw \
                --volume=$PG_DATA:/var/lib/postgresql:rw \
-               --volume=$ARV_DATA:/var/lib/arvados:rw \
+               --volume=$VAR_DATA:/var/lib/arvados:rw \
                --volume=$PASSENGER:/var/lib/passenger:rw \
                --volume=/var/lib/docker \
                arvados/arvbox
@@ -87,7 +87,7 @@ stop() {
 
 case $1 in
     build)
-        cd $ARVBOX
+        cd $ARVBOX_SCRIPT
         docker build -t arvados/arvbox docker
         ;;
 
@@ -126,12 +126,11 @@ case $1 in
 
     reset)
         if test "$2" != -f ; then
-            echo "WARNING!  This will delete all data inside your arvbox ($PG_DATA and $ARV_DATA).  Use reset -f if you really mean it."
+            echo "WARNING!  This will delete all code and data inside your arvbox ($ARVBOX_DATA).  Use reset -f if you really mean it."
             exit 1
         fi
         stop
-        sudo rm -rf $PG_DATA
-        sudo rm -rf $ARV_DATA
+        sudo rm -rf $ARVBOX_DATA
         ;;
 
     log)

commit 05348254357bd32e126e7a7ee0e8cfc5bb87cc5f
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 30 16:57:38 2015 -0500

    Use default docker driver with overlayfs as fallback.

diff --git a/arvbox/docker/service/docker/run b/arvbox/docker/service/docker/run
index 99540e6..93834ee 100755
--- a/arvbox/docker/service/docker/run
+++ b/arvbox/docker/service/docker/run
@@ -92,4 +92,9 @@ popd >/dev/null
 # delete it so that docker can start.
 rm -rf /var/run/docker.pid
 
-exec docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
+read pid cmd state ppid pgrp session tty_nr tpgid rest < /proc/self/stat
+trap "kill -TERM -$pgrp; exit" EXIT TERM KILL SIGKILL SIGTERM SIGQUIT
+
+if ! docker daemon $DOCKER_DAEMON_ARGS ; then
+    docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
+fi

commit 01cf08200a38f781a62283ebb6099f46a5bf13ef
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 30 16:34:14 2015 -0500

    --frozen based on whether there is Gemfile.lock

diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index 80fbac3..ffed7fd 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -3,8 +3,13 @@ export GEM_HOME=/var/lib/arvados/gems
 export GEM_PATH=/var/lib/arvados/gems
 
 run_bundler() {
-    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment --frozen "$@" ; then
-        flock /var/lib/arvados/gems.lock bundle install --no-deployment --frozen "$@"
+    if test -f Gemfile.lock ; then
+        frozen=--frozen
+    else
+        frozen=""
+    fi
+    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment $frozen "$@" ; then
+        flock /var/lib/arvados/gems.lock bundle install --no-deployment $frozen "$@"
     fi
 }
 

commit db8892cca34b4fcc62c3352d1a8bd828abaa0a3d
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 30 16:06:23 2015 -0500

    8080: Remove call to bundle install outside if statement.

diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index 37bbf8e..80fbac3 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -3,7 +3,6 @@ export GEM_HOME=/var/lib/arvados/gems
 export GEM_PATH=/var/lib/arvados/gems
 
 run_bundler() {
-    flock /var/lib/arvados/gems.lock bundle install --local --no-deployment --frozen "$@"
     if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment --frozen "$@" ; then
         flock /var/lib/arvados/gems.lock bundle install --no-deployment --frozen "$@"
     fi

commit e6b3ab8f24716b8bc8f09fcd3bcbbfa2c154d330
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Dec 29 16:48:28 2015 -0500

    8080: Set GEM_HOME in arvbox shell

diff --git a/arvbox/arvbox b/arvbox/arvbox
index ed31d9b..b0c1f52 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -96,7 +96,7 @@ case $1 in
         ;;
 
     sh*)
-        docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM /bin/bash
+        docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM GEM_HOME=/var/lib/arvados/gems /bin/bash
         ;;
 
     stop)

commit 1fd2d0cc54423adb0e0fd3676af15a7ace42f468
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sun Dec 27 16:53:28 2015 -0500

    8080: Easier support for multiple containers.  Rename tests -> run-tests.  Refactor run()

diff --git a/arvbox/arvbox b/arvbox/arvbox
index 931a248..ed31d9b 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -16,14 +16,14 @@ if test -z "$SSO_ROOT" ; then
     SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
 fi
 
-if test -z "$ARVBOX_DATA" ; then
-    ARVBOX_DATA=$HOME/.arvbox
-fi
-
 if test -z "$ARVBOX_CONTAINER" ; then
     ARVBOX_CONTAINER=arvbox
 fi
 
+if test -z "$ARVBOX_DATA" ; then
+    ARVBOX_DATA=$HOME/.arvbox/$ARVBOX_CONTAINER
+fi
+
 PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
 ARV_DATA=$ARVBOX_DATA/var
@@ -38,33 +38,48 @@ run() {
         git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
     fi
 
-    docker run \
-           --detach \
-           --name=$ARVBOX_CONTAINER \
-           --privileged \
-           --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-           --volume=$SSO_ROOT:/usr/src/sso:rw \
-           --volume=$PG_DATA:/var/lib/postgresql:rw \
-           --volume=$ARV_DATA:/var/lib/arvados:rw \
-           --volume=$PASSENGER:/var/lib/passenger:rw \
-           --volume=/var/lib/docker \
-           arvados/arvbox
-
-    FF=/tmp/arvbox-fifo-$$
-    mkfifo $FF
-    docker logs -f $ARVBOX_CONTAINER > $FF &
-    LOGPID=$!
-    while read line ; do
-        echo $line
-        if echo $line | grep "Workbench is running at" >/dev/null ; then
-            kill $LOGPID
-        fi
-    done < $FF
-    rm $FF
+    if test "$1" = testing ; then
+        docker run \
+               --detach \
+               --name=$ARVBOX_CONTAINER \
+               --privileged \
+               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+               --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
+               --volume=$SSO_ROOT:/usr/src/sso:rw \
+               --volume=$PG_DATA:/var/lib/postgresql:rw \
+               --volume=$ARV_DATA:/var/lib/arvados:rw \
+               --volume=$PASSENGER:/var/lib/passenger:rw \
+               --volume=/var/lib/docker \
+               arvados/arvbox \
+               runsvdir /etc/tests-service
+    else
+        docker run \
+               --detach \
+               --name=$ARVBOX_CONTAINER \
+               --privileged \
+               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+               --volume=$SSO_ROOT:/usr/src/sso:rw \
+               --volume=$PG_DATA:/var/lib/postgresql:rw \
+               --volume=$ARV_DATA:/var/lib/arvados:rw \
+               --volume=$PASSENGER:/var/lib/passenger:rw \
+               --volume=/var/lib/docker \
+               arvados/arvbox
+        FF=/tmp/arvbox-fifo-$$
+        mkfifo $FF
+        docker logs -f $ARVBOX_CONTAINER > $FF &
+        LOGPID=$!
+        while read line ; do
+            echo $line
+            if echo $line | grep "Workbench is running at" >/dev/null ; then
+                kill $LOGPID
+            fi
+        done < $FF
+        rm $FF
+    fi
 }
 
 stop() {
-    if docker ps |grep -E "\b$ARVBOX_CONTAINER\b" -q ; then
+    if docker ps -a |grep -E "\b$ARVBOX_CONTAINER\b" -q ; then
         docker stop $ARVBOX_CONTAINER
         docker rm $ARVBOX_CONTAINER
     fi
@@ -123,26 +138,14 @@ case $1 in
         docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
         ;;
 
-    tests)
+    run-tests)
         stop
 
         if ! test -d $ARVADOS_DEV_ROOT ; then
             git clone https://github.com/curoverse/arvados-dev.git $ARVADOS_DEV_ROOT
         fi
 
-        docker run \
-               --detach \
-               --name=$ARVBOX_CONTAINER \
-               --privileged \
-               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-               --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
-               --volume=$SSO_ROOT:/usr/src/sso:rw \
-               --volume=$PG_DATA:/var/lib/postgresql:rw \
-               --volume=$ARV_DATA:/var/lib/arvados:rw \
-               --volume=$PASSENGER:/var/lib/passenger:rw \
-               --volume=/var/lib/docker \
-               arvados/arvbox \
-               runsvdir /etc/tests-service
+        run testing
 
         shift
         docker exec -ti $ARVBOX_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
@@ -154,11 +157,8 @@ case $1 in
                GEMHOME=/var/lib/arvados/gems \
                GEM_HOME=/var/lib/arvados/gems \
                "$@"
-        docker stop $ARVBOX_CONTAINER
-        docker rm $ARVBOX_CONTAINER
         ;;
 
-
     *)
         echo "Arvados-in-a-box"
         echo
@@ -174,6 +174,6 @@ case $1 in
         echo "reset      delete persistent data (careful!)"
         echo "log <component>       tail log of specified service"
         echo "svrestart <component> restart specified service inside arvbox"
-        echo "tests      run run-tests.sh inside $ARVBOX_CONTAINER container"
+        echo "run-tests  run run-tests.sh inside $ARVBOX_CONTAINER container"
         ;;
 esac

commit 7c7c2f10db25a1af16a523677444c802c66c807b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Thu Dec 24 14:59:11 2015 -0500

    8080: Don't do "bundle package" (breaks arvbox gem sharing).

diff --git a/jenkins/run-tests.sh b/jenkins/run-tests.sh
index a7b5fda..e50d485 100755
--- a/jenkins/run-tests.sh
+++ b/jenkins/run-tests.sh
@@ -589,7 +589,6 @@ bundle_install_trylocal() {
             echo "(Running bundle install again, without --local.)"
             bundle install --no-deployment
         fi
-        bundle package --all
     )
 }
 

commit e2bffcc799a671160c682131799c5b15e0a154d8
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 17:59:03 2015 -0500

    Adjust workbench secret token path.

diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run
index a08136c..ea7d242 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
@@ -11,10 +11,10 @@ export GEM_HOME=/var/lib/arvados/gems
 
 run_bundler --without=development
 
-if ! test -s secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+if ! test -s /var/lib/arvados/workbench_secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/workbench_secret_token
 fi
-secret_token=$(cat secret_token)
+secret_token=$(cat /var/lib/arvados/workbench_secret_token)
 
 if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'

commit 2bcee7c582ec07011c4e097703d4d56358ce15de
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 17:56:04 2015 -0500

    8080: Set GEM_HOME as well as GEMHOME.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index 22973ac..931a248 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -152,6 +152,7 @@ case $1 in
                VENV3DIR=/var/lib/arvados/tests-venv3 \
                GOPATH=/var/lib/arvados/tests-gostuff \
                GEMHOME=/var/lib/arvados/gems \
+               GEM_HOME=/var/lib/arvados/gems \
                "$@"
         docker stop $ARVBOX_CONTAINER
         docker rm $ARVBOX_CONTAINER

commit 46dca4e67a3068571e8787fd43bde3520cf8c71b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 17:33:52 2015 -0500

    Use locally cached gems and python packages.

diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index 20d164c..37bbf8e 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -1,8 +1,29 @@
 localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
 export GEM_HOME=/var/lib/arvados/gems
+export GEM_PATH=/var/lib/arvados/gems
 
 run_bundler() {
-    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment "$@" ; then
-        flock /var/lib/arvados/gems.lock bundle install --no-deployment "$@"
+    flock /var/lib/arvados/gems.lock bundle install --local --no-deployment --frozen "$@"
+    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment --frozen "$@" ; then
+        flock /var/lib/arvados/gems.lock bundle install --no-deployment --frozen "$@"
+    fi
+}
+
+pip_install() {
+    pushd /var/lib/arvados/pip
+    for p in $(ls http*.tar.gz) ; do
+        if test -f $p ; then
+            ln -sf $p $(echo $p | sed 's/.*%2F\(.*\)/\1/')
+        fi
+    done
+    for p in $(ls http*.whl) ; do
+        if test -f $p ; then
+            ln -sf $p $(echo $p | sed 's/.*%2F\(.*\)/\1/')
+        fi
+    done
+    popd
+
+    if ! pip install --no-index --find-links /var/lib/arvados/pip $1 ; then
+        pip install $1
     fi
 }
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
index 60ac664..2b117b9 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
@@ -49,6 +49,14 @@ if ! docker version >/dev/null 2>/dev/null ; then
   waiting="$waiting docker"
 fi
 
+if ! which arv >/dev/null ; then
+  waiting="$waiting ruby_sdk"
+fi
+
+if ! which arv-get >/dev/null ; then
+  waiting="$waiting python_sdk"
+fi
+
 if ! [[ -z "$waiting" ]] ; then
     if ps x | grep -v grep | grep "bundle install" > /dev/null; then
         waiting="$waiting (installing ruby gems)"
diff --git a/arvbox/docker/service/sdk/run b/arvbox/docker/service/sdk/run
index 10df374..e5e8880 100755
--- a/arvbox/docker/service/sdk/run
+++ b/arvbox/docker/service/sdk/run
@@ -1,8 +1,10 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 mkdir -p ~/.pip /var/lib/arvados/pip
 cat > ~/.pip/pip.conf <<EOF
 [global]
@@ -10,15 +12,16 @@ download_cache = /var/lib/arvados/pip
 EOF
 
 cd /usr/src/arvados/sdk/cli
-bundle install --binstubs=binstubs
+run_bundler --binstubs=$PWD/binstubs
 ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/bin/arv
 
+
 cd /usr/src/arvados/sdk/python
 python setup.py sdist
-pip install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
+pip_install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
 
 cd /usr/src/arvados/services/fuse
 python setup.py sdist
-pip install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
+pip_install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
 
 sv stop sdk

commit e5a6bdc1950973910e748f2ccc655741d3a73604
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 15:59:08 2015 -0500

    8080: Use shared gems directory, use flock so that only one bundler process updates
    it at once.  Use same container for tests or demo run.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index 357cefc..22973ac 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -23,9 +23,6 @@ fi
 if test -z "$ARVBOX_CONTAINER" ; then
     ARVBOX_CONTAINER=arvbox
 fi
-if test -z "$ARVBOX_TEST_CONTAINER" ; then
-    ARVBOX_TEST_CONTAINER=arvbox-tests
-fi
 
 PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
@@ -84,7 +81,7 @@ case $1 in
         ;;
 
     sh*)
-        docker exec -ti $ARVBOX_CONTAINER /bin/bash
+        docker exec -ti $ARVBOX_CONTAINER /usr/bin/env TERM=$TERM /bin/bash
         ;;
 
     stop)
@@ -127,47 +124,44 @@ case $1 in
         ;;
 
     tests)
+        stop
+
         if ! test -d $ARVADOS_DEV_ROOT ; then
             git clone https://github.com/curoverse/arvados-dev.git $ARVADOS_DEV_ROOT
         fi
 
-        if docker ps |grep -E "\b$ARVBOX_TEST_CONTAINER\b" -q ; then
-            docker start $ARVBOX_TEST_CONTAINER
-        else
-            docker run \
-                   --detach \
-                   --name=$ARVBOX_TEST_CONTAINER \
-                   --privileged \
-                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-                   --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
-                   --volume=$SSO_ROOT:/usr/src/sso:rw \
-                   --volume=$PG_DATA:/var/lib/postgresql:rw \
-                   --volume=$ARV_DATA:/var/lib/arvados:rw \
-                   --volume=$PASSENGER:/var/lib/passenger:rw \
-                   --volume=/var/lib/docker \
-                   arvados/arvbox \
-                   runsvdir /etc/tests-service
-        fi
+        docker run \
+               --detach \
+               --name=$ARVBOX_CONTAINER \
+               --privileged \
+               --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+               --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
+               --volume=$SSO_ROOT:/usr/src/sso:rw \
+               --volume=$PG_DATA:/var/lib/postgresql:rw \
+               --volume=$ARV_DATA:/var/lib/arvados:rw \
+               --volume=$PASSENGER:/var/lib/passenger:rw \
+               --volume=/var/lib/docker \
+               arvados/arvbox \
+               runsvdir /etc/tests-service
+
         shift
-        docker exec -ti $ARVBOX_TEST_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
+        docker exec -ti $ARVBOX_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
                --leave-temp \
                WORKSPACE=/usr/src/arvados \
                VENVDIR=/var/lib/arvados/tests-venv \
                VENV3DIR=/var/lib/arvados/tests-venv3 \
                GOPATH=/var/lib/arvados/tests-gostuff \
+               GEMHOME=/var/lib/arvados/gems \
                "$@"
-        docker stop $ARVBOX_TEST_CONTAINER
-        ;;
-
-    stop-tests)
-        docker rm $ARVBOX_TEST_CONTAINER
+        docker stop $ARVBOX_CONTAINER
+        docker rm $ARVBOX_CONTAINER
         ;;
 
 
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|log|svrestart|tests|stop-tests)"
+        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|log|svrestart|tests)"
         echo
         echo "build      build arvbox Docker image"
         echo "start|run  start $ARVBOX_CONTAINER container "
@@ -179,7 +173,6 @@ case $1 in
         echo "reset      delete persistent data (careful!)"
         echo "log <component>       tail log of specified service"
         echo "svrestart <component> restart specified service inside arvbox"
-        echo "tests      run run-tests.sh inside $ARVBOX_TEST_CONTAINER container"
-        echo "stop-tests stop $ARVBOX_TEST_CONTAINER container"
+        echo "tests      run run-tests.sh inside $ARVBOX_CONTAINER container"
         ;;
 esac
diff --git a/arvbox/docker/common.sh b/arvbox/docker/common.sh
index 16297ba..20d164c 100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
@@ -1 +1,8 @@
 localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
+export GEM_HOME=/var/lib/arvados/gems
+
+run_bundler() {
+    if ! flock /var/lib/arvados/gems.lock bundle install --local --no-deployment "$@" ; then
+        flock /var/lib/arvados/gems.lock bundle install --no-deployment "$@"
+    fi
+}
diff --git a/arvbox/docker/service/api/run b/arvbox/docker/service/api/run
index 935c93c..d290420 100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run
@@ -8,7 +8,7 @@ set -eux
 cd /usr/src/arvados/services/api
 export RAILS_ENV=development
 
-bundle install --without=development --path=vendor
+run_bundler --without=development
 
 if ! test -s /var/lib/arvados/api_uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
index 17cc0eb..60ac664 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
@@ -50,8 +50,14 @@ if ! docker version >/dev/null 2>/dev/null ; then
 fi
 
 if ! [[ -z "$waiting" ]] ; then
-  echo "    Waiting for$waiting ..."
-  exit 1
+    if ps x | grep -v grep | grep "bundle install" > /dev/null; then
+        waiting="$waiting (installing ruby gems)"
+    fi
+    if ps x | grep -v grep | grep "pip install" > /dev/null; then
+        waiting="$waiting (installing python packages)"
+    fi
+    echo "    Waiting for$waiting ..."
+    exit 1
 fi
 
 echo
diff --git a/arvbox/docker/service/sso/run b/arvbox/docker/service/sso/run
index 953ea95..718c526 100755
--- a/arvbox/docker/service/sso/run
+++ b/arvbox/docker/service/sso/run
@@ -7,8 +7,9 @@ set -eux
 
 cd /usr/src/sso
 export RAILS_ENV=development
+export GEM_HOME=/var/lib/arvados/gems
 
-bundle install --without=development --path=vendor
+run_bundler --without=development
 
 if ! test -s /var/lib/arvados/sso_uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/sso_uuid_prefix
diff --git a/arvbox/docker/service/vm/run b/arvbox/docker/service/vm/run
index 0bdd8ea..807d976 100755
--- a/arvbox/docker/service/vm/run
+++ b/arvbox/docker/service/vm/run
@@ -38,7 +38,7 @@ fi
 export ARVADOS_VIRTUAL_MACHINE_UUID
 
 cd /usr/src/arvados/services/login-sync
-bundle install
+run_bundler
 
 while true ; do
       bundle exec arvados-login-sync
diff --git a/arvbox/docker/service/workbench/run b/arvbox/docker/service/workbench/run
index 50f5b26..a08136c 100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
@@ -7,8 +7,9 @@ set -eux
 
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development
+export GEM_HOME=/var/lib/arvados/gems
 
-bundle install --without=development --path=vendor
+run_bundler --without=development
 
 if ! test -s secret_token ; then
   ruby -e 'puts rand(2**400).to_s(36)' > secret_token

commit 0df06e7b8af5ebfa6675fc84f2d3bd81a1b0d7a8
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 13:34:25 2015 -0500

    Added readme.  Fixed keep-setup.sh.  Add note that first time takes a long time.

diff --git a/arvbox/.gitignore b/arvbox/.gitignore
deleted file mode 100644
index a79dc86..0000000
--- a/arvbox/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-arvados/
-arvados-dev/
-sso-devise-omniauth-provider/
-passenger/
-postgres/
-var/
-*~
diff --git a/arvbox/README b/arvbox/README
new file mode 100644
index 0000000..f6b7267
--- /dev/null
+++ b/arvbox/README
@@ -0,0 +1,18 @@
+Arvados-in-a-box
+
+Development and demonstration environment for Arvados.
+
+Usage:
+
+$ ./arvbox reboot
+
+Notes:
+
+Services are designed to install and auto-configure on start or restart.  For
+example, the service script for keepstore always compiles keepstore from source
+and registers the daemon with the API server.
+
+Services are run with process supervision, so a service which exits will be
+restarted.  Dependencies between services are handled by repeatedly trying and
+failing the service script until dependencies are fulfilled (by other service
+scripts) enabling the service script to complete.
diff --git a/arvbox/docker/keep-setup.sh b/arvbox/docker/keep-setup.sh
index c321070..a4f8832 100755
--- a/arvbox/docker/keep-setup.sh
+++ b/arvbox/docker/keep-setup.sh
@@ -50,6 +50,6 @@ fi
 exec /usr/local/bin/keepstore \
      -listen=:$2 \
      -enforce-permissions=true \
-     -blob-signing-key-file=/usr/src/arvados/services/api/blob_signing_key \
+     -blob-signing-key-file=/var/lib/arvados/blob_signing_key \
      -max-buffers=20 \
      -volume=/var/lib/arvados/keep
diff --git a/arvbox/docker/service/ready/run b/arvbox/docker/service/ready/run
index 8e03e3e..17cc0eb 100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
@@ -8,6 +8,9 @@ if ! [[ -d /root/ready ]] ; then
    echo
    echo "Arvados-in-a-box starting"
    echo
+   echo "Note: if this is a fresh arvbox installation, it may take 10-15 minutes to download and"
+   echo "install dependencies.  Use \"arvbox log\" to monitor the progress of specific services."
+   echo
    mkdir -p /root/ready
 fi
 
@@ -57,4 +60,4 @@ echo "Workbench is running at http://$localip"
 
 rm -r /root/ready
 
-sv stop ready >/dev/null
\ No newline at end of file
+sv stop ready >/dev/null

commit 367e48543244672e1656e50848bed6f99dc393dc
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 13:22:29 2015 -0500

    8080: Missing postgres

diff --git a/arvbox/arvbox b/arvbox/arvbox
index 737f800..357cefc 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -1,8 +1,8 @@
 #!/bin/sh
 
-ARVBOX=$(readlink -f $(dirname $0))
-
+set -e
 
+ARVBOX=$(readlink -f $(dirname $0))
 
 if test -z "$ARVADOS_ROOT" ; then
     ARVADOS_ROOT=$ARVBOX/arvados
@@ -34,6 +34,13 @@ ARV_DATA=$ARVBOX_DATA/var
 mkdir -p $PASSENGER $PG_DATA $ARV_DATA
 
 run() {
+    if ! test -d $ARVADOS_ROOT ; then
+        git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
+    fi
+    if ! test -d $SSO_ROOT ; then
+        git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
+    fi
+
     docker run \
            --detach \
            --name=$ARVBOX_CONTAINER \
@@ -60,8 +67,10 @@ run() {
 }
 
 stop() {
+    if docker ps |grep -E "\b$ARVBOX_CONTAINER\b" -q ; then
         docker stop $ARVBOX_CONTAINER
         docker rm $ARVBOX_CONTAINER
+    fi
 }
 
 case $1 in
@@ -71,13 +80,6 @@ case $1 in
         ;;
 
     start|run)
-        if ! test -d $ARVADOS_ROOT ; then
-            git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
-        fi
-        if ! test -d $SSO_ROOT ; then
-            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
-        fi
-
         run
         ;;
 
diff --git a/arvbox/docker/service/postgres/log/main/.gitstub b/arvbox/docker/service/postgres/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/arvbox/docker/service/postgres/log/run b/arvbox/docker/service/postgres/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/arvbox/docker/service/postgres/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/arvbox/docker/service/postgres/run b/arvbox/docker/service/postgres/run
new file mode 100755
index 0000000..2b7c497
--- /dev/null
+++ b/arvbox/docker/service/postgres/run
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+exec 2>&1
+set -eux
+
+chown postgres:postgres -R /var/lib/postgresql
+if ! test -d /var/lib/postgresql/9.4/main ; then
+   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
+fi
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+chown postgres:postgres -R /var/run/postgresql
+
+exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"

commit 867a4c46855eaaf4b018da59c973806dbb4c7e04
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 13:03:08 2015 -0500

    Update documentation, and use ~/.arvbox as default data dir.

diff --git a/arvbox/arvbox b/arvbox/arvbox
index 44bde6f..737f800 100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
@@ -2,6 +2,8 @@
 
 ARVBOX=$(readlink -f $(dirname $0))
 
+
+
 if test -z "$ARVADOS_ROOT" ; then
     ARVADOS_ROOT=$ARVBOX/arvados
 fi
@@ -15,7 +17,7 @@ if test -z "$SSO_ROOT" ; then
 fi
 
 if test -z "$ARVBOX_DATA" ; then
-    ARVBOX_DATA=$ARVBOX
+    ARVBOX_DATA=$HOME/.arvbox
 fi
 
 if test -z "$ARVBOX_CONTAINER" ; then
@@ -94,8 +96,8 @@ case $1 in
         run
         ;;
 
-    sv|service)
-        docker exec -ti $ARVBOX_CONTAINER sv $2 $3
+    svrestart)
+        docker exec -ti $ARVBOX_CONTAINER sv restart $2
         docker exec -ti $ARVBOX_CONTAINER sv restart ready
         ;;
 
@@ -163,15 +165,19 @@ case $1 in
     *)
         echo "Arvados-in-a-box"
         echo
-        echo "$0 (build|start|run|stop|shell|stop|ip|open|resetdb|log)"
+        echo "$0 (build|start|run|open|shell|ip|stop|reboot|reset|log|svrestart|tests|stop-tests)"
         echo
         echo "build      build arvbox Docker image"
-        echo "start|run  start arvbox"
-        echo "stop       stop arvbo"
+        echo "start|run  start $ARVBOX_CONTAINER container "
+        echo "open       open arvbox workbench in a web browser"
         echo "shell      enter arvbox shell"
         echo "ip         print arvbox ip address"
-        echo "open       open arvbox workbench in a web browser"
-        echo "resetdb    delete persistent data (be careful!)"
-        echo "log <component> tail log of specified service"
+        echo "stop       stop arvbox"
+        echo "reboot     stop arvbox, build image, run"
+        echo "reset      delete persistent data (careful!)"
+        echo "log <component>       tail log of specified service"
+        echo "svrestart <component> restart specified service inside arvbox"
+        echo "tests      run run-tests.sh inside $ARVBOX_TEST_CONTAINER container"
+        echo "stop-tests stop $ARVBOX_TEST_CONTAINER container"
         ;;
 esac

commit c99df01959c27fc140e0124c509380a81948847b
Merge: bbed885 3e62819
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 12:55:36 2015 -0500

    Add 'arvbox/' from commit '3e62819af509f174087c3cebbccb97f3851e6919'
    
    git-subtree-dir: arvbox
    git-subtree-mainline: bbed885afb9007c5d81ff55af70e8d7be92f8f37
    git-subtree-split: 3e62819af509f174087c3cebbccb97f3851e6919

diff --cc arvbox/.gitignore
index 0000000,a79dc86..a79dc86
mode 000000,100644..100644
--- a/arvbox/.gitignore
+++ b/arvbox/.gitignore
diff --cc arvbox/arvbox
index 0000000,44bde6f..44bde6f
mode 000000,100755..100755
--- a/arvbox/arvbox
+++ b/arvbox/arvbox
diff --cc arvbox/docker/Dockerfile
index 0000000,85acd0f..85acd0f
mode 000000,100644..100644
--- a/arvbox/docker/Dockerfile
+++ b/arvbox/docker/Dockerfile
diff --cc arvbox/docker/common.sh
index 0000000,16297ba..16297ba
mode 000000,100644..100644
--- a/arvbox/docker/common.sh
+++ b/arvbox/docker/common.sh
diff --cc arvbox/docker/crunch-setup.sh
index 0000000,4b70250..4b70250
mode 000000,100755..100755
--- a/arvbox/docker/crunch-setup.sh
+++ b/arvbox/docker/crunch-setup.sh
diff --cc arvbox/docker/fuse.conf
index 0000000,a439ab8..a439ab8
mode 000000,100644..100644
--- a/arvbox/docker/fuse.conf
+++ b/arvbox/docker/fuse.conf
diff --cc arvbox/docker/gitolite-setup.sh
index 0000000,9a2ff9e..9a2ff9e
mode 000000,100755..100755
--- a/arvbox/docker/gitolite-setup.sh
+++ b/arvbox/docker/gitolite-setup.sh
diff --cc arvbox/docker/gitolite.rc
index 0000000,b735870..b735870
mode 000000,100644..100644
--- a/arvbox/docker/gitolite.rc
+++ b/arvbox/docker/gitolite.rc
diff --cc arvbox/docker/gitssh-setup.sh
index 0000000,aad7dd4..aad7dd4
mode 000000,100755..100755
--- a/arvbox/docker/gitssh-setup.sh
+++ b/arvbox/docker/gitssh-setup.sh
diff --cc arvbox/docker/keep-setup.sh
index 0000000,c321070..c321070
mode 000000,100755..100755
--- a/arvbox/docker/keep-setup.sh
+++ b/arvbox/docker/keep-setup.sh
diff --cc arvbox/docker/runit-docker/.gitignore
index 0000000,bbf313b..bbf313b
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/.gitignore
+++ b/arvbox/docker/runit-docker/.gitignore
diff --cc arvbox/docker/runit-docker/LICENSE
index 0000000,d158667..d158667
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/LICENSE
+++ b/arvbox/docker/runit-docker/LICENSE
diff --cc arvbox/docker/runit-docker/Makefile
index 0000000,9a28963..9a28963
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/Makefile
+++ b/arvbox/docker/runit-docker/Makefile
diff --cc arvbox/docker/runit-docker/README.md
index 0000000,1bcb8cc..1bcb8cc
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/README.md
+++ b/arvbox/docker/runit-docker/README.md
diff --cc arvbox/docker/runit-docker/debian/changelog
index 0000000,7d8689f..7d8689f
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/changelog
+++ b/arvbox/docker/runit-docker/debian/changelog
diff --cc arvbox/docker/runit-docker/debian/compat
index 0000000,ec63514..ec63514
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/compat
+++ b/arvbox/docker/runit-docker/debian/compat
diff --cc arvbox/docker/runit-docker/debian/control
index 0000000,4060915..4060915
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/control
+++ b/arvbox/docker/runit-docker/debian/control
diff --cc arvbox/docker/runit-docker/debian/copyright
index 0000000,8679a6a..8679a6a
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/copyright
+++ b/arvbox/docker/runit-docker/debian/copyright
diff --cc arvbox/docker/runit-docker/debian/docs
index 0000000,b43bf86..b43bf86
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/docs
+++ b/arvbox/docker/runit-docker/debian/docs
diff --cc arvbox/docker/runit-docker/debian/rules
index 0000000,ce15cce..ce15cce
mode 000000,100755..100755
--- a/arvbox/docker/runit-docker/debian/rules
+++ b/arvbox/docker/runit-docker/debian/rules
diff --cc arvbox/docker/runit-docker/debian/source/format
index 0000000,163aaf8..163aaf8
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/debian/source/format
+++ b/arvbox/docker/runit-docker/debian/source/format
diff --cc arvbox/docker/runit-docker/runit-docker
index 0000000,fdbaad5..fdbaad5
mode 000000,100755..100755
--- a/arvbox/docker/runit-docker/runit-docker
+++ b/arvbox/docker/runit-docker/runit-docker
diff --cc arvbox/docker/runit-docker/runit-docker.c
index 0000000,825a35f..825a35f
mode 000000,100644..100644
--- a/arvbox/docker/runit-docker/runit-docker.c
+++ b/arvbox/docker/runit-docker/runit-docker.c
diff --cc arvbox/docker/service/api/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/api/log/main/.gitstub
+++ b/arvbox/docker/service/api/log/main/.gitstub
diff --cc arvbox/docker/service/api/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/api/log/run
+++ b/arvbox/docker/service/api/log/run
diff --cc arvbox/docker/service/api/run
index 0000000,935c93c..935c93c
mode 000000,100755..100755
--- a/arvbox/docker/service/api/run
+++ b/arvbox/docker/service/api/run
diff --cc arvbox/docker/service/crunch0/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/crunch0/log/main/.gitstub
+++ b/arvbox/docker/service/crunch0/log/main/.gitstub
diff --cc arvbox/docker/service/crunch0/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/crunch0/log/run
+++ b/arvbox/docker/service/crunch0/log/run
diff --cc arvbox/docker/service/crunch0/run
index 0000000,dd864a0..dd864a0
mode 000000,100755..100755
--- a/arvbox/docker/service/crunch0/run
+++ b/arvbox/docker/service/crunch0/run
diff --cc arvbox/docker/service/crunch1/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/crunch1/log/main/.gitstub
+++ b/arvbox/docker/service/crunch1/log/main/.gitstub
diff --cc arvbox/docker/service/crunch1/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/crunch1/log/run
+++ b/arvbox/docker/service/crunch1/log/run
diff --cc arvbox/docker/service/crunch1/run
index 0000000,d7583e5..d7583e5
mode 000000,100755..100755
--- a/arvbox/docker/service/crunch1/run
+++ b/arvbox/docker/service/crunch1/run
diff --cc arvbox/docker/service/docker/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/docker/log/main/.gitstub
+++ b/arvbox/docker/service/docker/log/main/.gitstub
diff --cc arvbox/docker/service/docker/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/docker/log/run
+++ b/arvbox/docker/service/docker/log/run
diff --cc arvbox/docker/service/docker/run
index 0000000,99540e6..99540e6
mode 000000,100755..100755
--- a/arvbox/docker/service/docker/run
+++ b/arvbox/docker/service/docker/run
diff --cc arvbox/docker/service/git/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/git/log/main/.gitstub
+++ b/arvbox/docker/service/git/log/main/.gitstub
diff --cc arvbox/docker/service/git/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/git/log/run
+++ b/arvbox/docker/service/git/log/run
diff --cc arvbox/docker/service/git/run
index 0000000,c87c25b..c87c25b
mode 000000,100755..100755
--- a/arvbox/docker/service/git/run
+++ b/arvbox/docker/service/git/run
diff --cc arvbox/docker/service/githttp/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/githttp/log/main/.gitstub
+++ b/arvbox/docker/service/githttp/log/main/.gitstub
diff --cc arvbox/docker/service/githttp/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/githttp/log/run
+++ b/arvbox/docker/service/githttp/log/run
diff --cc arvbox/docker/service/githttp/run
index 0000000,390727d..390727d
mode 000000,100755..100755
--- a/arvbox/docker/service/githttp/run
+++ b/arvbox/docker/service/githttp/run
diff --cc arvbox/docker/service/keep0/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/keep0/log/main/.gitstub
+++ b/arvbox/docker/service/keep0/log/main/.gitstub
diff --cc arvbox/docker/service/keep0/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/keep0/log/run
+++ b/arvbox/docker/service/keep0/log/run
diff --cc arvbox/docker/service/keep0/run
index 0000000,aa5b69c..aa5b69c
mode 000000,100755..100755
--- a/arvbox/docker/service/keep0/run
+++ b/arvbox/docker/service/keep0/run
diff --cc arvbox/docker/service/keep1/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/keep1/log/main/.gitstub
+++ b/arvbox/docker/service/keep1/log/main/.gitstub
diff --cc arvbox/docker/service/keep1/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/keep1/log/run
+++ b/arvbox/docker/service/keep1/log/run
diff --cc arvbox/docker/service/keep1/run
index 0000000,8b0d907..8b0d907
mode 000000,100755..100755
--- a/arvbox/docker/service/keep1/run
+++ b/arvbox/docker/service/keep1/run
diff --cc arvbox/docker/service/keepproxy/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/keepproxy/log/main/.gitstub
+++ b/arvbox/docker/service/keepproxy/log/main/.gitstub
diff --cc arvbox/docker/service/keepproxy/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/keepproxy/log/run
+++ b/arvbox/docker/service/keepproxy/log/run
diff --cc arvbox/docker/service/keepproxy/run
index 0000000,fe52d6d..fe52d6d
mode 000000,100755..100755
--- a/arvbox/docker/service/keepproxy/run
+++ b/arvbox/docker/service/keepproxy/run
diff --cc arvbox/docker/service/keepweb/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/keepweb/log/main/.gitstub
+++ b/arvbox/docker/service/keepweb/log/main/.gitstub
diff --cc arvbox/docker/service/keepweb/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/keepweb/log/run
+++ b/arvbox/docker/service/keepweb/log/run
diff --cc arvbox/docker/service/keepweb/run
index 0000000,ad808fe..ad808fe
mode 000000,100755..100755
--- a/arvbox/docker/service/keepweb/run
+++ b/arvbox/docker/service/keepweb/run
diff --cc arvbox/docker/service/logger
index 0000000,a79a518..a79a518
mode 000000,100755..100755
--- a/arvbox/docker/service/logger
+++ b/arvbox/docker/service/logger
diff --cc arvbox/docker/service/ready/run
index 0000000,8e03e3e..8e03e3e
mode 000000,100755..100755
--- a/arvbox/docker/service/ready/run
+++ b/arvbox/docker/service/ready/run
diff --cc arvbox/docker/service/sdk/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/sdk/log/main/.gitstub
+++ b/arvbox/docker/service/sdk/log/main/.gitstub
diff --cc arvbox/docker/service/sdk/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/sdk/log/run
+++ b/arvbox/docker/service/sdk/log/run
diff --cc arvbox/docker/service/sdk/run
index 0000000,10df374..10df374
mode 000000,100755..100755
--- a/arvbox/docker/service/sdk/run
+++ b/arvbox/docker/service/sdk/run
diff --cc arvbox/docker/service/ssh/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/ssh/log/main/.gitstub
+++ b/arvbox/docker/service/ssh/log/main/.gitstub
diff --cc arvbox/docker/service/ssh/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/ssh/log/run
+++ b/arvbox/docker/service/ssh/log/run
diff --cc arvbox/docker/service/ssh/run
index 0000000,0db3ffa..0db3ffa
mode 000000,100755..100755
--- a/arvbox/docker/service/ssh/run
+++ b/arvbox/docker/service/ssh/run
diff --cc arvbox/docker/service/sso/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/sso/log/main/.gitstub
+++ b/arvbox/docker/service/sso/log/main/.gitstub
diff --cc arvbox/docker/service/sso/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/sso/log/run
+++ b/arvbox/docker/service/sso/log/run
diff --cc arvbox/docker/service/sso/run
index 0000000,953ea95..953ea95
mode 000000,100755..100755
--- a/arvbox/docker/service/sso/run
+++ b/arvbox/docker/service/sso/run
diff --cc arvbox/docker/service/vm/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/vm/log/main/.gitstub
+++ b/arvbox/docker/service/vm/log/main/.gitstub
diff --cc arvbox/docker/service/vm/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/vm/log/run
+++ b/arvbox/docker/service/vm/log/run
diff --cc arvbox/docker/service/vm/run
index 0000000,0bdd8ea..0bdd8ea
mode 000000,100755..100755
--- a/arvbox/docker/service/vm/run
+++ b/arvbox/docker/service/vm/run
diff --cc arvbox/docker/service/workbench/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/service/workbench/log/main/.gitstub
+++ b/arvbox/docker/service/workbench/log/main/.gitstub
diff --cc arvbox/docker/service/workbench/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/service/workbench/log/run
+++ b/arvbox/docker/service/workbench/log/run
diff --cc arvbox/docker/service/workbench/run
index 0000000,50f5b26..50f5b26
mode 000000,100755..100755
--- a/arvbox/docker/service/workbench/run
+++ b/arvbox/docker/service/workbench/run
diff --cc arvbox/docker/tests-service/postgres/log/main/.gitstub
index 0000000,e69de29..e69de29
mode 000000,100644..100644
--- a/arvbox/docker/tests-service/postgres/log/main/.gitstub
+++ b/arvbox/docker/tests-service/postgres/log/main/.gitstub
diff --cc arvbox/docker/tests-service/postgres/log/run
index 0000000,f99cc1d..f99cc1d
mode 000000,120000..120000
--- a/arvbox/docker/tests-service/postgres/log/run
+++ b/arvbox/docker/tests-service/postgres/log/run
diff --cc arvbox/docker/tests-service/postgres/run
index 0000000,2b7c497..2b7c497
mode 000000,100755..100755
--- a/arvbox/docker/tests-service/postgres/run
+++ b/arvbox/docker/tests-service/postgres/run

commit 3e62819af509f174087c3cebbccb97f3851e6919
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 12:52:28 2015 -0500

    Can also run run-tests (but many tests fail).

diff --git a/arvbox b/arvbox
index 98e4838..44bde6f 100755
--- a/arvbox
+++ b/arvbox
@@ -147,10 +147,10 @@ case $1 in
         shift
         docker exec -ti $ARVBOX_TEST_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
                --leave-temp \
-               WORKSPACE=/usr/src/arvados
-               VENVDIR=/var/lib/arvados/tests/venv \
-               VENV3DIR=/var/lib/arvados/tests/venv3 \
-               GOPATH=/var/lib/arvados/tests/gostuff \
+               WORKSPACE=/usr/src/arvados \
+               VENVDIR=/var/lib/arvados/tests-venv \
+               VENV3DIR=/var/lib/arvados/tests-venv3 \
+               GOPATH=/var/lib/arvados/tests-gostuff \
                "$@"
         docker stop $ARVBOX_TEST_CONTAINER
         ;;
diff --git a/docker/Dockerfile b/docker/Dockerfile
index d004952..85acd0f 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,6 +1,7 @@
 FROM debian:8
 
-RUN apt-get update && apt-get -y -q install \
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -yq install \
     postgresql-9.4 git gcc golang-go runit \
     ruby rake bundler curl libpq-dev \
     libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
@@ -9,12 +10,11 @@ RUN apt-get update && apt-get -y -q install \
     pkg-config libattr1-dev python-llfuse python-pycurl \
     libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
     libjson-perl python-virtualenv python3-virtualenv nginx \
-    gitolite3
+    gitolite3 lsof
 
 RUN curl -sSL https://get.docker.com/ | sh
 
 ADD runit-docker /root/runit-docker
-ADD gitolite /root/gitolite
 
 RUN cd /root/runit-docker && \
     make && \
diff --git a/docker/gitssh-setup.sh b/docker/gitssh-setup.sh
index 41c280d..aad7dd4 100755
--- a/docker/gitssh-setup.sh
+++ b/docker/gitssh-setup.sh
@@ -11,7 +11,9 @@ if ! test -s .ssh/id_rsa ; then
     ssh-keygen -t rsa -P '' -f .ssh/id_rsa
 fi
 
-ssh-keygen -f "/var/lib/arvados/git/.ssh/known_hosts" -R localhost
+if test -s /var/lib/arvados/git/.ssh/known_hosts ; then
+    ssh-keygen -f "/var/lib/arvados/git/.ssh/known_hosts" -R localhost
+fi
 
 if ! test -s .ssh/authorized_keys ; then
     cp .ssh/id_rsa.pub .ssh/authorized_keys
diff --git a/docker/service/git/run b/docker/service/git/run
index 6e79f26..c87c25b 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -9,11 +9,10 @@ mkdir -p /var/lib/arvados/git
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 if ! test -f /var/lib/arvados/gitolite-setup ; then
-   cp -r /root/gitolite /var/lib/arvados/git/
-   cp -r /root/gitolite /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
+   cp -r /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
 
    chown -R git:git ~git
 
@@ -26,6 +25,8 @@ else
     su git -c "/var/lib/arvados/git/gitssh-setup.sh"
 fi
 
+prefix=$(arv --format=uuid user current | cut -d- -f1)
+
 if ! test -s /var/lib/arvados/arvados-git-uuid ; then
     repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
     echo $repo_uuid > /var/lib/arvados/arvados-git-uuid
@@ -34,7 +35,6 @@ fi
 repo_uuid=$(cat /var/lib/arvados/arvados-git-uuid)
 
 if ! test -s /var/lib/arvados/arvados-git-link-uuid ; then
-    prefix=$(arv --format=uuid user current | cut -d- -f1)
     all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
 
     set +e
diff --git a/docker/service/keepweb/run b/docker/service/keepweb/run
index e549087..ad808fe 100755
--- a/docker/service/keepweb/run
+++ b/docker/service/keepweb/run
@@ -16,6 +16,6 @@ install bin/keep-web /usr/local/bin
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
diff --git a/docker/service/vm/run b/docker/service/vm/run
index 0c13547..0bdd8ea 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -11,7 +11,7 @@ git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/de
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 if test -s /var/lib/arvados/vm-uuid ; then
     ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)

commit 5240eb2532921c731553615c1bb96463b82cfb4b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 11:08:15 2015 -0500

    Use debian gitolite3 package instead of bundling our own.

diff --git a/.gitignore b/.gitignore
index a45f4ab..a79dc86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 arvados/
+arvados-dev/
 sso-devise-omniauth-provider/
 passenger/
 postgres/
 var/
-*~
\ No newline at end of file
+*~
diff --git a/arvbox b/arvbox
index 42fa3a9..98e4838 100755
--- a/arvbox
+++ b/arvbox
@@ -6,6 +6,10 @@ if test -z "$ARVADOS_ROOT" ; then
     ARVADOS_ROOT=$ARVBOX/arvados
 fi
 
+if test -z "$ARVADOS_DEV_ROOT" ; then
+    ARVADOS_DEV_ROOT=$ARVBOX/arvados-dev
+fi
+
 if test -z "$SSO_ROOT" ; then
     SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
 fi
@@ -17,6 +21,9 @@ fi
 if test -z "$ARVBOX_CONTAINER" ; then
     ARVBOX_CONTAINER=arvbox
 fi
+if test -z "$ARVBOX_TEST_CONTAINER" ; then
+    ARVBOX_TEST_CONTAINER=arvbox-tests
+fi
 
 PASSENGER=$ARVBOX_DATA/passenger
 PG_DATA=$ARVBOX_DATA/postgres
@@ -115,6 +122,44 @@ case $1 in
         docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
         ;;
 
+    tests)
+        if ! test -d $ARVADOS_DEV_ROOT ; then
+            git clone https://github.com/curoverse/arvados-dev.git $ARVADOS_DEV_ROOT
+        fi
+
+        if docker ps |grep -E "\b$ARVBOX_TEST_CONTAINER\b" -q ; then
+            docker start $ARVBOX_TEST_CONTAINER
+        else
+            docker run \
+                   --detach \
+                   --name=$ARVBOX_TEST_CONTAINER \
+                   --privileged \
+                   --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+                   --volume=$ARVADOS_DEV_ROOT:/usr/src/arvados-dev:rw \
+                   --volume=$SSO_ROOT:/usr/src/sso:rw \
+                   --volume=$PG_DATA:/var/lib/postgresql:rw \
+                   --volume=$ARV_DATA:/var/lib/arvados:rw \
+                   --volume=$PASSENGER:/var/lib/passenger:rw \
+                   --volume=/var/lib/docker \
+                   arvados/arvbox \
+                   runsvdir /etc/tests-service
+        fi
+        shift
+        docker exec -ti $ARVBOX_TEST_CONTAINER /usr/src/arvados-dev/jenkins/run-tests.sh \
+               --leave-temp \
+               WORKSPACE=/usr/src/arvados
+               VENVDIR=/var/lib/arvados/tests/venv \
+               VENV3DIR=/var/lib/arvados/tests/venv3 \
+               GOPATH=/var/lib/arvados/tests/gostuff \
+               "$@"
+        docker stop $ARVBOX_TEST_CONTAINER
+        ;;
+
+    stop-tests)
+        docker rm $ARVBOX_TEST_CONTAINER
+        ;;
+
+
     *)
         echo "Arvados-in-a-box"
         echo
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 60a6823..d004952 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -8,7 +8,8 @@ RUN apt-get update && apt-get -y -q install \
     libpython-dev fuse libfuse-dev python-pip \
     pkg-config libattr1-dev python-llfuse python-pycurl \
     libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
-    libjson-perl
+    libjson-perl python-virtualenv python3-virtualenv nginx \
+    gitolite3
 
 RUN curl -sSL https://get.docker.com/ | sh
 
@@ -29,6 +30,7 @@ RUN useradd crunch && \
 
 ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh /root/
 ADD service /etc/service
+ADD tests-service /etc/tests-service
 
 # Start the supervisor.
 CMD ["runsvdir", "/etc/service"]
diff --git a/docker/gitolite-setup.sh b/docker/gitolite-setup.sh
index 203fbec..9a2ff9e 100755
--- a/docker/gitolite-setup.sh
+++ b/docker/gitolite-setup.sh
@@ -4,9 +4,7 @@ set -e
 
 cd ~
 
-mkdir -p bin
-gitolite/install -ln ~git/bin
-bin/gitolite setup -pk .ssh/id_rsa.pub
+gitolite setup -pk .ssh/id_rsa.pub
 
 if ! test -d gitolite-admin ; then
     git clone git at localhost:gitolite-admin
diff --git a/docker/gitolite/CHANGELOG b/docker/gitolite/CHANGELOG
deleted file mode 100644
index 98e0174..0000000
--- a/docker/gitolite/CHANGELOG
+++ /dev/null
@@ -1,221 +0,0 @@
-2015-11-01  v3.6.4  a ref-create bug in wild repos was fixed
-
-                    some contrib code related to AD integration, and to
-                    redmine user aliases
-
-                    teach Alias.pm a few new tricks
-
-                    remove a race condition in 'create' command that affected
-                    the 'default roles' setting
-
-                    make 'who-pushed' more efficient (local push logs, and
-                    'tip search')
-
-                    'gitolite query-rc' learns '-d' ('--dump') option
-
-2015-04-26  v3.6.3  allow limited use of 'git config' using the new 'config'
-                    command
-
-                    accept openssh 6.8's new fingerprint output format
-
-                    (finally!) allow limited symlinks within ~/repositories;
-                    see commit 8e36230 for details
-
-                    perms command now lists available roles
-
-                    minor backward compat breakage: 'perms -l repo' no longer
-                    works; see 'perms -h' for new usage
-
-                    allow gitolite-shell to be used as $SHELL (experts only;
-                    no support, no docs; see commit 9cd1e37 for details)
-
-                    help with 'git push --signed' using a post-receive hook to
-                    adopt push certs into 'refs/push-certs'; for details see
-                    contrib/hooks/repo-specific/save-push-signatures
-
-                    new 'transparent proxy' feature for git repos; see
-                    src/lib/Gitolite/Triggers/TProxy.pm for details
-
-2014-11-10  v3.6.2  disable ../ everywhere (see mailing list thread for
-                    details)
-
-                    VREF/NAME_NC -- like VREF/NAME but for new commits only.
-                    Details within src/VREF/NAME_NC.
-
-                    allow gitolite.conf to be tested locally; details within
-                    contrib/utils/gitolite-local
-
-2014-06-22  v3.6.1  experimental rc format convertor for "<= 3.3" users who
-                    have already upgraded the *code* to ">= v3.4".  Program is
-                    in contrib/utils.
-
-                    giving shell access to a few users got a lot easier (see
-                    comments in the rc file).
-
-                    allow logging to syslog as well (see comments in the rc
-                    file)
-
-                    new 'motd' command
-
-                    redis caching redone and now in core; see
-                    http://gitolite.com/gitolite/cache.html
-
-2014-05-09  v3.6    (cool stuff) the access command can now help you debug
-                    your rules / understand how a specific access decision was
-                    arrived at.
-
-                    mirroring: since mirroring is asynchronous (by default
-                    anyway), when a 'git push --mirror' fails, you may not
-                    know it unless you look in the log file on the server.
-                    Now gitolite captures the info and -- if the word 'fatal'
-                    appears anywhere within it, it saves the entire output and
-                    prints it to STDERR for anyone who reads or writes the
-                    repo on the *master* server, until the error condition
-                    clears up.
-
-                    mirroring: allow 'nosync' slaves -- no attempt to
-                    automatically push to these slaves will be made.  Instead,
-                    you have to manually (or via cron, etc) trigger pushes.
-
-                    (backward compat breakage) the old v2 syntax for
-                    specifying gitweb owner and description is no longer
-                    supported.
-
-                    macros now allow strings as arguments (thanks to Jason
-                    Donenfeld for the idea/problem).
-
-                    the 'info' command can print in JSON format if asked to.
-
-                    repo-specific hooks: now you can specify more than one,
-                    and gitolite runs all of them in sequence.
-
-                    new trigger 'expand-deny-messages' to show more details
-                    when access is denied.
-
-                    git-annex support is finally in master, yaaay!
-
-                    new 'readme' command, modelled after 'desc'.  Apparently
-                    gitweb can use a README.html file in the *bare* repo
-                    directory -- who knew!
-
-2013-10-14  v3.5.3  catch undefined groupnames (when possible)
-
-                    mirroring: async push to slaves
-
-                    (some portability fixes)
-
-                    (a couple of contrib scripts - querying IPA based LDAP
-                    servers for group membership, and user key management)
-
-                    allow groups in subconf files (this *may* slow down
-                    compilation in extreme cases)
-
-                    make adding repo-specific hooks easier (see cust.mkd or
-                    cust.html online for docs)
-
-                    smart http now supports git 1.8.2 and above (which changed
-                    the protocol requirements a wee bit)
-
-2013-07-10  v3.5.2  allow ENV vars to be set from repo options, for use in
-                    triggers and hooks
-
-                    bug-fix: the new set-default-roles feature was being
-                    invoked on every run of "perms" and overriding it!
-
-2013-03-24  v3.5    (2 minor backward compat breakages)
-                    1.  'DEFAULT_ROLE_PERMS' replaced by per repo
-                        'default.roles' option
-                    2.  'gitolite list-memberships' now requires a '-r' or a
-                        '-u' flag
-
-                    new 'gitolite owns' command (thanks to Kevin Pulo)
-
-2013-03-05  v3.4    new rc file format makes it much easier to enable specific
-                    features
-
-2012-12-29  v3.3    bug fix: gl-perms propagation to slaves broke sometime
-                    after v3.2 (so if you're only picking up tagged releases
-                    you're OK)
-
-                    the "D" command now allows rm/unlock to be totally
-                    disabled
-
-                    new trigger: update-gitweb-daemon-from-options; another
-                    way to update gitweb and daemon access lists
-
-                    new 'create' command for explicit wild repo creation, and
-                    new AutoCreate trigger to control auto-creation
-
-                    allow simple macros in conf file
-
-2012-11-14  v3.2    major efficiency boost for large setups
-
-                    optional support for multi-line pubkeys; see
-                    src/triggers/post-compile/ssh-authkeys-split
-
-                    bug fix for not creating gl-conf when repo para has only
-                    config lines and no access rules
-
-                    new 'bg' trigger command to put long jobs started from a
-                    trigger into background
-
-                    %GL_REPO and %GL_CREATOR now work for 'option's also
-
-                    test suite now much more BSD friendly
-
-2012-10-05  v3.1    (security) fix path traversal on wild repos
-
-                    new %GL_CREATOR variable for git-config lines
-
-                    rsync command to create and send bundles automagically
-
-                    migrated 'who-pushed'
-
-                    logical expressions on refexes!!!
-
-2012-06-27  v3.04   documentation graduated and moved out of parents house :)
-
-                    new trigger for 'repo specific umask'
-
-                    new 'list-dangling-repos' command
-
-                    new LOCAL_CODE rc var; allow admin specified programs to
-                    override system-installed ones
-
-                    new 'upstream' trigger-cum-command to maintain local
-                    copies of external repos
-
-                    new 'sudo' command
-
-                    minor backward compat breakage in 'gitolite query-rc'
-
-                    'perms' command can now create repo if needed
-
-                    migrated 'symbolic-ref' command
-
-                    'gitolite setup --hooks-only'
-
-2012-05-23  v3.03   fix major bug that allowed an admin to get a shell
-
-2012-05-20  v3.02   packaging instructions fixed up and smoke tested
-
-                    make it easier to give some users a full shell
-
-                    allow aliasing a repo to another name
-
-                    simulate POST_CREATE for new normal (non-wild) repos
-
-                    (just for kicks) a VREF that allows for voting on changes
-                    to a branch
-
-                    bug fix: smart http was not running PRE_ and POST_GIT
-                    triggers
-
-                    htpasswd migrated
-
-2012-04-29  v3.01   mostly BSD and Solaris compat
-                    also fork command added
-
-2012-04-18  v3.0    first release to "master"
-                    This is a compete rewrite of gitolite; please see
-                    documentation before upgrading.
diff --git a/docker/gitolite/CONTRIBUTING b/docker/gitolite/CONTRIBUTING
deleted file mode 100644
index 11009ba..0000000
--- a/docker/gitolite/CONTRIBUTING
+++ /dev/null
@@ -1,24 +0,0 @@
-Go to http://gitolite.com/gitolite/index.html#contact for information on
-contacting me, the mailing list, and IRC channel.  *Unless you are reporting
-what you think is a security issue, I prefer you send to the mailing list,
-not to me directly.*
-
-Please DO NOT send messages via github's "issues" system, linkedin
-comments/discussion, stackoverflow questions, google+, and any other Web 3.0
-"coolness". (The issues system does have an email interface, but it is not a
-substitute for email. I can't cc anyone else when I want to, for instance.
-Well I can, but any response the original requester then makes using the
-website will not get cc-d to the person I cc-d).
-
-Please send patches *via email*, not as github pull requests. Again, if you
-think it's a security issue, send it directly to my gmail address, but
-otherwise please send it to the mailing list, so others can see it and comment
-on it.
-
-The preferred format is the files created by git-format-patch, as attachments.
-However, if your repo has a public clone URL, you can make a new branch just
-for this fix, and send the repo URL and branch name to the mailing list.
-
-(If you do send me a github pull request, I may take it if it's a trivial
-patch, but otherwise I'll ask you to close the pull request, then read
-this URL for how to send me the patch.)
diff --git a/docker/gitolite/COPYING b/docker/gitolite/COPYING
deleted file mode 100644
index 7d5393a..0000000
--- a/docker/gitolite/COPYING
+++ /dev/null
@@ -1,278 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
diff --git a/docker/gitolite/README.markdown b/docker/gitolite/README.markdown
deleted file mode 100644
index a211fab..0000000
--- a/docker/gitolite/README.markdown
+++ /dev/null
@@ -1,231 +0,0 @@
-Gitolite README
-===============
-
-## about this README
-
-**(Github-users: click the "wiki" link before sending me anything via github.)**
-
-**This is a minimal README for gitolite**, so you can quickly get started with:
-
-*   installing gitolite on a fresh userid on a Unix(-like) machine
-*   learning enough to do some basic access control
-
-**For anything more, you need to look at the complete documentation, at:
-<http://gitolite.com/gitolite>**.  Please go there for what/why/how, concepts,
-background, troubleshooting, more details on what is covered here, advanced
-features not covered here, migration from older gitolite, and many more
-topics.
-
-<!-- --------------------------------------------------------------------- -->
-
-## Assumptions
-
-*   You are familiar with:
-    *   OS: at least one Unix-like OS
-    *   ssh: ssh, ssh keys, ssh authorized keys file
-    *   git: basic use of git, bare and non-bare remotes
-
-*   You are setting up a fresh, ssh-based, installation of gitolite on a Unix
-    machine of some sort.
-
-*   You have root access, or someone has created a userid called "git" for you
-    to use and given you a password for it.  This is a brand new userid (or
-    you have deleted everything but `.bashrc` and similar files to make it
-    look like one!)
-
-*   If your server is not connected to the internet, you know how to clone the
-    gitolite source code by using some in-between server or "git bundle".
-
-<!-- --------------------------------------------------------------------- -->
-
-## Installation and setup
-
-### server requirements
-
-*   any unix system
-*   sh
-*   git 1.6.6 or later
-*   perl 5.8.8 or later
-*   openssh 5.0 or later
-*   a dedicated userid to host the repos (in this document, we assume it is
-    "git", but it can be anything; substitute accordingly)
-*   this user id does NOT currently have any ssh pubkey-based access
-    *   ideally, this user id has shell access ONLY by "su - git" from some
-        other userid on the same server (this ensure minimal confusion for ssh
-        newbies!)
-
-### steps to install
-
-First, prepare the ssh key:
-
-*   login to "git" on the server
-*   make sure `~/.ssh/authorized_keys` is empty or non-existent
-*   make sure your ssh public key from your workstation has been copied as
-    $HOME/YourName.pub
-
-Next, install gitolite by running these commands:
-
-    git clone git://github.com/sitaramc/gitolite
-    mkdir -p $HOME/bin
-    gitolite/install -to $HOME/bin
-
-Finally, setup gitolite with yourself as the administrator:
-
-    gitolite setup -pk YourName.pub
-
-If the last command doesn't run perhaps "bin" is not in your "PATH". You can
-either add it, or just run:
-
-    $HOME/bin/gitolite setup -pk YourName.pub
-
-If you get any other errors please refer to the online documentation whose URL
-was given at the top of this file.
-
-## adding users and repos
-
-*Do NOT add new repos or users manually on the server.*  Gitolite users,
-repos, and access rules are maintained by making changes to a special repo
-called "gitolite-admin" and *pushing* those changes to the server.
-
-To administer your gitolite installation, start by doing this on your
-workstation (if you have not already done so):
-
-    git clone git at host:gitolite-admin
-
->   -------------------------------------------------------------------------
-
->   **NOTE: if you are asked for a password, something went wrong.**.  Go hit
->   the link for the complete documentation earlier in this file.
-
->   -------------------------------------------------------------------------
-
-Now if you "cd gitolite-admin", you will see two subdirectories in it: "conf"
-and "keydir".
-
-To add new users alice, bob, and carol, obtain their public keys and add them
-to "keydir" as alice.pub, bob.pub, and carol.pub respectively.
-
-To add a new repo "foo" and give different levels of access to these
-users, edit the file "conf/gitolite.conf" and add lines like this:
-
-    repo foo
-        RW+         =   alice
-        RW          =   bob
-        R           =   carol
-
-Once you have made these changes, do something like this:
-
-    git add conf
-    git add keydir
-    git commit -m "added foo, gave access to alice, bob, carol"
-    git push
-
-When the push completes, gitolite will add the new users to
-`~/.ssh/authorized_keys` on the server, as well as create a new, empty, repo
-called "foo".
-
-## help for your users
-
-Once a user has sent you their public key and you have added them as
-specified above and given them access, you have to tell them what URL to
-access their repos at.  This is usually "git clone git at host:reponame"; see
-man git-clone for other forms.
-
-**NOTE**: again, if they are asked for a password, something is wrong.
-
-If they need to know what repos they have access to, they just have to run
-"ssh git at host info".
-
-## access rule examples
-
-Gitolite's access rules are very powerful.  The simplest use was already
-shown above.  Here is a slightly more detailed example:
-
-    repo foo
-        RW+                     =   alice
-        -   master              =   bob
-        -   refs/tags/v[0-9]    =   bob
-        RW                      =   bob
-        RW  refs/tags/v[0-9]    =   carol
-        R                       =   dave
-
-Here's what these example rules say:
-
-  * alice can do anything to any branch or tag -- create, push,
-    delete, rewind/overwrite etc.
-
-  * bob can create or fast-forward push any branch whose name does
-    not start with "master" and create any tag whose name does not
-    start with "v"+digit.
-
-  * carol can create tags whose names start with "v"+digit.
-
-  * dave can clone/fetch.
-
-Please see the main documentation linked above for all the gory details, as
-well as more features and examples.
-
-## groups
-
-Gitolite allows you to group users or repos for convenience.  Here's an
-example that creates two groups of users:
-
-    @staff      =   alice bob carol
-    @interns    =   ashok
-
-    repo secret
-        RW      =   @staff
-
-    repo foss
-        RW+     =   @staff
-        RW      =   @interns
-
-Group lists accumulate.  The following two lines have the same effect as
-the earlier definition of @staff above:
-
-    @staff      =   alice bob
-    @staff      =   carol
-
-You can also use group names in other group names:
-
-    @all-devs   =   @staff @interns
-
-Finally, @all is a special group name that is often convenient to use if
-you really mean "all repos" or "all users".
-
-## commands
-
-Users can run certain commands remotely, using ssh.  Running
-
-    ssh git at host help
-
-prints a list of available commands.
-
-The most commonly used command is "info".  All commands respond to a
-single argument of "-h" with suitable information.
-
-If you have shell on the server, you have a lot more commands available to
-you; try running "gitolite help".
-
-<!-- --------------------------------------------------------------------- -->
-
-## LICENSE
-
-# contact and support
-
-Please see <http://gitolite.com/gitolite/#contact> for mailing list and IRC
-info.
-
-# license
-
-The gitolite software is copyright Sitaram Chamarty and is licensed under the
-GPL v2; please see the file called COPYING in the source distribution.
-
-Please see <http://gitolite.com/gitolite/#license> for more.
-
->   -------------------------------------------------------------------------
-
->   **NOTE**: GIT is a trademark of Software Freedom Conservancy and my use of
->   "Gitolite" is under license.
-
->   -------------------------------------------------------------------------
diff --git a/docker/gitolite/check-g2-compat b/docker/gitolite/check-g2-compat
deleted file mode 100755
index 508c6fd..0000000
--- a/docker/gitolite/check-g2-compat
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/perl
-
-use Cwd;
-
-my $h  = $ENV{HOME};
-my $rc = "$h/.gitolite.rc";
-my %count;
-
-intro();
-
-msg( FATAL => "no rc file found; do you even *have* g2 running?" ) if not -f $rc;
-do $rc;
-unless ( $return = do $rc ) {
-    msg( FATAL => "couldn't parse $rc: $@" ) if $@;
-    msg( FATAL   => "couldn't do $rc: $!" ) unless defined $return;
-    msg( WARNING => "couldn't run $rc" )    unless $return;
-}
-
-print "checking rc file...\n";
-rc_basic();
-rest_of_rc();
-print "\n";
-
-print "checking conf file(s)...\n";
-conf();
-print "\n";
-
-print "checking repos...\n";
-repo();
-print "\n";
-
-print "...all done...\n";
-
-# ----------------------------------------------------------------------
-
-sub intro {
-    msg( INFO => "This program only checks for uses that make the new g3 completely unusable" );
-    msg( ''   => "or that might end up giving *more* access to someone if migrated as-is." );
-    msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
-    msg( '', '' );
-    msg( INFO => "'see docs' usually means the pre-migration checklist in" );
-    msg( '',  => "'g2migr.html'; to get there, start from the main migration" );
-    msg( '',  => "page at http://gitolite.com/gitolite/migr.html" );
-    msg( '', '' );
-}
-
-sub rc_basic {
-    msg( FATAL => "GL_ADMINDIR in the wrong place -- aborting; see docs" ) if $GL_ADMINDIR ne "$h/.gitolite";
-    msg( NOTE => "GL_ADMINDIR is in the right place; assuming you did not mess with" );
-    msg( '', "GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED" );
-    msg( FATAL => "REPO_BASE in the wrong place -- aborting; see docs" ) if $REPO_BASE ne "$h/repositories" and $REPO_BASE ne "repositories";
-# ( abs or rel both ok)
-}
-
-sub rest_of_rc {
-    msg( SEVERE  => "GIT_PATH found; see docs" )                          if $GIT_PATH;
-    msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )           if $GL_ALL_INCLUDES_SPECIAL;
-    msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )            if $GL_NO_CREATE_REPOS;
-    msg( SEVERE  => "rsync not yet implemented" )                         if $RSYNC_BASE;
-    msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )       if $ADMIN_POST_UPDATE_CHAINS_TO;
-    msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )            if $GL_NO_DAEMON_NO_GITWEB;
-    msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )              if $GL_NO_SETUP_AUTHKEYS;
-    msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                  if $UPDATE_CHAINS_TO;
-    msg( WARNING => "GL_ADC_PATH found; see docs" )                       if $GL_ADC_PATH;
-    msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
-}
-
-sub conf {
-    chdir($h);
-    chdir($GL_ADMINDIR);
-
-    my $conf = `find . -name "*.conf" | xargs cat`;
-    msg( "SEVERE", "NAME rules; see docs" )                    if $conf =~ m(NAME/);
-    msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
-    msg( "SEVERE", "mirroring used; see docs" )                if $conf =~ m(config +gitolite\.mirror\.);
-}
-
-sub repo {
-    chdir($h);
-    chdir($REPO_BASE);
-    my @creater = `find . -name gl-creater`;
-    if (@creater) {
-        msg( WARNING => "found " . scalar(@creater) . " gl-creater files; see docs" );
-    }
-
-    my @perms = `find . -name gl-perms | xargs egrep -l -w R\\|RW`;
-    if (@perms) {
-        msg( WARNING => "found " . scalar(@perms) . " gl-perms files with R or RW; see docs" );
-    }
-}
-
-sub msg {
-    my ( $type, $text ) = @_;
-    print "$type" if $type;
-    print "\t$text\n";
-    exit 1 if $type eq 'FATAL';
-
-    $count{$type}++ if $type;
-}
diff --git a/docker/gitolite/contrib/commands/ukm b/docker/gitolite/contrib/commands/ukm
deleted file mode 100755
index 8a2d361..0000000
--- a/docker/gitolite/contrib/commands/ukm
+++ /dev/null
@@ -1,732 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Easy;
-
-=for usage
-Usage for this command is not that simple. Please read the full
-documentation in
-https://github.com/sitaramc/gitolite-doc/blob/master/contrib/ukm.mkd
-or online at http://gitolite.com/gitolite/ukm.html.
-=cut
-
-usage() if @ARGV and $ARGV[0] eq '-h';
-
-# Terms used in this file.
-# pubkeypath: the (relative) filename of a public key starting from
-#   gitolite-admin/keydir. Examples: alice.pub, foo/bar/alice.pub,
-#   alice at home.pub, foo/alice at laptop.pub. You get more examples, if you
-#   replace "alice" by "bob at example.com".
-# userid: computed from a pubkeypath by removing any directory
-#   part, the '.pub' extension and the "old-style" @NAME classifier.
-#   The userid identifies a user in the gitolite.conf file.
-# keyid: an identifier for a key given on the command line.
-#   If the script is called by one of the super_key_managers, then the
-#   keyid is the pubkeypath without the '.pub' extension. Otherwise it
-#   is the userid for a guest.
-#   The keyid is normalized to lowercase letters.
-
-my $rb = $rc{GL_REPO_BASE};
-my $ab = $rc{GL_ADMIN_BASE};
-
-# This will be the subdirectory under "keydir" in which the guest
-# keys will be stored. To prevent denial of service, this directory
-# should better start with 'zzz'.
-# The actual value can be set through the GUEST_DIRECTORY resource.
-# WARNING: If this value is changed you must understand the consequences.
-#          There will be no support if guestkeys_dir is anything else than
-#          'zzz/guests'.
-my $guestkeys_dir = 'zzz/guests';
-
-# A guest key cannot have arbitrary names (keyid). Only keys that do *not*
-# match $forbidden_guest_pattern are allowed. Super-key-managers can add
-# any keyid.
-
-# This is the directory for additional keys of a self key manager.
-my $selfkeys_dir = 'zzz/self';
-# There is no flexibility for selfkeys. One must specify a keyid that
-# matches the regular expression '^@[a-z0-9]+$'. Note that all keyids
-# are transformed to lowercase before checking.
-my $required_self_pattern = qr([a-z0-9]+);
-my $selfkey_management = 0; # disable selfkey managment
-
-# For guest key managers the keyid must pass two tests.
-#   1) It must match the $required_guest_pattern regular expression.
-#   2) It must not match the $forbidden_guest_pattern regular expression.
-# Default for $forbidden_guest_pattern is qr(.), i.e., every keyid is
-# forbidden, or in other words, only the gitolite-admin can manage keys.
-# Default for $required_guest_pattern is such that the keyid must look
-# like an email address, i.e. must have exactly one @ and at least one
-# dot after the @.
-# Just setting 'ukm' => 1 in .gitolite.rc only allows the super-key-managers
-# (i.e., only the gitolite admin(s)) to manage keys.
-my $required_guest_pattern =
-    qr(^[0-9a-z][-0-9a-z._+]*@[-0-9a-z._+]+[.][-0-9a-z._+]+$);
-my $forbidden_guest_pattern = qr(.);
-
-die "The command 'ukm' is not enabled.\n" if ! $rc{'COMMANDS'}{'ukm'};
-
-my $km = $rc{'UKM_CONFIG'};
-if(ref($km) eq 'HASH') {
-    # If not set we only allow keyids that look like emails
-    my $rgp = $rc{'UKM_CONFIG'}{'REQUIRED_GUEST_PATTERN'} || '';
-    $required_guest_pattern = qr(^($rgp)$) if $rgp;
-    $forbidden_guest_pattern = $rc{'UKM_CONFIG'}{'FORBIDDEN_GUEST_PATTERN'}
-                            || $forbidden_guest_pattern;
-    $selfkey_management = $rc{'UKM_CONFIG'}{'SELFKEY_MANAGEMENT'} || 0;
-}
-
-# get the actual userid
-my $gl_user = $ENV{GL_USER};
-my $super_key_manager = is_admin(); # or maybe is_super_admin() ?
-
-# save arguments for later
-my $operation = shift || 'list';
-my $keyid     = shift || '';
-$keyid = lc $keyid; # normalize to lowercase ids
-
-my ($zop, $zfp, $zselector, $zuser) = get_pending($gl_user);
-# The following will only be true if a selfkey manager logs in to
-# perform a pending operation.
-my $pending_self = ($zop ne '');
-
-die "You are not a key manager.\n"
-    unless $super_key_manager || $pending_self
-           || in_group('guest-key-managers')
-           || in_group('self-key-managers');
-
-# Let's deal with the pending user first. The only allowed operations
-# that are to confirm the add operation with the random code
-# that must be provided via stdin or to undo a pending del operation.
-if ($pending_self) {
-    pending_user($gl_user, $zop, $zfp, $zselector, $zuser);
-    exit;
-}
-
-my @available_operations = ('list','add','del');
-die "unknown ukm subcommand: $operation\n"
-    unless grep {$operation eq $_} @available_operations;
-
-# get to the keydir
-_chdir("$ab/keydir");
-
-# Note that the program warns if it finds a fingerprint that maps to
-# different userids.
-my %userids = (); # mapping from fingerprint to userid
-my %fingerprints = (); # mapping from pubkeypath to fingerprint
-my %pubkeypaths = (); # mapping from userid to pubkeypaths
-                      # note that the result is a list of pubkeypaths
-
-# Guest keys are managed by people in the @guest-key-managers group.
-# They can only add/del keys in the $guestkeys_dir directory. In fact,
-# the guest key manager $gl_user has only access to keys inside
-# %guest_pubkeypaths.
-my %guest_pubkeypaths = (); # mapping from userid to pubkeypath for $gl_user
-
-# Self keys are managed by people in the @self-key-managers group.
-# They can only add/del keys in the $selfkeys_dir directory. In fact,
-# the self key manager $gl_user has only access to keys inside
-# %self_pubkeypaths.
-my %self_pubkeypaths = ();
-
-# These are the keys that are managed by a super key manager.
-my @all_pubkeypaths = `find . -type f -name "*.pub" 2>/dev/null | sort`;
-
-for my $pubkeypath (@all_pubkeypaths) {
-    chomp($pubkeypath);
-    my $fp = fingerprint($pubkeypath);
-    $fingerprints{$pubkeypath} = $fp;
-    my $userid = get_userid($pubkeypath);
-    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
-    $userid = $zuser if $zop;
-    if (! defined $userids{$fp}) {
-        $userids{$fp} = $userid;
-    } else {
-        warn "key $fp is used for different user ids\n"
-            unless $userids{$fp} eq $userid;
-    }
-    push @{$pubkeypaths{$userid}}, $pubkeypath;
-    if ($pubkeypath =~ m|^./$guestkeys_dir/([^/]+)/[^/]+\.pub$|) {
-        push @{$guest_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
-    }
-    if ($pubkeypath =~ m|^./$selfkeys_dir/([^/]+)/[^/]+\.pub$|) {
-        push @{$self_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
-    }
-}
-
-###################################################################
-# do stuff according to the operation
-###################################################################
-
-if ( $operation eq 'list' ) {
-    list_pubkeys();
-    print "\n\n";
-    exit;
-}
-
-die "keyid required\n" unless $keyid;
-die "Not allowed to use '..' in keyid.\n" if $keyid =~ /\.\./;
-
-if ( $operation eq 'add' ) {
-    if ($super_key_manager) {
-        add_pubkey($gl_user, "$keyid.pub", safe_stdin());
-    } elsif (selfselector($keyid)) {
-        add_self($gl_user, $keyid, safe_stdin());
-    } else {
-        # assert ingroup('guest-key-managers');
-        add_guest($gl_user, $keyid, safe_stdin());
-    }
-} elsif ( $operation eq 'del' ) {
-    if ($super_key_manager) {
-        del_super($gl_user, "$keyid.pub");
-    } elsif (selfselector($keyid)) {
-        del_self($gl_user, $keyid);
-    } else {
-        # assert ingroup('guest-key-managers');
-        del_guest($gl_user, $keyid);
-    }
-}
-
-exit;
-
-
-###################################################################
-# only function definitions are following
-###################################################################
-
-# make a temp clone and switch to it
-our $TEMPDIR;
-BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; chomp($TEMPDIR) }
-END { my $err = $?; `/bin/rm -rf $TEMPDIR`; $? = $err; }
-
-sub cd_temp_clone {
-    chomp($TEMPDIR);
-    hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR/gitolite-admin" );
-    chdir("$TEMPDIR/gitolite-admin");
-    my $ip = $ENV{SSH_CONNECTION};
-    $ip =~ s/ .*//;
-    my ($zop, $zfp, $zselector, $zuser) = get_pending($ENV{GL_USER});
-    my $email = $zuser;
-    $email .= '@' . $ip  unless $email =~ m(@);
-    my $name = $zop ? "\@$zselector" : $zuser;
-    # Record the keymanager in the gitolite-admin repo as author of the change.
-    hushed_git( "config", "user.email", "$email" );
-    hushed_git( "config", "user.name",  "'$name from $ip'" );
-}
-
-# compute the fingerprint from the full path of a pubkey file
-sub fingerprint {
-    my $fp = `ssh-keygen -l -f $_[0]`;
-    die "does not seem to be a valid pubkey\n"
-        unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+) /i;
-    return $1;
-}
-
-
-# Read one line from STDIN and return it.
-#  If no data is available on STDIN after one second, the empty string
-# is returned.
-# If there is more than one line or there was an error in reading, the
-# function dies.
-sub safe_stdin {
-    use IO::Select;
-    my $s=IO::Select->new(); $s->add(\*STDIN);
-    return '' unless $s->can_read(1);
-    my $data;
-    my $ret = read STDIN, $data, 4096;
-    # current pubkeys are approx 400 bytes so we go a little overboard
-    die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n"
-        unless $ret;
-    die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
-    return $data;
-}
-
-# call git, be quiet
-sub hushed_git {
-    system("git " . join(" ", @_) . ">/dev/null 2>/dev/null");
-}
-
-# Extract the userid from the full path of the pubkey file (relative
-# to keydir/ and including the '.pub' extension.
-sub get_userid {
-    my ($u) = @_; # filename of pubkey relative to keydir/.
-    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
-    $u =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
-    return $u;
-}
-
-# Extract the @selector part from the full path of the pubkey file
-# (relative to keydir/ and including the '.pub' extension).
-# If there is no @selector part, the empty string is returned.
-# We also correctly extract the selector part from pending keys.
-sub get_selector {
-    my ($u) = @_; # filename of pubkey relative to keydir/.
-    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
-    $u =~ s(\.pub$)();             # baz at home.pub -> baz at home
-    return $1 if $u =~ m/.\@($required_self_pattern)$/; # baz at home -> home
-    my ($zop, $zfp, $zselector, $zuser) = get_pending($u);
-    # If $u was not a pending key, then $zselector is the empty string.
-    return $zselector;
-}
-
-# Extract fingerprint, operation, selector, and true userid from a
-# pending userid.
-sub get_pending {
-    my ($gl_user) = @_;
-    return ($1, $2, $3, $4)
-       if ($gl_user=~/^zzz-(...)-([0-9a-f]{32})-($required_self_pattern)-(.*)/);
-    return ('', '', '', $gl_user)
-}
-
-# multiple / and are simplified to one / and the path is made relative
-sub sanitize_pubkeypath {
-    my ($pubkeypath) = @_;
-    $pubkeypath =~ s|//|/|g; # normalize path
-    $pubkeypath =~ s,\./,,g; # remove './' from path
-    return './'.$pubkeypath; # Don't allow absolute paths.
-}
-
-# This function is only relavant for guest key managers.
-# It returns true if the pattern is OK and false otherwise.
-sub required_guest_keyid {
-    my ($_) = @_;
-    /$required_guest_pattern/ and ! /$forbidden_guest_pattern/;
-}
-
-# The function takes a $keyid as input and returns the keyid with the
-# initial @ stripped if everything is fine. It aborts with an error if
-# selfkey management is not enabled or the function is called for a
-# non-self-key-manager.
-# If the required selfkey pattern is not matched, it returns an empty string.
-# Thus the function can be used to check whether a given keyid is a
-# proper selfkeyid.
-sub selfselector {
-    my ($keyid) = @_;
-    return '' unless $keyid =~ m(^\@($required_self_pattern)$);
-    $keyid = $1;
-    die "selfkey management is not enabled\n" unless $selfkey_management;
-    die "You are not a selfkey manager.\n" if ! in_group('self-key-managers');
-    return $keyid;
-}
-
-# Return the number of characters reserved for the userid field.
-sub userid_width {
-    my ($paths) = @_;
-    my (%pkpaths) = %{$paths};
-    my (@userid_lengths) = sort {$a <=> $b} (map {length($_)} keys %pkpaths);
-    @userid_lengths ? $userid_lengths[-1] : 0;
-}
-
-# List the keys given by a reference to a hash.
-# The regular expression $re is used to remove the initial part of the
-# keyid and replace it by what is matched inside the parentheses.
-# $format and $width are used for pretty printing
-sub list_keys {
-    my ($paths, $tokeyid, $format, $width) = @_;
-    my (%pkpaths) = %{$paths};
-    for my $userid (sort keys %pkpaths) {
-        for my $pubkeypath (sort @{$pkpaths{$userid}}) {
-            my $fp = $fingerprints{$pubkeypath};
-            my $userid = $userids{$fp};
-            my $keyid = &{$tokeyid}($pubkeypath);
-            printf $format,$fp,$userid,$width+1-length($userid),"",$keyid
-                if ($super_key_manager
-                    || required_guest_keyid($keyid)
-                    || $keyid=~m(^\@));
-        }
-    }
-}
-
-# Turn a pubkeypath into a keyid for super-key-managers, guest-keys,
-# and self-keys.
-sub superkeyid {
-    my ($keyid) = @_;
-    $keyid =~ s(\.pub$)();
-    $keyid =~ s(^\./)();
-    return $keyid;
-}
-
-sub guestkeyid {
-    my ($keyid) = @_;
-    $keyid =~ s(\.pub$)();
-    $keyid =~ s(^.*/)();
-    return $keyid;
-}
-
-sub selfkeyid {
-    my ($keyid) = @_;
-    $keyid =~ s(\.pub$)();
-    $keyid =~ s(^.*/)();
-    my ($zop, $zfp, $zselector, $zuser) = get_pending($keyid);
-    return "\@$zselector (pending $zop)" if $zop;
-    $keyid =~ s(.*@)(@);
-    return $keyid;
-}
-
-###################################################################
-
-# List public keys managed by the respective user.
-# The fingerprints, userids and keyids are printed.
-# keyids are shown in a form that can be used for add and del
-# subcommands. 
-sub list_pubkeys {
-    print "Hello $gl_user, you manage the following keys:\n";
-    my $format = "%-47s %s%*s%s\n";
-    my $width = 0;
-    if ($super_key_manager) {
-        $width = userid_width(\%pubkeypaths);
-        $width = 6 if $width < 6; # length("userid")==6
-        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
-        list_keys(\%pubkeypaths, , \&superkeyid, $format, $width);
-    } else {
-        my $widths = $selfkey_management?userid_width(\%self_pubkeypaths):0;
-        my $widthg = userid_width(\%guest_pubkeypaths);
-        $width = $widths > $widthg ? $widths : $widthg; # maximum width
-        return unless $width; # there are no keys
-        $width = 6 if $width < 6; # length("userid")==6
-        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
-        list_keys(\%self_pubkeypaths, \&selfkeyid, $format, $width)
-            if $selfkey_management;
-        list_keys(\%guest_pubkeypaths, \&guestkeyid,  $format, $width);
-    }
-}
-
-
-###################################################################
-
-# Add a public key for the user $gl_user.
-# $pubkeypath is the place where the new key will be stored.
-# If the file or its fingerprint already exists, the operation is
-# rejected.
-sub add_pubkey {
-    my ( $gl_user, $pubkeypath, $keymaterial ) = @_;
-    if(! $keymaterial) {
-        print STDERR "Please supply the new key on STDIN.\n";
-        print STDERR "Try something like this:\n";
-        print STDERR "cat FOO.pub | ssh GIT\@GITOLITESERVER ukm add KEYID\n";
-        die "missing public key data\n";
-    }
-    # clean pubkeypath a bit
-    $pubkeypath = sanitize_pubkeypath($pubkeypath);
-    # Check that there is not yet something there already.
-    die "cannot override existing key\n" if $fingerprints{$pubkeypath};
-
-    my $userid = get_userid($pubkeypath);
-    # Super key managers shouldn't be able to add a that leads to
-    # either an empty userid or to a userid that starts with @.
-    #
-    # To avoid confusion, all keyids for super key managers must be in
-    # a full path format. Having a public key of the form
-    # gitolite-admin/keydir/@foo.pub might be confusing and might lead
-    # to other problems elsewhere.
-    die "cannot add key that starts with \@\n" if (!$userid) || $userid=~/^@/;
-
-    cd_temp_clone();
-    _chdir("keydir");
-    $pubkeypath =~ m((.*)/); # get the directory part
-    _mkdir($1);
-    _print($pubkeypath, $keymaterial);
-    my $fp = fingerprint($pubkeypath);
-
-    # Maybe we are adding a selfkey.
-    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
-    my $user = $zop ? "$zuser\@$zselector" : $userid;
-    $userid = $zuser;
-    # Check that there isn't a key with the same fingerprint under a
-    # different userid.
-    if (defined $userids{$fp}) {
-        if ($userid ne $userids{$fp}) {
-            print STDERR "Found  $fp $userids{$fp}\n" if $super_key_manager;
-            print STDERR "Same key is already available under another userid.\n";
-            die "cannot add key\n";
-        } elsif ($zop) {
-            # Because of the way a key is confirmed with ukm, it is
-            # impossible to confirm the initial key of the user as a
-            # new selfkey. (It will lead to the function list_pubkeys
-            # instead of pending_user_add, because the gl_user will
-            # not be that of a pending user.) To avoid confusion, we,
-            # therefore, forbid to add the user's initial key
-            # altogether.
-            # In fact, we here also forbid to add any key for that
-            # user that is already in the system.
-            die "You cannot add a key that already belongs to you.\n";
-        }
-    } else {# this fingerprint does not yet exist
-        my @paths = @{$pubkeypaths{$userid}} if defined $pubkeypaths{$userid};
-        if (@paths) {# there are already keys for $userid
-            if (grep {$pubkeypath eq $_} @paths) {
-                print STDERR "The keyid is already present. Nothing changed.\n";
-            } elsif ($super_key_manager) {
-                # It's OK to add new selfkeys, but here we are in the case
-                # of adding multiple keys for guests. That is forbidden.
-                print STDERR "Adding new public key for $userid.\n";
-            } elsif ($pubkeypath =~ m(^\./$guestkeys_dir/)) {
-                # Arriving here means we are about to add a *new*
-                # guest key, because the fingerprint is not yet
-                # existing. This would be for an already existing
-                # userid (added by another guest key manager). Since
-                # that effectively means to (silently) add an
-                # additional key for an existing user, it must be
-                # forbidden.
-                die "cannot add another public key for an existing user\n";
-            }
-        }
-    }
-    exit if (`git status -s` eq ''); # OK to add identical keys twice
-    hushed_git( "add", "." ) and die "git add failed\n";
-    hushed_git( "commit", "-m", "'ukm add $gl_user $userid\n\n$fp'" )
-        and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-# Guest key managers should not be allowed to add directories or
-# multiple keys via the @domain mechanism, since this might allow
-# another guest key manager to give an attacker access to another
-# user's repositories.
-#
-# Example: Alice adds bob.pub for bob at example.org. David adds eve.pub
-# (where only Eve but not Bob has the private key) under the keyid
-# bob at example.org@foo. This basically gives Eve the same rights as
-# Bob.
-sub add_guest {
-    my ( $gl_user, $keyid, $keymaterial ) = @_;
-    die "keyid not allowed: '$keyid'\n"
-        if $keyid =~ m(@.*@) or $keyid =~ m(/) or !required_guest_keyid($keyid);
-    add_pubkey($gl_user, "$guestkeys_dir/$gl_user/$keyid.pub", $keymaterial);
-}
-
-# Add a new selfkey for user $gl_user.
-sub add_self {
-    my ( $gl_user, $keyid, $keymaterial ) = @_;
-    my $selector = "";
-    $selector = selfselector($keyid); # might return empty string
-    die "keyid not allowed: $keyid\n" unless $selector;
-
-    # Check that the new selector is not already in use even not in a
-    # pending state.
-    die "keyid already in use: $keyid\n"
-        if grep {selfkeyid($_)=~/^\@$selector( .*)?$/} @{$self_pubkeypaths{$gl_user}};
-    # generate new pubkey create fingerprint
-    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
-    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
-    $sessionfp =~ s/://g;
-    my $user = "zzz-add-$sessionfp-$selector-$gl_user";
-    add_pubkey($gl_user, "$selfkeys_dir/$gl_user/$user.pub", $keymaterial);
-    print `cat "$TEMPDIR/session.pub"`;
-}
-
-###################################################################
-
-
-# Delete a key of user $gl_user.
-sub del_pubkey {
-    my ($gl_user, $pubkeypath) = @_;
-    $pubkeypath = sanitize_pubkeypath($pubkeypath);
-    my $fp = $fingerprints{$pubkeypath};
-    die "key not found\n" unless $fp;
-    cd_temp_clone();
-    chdir("keydir");
-    hushed_git( "rm", "$pubkeypath" ) and die "git rm failed\n";
-    my $userid = get_userid($pubkeypath);
-    hushed_git( "commit", "-m", "'ukm del $gl_user $userid\n\n$fp'" )
-        and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-# $gl_user is a super key manager. This function aborts if the
-# superkey manager tries to remove his last key.
-sub del_super {
-    my ($gl_user, $pubkeypath) = @_;
-    $pubkeypath = sanitize_pubkeypath($pubkeypath);
-    die "You are not managing the key $keyid.\n"
-        unless grep {$_ eq $pubkeypath} @all_pubkeypaths;
-    my $userid = get_userid($pubkeypath);
-    if ($gl_user eq $userid) {
-        my @paths = @{$pubkeypaths{$userid}};
-        die "You cannot delete your last key.\n"
-            if scalar(grep {$userid eq get_userid($_)} @paths)<2;
-    }
-    del_pubkey($gl_user, $pubkeypath);
-}
-
-sub del_guest {
-    my ($gl_user, $keyid) = @_;
-    my $pubkeypath = sanitize_pubkeypath("$guestkeys_dir/$gl_user/$keyid.pub");
-    my $userid = get_userid($pubkeypath);
-    # Check whether $gl_user actually manages $keyid.
-    my @paths = ();
-    @paths = @{$guest_pubkeypaths{$userid}}
-        if defined $guest_pubkeypaths{$userid};
-    die "You are not managing the key $keyid.\n"
-        unless grep {$_ eq $pubkeypath} @paths;
-    del_pubkey($gl_user, $pubkeypath);
-}
-
-# Delete a selfkey of $gl_user. The first delete is a preparation of
-# the deletion and only a second call will actually delete the key. If
-# the second call is done with the key that is scheduled for deletion,
-# it is basically undoing the previous del call. This last case is
-# handled in function pending_user_del.
-sub del_self {
-    my ($gl_user, $keyid) = @_;
-    my $selector = selfselector($keyid); # might return empty string
-    die "keyid not allowed: '$keyid'\n" unless $selector;
-
-    # Does $gl_user actually manage that keyid?
-    # All (non-pending) selfkeys have an @selector part in their pubkeypath.
-    my @paths = @{$self_pubkeypaths{$gl_user}};
-    die "You are not managing the key $keyid.\n"
-        unless grep {$selector eq get_selector($_)} @paths;
-
-    cd_temp_clone();
-    _chdir("keydir");
-    my $fp = '';
-    # Is it the first or the second del call? It's the second call, if
-    # there is a scheduled-for-deletion or scheduled-for-addition
-    # selfkey which has the given keyid as a selector part.
-    @paths = grep {
-        my ($zop, $zfp, $zselector, $zuser) = get_pending(get_userid($_));
-        $zselector eq $selector
-    } @paths;
-    if (@paths) {# start actual deletion of the key (second call)
-        my $pubkeypath = $paths[0];
-        $fp = fingerprint($pubkeypath);
-        my ($zop, $zf, $zs, $zu) = get_pending(get_userid($pubkeypath));
-        $zop = $zop eq 'add' ? 'undo-add' : 'confirm-del';
-        hushed_git("rm", "$pubkeypath") and die "git rm failed\n";
-        hushed_git("commit", "-m", "'ukm $zop $gl_user\@$selector\n\n$fp'")
-            and die "git commit failed\n";
-        system("gitolite push >/dev/null 2>/dev/null")
-            and die "git push failed\n";
-        print STDERR "pending keyid deleted: \@$selector\n";
-        return;
-    }
-    my $oldpubkeypath = "$selfkeys_dir/$gl_user/$gl_user\@$selector.pub";
-    # generate new pubkey and create fingerprint to get a random number
-    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
-    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
-    $sessionfp =~ s/://g;
-    my $user = "zzz-del-$sessionfp-$selector-$gl_user";
-    my $newpubkeypath = "$selfkeys_dir/$gl_user/$user.pub";
-
-    # A key for gitolite access that is in authorized_keys and not
-    # existing in the expected place under keydir/ should actually not
-    # happen, but one never knows.
-    die "key not available\n" unless -r $oldpubkeypath;
-
-    # For some strange reason the target key already exists.
-    die "cannot override existing key\n" if -e $newpubkeypath;
-
-    $fp = fingerprint($oldpubkeypath);
-    print STDERR "prepare deletion of key \@$selector\n";
-    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
-        and die "git mv failed\n";
-    hushed_git("commit", "-m", "'ukm prepare-del $gl_user\@$selector\n\n$fp'")
-        and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null")
-        and die "git push failed\n";
-}
-
-###################################################################
-# Adding a selfkey should be done as follows.
-#
-#   cat newkey.pub | ssh git at host ukm add @selector > session
-#   cat session | ssh -i newkey git at host ukm
-#
-# The provided random data will come from a newly generated ssh key
-# whose fingerprint will be stored in $gl_user. So we compute the
-# fingerprint of the data that is given to us. If it doesn't match the
-# fingerprint, then something went wrong and the confirm operation is
-# forbidden, in fact, the pending key will be removed from the system.
-sub pending_user_add {
-    my ($gl_user, $zfp, $zselector, $zuser) = @_;
-    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
-    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";
-
-    # A key for gitolite access that is in authorized_keys and not
-    # existing in the expected place under keydir/ should actually not
-    # happen, but one never knows.
-    die "key not available\n" unless -r $oldpubkeypath;
-
-    my $keymaterial = safe_stdin();
-    # If there is no keymaterial (which corresponds to a session key
-    # for the confirm-add operation), logging in to this key, removes
-    # it from the system.
-    my $session_key_not_provided = '';
-    if (!$keymaterial) {
-        $session_key_not_provided = "missing session key";
-    } else {
-        _print("$TEMPDIR/session.pub", $keymaterial);
-        my $sessionfp = fingerprint("$TEMPDIR/session.pub");
-        $sessionfp =~ s/://g;
-        $session_key_not_provided = "session key not accepted"
-            unless ($zfp eq $sessionfp)
-    }
-    my $fp = fingerprint($oldpubkeypath);
-    if ($session_key_not_provided) {
-        print STDERR "$session_key_not_provided\n";
-        print STDERR "pending keyid deleted: \@$zselector\n";
-        hushed_git("rm", "$oldpubkeypath") and die "git rm failed\n";
-        hushed_git("commit", "-m", "'ukm del $zuser\@$zselector\n\n$fp'")
-            and die "git commit failed\n";
-        system("gitolite push >/dev/null 2>/dev/null")
-            and die "git push failed\n";
-        return;
-    }
-
-    # For some strange reason the target key already exists.
-    die "cannot override existing key\n" if -e $newpubkeypath;
-
-    print STDERR "pending keyid added: \@$zselector\n";
-    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
-        and die "git mv failed\n";
-    hushed_git("commit", "-m", "'ukm confirm-add $zuser\@$zselector\n\n$fp'")
-        and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null")
-        and die "git push failed\n";
-}
-
-# To delete a key, one must first bring the key into a pending state
-# and then truely delete it with another key. In case, the login
-# happens with the pending key (implemented below), it means that the
-# delete operation has to be undone.
-sub pending_user_del {
-    my ($gl_user, $zfp, $zselector, $zuser) = @_;
-    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
-    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";
-    print STDERR "undo pending deletion of keyid \@$zselector\n";
-    # A key for gitolite access that is in authorized_keys and not
-    # existing in the expected place under keydir/ should actually not
-    # happen, but one never knows.
-    die "key not available\n" unless -r $oldpubkeypath;
-    # For some strange reason the target key already exists.
-    die "cannot override existing key\n" if -e $newpubkeypath;
-    my $fp = fingerprint($oldpubkeypath);
-    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
-        and die "git mv failed\n";
-    hushed_git("commit", "-m", "'ukm undo-del $zuser\@$zselector\n\n$fp'")
-        and die "git commit failed\n";
-}
-
-# A user whose key is in pending state cannot do much. In fact,
-# logging in as such a user simply takes back the "bringing into
-# pending state", i.e. a key scheduled for adding is remove and a key
-# scheduled for deletion is brought back into its properly added state.
-sub pending_user {
-    my ($gl_user, $zop, $zfp, $zselector, $zuser) = @_;
-    cd_temp_clone();
-    _chdir("keydir");
-    if ($zop eq 'add') {
-        pending_user_add($gl_user, $zfp, $zselector, $zuser);
-    } elsif ($zop eq 'del') {
-        pending_user_del($gl_user, $zfp, $zselector, $zuser);
-    } else {
-        die "unknown operation\n";
-    }
-    system("gitolite push >/dev/null 2>/dev/null")
-        and die "git push failed\n";
-}
diff --git a/docker/gitolite/contrib/hooks/repo-specific/save-push-signatures b/docker/gitolite/contrib/hooks/repo-specific/save-push-signatures
deleted file mode 100755
index 2470491..0000000
--- a/docker/gitolite/contrib/hooks/repo-specific/save-push-signatures
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/bin/sh
-
-# ----------------------------------------------------------------------
-# post-receive hook to adopt push certs into 'refs/push-certs'
-
-# Collects the cert blob on push and saves it, then, if a certain number of
-# signed pushes have been seen, processes all the "saved" blobs in one go,
-# adding them to the special ref 'refs/push-certs'.  This is done in a way
-# that allows searching for all the certs pertaining to one specific branch
-# (thanks to Junio Hamano for this idea plus general brainstorming).
-
-# The "collection" happens only if $GIT_PUSH_CERT_NONCE_STATUS = OK; again,
-# thanks to Junio for pointing this out; see [1]
-#
-# [1]: https://groups.google.com/forum/#!topic/gitolite/7cSrU6JorEY
-
-# WARNINGS:
-#   Does not check that GIT_PUSH_CERT_STATUS = "G".  If you want to check that
-#   and FAIL the push, you'll have to write a simple pre-receive hook
-#   (post-receive is not the place for that; see 'man githooks').
-#
-#   Gitolite users: failing the hook cannot be done as a VREF because git does
-#   not set those environment variables in the update hook.  You'll have to
-#   write a trivial pre-receive hook and add that in.
-
-# Relevant gitolite doc links:
-#   repo-specific environment variables
-#       http://gitolite.com/gitolite/dev-notes.html#rsev
-#   repo-specific hooks
-#       http://gitolite.com/gitolite/non-core.html#rsh
-#       http://gitolite.com/gitolite/cookbook.html#v3.6-variation-repo-specific-hooks
-
-# Environment:
-#   GIT_PUSH_CERT_NONCE_STATUS should be "OK" (as mentioned above)
-#
-#   GL_OPTIONS_GPC_PENDING (optional; defaults to 1).  This is the number of
-#   git push certs that should be waiting in order to trigger the post
-#   processing.  You can set it within gitolite like so:
-#
-#       repo foo bar    # or maybe just 'repo @all'
-#           option ENV.GPC_PENDING = 5
-
-# Setup:
-#   Set up this code as a post-receive hook for whatever repos you need to.
-#   Then arrange to have the environment variable GL_OPTION_GPC_PENDING set to
-#   some number, as shown above.  (This is only required if you need it to be
-#   greater than 1.)  It could of course be different for different repos.
-#   Also see "Invocation" section below.
-
-# Invocation:
-#   Normally via git (see 'man githooks'), once it is setup as a post-receive
-#   hook.
-#
-#   However, if you set the "pending" limit high, and want to periodically
-#   "clean up" pending certs without necessarily waiting for the counter to
-#   trip, do the following (untested):
-#
-#       RB=$(gitolite query-rc GL_REPO_BASE)
-#       for r in $(gitolite list-phy-repos)
-#       do
-#           cd $RB/$repo.git
-#           unset GL_OPTIONS_GPC_PENDING    # if it is set higher up
-#           hooks/post-receive post_process
-#       done
-#
-#   That will take care of it.
-
-# Using without gitolite:
-#   Just set GL_OPTIONS_GPC_PENDING within the script (maybe read it from git
-#   config).  Everything else is independent of gitolite.
-
-# ----------------------------------------------------------------------
-# make it work on BSD also (but NOT YET TESTED on FreeBSD!)
-uname_s=`uname -s`
-if [ "$uname_s" = "Linux" ]
-then
-    _lock() { flock "$@"; }
-else
-    _lock() { lockf -k "$@"; }
-    # I'm assuming other BSDs also have this; I only have FreeBSD.
-fi
-
-# ----------------------------------------------------------------------
-# standard stuff
-die() { echo "$@" >&2; exit 1; }
-warn() { echo "$@" >&2; }
-
-# ----------------------------------------------------------------------
-# if there are no arguments, we're running as a "post-receive" hook
-if [ -z "$1" ]
-then
-    # ignore if it may be a replay attack
-    [ "$GIT_PUSH_CERT_NONCE_STATUS" = "OK" ] || exit 1
-    # I don't think "exit 1" does anything in a post-receive anyway, so that's
-    # just a symbolic gesture!
-
-    # note the lock file used
-    _lock .gpc.lock $0 cat_blob
-
-    # if you want to initiate the post-processing ONLY from outside (for
-    # example via cron), comment out the next line.
-    exec $0 post_process
-fi
-
-# ----------------------------------------------------------------------
-# the 'post_process' part; see "Invocation" section in the doc at the top
-if [ "$1" = "post_process" ]
-then
-    # this is the same lock file as above
-    _lock .gpc.lock $0 count_and_rotate $$
-
-    [ -d git-push-certs.$$ ] || exit 0
-
-    # but this is a different one
-    _lock .gpc.ref.lock $0 update_ref $$
-
-    exit 0
-fi
-
-# ----------------------------------------------------------------------
-# other values for "$1" are internal use only
-
-if [ "$1" = "cat_blob" ]
-then
-    mkdir -p git-push-certs
-    git cat-file blob $GIT_PUSH_CERT > git-push-certs/$GIT_PUSH_CERT
-    echo $GIT_PUSH_CERT >> git-push-certs/.blob.list
-fi
-
-if [ "$1" = "count_and_rotate" ]
-then
-    count=$(ls git-push-certs | wc -l)
-    if test $count -ge ${GL_OPTIONS_GPC_PENDING:-1}
-    then
-        # rotate the directory
-        mv git-push-certs git-push-certs.$2
-    fi
-fi
-
-if [ "$1" = "update_ref" ]
-then
-    # use a different index file for all this
-    GIT_INDEX_FILE=push_certs_index; export GIT_INDEX_FILE
-
-    # prepare the special ref to receive commits
-    PUSH_CERTS=refs/push-certs
-    if git rev-parse -q --verify $PUSH_CERTS >/dev/null
-    then
-        git read-tree $PUSH_CERTS
-    else
-        git read-tree --empty
-        T=$(git write-tree)
-        C=$(echo 'start' | git commit-tree $T)
-        git update-ref $PUSH_CERTS $C
-    fi
-
-    # for each cert blob...
-    for b in `cat git-push-certs.$2/.blob.list`
-    do
-        cf=git-push-certs.$2/$b
-
-        # it's highly unlikely that the blob got GC-ed already but write it
-        # back anyway, just in case
-        B=$(git hash-object -w $cf)
-
-        # bit of a sanity check
-        [ "$B" = "$b" ] || warn "this should not happen: $B is not equal to $b"
-
-        # for each ref described within the cert, update the index
-        for ref in `cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/' | cut -f3 -d' '`
-        do
-            git update-index --add --cacheinfo 100644,$b,$ref
-            # we're using the ref name as a "fake" filename, so people can,
-            # for example, 'git log refs/push-certs -- refs/heads/master', to
-            # see all the push certs pertaining to the master branch.  This
-            # idea came from Junio Hamano, the git maintainer (I certainly
-            # don't deal with git plumbing enough to have thought of it!)
-        done
-
-        T=$(git write-tree)
-        C=$( git commit-tree -p $PUSH_CERTS $T < $cf )
-        git update-ref $PUSH_CERTS $C
-
-        rm -f $cf
-    done
-    rm -f git-push-certs.$2/.blob.list
-    rmdir git-push-certs.$2
-fi
diff --git a/docker/gitolite/contrib/lib/Apache/gitolite.conf b/docker/gitolite/contrib/lib/Apache/gitolite.conf
deleted file mode 100644
index 87ba843..0000000
--- a/docker/gitolite/contrib/lib/Apache/gitolite.conf
+++ /dev/null
@@ -1,47 +0,0 @@
-# Apache Gitolite smart-http install Active Directory Authentication
-
-# Author: Jonathan Gray
-
-# It is assumed you already have mod_ssl, mod_ldap, & mod_authnz configured for apache
-# It is also assumed you are disabling http on port 80 and requiring the use of https on port 443
-
-# Boiler plate configuration from the smart-http deployment documentation script
-# Adjust paths if you use something other than the default
-SetEnv GIT_PROJECT_ROOT /var/www/gitolite-home/repositories
-ScriptAlias /git/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
-ScriptAlias /gitmob/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
-SetEnv GITOLITE_HTTP_HOME /var/www/gitolite-home
-SetEnv GIT_HTTP_EXPORT_ALL
- 
-# Setup LDAP trusted root certificate from your domain
-LDAPTrustedGlobalCert CA_BASE64 /etc/httpd/conf.d/domain.ca.cer
-
-# In case you havn't setup proper SSL certificates in ssl.conf, go ahead and do it here to save headache later with git
-SSLCertificateFile /etc/httpd/conf.d/gitolite.server.crt
-SSLCertificateKeyFile /etc/httpd/conf.d/gitolite.server.key
-SSLCertificateChainFile /etc/httpd/conf.d/DigiCertCA.crt
- 
-<Location /git>
-        Order deny,allow
-	# In case you want to restrict access to a given ip/subnet
-        #Allow from my.ip.range/cidr
-        #Deny from All
-        AuthType Basic
-        AuthName "Git"
-        AuthBasicProvider ldap
-        AuthUserFile /dev/null
-        AuthzLDAPAuthoritative on
-        AuthLDAPURL ldaps://AD.DC1.local:3269 AD.DC2.local:3269 AD.DC3.local:3269/?sAMAccountName?sub
-        AuthLDAPBindDN git at domain.local
-        AuthLDAPBindPassword super.secret.password
-        AuthLDAPGroupAttributeIsDN on
- 
-	# You must use one of the two following approaches to handle authentication via active directory
-	
-        # Require membership in the gitolite users group in AD
-        # The ldap-filter option is used to handle nested groups on the AD server rather than multiple calls to traverse from apache
-        # Require ldap-filter memberof:1.2.840.113556.1.4.1941:=cn=Gitolite Users,ou=Security Groups,dc=domain,dc=local
-
-	# Alternatively, require a valid user account only since you're going to control authorization in gitolite anyway
-	Require valid-user
-</Location>
diff --git a/docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm b/docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
deleted file mode 100644
index 8fde513..0000000
--- a/docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
+++ /dev/null
@@ -1,55 +0,0 @@
-package Gitolite::Triggers::RedmineUserAlias;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# aliasing a redmine username to a more user-friendly one
-# ----------------------------------------------------------------------
-
-=for usage
-
-Why:
-
-    Redmine creates users like "redmine_alice_123"; we want the users to just
-    see "alice" instead of that.
-
-Assumption:
-
-*   Redmine does not allow duplicates in the middle bit; i.e., you can't
-    create redmine_alice_123 and redmine_alice_456 also.
-
-How:
-
-*   add this code as lib/Gitolite/Triggers/RedmineUserAlias.pm to your
-    site-local code directory; see this link for how:
-
-        http://gitolite.com/gitolite/non-core.html#ncloc
-
-*   add the following to the rc file, just before the ENABLE section (don't
-    forget the trailing comma):
-
-        INPUT   =>  [ 'RedmineUserAlias::input' ],
-
-Notes:
-
-*   http mode has not been tested and will not be.  If someone has the time to
-    test it and make it work please let me know.
-
-*   not tested with mirroring.
-
-Quote:
-
-*   "All that for what is effectively one line of code.  I need a life".
-
-=cut
-
-sub input {
-    $ARGV[0] or _die "no username???";
-    $ARGV[0] =~ s/^redmine_(\S+)_\d+$/$1/;
-}
-
-1;
diff --git a/docker/gitolite/contrib/t/ukm.t b/docker/gitolite/contrib/t/ukm.t
deleted file mode 100644
index da4fc0b..0000000
--- a/docker/gitolite/contrib/t/ukm.t
+++ /dev/null
@@ -1,447 +0,0 @@
-#!/usr/bin/perl
-
-# Call like this:
-# TSH_VERBOSE=1 TSH_ERREXIT=1 HARNESS_ACTIVE=1 GITOLITE_TEST=y prove t/ukm.t
-
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Common;
-use Gitolite::Test;
-
-# basic tests using ssh
-# ----------------------------------------------------------------------
-
-my $bd = `gitolite query-rc -n GL_BINDIR`;
-my $h  = $ENV{HOME};
-my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
-my $pd = "$bd/../t/keys"; # source for pubkeys
-umask 0077;
-
-_mkdir( "$h/.ssh", 0700 ) if not -d "$h/.ssh";
-
-try "plan 204";
-
-
-# Reset everything.
-# Only admin and u1, u2, and u3 keys are available initially
-# Keys u4, u5, and u6 are used as guests later.
-# For easy access, we put the keys into ~/.ssh/, though.
-try "
-    rm -f $h/.ssh/authorized_keys; ok or die 1
-    cp $pd/u[1-6]* $h/.ssh; ok or die 2
-    cp $pd/admin*  $h/.ssh; ok or die 3
-    cp $pd/config  $h/.ssh; ok or die 4
-        cat $h/.ssh/config
-        perl s/%USER/$ENV{USER}/
-        put $h/.ssh/config
-    mkdir             $ab/keydir; ok or die 5
-    cp $pd/u[1-3].pub $ab/keydir; ok or die 6
-    cp $pd/admin.pub  $ab/keydir; ok or die 7
-";
-
-# Put the keys into ~/.ssh/authorized_keys
-system("gitolite ../triggers/post-compile/ssh-authkeys");
-
-# enable user key management in a simple form.
-# Guest key managers can add keyids looking like email addresses, but
-# cannot add emails containing example.com or hemmecke.org.
-system("sed -i \"s/.*ENABLE =>.*/'UKM_CONFIG'=>{'FORBIDDEN_GUEST_PATTERN'=>'example.com|hemmecke.org'}, ENABLE => ['ukm',/\" $h/.gitolite.rc");
-
-# super-key-managers can add/del any key
-# super-key-managers should in fact agree with people having write
-# access to gitolite-admin repo.
-# guest-key-managers can add/del guest keys
-confreset; confadd '
-    @guest-key-managers = u2 u3
-    @creators = u2 u3
-    repo pub/CREATOR/..*
-        C   =   @creators
-        RW+ =   CREATOR
-        RW  =   WRITERS
-        R   =   READERS
-';
-
-# Populate the gitolite-admin/keydir in the same way as it was used for
-# the initialization of .ssh/authorized_keys above.
-try "
-    mkdir             keydir; ok or die 8
-    cp $pd/u[1-3].pub keydir; ok or die 9;
-    cp $pd/admin.pub  keydir; ok or die 10;
-    git add conf keydir; ok
-    git commit -m ukm; ok; /master.* ukm/
-";
-
-# Activate new config data.
-try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();
-
-# Check whether the above setup yields the expected behavior for ukm.
-# The admin is super-key-manager, thus can manage every key.
-try "
-    ssh admin ukm; ok; /Hello admin, you manage the following keys:/
-                       / admin +admin/
-                       / u1 +u1/
-                       / u2 +u2/
-                       / u3 +u3/
-";
-
-# u1 isn't a key manager, so shouldn't be above to manage keys.
-try "ssh u1 ukm; !ok; /FATAL: You are not a key manager./";
-
-# u2 and u3 are guest key managers, but don't yet manage any key.
-try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:\n\n\n";
-try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:\n\n\n";
-
-
-###################################################################
-# Unknows subkommands abort ukm.
-try "ssh u2 ukm fake; !ok; /FATAL: unknown ukm subcommand: fake/";
-
-
-###################################################################
-# Addition of keys.
-
-# If no data is provided on stdin, we don't block, but rather timeout
-# after one second and abort the program.
-try "ssh u2 ukm add u4\@example.org; !ok; /FATAL: missing public key data/";
-
-# If no keyid is given, we cannot add a key.
-try "ssh u2 ukm add; !ok; /FATAL: keyid required/";
-
-try "
-    DEF ADD = cat $pd/%1.pub|ssh %2 ukm add %3
-    DEF ADDOK = ADD %1 %2 %3; ok
-    DEF ADDNOK = ADD %1 %2 %3; !ok
-    DEF FP = ADDNOK u4 u2 %1
-    DEF FORBIDDEN_PATTERN = FP %1; /FATAL: keyid not allowed:/
-";
-
-# Neither a guest key manager nor a super key manager can add keys that have
-# double dot in their keyid. This is hardcoded to forbid paths with .. in it.
-try "
-    ADDNOK u4 u2    u4\@hemmecke..org; /Not allowed to use '..' in keyid./
-    ADDNOK u4 admin u4\@hemmecke..org; /Not allowed to use '..' in keyid./
-    ADDNOK u4 admin ./../.myshrc;      /Not allowed to use '..' in keyid./
-";
-
-# guest-key-managers can only add keys that look like emails.
-try "
-    FORBIDDEN_PATTERN u4
-    FORBIDDEN_PATTERN u4\@example
-    FORBIDDEN_PATTERN u4\@foo\@example.org
-
-    # No support for 'old style' multiple keys.
-    FORBIDDEN_PATTERN u4\@example.org\@foo
-
-    # No path delimiter in keyid
-    FORBIDDEN_PATTERN foo/u4\@example.org
-
-    # Certain specific domains listed in FORBIDDEN_GUEST_PATTERN are forbidden.
-    # Note that also u4\@example-com would be rejected, because MYDOMAIN
-    # contains a regular expression --> I don't care.
-    FORBIDDEN_PATTERN u4\@example.com
-    FORBIDDEN_PATTERN u4\@hemmecke.org
-";
-
-# Accept one guest key.
-try "ADDOK u4 u2 u4\@example.org";
-try "ssh u2 ukm; ok; /Hello u2, you manage the following keys:/
-                     / u4\@example.org *u4\@example.org/";
-
-# Various ways how a key must be rejected.
-try "
-    # Cannot add the same key again.
-    ADDNOK u4 u2 u4\@example.org; /FATAL: cannot override existing key/
-
-    # u2 can also not add u4.pub under another keyid
-    ADDNOK u4 u2 u4\@example.net; /FATAL: cannot add key/
-         /Same key is already available under another userid./
-
-    # u2 can also not add another key under the same keyid.
-    ADDNOK u5 u2 u4\@example.org; /FATAL: cannot override existing key/
-
-    # Also u3 cannot not add another key under the same keyid.
-    ADDNOK u5 u3 u4\@example.org
-         /FATAL: cannot add another public key for an existing user/
-
-    # And u3 cannot not add u4.pub under another keyid.
-    ADDNOK u4 u3 u4\@example.net; /FATAL: cannot add key/
-         /Same key is already available under another userid./
-
-    # Not even the admin can add the same key u4 under a different userid.
-    ADDNOK u4 admin u4\@example.net; /FATAL: cannot add key/
-         /Same key is already available under another userid./
-         /Found  .* u4\@example.org/
-
-    # Super key managers cannot add keys that start with @.
-    # We don't care about @ in the dirname, though.
-    ADDNOK u4 admin foo/\@ex.net; /FATAL: cannot add key that starts with \@/
-    ADDNOK u4 admin foo/\@ex;     /FATAL: cannot add key that starts with \@/
-    ADDNOK u4 admin     \@ex.net; /FATAL: cannot add key that starts with \@/
-    ADDNOK u4 admin     \@ex;     /FATAL: cannot add key that starts with \@/
-";
-
-# But u3 can add u4.pub under the same keyid.
-try "ADDOK u4 u3 u4\@example.org";
-
-try "ssh u3 ukm; ok; /Hello u3, you manage the following keys:/
-                     / u4\@example.org *u4\@example.org/";
-
-# The admin can add multiple keys for the same userid.
-try "
-    ADDOK u5 admin u4\@example.org
-    ADDOK u5 admin u4\@example.org\@home
-    ADDOK u5 admin laptop/u4\@example.org
-    ADDOK u5 admin laptop/u4\@example.org\@home
-";
-
-# And admin can also do this for other guest key managers. Note,
-# however, that the gitolite-admin must be told where the
-# GUEST_DIRECTORY is. But he/she could find out by cloning the
-# gitolite-admin repository and adding the same key directly.
-try "
-    ADDOK u5 admin zzz/guests/u2/u4\@example.org\@foo
-    ADDOK u6 admin zzz/guests/u3/u6\@example.org
-";
-
-try "ssh admin ukm; ok"; cmp "Hello admin, you manage the following keys:
-fingerprint                                     userid         keyid
-a4:d1:11:1d:25:5c:55:9b:5f:91:37:0e:44:a5:a5:f2 admin          admin
-00:2c:1f:dd:a3:76:5a:1e:c4:3c:01:15:65:19:a5:2e u1             u1
-69:6f:b5:8a:f5:7b:d8:40:ce:94:09:a2:b8:95:79:5b u2             u2
-26:4b:20:24:98:a4:e4:a5:b9:97:76:9a:15:92:27:2d u3             u3
-78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org
-78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org\@home
-78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org
-78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org\@home
-8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u2/u4\@example.org
-78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org zzz/guests/u2/u4\@example.org\@foo
-8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u3/u4\@example.org
-fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org zzz/guests/u3/u6\@example.org
-\n\n";
-
-# Now, u2 has two keys in his directory, but u2 can manage only one of
-# them, since the one added by the admin has two @ in it. Thus the key
-# added by admin is invisible to u2.
-try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:
-fingerprint                                     userid         keyid
-8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
-\n\n";
-
-# Since admin added key u6 at example.org to the directory of u2, u2 is
-# also able to see it and, in fact, to manage it.
-try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:
-fingerprint                                     userid         keyid
-8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
-fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org u6\@example.org
-\n\n";
-
-###################################################################
-# Deletion of keys.
-try "
-    DEF DEL = ssh %1 ukm del %2
-    DEF DELOK  = DEL %1 %2; ok
-    DEF DELNOK = DEL %1 %2; !ok
-    DEF DELNOMGR = DELNOK %1 %2; /FATAL: You are not managing the key /
-";
-
-# Deletion requires a keyid.
-try "ssh u3 ukm del; !ok; /FATAL: keyid required/";
-
-# u3 can, of course, not remove any unmanaged key.
-try "DELNOMGR u3 u2";
-
-# But u3 can delete u4 at example.org and u6 at example.org. This will, of course,
-# not remove the key u4 at example.org that u2 manages.
-try "
-    DELOK u3 u4\@example.org
-    DELOK u3 u6\@example.org
-";
-
-# After having deleted u4 at example.org, u3 cannot remove it again,
-# even though, u2 still manages that key.
-try "DELNOMGR u3 u4\@example.org";
-
-# Of course a super-key-manager can remove any (existing) key.
-try "
-    DELOK  admin zzz/guests/u2/u4\@example.org
-    DELNOK admin zzz/guests/u2/u4\@example.org
-        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
-    DELNOK admin zzz/guests/u2/u4\@example.org\@x
-        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
-    DELOK  admin zzz/guests/u2/u4\@example.org\@foo
-";
-
-# As the admin could do that via pushing to the gitolite-admin manually,
-# it's also allowed to delete even non-guest keys.
-try "DELOK admin u3";
-
-# Let's clean the environment again.
-try "
-    DELOK admin laptop/u4\@example.org\@home
-    DELOK admin laptop/u4\@example.org
-    DELOK admin        u4\@example.org\@home
-    DELOK admin        u4\@example.org
-    ADDOK u3 admin u3
- ";
-
-# Currently the admin has just one key. It cannot be removed.
-# But after adding another key, deletion should work fine.
-try "
-    DELNOK admin admin; /FATAL: You cannot delete your last key./
-    ADDOK u6 admin second/admin; /Adding new public key for admin./
-    DELOK admin admin
-    DELNOK u6 admin; /FATAL: You are not managing the key admin./
-    DELNOK u6 second/admin; /FATAL: You cannot delete your last key./
-    ADDOK admin u6 admin; /Adding new public key for admin./
-    DELOK u6 second/admin
-";
-
-###################################################################
-# Selfkey management.
-
-# If self key management is not switched on in the .gitolite.rc file,
-# it's not allowed at all.
-try "ssh u2 ukm add \@second; !ok; /FATAL: selfkey management is not enabled/";
-
-# Let's enable it.
-system("sed -i \"/'UKM_CONFIG'=>/s/=>{/=>{'SELFKEY_MANAGEMENT'=>1,/\" $h/.gitolite.rc");
-
-# And add self-key-managers to gitolite.conf
-# chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
-try "glt pull admin origin master; ok";
-put "|cut -c5- > conf/gitolite.conf", '
-    repo gitolite-admin
-        RW+ = admin
-    repo testing
-        RW+ = @all
-    @guest-key-managers = u2 u3
-    @self-key-managers = u1 u2
-    @creators = u2 u3
-    repo pub/CREATOR/..*
-        C   =   @creators
-        RW+ =   CREATOR
-        RW  =   WRITERS
-        R   =   READERS
-';
-try "
-    git add conf keydir; ok
-    git commit -m selfkey; ok; /master.* selfkey/
-";
-try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();
-
-# Now we can start with the tests.
-
-# Only self key managers are allowed to use selfkey management.
-# See variable @self-key-managers.
-try "ssh u3 ukm add \@second; !ok; /FATAL: You are not a selfkey manager./";
-
-# Cannot add keyid that are not alphanumeric.
-try "ssh u1 ukm add \@second-key; !ok; /FATAL: keyid not allowed:/";
-
-# Add a second key for u1, but leave it pending by not feeding in the
-# session key. The new user can login, but he/she lives under a quite
-# random gl_user name and thus is pretty much excluded from everything
-# except permissions given to @all. If this new id calls ukm without
-# providing the session key, this (pending) key is automatically
-# removed from the system.
-# If a certain keyid is in the system, then it cannot be added again.
-try "
-    ADDOK u4 u1 \@second
-    ssh admin ukm; ok; /u1     zzz/self/u1/zzz-add-[a-z0-9]{32}-second-u1/
-    ssh u1    ukm; ok; /u1     \@second .pending add./
-    ADDNOK u4 u1 \@second; /FATAL: keyid already in use: \@second/
-    ssh u4    ukm; ok; /pending keyid deleted: \@second/
-    ssh admin ukm; ok; !/zzz/; !/second/
-";
-
-# Not providing a proper ssh public key will abort. Providing a good
-# ssh public key, which is not a session key makes the key invalid.
-# The key will, therefore, be deleted by this operation.
-try "
-    ADDOK u4 u1 \@second
-    echo fake|ssh u4 ukm; !ok; /FATAL: does not seem to be a valid pubkey/
-    cat $pd/u5.pub | ssh u4 ukm; ok;
-        /session key not accepted/
-        /pending keyid deleted: \@second/
-";
-
-# True addition of a new selfkey is done via piping it to a second ssh
-# call that uses the new key to call ukm. Note that the first ssh must
-# have completed its job before the second ssh is able to successfully
-# log in. This can be done via sleep or via redirecting to a file and
-# then reading from it.
-try "
-    # ADDOK u4 u1 \@second | (sleep 2; ssh u4 ukm); ok
-    ADD u4 u1 \@second > session; ok
-    cat session | ssh u4 ukm; ok;  /pending keyid added: \@second/
-";
-
-# u1 cannot add his/her initial key, since that key can never be
-# confirmed via ukm, so it is forbidden altogether. In fact, u1 is not
-# allowed to add any key twice.
-try "
-    ADDNOK u1 u1 \@first
-       /FATAL: You cannot add a key that already belongs to you./
-    ADDNOK u4 u1 \@first
-       /FATAL: You cannot add a key that already belongs to you./
-";
-
-# u1 also can add more keys, but not under an existing keyid. That can
-# be done by any of his/her identities (here we choose u4).
-try "
-    ADDNOK u5 u1 \@second; /FATAL: keyid already in use: \@second/
-    ADD u5 u4 \@third > session; ok
-    cat session | ssh u5 ukm; ok;  /pending keyid added: \@third/
-";
-
-# u2 cannot add the same key, but is allowed to use the same name (@third).
-try "
-    ADDNOK u5 u2 \@third; /FATAL: cannot add key/
-        /Same key is already available under another userid./
-    ADD u6 u2 \@third > session; ok
-    cat session | ssh u6 ukm; ok;  /pending keyid added: \@third/
-";
-
-# u6 can schedule his/her own key for deletion, but cannot actually
-# remove it. Trying to do so results in bringing back the key. Actual
-# deletion must be confirmed by another key.
-try "
-    ssh u6 ukm del \@third; /prepare deletion of key \@third/
-    ssh u2 ukm; ok; /u2     \@third .pending del./
-    ssh u6 ukm; ok; /undo pending deletion of keyid \@third/
-    ssh u6 ukm del \@third; /prepare deletion of key \@third/
-    ssh u2 ukm del \@third; ok;  /pending keyid deleted: \@third/
-";
-
-# While in pending-deletion state, it's forbidden to add another key
-# with the same keyid. It's also forbidden to add a key with the same
-# fingerprint as the to-be-deleted key).
-# A new key under another keyid, is OK.
-try "
-    ssh u1 ukm del \@third; /prepare deletion of key \@third/
-    ADDNOK u4 u1 \@third; /FATAL: keyid already in use: \@third/
-    ADDNOK u5 u1 \@fourth;
-        /FATAL: You cannot add a key that already belongs to you./
-    ADD u6 u1 \@fourth > session; ok
-    ssh u1 ukm; ok;
-        /u1     \@second/
-        /u1     \@fourth .pending add./
-        /u1     \@third .pending del./
-";
-# We can remove a pending-for-addition key (@fourth) by logging in
-# with a non-pending key. Trying to do anything with key u5 (@third)
-# will just bring it back to its normal state, but not change the
-# state of any other key. As already shown above, using u6 (@fourth)
-# without a proper session key, would remove it from the system.
-# Here we want to demonstrate that key u1 can delete u6 immediately.
-try "ssh u1 ukm del \@fourth; /pending keyid deleted: \@fourth/";
-
-# The pending-for-deletion key @third can also be removed via the u4
-# (@second) key.
-try "ssh u4 ukm del \@third; ok; /pending keyid deleted: \@third/";
-
-# Non-existing selfkeys cannot be deleted.
-try "ssh u4 ukm del \@x; !ok; /FATAL: You are not managing the key \@x./";
diff --git a/docker/gitolite/contrib/triggers/file_mirror b/docker/gitolite/contrib/triggers/file_mirror
deleted file mode 100755
index e3d083b..0000000
--- a/docker/gitolite/contrib/triggers/file_mirror
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# Use an external (non-gitolite) mirror to backup gitolite repos.  They will
-# be automatically kept uptodate as people push to your gitolite server.  If
-# your server should die and you create a new one, you can quickly and easily
-# get everything back from the external mirror with a few simple commands.
-
-#       -------------------------------------------------------------
-#       SEE WARNINGS/CAVEATS AND INSTRUCTIONS AT THE END OF THIS FILE
-#       -------------------------------------------------------------
-
-# ----------------------------------------------------------------------
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-my ( $trigger, $repo, $dummy, $op ) = @ARGV;
-exit 0 unless $trigger eq 'POST_GIT' or $trigger eq 'POST_CREATE';
-exit 0 if     $trigger eq 'POST_GIT' and $op ne 'W';
-
-chdir("$rc{GL_REPO_BASE}/$repo.git") or _die "chdir failed: $!\n";
-
-my %config = config( $repo, "gitolite-options\\.mirror\\.extslave" );
-for my $slave ( values %config ) {
-    _do($slave);
-
-    # processing one slave is sufficient for restoring!
-    last if $trigger eq 'POST_CREATE';
-}
-
-# in shell, that would be something like:
-#   gitolite git-config -r $repo gitolite-options\\.mirror\\.extslave | cut -f3 | while read slave
-#   do
-#       ...
-
-# ----------------------------------------------------------------------
-
-sub _do {
-    my $url = shift;
-
-    if ( $trigger eq 'POST_CREATE' ) {
-        # brand new repo just created; needs to be populated from mirror
-
-        # For your urls you will need a way to somehow query the server and
-        # ask if the repo is present; it's upto you how you do it.
-        my $path = $url;
-        $path =~ s(^file://)();
-        return unless -d $path;
-
-        # now fetch.  Maybe we can put a "-q" in there?
-        system( "git", "fetch", $url, "+refs/*:refs/*" );
-
-    } elsif ( $trigger eq 'POST_GIT' ) {
-        # someone just pushed; we need to update our mirrors
-
-        # need to create the repo on the mirror.  Again, it's upto you how you
-        # make sure there's a repo on the mirror that can receive the push.
-        make_repo($url);    # in case it doesn't already exist
-
-        # now push
-        system( "git", "push", "--mirror", $url );
-    }
-}
-
-sub make_repo {
-    my $url = shift;
-    # in this example, the URL is 'file:///...'; for other urls, presumably
-    # the url tells you enough about how to *create* a repo.
-
-    my $path = $url;
-    $path =~ s(^file://)();
-    return if -d $path;
-    system( "git", "init", "--bare", $path );
-}
-
-__END__
-
-WARNINGS
---------
-
-1.  THIS IS SAMPLE CODE.  You will AT LEAST have to customise the _do() and
-    make_repo() functions above based on what your remote URLs are.  For
-    example, I don't even know how to create a repo from the command line if
-    your external store is, say, github!
-
-2.  THIS DOES NOT WORK FOR WILD REPOs.  It can be made to work, with a few
-    extra steps to backup and restore the "gl-perms" and "gl-creator" files.
-
-    "Left as an exercise for the reader!"
-
-DESIGN NOTES
-------------
-
-This is really just a combination of "upstream" (see src/triggers/upstream)
-and mirroring (gitolite mirroring does allow a slave to be non-gitolite, as
-long as the ssh stuff is done the same way).
-
-The main difference is that gitolite mirroring expects peers to all talk ssh,
-whereas this method lets you use other protocols.  Specifically, since this
-whole thing was started off by someone wanting to put his repos on s3
-(apparently jgit can talk to s3 directly), you can modify the two functions to
-deal with whatever remote server you have.
-
-LANGUAGE
---------
-
-This doesn't have to be in perl.  Shell equivalent for the only gitolite
-specific code is supplied; the rest of the code is fairly straightforward.
-
-SETUP
------
-
-1.  Put this code into your LOCAL_CODE directory under "triggers"; see
-    non-core.html for details.
-
-2.  Add these lines to your rc file, just before the ENABLE line.  (I'm
-    assuming a v3.4 or later installation here).
-
-        POST_CREATE => [ 'file_mirror' ],
-        POST_GIT => [ 'file_mirror' ],
-
-3.  Backup your rc file, since you may have other changes in it that you'll
-    want to preserve.
-
-4.  Do something like this in your gitolite.conf file:
-
-        repo @all
-            option mirror.extslave-1    =   file:///tmp/he1/%GL_REPO.git
-            option mirror.extslave-2    =   file:///tmp/he2/%GL_REPO.git
-
-    As you can see, since this is just for demo/test, we're using a couple of
-    temp directories to serve as our "remotes" using the file:// protocol.
-
-5.  Do a one-time manual sync of all the repos (subsequent syncs happen on
-    each push):
-
-        gitolite list-phy-repos | xargs -I xx gitolite trigger POST_GIT xx admin W
-
-    (This is a little trick we're playing on the trigger stuff, but it should
-    work fine.  Just make sure that, if you have other things in your POST_GIT
-    trigger list, they're not affected in some way.  'gitolite query-rc
-    POST_GIT' will tell you what else you have.)
-
-That takes care of the "setup" and "regular backup".
-
-RESTORE
--------
-
-1.  Install gitolite normally.  You'll get the usual two repos.
-
-2.  Restore the previously backed up rc file to replace the default one that
-    gitolite created.  At the very least, the rc file should have the
-    POST_CREATE and POST_GIT entries.
-
-        ---------------------------------------------------------
-        IF YOU FORGET THIS STEP, NASTY THINGS WILL HAPPEN TO YOU!
-        ---------------------------------------------------------
-
-3.  Clone the admin repo from one of your backup servers to some temp dir.  In
-    our example,
-
-        git clone /tmp/he1/gitolite-admin.git old-ga
-
-4.  'cd' to that clone and force push to your *new* admin repo:
-
-        cd old-ga
-        git push -f admin:gitolite-admin
-
-That's it.  As each repo gets created by the admin push, they'll get populated
-by the backed up stuff due to the POST_CREATE trigger.
diff --git a/docker/gitolite/contrib/utils/ad_groups.sh b/docker/gitolite/contrib/utils/ad_groups.sh
deleted file mode 100755
index cc86692..0000000
--- a/docker/gitolite/contrib/utils/ad_groups.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-# author derived from: damien.nozay at gmail.com
-# author: Jonathan Gray
-
-# Given a username,
-# Provides a space-separated list of groups that the user is a member of.
-#
-# see http://gitolite.com/gitolite/conf.html#ldap
-# GROUPLIST_PGM => /path/to/ldap_groups.sh
-
-# Be sure to add your domain CA to the trusted certificates in /etc/openldap/ldap.conf using the TLS_CACERT option or you'll get certificate validation errors
-
-ldaphost='ldap://AD.DC1.local:3268,ldap://AD.DC2.local:3268,ldap://AD.DC3.local:3268'
-ldapuser='git at domain.local'
-ldappass='super.secret.password'
-binddn='dc=domain,dc=local'
-username=$1;
-
-# I don't assume your users share a common OU, so I search the entire domain
-ldap_groups() {
-        # Go fetch the full user CN as it could be anywhere inside the DN
-        usercn=$(
-                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(sAMAccountName=${username})" \
-                | grep "^dn:" \
-                | perl -pe 's|dn: (.*?)|\1|'
-        )
-
-        # Using a proprietary AD extension, let the AD Controller resolve all nested group memberships
-        # http://ddkonline.blogspot.com/2010/05/how-to-recursively-get-group-membership.html
-        # Also, substitute spaces in AD group names for '_' since gitolite expects a space separated list
-        echo $(
-                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(member:1.2.840.113556.1.4.1941:=${usercn})" \
-                | grep "^dn:" \
-                | perl -pe 's|dn: CN=(.*?),.*|\1|' \
-                | sed 's/ /_/g'
-        )
-}
-
-ldap_groups $@
diff --git a/docker/gitolite/contrib/utils/gitolite-local b/docker/gitolite/contrib/utils/gitolite-local
deleted file mode 100755
index 5faf0c7..0000000
--- a/docker/gitolite/contrib/utils/gitolite-local
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/bin/bash
-
-# ----------------------------------------------------------------------
-# change these lines to suit
-testconf=$HOME/GITOLITE-TESTCONF
-gitolite_url=git://github.com/sitaramc/gitolite
-    # change it to something local for frequent use
-    # gitolite_url=file:///tmp/gitolite.git
-
-# ----------------------------------------------------------------------
-# Usage: gitolite-local <options>
-#
-# Test your gitolite.conf rule lists on your LOCAL machine (without even
-# pushing to the server!)
-#
-# (one-time)
-#
-#   1.  put this code somewhere in your $PATH if you wish
-#   2.  edit the line near the top of the script if you want to use some other
-#       directory than the default, for "testconf".
-#   2.  prepare the "testconf" directory by running:
-#           gitolite-local prep
-#
-# (lather, rinse, repeat)
-#
-#   1.  edit the conf (see notes below for more)
-#           gitolite-local edit
-#   2.  compile the conf
-#           gitolite-local compile
-#   3.  check permissions using "info" command:
-#           gitolite-local info USERNAME
-#   4.  check permissions using "access" command:
-#           gitolite-local access <options for gitolite access command>
-#   5.  clone, fetch, and push if you like!
-#           gitolite-local clone <username> <reponame> <other options for clone>
-#           gitolite-local fetch <username> <options for fetch>
-#           gitolite-local push  <username> <options for push>
-#
-# note on editing the conf: you don't have to use the edit command; you can
-# also directly edit '.gitolite/conf/gitolite.conf' in the 'testconf'
-# directory.  You'll need to do that if your gitolite conf consists of more
-# than just one file (like if you have includes, etc.)
-#
-# note on the clone command: most of the options won't work for clone, unless
-# git is ok with them being placed *after* the repo name.
-
-# ----------------------------------------------------------------------
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-
-# ----------------------------------------------------------------------
-if [ $1 == prep ]
-then
-    set -e
-
-    [ -d $testconf ] && die "directory '$testconf' already exists"
-
-    mkdir $testconf
-    cd $testconf
-
-    export HOME=$PWD
-
-    echo getting gitolite source...
-    git clone $gitolite_url gitolite
-    echo
-
-    echo installing gitolite...
-    gitolite/install >/dev/null
-    echo
-
-    echo setting up gitolite...
-    export PATH=$PWD/gitolite/src:$PATH
-    gitolite setup -a admin
-    echo
-
-    exit 0
-fi
-
-od=$PWD
-cd $testconf
-export HOME=$PWD
-export PATH=$PWD/gitolite/src:$PATH
-
-if [ $1 = edit ]
-then
-    editor=${EDITOR:-vim}
-    $editor .gitolite/conf/gitolite.conf
-elif [ $1 = compile ]
-then
-    gitolite compile
-elif [ $1 = compile+ ]
-then
-    gitolite compile\; gitolite trigger POST_COMPILE
-elif [ $1 = info ]
-then
-    shift
-    user=$1
-    shift
-
-    GL_USER=$user gitolite info "$@"
-elif [ $1 = access ]
-then
-    shift
-
-    gitolite access "$@"
-elif [ $1 = clone ]
-then
-    shift
-    export G3T_USER=$1
-    shift
-
-    cd $od
-    export GL_BINDIR=$HOME/gitolite/t
-        # or you could do it the long way, using 'gitolite query-rc GL_BINDIR'
-    repo=$1; shift
-    git clone --upload-pack=$GL_BINDIR/gitolite-upload-pack file:///$repo "$@"
-elif [ $1 = fetch ]
-then
-    shift
-    export G3T_USER=$1
-    shift
-
-    cd $od
-    export GL_BINDIR=$HOME/gitolite/t
-    git fetch --upload-pack=$GL_BINDIR/gitolite-upload-pack "$@"
-elif [ $1 = push ]
-then
-    shift
-    export G3T_USER=$1
-    shift
-
-    cd $od
-    export GL_BINDIR=$HOME/gitolite/t
-    git push --receive-pack=$GL_BINDIR/gitolite-receive-pack "$@"
-fi
diff --git a/docker/gitolite/contrib/utils/ipa_groups.pl b/docker/gitolite/contrib/utils/ipa_groups.pl
deleted file mode 100755
index 9cffa40..0000000
--- a/docker/gitolite/contrib/utils/ipa_groups.pl
+++ /dev/null
@@ -1,229 +0,0 @@
-#!/usr/bin/env perl
-#
-# ipa_groups.pl
-#
-# See perldoc for usage
-#
-use Net::LDAP;
-use Net::LDAP::Control::Paged;
-use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED);
-use strict;
-use warnings;
-
-my $usage = <<EOD;
-Usage: $0 \$uid
-This script returns a list of groups that \$uid is a member of
-EOD
-
-my $uid = shift or die $usage;
-
-## CONFIG SECTION
-
-# If you want to do plain-text LDAP, then set ldap_opts to an empty hash and
-# then set protocols of ldap_hosts to ldap://
-my @ldap_hosts = [
-  'ldaps://auth-ldap-001.prod.example.net',
-  'ldaps://auth-ldap-002.prod.example.net',
-];
-my %ldap_opts = (
-    verify => 'require',
-    cafile => '/etc/pki/tls/certs/prod.example.net_CA.crt'
-);
-
-# Base DN to search
-my $base_dn = 'dc=prod,dc=example,dc=net';
-
-# User for binding to LDAP server with
-my $user = 'uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net';
-my $pass = 'reallysecurepasswordstringhere';
-
-## Below variables should not need to be changed under normal circumstances
-
-# OU where groups are located. Anything return that is not within this OU is
-# removed from results. This OU is static on FreeIPA so will only need updating
-# if you want to support other LDAP servers. This is a regex so can be set to
-# anything you want (E.G '.*').
-my $groups_ou = qr/cn=groups,cn=accounts,${base_dn}$/;
-
-# strip path - if you want to return the full path of the group object then set
-# this to 0
-my $strip_group_paths = 1;
-
-# Number of seconds before timeout (for each query)
-my $timeout=5;
-
-# user object class
-my $user_oclass = 'person';
-
-# group attribute
-my $group_attrib = 'memberOf';
-
-## END OF CONFIG SECTION
-
-# Catch timeouts here
-$SIG{'ALRM'} = sub {
-  die "LDAP queries timed out";
-};
-
-alarm($timeout);
-
-# try each server until timeout is reached, has very fast failover if a server
-# is totally unreachable
-my $ldap = Net::LDAP->new(@ldap_hosts, %ldap_opts) ||
-  die "Error connecting to specified servers: $@ \n";
-
-my $mesg = $ldap->bind(
-    dn       => $user,
-    password => $pass
-);
-
-if ($mesg->code()) {
-  die ("error:",      $mesg->code(),"\n",
-       "error name: ",$mesg->error_name(),"\n",
-       "error text: ",$mesg->error_text(),"\n");
-}
-
-# How many LDAP query results to grab for each paged round
-# Set to under 1000 to limit load on LDAP server
-my $page = Net::LDAP::Control::Paged->new(size => 500);
-
-# @queries is an array or array references. We initially fill it up with one
-# arrayref (The first LDAP search) and then add more during the execution.
-# First start by resolving the group.
-my @queries = [ ( base    => $base_dn,
-                  filter  => "(&(objectClass=${user_oclass})(uid=${uid}))",
-                  control => [ $page ],
-) ];
-
-# array to store groups matching $groups_ou
-my @verified_groups;
-
-# Loop until @queries is empty...
-foreach my $queryref (@queries) {
-
-  # set cookie for paged querying
-  my $cookie;
-  alarm($timeout);
-  while (1) {
-    # Perform search
-    my $mesg = $ldap->search( @{$queryref} );
-
-    foreach my $entry ($mesg->entries) {
-      my @groups = $entry->get_value($group_attrib);
-      # find any groups matching $groups_ou  regex and push onto $verified_groups array
-      foreach my $group (@groups) {
-        if ($group =~ /$groups_ou/) {
-          push @verified_groups, $group;
-        }
-      }
-    }
-
-    # Only continue on LDAP_SUCCESS
-    $mesg->code and last;
-
-    # Get cookie from paged control
-    my($resp)  = $mesg->control(LDAP_CONTROL_PAGED) or last;
-    $cookie    = $resp->cookie or last;
-
-    # Set cookie in paged control
-    $page->cookie($cookie);
-  } # END: while(1)
-
-  # Reset the page control for the next query
-  $page->cookie(undef);
-
-  if ($cookie) {
-    # We had an abnormal exit, so let the server know we do not want any more
-    $page->cookie($cookie);
-    $page->size(0);
-    $ldap->search( @{$queryref} );
-    # Then die
-    die("LDAP query unsuccessful");
-  }
-
-} # END: foreach my $queryref (...)
-
-# we're assuming that the group object looks something like
-# cn=name,cn=groups,cn=accounts,dc=X,dc=Y and there are no ',' chars in group
-# names
-if ($strip_group_paths) {
-  for (@verified_groups) { s/^cn=([^,]+),.*$/$1/g };
-}
-
-foreach my $verified (@verified_groups) {
-  print $verified . "\n";
-}
-
-alarm(0);
-
-__END__
-
-=head1 NAME
-
-ipa_groups.pl
-
-=head2 VERSION
-
-0.1.1
-
-=head2 DESCRIPTION
-
-Connects to one or more FreeIPA-based LDAP servers in a first-reachable fashion and returns a newline separated list of groups for a given uid. Uses memberOf attribute and thus supports nested groups.
-
-=head2 AUTHOR
-
-Richard Clark <rclark at telnic.org>
-
-=head2 FreeIPA vs Generic LDAP
-
-This script uses regular LDAP, but is focussed on support for FreeIPA, where users and groups are generally contained within single OUs, and memberOf attributes within the user object are enumerated with a recursive list of groups that the user is a member of.
-
-It is mostly impossible to provide generic out of the box LDAP support due to varying schemas, supported extensions and overlays between implementations.
-
-=head2 CONFIGURATION
-
-=head3  LDAP Bind Account 
-
-To setup an LDAP bind user in FreeIPA, create a svc_gitolite_bind.ldif file along the following lines:
-
-    dn: uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net
-    changetype: add
-    objectclass: account
-    objectclass: simplesecurityobject
-    uid: svc_gitolite_bind
-    userPassword: reallysecurepasswordstringhere
-    passwordExpirationTime: 20150201010101Z
-    nsIdleTimeout: 0
-
-Then create the service account user, using ldapmodify authenticating as the the directory manager account (or other acccount with appropriate privileges to the sysaccounts OU):
-
-    $ ldapmodify -h auth-ldap-001.prod.example.net -Z -x -D "cn=Directory Manager" -W -f svc_gitolite_bind.ldif
-
-=head3 Required Configuration
-
-The following variables within the C<## CONFIG SECTION ##> need to be configured before the script will work.
-
-C<@ldap_hosts> - Should be set to an array of URIs or hosts to connect to. Net::LDAP will attempt to connect to each host in this list and stop on the first reachable server. The example shows TLS-supported URIs, if you want to use plain-text LDAP then set the protocol part of the URI to LDAP:// or just provide hostnames as this is the default behavior for Net::LDAP.
-
-C<%ldap_opts> - To use LDAP-over-TLS, provide the CA certificate for your LDAP servers. To use plain-text LDAP, then empty this hash of it's values or provide other valid arguments to Net::LDAP.
-
-C<%base_dn> - This can either be set to the 'true' base DN for your directory, or alternatively you can set it the the OU that your users are located in (E.G cn=users,cn=accounts,dc=prod,dc=example,dc=net).
-
-C<$user> - Provide the full Distinguished Name of your directory bind account as configured above.
-
-C<$pass> - Set to password of your directory bind account as configured above.
-
-=head3 Optional Configuration
-
-C<$groups_ou> - By default this is a regular expression matching the default groups OU. Any groups not matching this regular expression are removed from the search results. This is because FreeIPA enumerates non-user type groups (E.G system, sudoers, policy and other types) within the memberOf attribute. To change this behavior, set C<$groups_ou> to a regex matching anything you want (E.G: '.*').
-
-C<$strip_group_paths> - If this is set to perl boolean false (E.G '0') then groups will be returned in DN format. Default is true, so just the short/CN value is returned.
-
-C<$timeout> - Number of seconds to wait for an LDAP query before determining that it has failed and trying the next server in the list. This does not affect unreachable servers, which are failed immediately.
-
-C<$user_oclass> - Object class of the user to search for.
-
-C<$group_attrib> - Attribute to search for within the user object that denotes the membership of a group.
-
-=cut
-
diff --git a/docker/gitolite/contrib/utils/ldap_groups.sh b/docker/gitolite/contrib/utils/ldap_groups.sh
deleted file mode 100755
index 01bf5ee..0000000
--- a/docker/gitolite/contrib/utils/ldap_groups.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# author: damien.nozay at gmail.com
-
-# Given a username,
-# Provides a space-separated list of groups that the user is a member of.
-#
-# see http://gitolite.com/gitolite/conf.html#ldap
-# GROUPLIST_PGM => /path/to/ldap_groups.sh
-
-ldap_groups() {
-    username=$1;
-    # this relies on openldap / pam_ldap to be configured properly on your
-    # system. my system allows anonymous search.
-    echo $(
-        ldapsearch -x -LLL "(&(objectClass=posixGroup)(memberUid=${username}))" cn \
-        | grep "^cn" \
-        | cut -d' ' -f2
-    );
-}
-
-ldap_groups $@
diff --git a/docker/gitolite/contrib/utils/rc-format-v3.4 b/docker/gitolite/contrib/utils/rc-format-v3.4
deleted file mode 100755
index 1a11737..0000000
--- a/docker/gitolite/contrib/utils/rc-format-v3.4
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/usr/bin/perl
-
-# help with rc file format change at v3.4 -- help upgrade v3 rc files from
-# v3.3 and below to the new v3.4 and above format
-
-# once you upgrade gitolite past 3.4, you may want to use the new rc file
-# format, because it's really much nicer (just to recap: the old format will
-# still work, in fact internally the new format gets converted to the old
-# format before actually being used.  However, the new format makes it much
-# easier to enable and disable features).
-
-# PLEASE SEE WARNINGS BELOW
-
-# this program helps you upgrade your rc file.
-
-# STEPS
-#   cd gitolite-source-repo-clone
-#   contrib/utils/upgrade-rc33 /path/to/old.gitolite.rc > new.gitolite.rc
-
-# WARNINGS
-#   make sure you also READ ALL ERROR/WARNING MESSAGES GENERATED
-#   make sure you EXAMINE THE FILE AND CHECK THAT EVERYTHING LOOKS GOOD before using it
-#   be especially careful about
-#       variables which contains single/double quotes or other special characters
-#       variables that stretch across multiple lines
-#       features which take arguments (like 'renice')
-#       new features you've enabled which don't exist in the default rc
-
-# ----------------------------------------------------------------------
-
-use strict;
-use warnings;
-use 5.10.0;
-use Cwd;
-use Data::Dumper;
-$Data::Dumper::Terse    = 1;
-$Data::Dumper::Indent   = 1;
-$Data::Dumper::Sortkeys = 1;
-
-BEGIN {
-    $ENV{HOME} = getcwd;
-    $ENV{HOME} .= "/.home.rcupgrade.$$";
-    mkdir $ENV{HOME} or die "mkdir '$ENV{HOME}': $!\n";
-}
-
-END {
-    system("rm -rf ./.home.rcupgrade.$$");
-}
-
-use lib "./src/lib";
-use Gitolite::Rc;
-{
-    no warnings 'redefine';
-    sub Gitolite::Common::gl_log { }
-}
-
-# ----------------------------------------------------------------------
-
-# everything happens inside a fresh v3.6.1+ gitolite clone; no other
-# directories are used.
-
-# the old rc file to be migrated is somewhere *else* and is supplied as a
-# command line argument.
-
-# ----------------------------------------------------------------------
-
-my $oldrc = shift or die "need old rc filename as arg-1\n";
-
-{
-
-    package rcup;
-    do $oldrc;
-}
-
-my %oldrc;
-{
-    no warnings 'once';
-    %oldrc = %rcup::RC;
-}
-
-delete $rcup::{RC};
-{
-    my @extra = sort keys %rcup::;
-    warn "**** WARNING ****\nyou have variables declared outside the %RC hash; you must handle them manually\n" if @extra;
-}
-
-# this is the new rc text being built up
-my $newrc = glrc('default-text');
-
-# ----------------------------------------------------------------------
-
-# default disable all features in newrc
-map { disable( $_, 'sq' ) } (qw(help desc info perms writable ssh-authkeys git-config daemon gitweb));
-# map { disable($_, '') } (qw(GIT_CONFIG_KEYS));
-
-set_s('HOSTNAME');
-set_s( 'UMASK',               'num' );
-set_s( 'GIT_CONFIG_KEYS',     'sq' );
-set_s( 'LOG_EXTRA',           'num' );
-set_s( 'DISPLAY_CPU_TIME',    'num' );
-set_s( 'CPU_TIME_WARN_LIMIT', 'num' );
-set_s('SITE_INFO');
-
-set_s('LOCAL_CODE');
-
-if ( $oldrc{WRITER_CAN_UPDATE_DESC} ) {
-    die "tell Sitaram he changed the default rc too much" unless $newrc =~ /rc variables used by various features$/m;
-    $newrc =~ s/(rc variables used by various features\n)/$1\n    # backward compat\n        WRITER_CAN_UPDATE_DESC      =>  1,\n/;
-
-    delete $oldrc{WRITER_CAN_UPDATE_DESC};
-}
-
-if ( $oldrc{ROLES} ) {
-    my $t = '';
-    for my $r ( sort keys %{ $oldrc{ROLES} } ) {
-        $t .= ( " " x 8 ) . $r . ( " " x ( 28 - length($r) ) ) . "=>  1,\n";
-    }
-    $newrc =~ s/(ROLES *=> *\{\n).*?\n( *\},)/$1$t$2/s;
-
-    delete $oldrc{ROLES};
-}
-
-if ( $oldrc{DEFAULT_ROLE_PERMS} ) {
-    warn "DEFAULT_ROLE_PERMS has been replaced by per repo option\nsee http://gitolite.com/gitolite/wild.html\n";
-    delete $oldrc{DEFAULT_ROLE_PERMS};
-}
-
-# the following is a bit like the reverse of what the new Rc.pm does...
-
-for my $l ( split /\n/, $Gitolite::Rc::non_core ) {
-    next if $l =~ /^ *#/ or $l !~ /\S/;
-
-    my ( $name, $where, $module ) = split ' ', $l;
-    $module = $name if $module eq '.';
-    ( $module = $name ) .= "::" . lc($where) if $module eq '::';
-
-    # if you find $module as an element of $where, enable $name
-    enable($name) if miw( $module, $where );
-}
-
-# now deal with commands
-if ( $oldrc{COMMANDS} ) {
-    for my $c ( sort keys %{ $oldrc{COMMANDS} } ) {
-        if ( $oldrc{COMMANDS}{$c} == 1 ) {
-            enable($c);
-            # we don't handle anything else right (and so far only git-annex
-            # is affected, as far as I remember)
-
-            delete $oldrc{COMMANDS}{$c};
-        }
-    }
-}
-
-print $newrc;
-
-for my $w (qw(INPUT POST_COMPILE PRE_CREATE ACCESS_1 POST_GIT PRE_GIT ACCESS_2 POST_CREATE SYNTACTIC_SUGAR)) {
-    delete $oldrc{$w} unless scalar( @{ $oldrc{$w} } );
-}
-delete $oldrc{COMMANDS} unless scalar keys %{ $oldrc{COMMANDS} };
-
-exit 0 unless %oldrc;
-
-warn "the following parts of the old rc were NOT converted:\n";
-print STDERR Dumper \%oldrc;
-
-# ----------------------------------------------------------------------
-
-# set scalars that the new file defaults to "commented out"
-sub set_s {
-    my ( $key, $type ) = @_;
-    $type ||= '';
-    return unless exists $oldrc{$key};
-
-    # special treatment for UMASK
-    $oldrc{$key} = substr( "00" . sprintf( "%o", $oldrc{$key} ), -4 ) if ( $key eq 'UMASK' );
-
-    $newrc =~ s/# $key /$key   /;    # uncomment if needed
-    if ( $type eq 'num' ) {
-        $newrc =~ s/$key ( *=> *).*/$key $1$oldrc{$key},/;
-    } elsif ( $type eq 'sq' ) {
-        $newrc =~ s/$key ( *=> *).*/$key $1'$oldrc{$key}',/;
-    } else {
-        $newrc =~ s/$key ( *=> *).*/$key $1"$oldrc{$key}",/;
-    }
-
-    delete $oldrc{$key};
-}
-
-sub disable {
-    my ( $key, $type ) = @_;
-    if ( $type eq 'sq' ) {
-        $newrc =~ s/^( *)'$key'/$1# '$key'/m;
-    } else {
-        $newrc =~ s/^( *)$key\b/$1# $key/m;
-    }
-}
-
-sub enable {
-    my $key = shift;
-    $newrc =~ s/^( *)# *'$key'/$1'$key'/m;
-    return if $newrc =~ /^ *'$key'/m;
-    $newrc =~ s/(add new commands here.*\n)/$1            '$key',\n/;
-}
-
-sub miw {
-    my ( $m, $w ) = @_;
-    return 0 unless $oldrc{$w};
-    my @in = @{ $oldrc{$w} };
-    my @out = grep { !/^$m$/ } @{ $oldrc{$w} };
-    $oldrc{$w} = \@out;
-    return not scalar(@in) == scalar(@out);
-}
diff --git a/docker/gitolite/convert-gitosis-conf b/docker/gitolite/convert-gitosis-conf
deleted file mode 100755
index 9b92f68..0000000
--- a/docker/gitolite/convert-gitosis-conf
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/usr/bin/perl -w
-#
-# migrate gitosis.conf to gitolite.conf format
-#
-# Based on gl-conf-convert by: Sitaram Chamarty
-# Rewritten by: Behan Webster <behanw at websterwood.com>
-#
-
-use strict;
-use warnings;
-
-if (not @ARGV and -t or @ARGV and $ARGV[0] eq '-h') {
-    print "Usage:\n    gl-conf-convert < gitosis.conf > gitolite.conf\n(please see the documentation for details)\n";
-    exit 1;
-}
-
-my @comments = ();
-my $groupname;
-my %groups;
-my $reponame;
-my %repos;
-
-while (<>)
-{
-    # not supported
-    if (/^repositories *=/ or /^map /) {
-        print STDERR "not supported: $_";
-        s/^/NOT SUPPORTED: /;
-        print;
-        next;
-    }
-
-    # normalise whitespace to help later regexes
-    chomp;
-    s/\s+/ /g;
-    s/ ?= ?/ = /;
-    s/^ //;
-    s/ $//;
-
-    if (/^\s*$/ and @comments > 1) {
-        @{$repos{$reponame}{comments}} = @comments if $reponame;
-        @{$groups{$groupname}{comments}} = @comments if $groupname;
-        @comments = ();
-    } elsif (/^\s*#/) {
-        push @comments, $_;
-    } elsif (/^\[repo\s+(.*?)\]$/) {
-        $groupname = '';
-        $reponame = $1;
-        $reponame =~ s/\.git$//;
-    } elsif (/^\[gitosis\]$/) {
-        $groupname = '';
-        $reponame = '@all';
-    } elsif (/^gitweb\s*=\s*yes/i) {
-        push @{$repos{$reponame}{R}}, 'gitweb';
-    } elsif (/^daemon\s*=\s*yes/i) {
-        push @{$repos{$reponame}{R}}, 'daemon';
-    } elsif (/^description\s*=\s*(.+?)$/) {
-        $repos{$reponame}{desc} = $1;
-    } elsif (/^owner\s*=\s*(.+?)$/) {
-        $repos{$reponame}{owner} = $1;
-    } elsif (/^\[group\s+(.*)\]$/) {
-        $reponame = '';
-        $groupname = $1;
-    } elsif (/^members\s*=\s*(.*)/) {
-        push @{$groups{$groupname}{users}}, map {s/\@([^.]+)$/_$1/g; $_} split(' ', $1);
-    } elsif (/^write?able\s*=\s*(.*)/) {
-        foreach my $repo (split(' ', $1)) {
-            $repo =~ s/\.git$//;
-            push @{$repos{$repo}{RW}}, "\@$groupname";
-        }
-    } elsif (/^readonly\s*=\s*(.*)/) {
-        foreach my $repo (split(' ', $1)) {
-            $repo =~ s/\.git$//;
-            push @{$repos{$repo}{R}}, "\@$groupname";
-        }
-    }
-}
-
-#use Data::Dumper;
-#print Dumper(\%repos);
-#print Dumper(\%groups);
-
-# Groups
-print "#\n# Groups\n#\n\n";
-foreach my $grp (sort keys %groups) {
-    next unless @{$groups{$grp}{users}};
-    printf join("\n", @{$groups{$grp}{comments}})."\n" if $groups{$grp}{comments};
-    printf "\@%-19s = %s\n", $grp, join(' ', @{$groups{$grp}{users}});
-}
-
-# Gitweb
-print "\n#\n# Gitweb\n#\n\n";
-foreach my $repo (sort keys %repos) {
-    if ($repos{$repo}{desc}) {
-        @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}});
-        print $repo;
-        print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner};
-        print " = \"$repos{$repo}{desc}\"\n";
-    }
-}
-
-# Repos
-print "\n#\n# Repos\n#\n";
-foreach my $repo (sort keys %repos) {
-    print "\n";
-    printf join("\n", @{$repos{$repo}{comments}})."\n" if $repos{$repo}{comments};
-    #if ($repos{$repo}{desc}) {
-    #    @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}});
-    #}
-    print "repo\t$repo\n";
-    foreach my $access (qw(RW+ RW R)) {
-        next unless $repos{$repo}{$access};
-        my @keys;
-        foreach my $key (@{$repos{$repo}{$access}}) {
-            if ($key =~ /^\@(.*)/) {
-                next unless defined $groups{$1} and @{$groups{$1}{users}};
-            }
-            push @keys, $key;
-        }
-        printf "\t$access\t= %s\n", join(' ', @keys) if @keys;
-    }
-    #if ($repos{$repo}{desc}) {
-    #    print $repo;
-    #    print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner};
-    #    print " = \"$repos{$repo}{desc}\"\n";
-    #}
-}
diff --git a/docker/gitolite/install b/docker/gitolite/install
deleted file mode 100755
index 98d8aee..0000000
--- a/docker/gitolite/install
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# Clearly you don't need a program to make one measly symlink, but the git
-# describe command involved in generating the VERSION string is a bit fiddly.
-
-use Getopt::Long;
-use FindBin;
-
-# meant to be run from the root of the gitolite tree, one level above 'src'
-BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin . "/src"; }
-BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Common;
-
-=for usage
-Usage (from gitolite clone directory):
-
-    ./install
-        to run gitolite using an absolute or relative path, for example
-        'src/gitolite' or '/full/path/to/this/dir/src/gitolite'
-
-    ./install -ln [<dir>]
-        to symlink just the gitolite executable to some <dir> that is in
-        $PATH.  <dir> defaults to $HOME/bin if <dir> not specified.  <dir> is
-        assumed to exist; gitolite will not create it.
-
-        Please provide a full path, not a relative path.
-
-    ./install -to <dir>
-        to copy the entire 'src' directory to <dir>.  If <dir> is not in
-        $PATH, use the full path to run gitolite commands.
-
-        Please provide a full path, not a relative path.
-
-Simplest use, if $HOME/bin exists and is in $PATH, is:
-
-    git clone git://github.com/sitaramc/gitolite
-    gitolite/install -ln
-
-    # now run setup
-    gitolite setup -pk /path/to/YourName.pub
-=cut
-
-my ( $to, $ln, $help, $quiet );
-
-GetOptions(
-    'to=s' => \$to,
-    'ln:s' => \$ln,
-    'help|h'    => \$help,
-    'quiet|q'    => \$quiet,
-);
-usage() if $to and $ln or $help;
-$ln = "$ENV{HOME}/bin" if defined($ln) and not $ln;
-for my $d ($ln, $to) {
-    next unless $d;    # ignore empty values
-    unless ( $d =~ m(^/) ) {
-        print STDERR "FATAL: please use an absolute path, not a relative path\n";
-        usage();
-    }
-    if ( not -d $d ) {
-        print STDERR "FATAL: '$d' does not exist.\n";
-        usage();
-    }
-}
-
-chdir($ENV{GL_BINDIR});
-my $version = `git describe --tags --long --dirty=-dt 2>/dev/null`;
-unless ($version =~ /^v\d/) {
-    print STDERR "git describe failed; cannot deduce version number\n";
-    $version = "(unknown)";
-}
-
-if ($to) {
-    _mkdir($to);
-    system("cp -RpP * $to");
-    _print( "$to/VERSION", $version );
-} elsif ($ln) {
-    ln_sf( $ENV{GL_BINDIR}, "gitolite", $ln );
-    _print( "VERSION", $version );
-} else {
-    say "use the following full path for gitolite:";
-    say "\t$ENV{GL_BINDIR}/gitolite";
-    _print( "VERSION", $version );
-}
diff --git a/docker/gitolite/src/VREF/COUNT b/docker/gitolite/src/VREF/COUNT
deleted file mode 100755
index f4c3eae..0000000
--- a/docker/gitolite/src/VREF/COUNT
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/sh
-
-# gitolite VREF to count number of changed/new files in a push
-
-# see gitolite docs for what the first 7 arguments mean
-
-# inputs:
-#   arg-8 is a number
-#   arg-9 is optional, and can be "NEWFILES"
-# outputs (STDOUT)
-#   arg-7 if the number of changed (or new, if arg-9 supplied) files is > arg-8
-#   otherwise nothing
-# exit status:
-#   always 0
-
-die() { echo "$@" >&2; exit 1; }
-[ -z "$8" ] && die "not meant to be run manually"
-
-newsha=$3
-oldtree=$4
-newtree=$5
-refex=$7
-
-max=$8
-
-nf=
-[ "$9" = "NEWFILES" ] && nf='--diff-filter=A'
-# NO_SIGNOFF implies NEWFILES
-[ "$9" = "NO_SIGNOFF" ] && nf='--diff-filter=A'
-
-# count files against all the other commits in the system not just $oldsha
-# (why?  consider what is $oldtree when you create a new branch, or what is
-# $oldsha when you update an old feature branch from master and then push it
-count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`
-
-[ $count -gt $max ] && {
-    # count has been exceeded.  If $9 was NO_SIGNOFF there's still a chance
-    # for redemption -- if the top commit has a proper signed-off by line
-    [ "$9" = "NO_SIGNOFF" ] && {
-        author_email=$(git log --format=%ae -1 $newsha)
-        git cat-file -p $newsha |
-            egrep -i >/dev/null "^ *$count +new +files +signed-off by: *$author_email *$" && exit 0
-        echo $refex top commit message should include the text \'$count new files signed-off by: $author_email\'
-        exit 0
-    }
-    echo -n $refex "(too many "
-    [ -n "$nf" ] && echo -n "new " || echo -n "changed "
-    echo "files in this push)"
-}
-
-exit 0
diff --git a/docker/gitolite/src/VREF/EMAIL-CHECK b/docker/gitolite/src/VREF/EMAIL-CHECK
deleted file mode 100755
index 34c66f5..0000000
--- a/docker/gitolite/src/VREF/EMAIL-CHECK
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/perl
-
-# gitolite VREF to check if all *new* commits have author == pusher
-
-#                       THIS IS NOT READY TO USE AS IS
-#                       ------------------------------
-#           you MUST change the 'email_ok()' sub to suit *YOUR* site's
-#           gitolite username -> author email mapping!
-
-# See bottom of the program for important philosophical notes.
-
-use strict;
-use warnings;
-
-# mapping between gitolite userid and correct email address is encapsulated in
-# this subroutine; change as you like
-sub email_ok {
-    my ($author_email) = shift;
-    my $expected_email = "$ENV{GL_USER}\@atc.tcs.com";
-    return $author_email eq $expected_email;
-}
-
-my ( $ref, $old, $new ) = @ARGV;
-for my $rev (`git log --format="%ae\t%h\t%s" $new --not --all`) {
-    chomp($rev);
-    my ( $author_email, $hash, $subject ) = split /\t/, $rev;
-
-    # again, we use the trick that a vref can just choose to die instead of
-    # passing back a vref, having it checked, etc., if it's more convenient
-    die "$ENV{GL_USER}, you can't push $hash authored by $author_email\n" . "\t(subject of commit was $subject)\n"
-      unless email_ok($author_email);
-}
-
-exit 0;
-
-__END__
-
-The following discussion is for people who want to enforce this check on ALL
-their developers (i.e., not just the newbies).
-
-Doing this breaks the "D" in "DVCS", forcing all your developers to work to a
-centralised model as far as pushes are concerned.  It prevents amending
-someone else's commit and pushing (this includes rebasing, cherry-picking, and
-so on, which are all impossible now).  It also makes *any* off-line
-collabaration between two developers useless, because neither of them can push
-the result to the server.
-
-PHBs should note that validating the committer ID is NOT the same as reviewing
-the code and running QA/tests on it.  If you're not reviewing/QA-ing the code,
-it's probably worthless anyway.  Conversely, if you *are* going to review the
-code and run QA/tests anyway, then you don't really need to validate the
-author email!
-
-In a DVCS, if you *pushed* a series of commits, you have -- in some sense --
-signed off on them.  The most formal way to "sign" a series is to tack on and
-push a gpg-signed tag, although most people don't go that far.  Gitolite's log
-files are designed to preserve that accountability to *some* extent, though;
-see contrib/adc/who-pushed for an admin defined command that quickly and
-easily tells you who *pushed* a particular commit.
-
-Anyway, the point is that the only purpose of this script is to
-
-  * pander to someone who still has not grokked *D*VCS
-          OR
-  * tick off an item in some stupid PHB's checklist
-
diff --git a/docker/gitolite/src/VREF/FILETYPE b/docker/gitolite/src/VREF/FILETYPE
deleted file mode 100755
index 3f1d5f9..0000000
--- a/docker/gitolite/src/VREF/FILETYPE
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-
-# gitolite VREF to find autogenerated files
-
-# *completely* site specific; use it as an illustration of what can be done
-# with gitolite VREFs if you wish
-
-# see gitolite docs for what the first 7 arguments mean
-
-# inputs:
-#   arg-8 is currently only one possible value: AUTOGENERATED
-# outputs (STDOUT)
-#   arg-7 if any files changed in the push look like they were autogenerated
-#   otherwise nothing
-# exit status:
-#   always 0
-
-die() { echo "$@" >&2; exit 1; }
-[ -z "$8" ] && die "not meant to be run manually"
-
-newsha=$3
-oldtree=$4
-newtree=$5
-refex=$7
-
-option=$8
-
-[ "$option" = "AUTOGENERATED" ] && {
-    # currently we only look for ".java" programs with the string "Generated
-    # by the protocol buffer compiler.  DO NOT EDIT" in them.
-
-    git log --name-only $nf --format=%n $newtree --not --all |
-        grep . |
-        sort -u |
-        grep '\.java$' |
-    while read fn
-    do
-        git show "$newtree:$fn" | egrep >/dev/null \
-            'Generated by the protocol buffer compiler. +DO NOT EDIT' ||
-            continue
-
-        echo $refex
-        exit 0
-    done
-}
diff --git a/docker/gitolite/src/VREF/MAX_NEWBIN_SIZE b/docker/gitolite/src/VREF/MAX_NEWBIN_SIZE
deleted file mode 100755
index 84a9efa..0000000
--- a/docker/gitolite/src/VREF/MAX_NEWBIN_SIZE
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# gitolite VREF to check max size of new binary files
-
-# see gitolite docs for what the first 7 arguments mean
-
-# inputs:
-#   arg-8 is a number
-# outputs (STDOUT)
-#   arg-7 if any new binary files exist that are greater in size than arg-8
-#   *and* there is no "signed-off by" line for such a file in the top commit
-#   message.
-#
-#   Otherwise nothing
-# exit status:
-#   always 0
-
-die "not meant to be run manually" unless $ARGV[7];
-
-my ( $newsha, $oldtree, $newtree, $refex, $max ) = @ARGV[ 2, 3, 4, 6, 7 ];
-
-# / (.*) +\| Bin 0 -> (\d+) bytes/
-
-chomp( my $author_email = `git log --format=%ae -1 $newsha` );
-my $msg = `git cat-file -p $newsha`;
-$msg =~ s/\t/ /g;    # makes our regexes simpler
-
-for my $newbin (`git diff --stat=999,999 $oldtree $newtree | grep Bin.0.-`) {
-    next unless $newbin =~ /^ (.*) +\| +Bin 0 -> (\d+) bytes/;
-    my ( $f, $s ) = ( $1, $2 );
-    next if $s <= $max;
-
-    next if $msg =~ /^ *$f +signed-off by: *$author_email *$/mi;
-
-    print "$refex $f is larger than $max";
-}
-
-exit 0
diff --git a/docker/gitolite/src/VREF/MERGE-CHECK b/docker/gitolite/src/VREF/MERGE-CHECK
deleted file mode 100644
index 07f0351..0000000
--- a/docker/gitolite/src/VREF/MERGE-CHECK
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# gitolite VREF to check if there are any merge commits in the current push.
-
-# THIS IS DEMO CODE; please read all comments below as well as
-# doc/vref.mkd before trying to use this.
-
-# usage in conf/gitolite.conf goes like this:
-
-#       -   VREF/MERGE_CHECK/master     =   @all
-#       # reject only if the merge commit is being pushed to the master branch
-#       -   VREF/MERGE_CHECK            =   @all
-#       # reject merge commits to any branch
-
-my $ref    = $ARGV[0];
-my $oldsha = $ARGV[1];
-my $newsha = $ARGV[2];
-my $refex  = $ARGV[6];
-
-# The following code duplicates some code from parse_conf_line() and some from
-# check_ref().  This duplication is the only thing that is preventing me from
-# removing the "M" permission code from 'core' gitolite and using this
-# instead.  However, it does demonstrate how you would do this if you had to
-# create any other similar features, for example someone wanted "no non-merge
-# first-parent", which is far too specific for me to add to 'core'.
-
-# -- begin duplication --
-my $branch_refex = $ARGV[7] || '';
-if ($branch_refex) {
-    $branch_refex =~ m(^refs/) or $branch_refex =~ s(^)(refs/heads/);
-} else {
-    $branch_refex = 'refs/.*';
-}
-exit 0 unless $ref =~ /^$branch_refex/;
-# -- end duplication --
-
-# we can't run this check for tag creation or new branch creation, because
-# 'git log' does not deal well with $oldsha = '0' x 40.
-if ( $oldsha eq "0" x 40 or $newsha eq "0" x 40 ) {
-    print STDERR "ref create/delete ignored for purposes of merge-check\n";
-    exit 0;
-}
-
-my $ret = `git rev-list -n 1 --merges $oldsha..$newsha`;
-print "$refex FATAL: merge commits not allowed\n" if $ret =~ /./;
-
-exit 0;
diff --git a/docker/gitolite/src/VREF/NAME_NC b/docker/gitolite/src/VREF/NAME_NC
deleted file mode 100755
index 1a81714..0000000
--- a/docker/gitolite/src/VREF/NAME_NC
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-# ----------------------------------------------------------------------
-# VREF/NAME_NC
-#   Like VREF/NAME, but only considers "new commits" -- i.e., commits that
-#   don't already exist in the repo as part of some other ref.
-
-# ----------------------------------------------------------------------
-# WHY
-#   VREF/NAME doesn't deal well with tag creation (or new branch creation),
-#   since then all files in the project look like they are being created (due
-#   to comparison with an empty tree).
-
-#   Use this instead of VREF/NAME when you need to make that distinction.
-
-newsha=$3
-
-[ $newsha = "0000000000000000000000000000000000000000" ] && {
-    echo "we don't currently handle deletions" >&2
-    exit 1
-}
-
-git log --name-only --format=%n $newsha --not --all |
-    sort -u | grep . | sed -e 's.^.VREF/NAME_NC/.'
-
-# ----------------------------------------------------------------------
-# OTHER NOTES
-#   The built-in NAME does have a wee bit of a performance advantage.  I plan
-#   to ignore this until someone notices this enough to be a problem :)
-#
-#   I could explain it here at least, but I fear that any explanation will
-#   only add to the already rampant confusion about how VREFs work.  I'm not
-#   rocking THAT boat again, sorry!
diff --git a/docker/gitolite/src/VREF/VOTES b/docker/gitolite/src/VREF/VOTES
deleted file mode 100755
index 8dc3563..0000000
--- a/docker/gitolite/src/VREF/VOTES
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/sh
-
-# gitolite VREF to count votes before allowing pushes to certain branches.
-
-# This approximates gerrit's voting (but it is SHA based; I believe Gerrit is
-# more "changeset" based).  Here's how it works:
-
-# - A normal developer "bob" proposes changes to master by pushing a commit to
-#   "pers/bob/master", then informs the voting members by email.
-
-# - Some or all of the voting members fetch and examine the commit.  If they
-#   approve, they "vote" for the commit like so.  For example, say voting
-#   member "alice" fetched bob's proposed commit into "bob-master" on her
-#   clone, then tested or reviewed it.  She would approve it by running:
-#       git push origin bob-master:votes/alice/master
-
-# - Once enough votes have been tallied (hopefully there is normal team
-#   communication that says "hey I approved your commit", or it can be checked
-#   by 'git ls-remote origin' anyway), Bob, or any developer, can push the
-#   same commit (same SHA) to master and the push will succeed.
-
-# - Finally, a "trusted" developer can push a commit to master without
-#   worrying about the voting restriction at all.
-
-# The config for this example would look like this:
-
-#   repo foo
-#       # allow personal branches (to submit proposed changes)
-#       RW+ pers/USER/          =   @devs
-#       -   pers/               =   @all
-#
-#       # allow only voters to vote
-#       RW+ votes/USER/         =   @voters
-#       -   votes/              =   @all
-#
-#       # normal access rules go here; should allow *someone* to push master
-#       RW+                     =   @devs
-#
-#       # 2 votes required to push master, but trusted devs don't have this restriction
-#       RW+ VREF/VOTES/2/master =   @trusted-devs
-#       -   VREF/VOTES/2/master =   @devs
-
-# Note: "2 votes required to push master" means at least 2 refs matching
-# "votes/*/master" have the same SHA as the one currently being pushed.
-
-# ----------------------------------------------------------------------
-
-# see gitolite docs for what the first 7 arguments mean
-
-# inputs:
-#   arg-8 is a number; see below
-#   arg-9 is a simple branch name (i.e., "master", etc).  Currently this code
-#   does NOT do vote counting for branch names with more than one component
-#   (like foo/bar).
-# outputs (STDOUT)
-#   nothing
-# exit status:
-#   always 0
-
-die() { echo "$@" >&2; exit 1; }
-[ -z "$8" ] && die "not meant to be run manually"
-
-ref=$1
-newsha=$3
-refex=$7
-votes_needed=$8
-branch=$9
-
-# nothing to do if the branch being pushed is not "master" (using our example)
-[ "$ref" = "refs/heads/$branch" ] || exit 0
-
-# find how many votes have come in
-votes=`git for-each-ref refs/heads/votes/*/$branch | grep -c $newsha`
-
-# send back a vref if we don't have the minimum votes needed.  For trusted
-# developers this will invoke the RW+ rule and pass anyway, but for others it
-# will invoke the "-" rule and fail.
-[ $votes -ge $votes_needed ] || echo $refex "require at least $votes_needed votes to push $branch"
-
-exit 0
diff --git a/docker/gitolite/src/VREF/lock b/docker/gitolite/src/VREF/lock
deleted file mode 100755
index 0fc7681..0000000
--- a/docker/gitolite/src/VREF/lock
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Common;
-
-# gitolite VREF to lock and unlock (binary) files.  Requires companion command
-# 'lock' to be enabled; see doc/locking.mkd for details.
-
-# ----------------------------------------------------------------------
-
-# see gitolite docs for what the first 7 arguments mean
-
-die "not meant to be run manually" unless $ARGV[6];
-
-my $ff = "$ENV{GL_REPO_BASE}/$ENV{GL_REPO}.git/gl-locks";
-exit 0 unless -f $ff;
-
-our %locks;
-my $t = slurp($ff);
-eval $t;
-_die "do '$ff' failed with '$@', contact your administrator" if $@;
-
-my ( $oldtree, $newtree, $refex ) = @ARGV[ 3, 4, 6 ];
-
-for my $file (`git diff --name-only $oldtree $newtree`) {
-    chomp($file);
-
-    if ( $locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER} ) {
-        print "$refex '$file' locked by '$locks{$file}{USER}'";
-        last;
-    }
-}
-
-exit 0
diff --git a/docker/gitolite/src/VREF/partial-copy b/docker/gitolite/src/VREF/partial-copy
deleted file mode 100755
index 55a7dcf..0000000
--- a/docker/gitolite/src/VREF/partial-copy
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-
-# push updated branches back to the "main" repo.
-
-# This must be run as the *last* VREF, though it doesn't matter what
-# permission you give to it
-
-die() { echo "$@" >&2; exit 1; }
-
-repo=$GL_REPO
-user=$GL_USER
-ref=$1          # we're running like an update hook
-old=$2
-new=$3
-
-# never send any STDOUT back, to avoid looking like a ref.  If we fail, git
-# will catch it by our exit code
-exec >&2
-
-main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
-[ -z "$main" ] && exit 0
-
-rand=$$
-export GL_BYPASS_ACCESS_CHECKS=1
-
-if [ "$new" = "0000000000000000000000000000000000000000" ]
-then
-    # special case for deleting a ref (this is why it is important to put this
-    # VREF as the last one; if we got this far he is allowed to delete it)
-    git push -f $GL_REPO_BASE/$main.git :$ref || die "FATAL: failed to delete $ref"
-
-    exit 0
-fi
-
-git push -f $GL_REPO_BASE/$main.git $new:refs/partial/br-$rand || die "FATAL: failed to send $new"
-
-cd $GL_REPO_BASE/$main.git
-git update-ref -d refs/partial/br-$rand
-git update-ref $ref $new $old || die "FATAL: update-ref for $ref failed"
-
-exit 0
diff --git a/docker/gitolite/src/VREF/refex-expr b/docker/gitolite/src/VREF/refex-expr
deleted file mode 100755
index b788dd9..0000000
--- a/docker/gitolite/src/VREF/refex-expr
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# see bottom of this file for instructons and IMPORTANT WARNINGS!
-# ----------------------------------------------------------------------
-
-my $rule = $ARGV[7];
-die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
-  unless exists $ENV{ "GL_REFEX_EXPR_" . $rule };
-my $res = $ENV{ "GL_REFEX_EXPR_" . $rule } || 0;
-print "$ARGV[6] ($res)\n" if $res;
-
-exit 0;
-
-__END__
-
-------------------------------------------------------------------------
-IMPORTANT WARNINGS:
-  * has not been tested heavily
-      * SO PLEASE TEST YOUR SPECIFIC USE CASE THOROUGHLY!
-      * read the NOTES section below
-  * syntax and semantics are to be considered beta and may change as I find
-    better use cases
-------------------------------------------------------------------------
-
-Refex expressions, like VREFs, are best used as additional "deny" rules, to
-deny combinations that the normal ruleset cannot detect.
-
-To enable this, uncomment 'refex-expr' in the ENABLE list in the rc file.
-
-It allows you to say things like "don't allow users u3 and u4 to change the
-Makefile in the master branch" (i.e., they can change any other file in
-master, or the Makefile in any other branch, but not that specific combo).
-
-    repo foo
-        RW+                                 =   u1 u2   # line 1
-
-        RW+ master                          =   u3 u4   # line 2
-        RW+                                 =   u3 u4   # line 3
-        RW+ VREF/NAME/Makefile              =   u3 u4   # line 4
-        -   master and VREF/NAME/Makefile   =   u3 u4   # line 5
-
-Line 5 is a "refex expression".  Here are the rules:
-
-  * for each refex in the expression ("master" and "VREF/NAME/Makefile" in
-    this example), a count is kept of the number of times the EXACT refex was
-    matched and allowed in the *normal* rules (here, lines 2 and 4) during
-    this push.
-
-  * the expression is evaluated based on these counts.  0 is false, and
-    any non-zero is true (see more examples later).  The truth value of the
-    expression determines whether the refex expression matched.
-
-    You can use any logical or arithmetic expression using refexes as operands
-    and using these operators:
-
-        not and or xor + - == -lt -gt -eq -le -ge -ne
-
-    Parens are not allowed.  Precedence is as you might expect for those
-    operators.  It's actually perl that is evaluating it (you can guess what
-    the '-lt' etc., get translated to) so if in doubt, check 'man perlop'.
-
-  * the refexes that form the terms of the expression (in this case, lines 2
-    and 4) MUST come before the expression itself (i.e., line 5).
-
-  * note the words "EXACT refex was matched" above.
-
-    Let's say you add "u3" to line 1.  Then the refex expression in line 5
-    would never match for u3.  This is because line 1 prevents line 2 from
-    matching (being more general *and* appearing earlier), so the count for
-    the "master" refex would be 0.  If "master" is 0 (false), then "master and
-    <anything>" is also false.
-
-    (Same thing is you swap lines 2 and 3; i.e., put the "RW+ = ..." before
-    the "RW+ master = ...").
-
-    Put another way, the terms in the refex expression are refexes, not refs.
-    Merely pushing the master branch does not mean the count for "master"
-    increases; it has to *match* on a line that has "master" as the refex.
-
-Here are some more examples:
-
-  * user u2 is allowed to push either 'doc/' or 'src/' but not both
-
-        repo    foo
-            RW+                         =   u1 u2 u3
-
-            RW+ VREF/NAME/doc/                      =   u2
-            RW+ VREF/NAME/src/                      =   u2
-            -   VREF/NAME/doc/ and VREF/NAME/src/   =   u2
-
-  * user u3 is allowed to push at most 2 files to conf/
-
-        repo    foo
-            RW+                         =   u1 u2 u3
-
-            RW+ VREF/NAME/conf/         =   u3
-            -   VREF/NAME/conf/ -gt 2   =   u3
diff --git a/docker/gitolite/src/commands/D b/docker/gitolite/src/commands/D
deleted file mode 100755
index 016a365..0000000
--- a/docker/gitolite/src/commands/D
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/bin/sh
-
-# ----------------------------------------------------------------------
-# ADMINISTRATOR NOTES:
-# ----------------------------------------------------------------------
-
-# - set TRASH_CAN in the rc if you don't like the default.  It should be
-#   relative to GL_REPO_BASE or an absolute value.  It should also be on the
-#   same filesystem as GL_REPO_BASE, otherwise the 'mv' will take too long.
-
-# - you could set TRASH_SUFFIX also but I recomend you leave it as it is
-
-# - run a cron job to delete old repos based on age (the TRASH_SUFFIX has a
-#   timestamp); your choice how/how often you do that
-
-# - you can completely disable the 'rm' command by setting an rc variable
-#   called D_DISABLE_RM to "1".
-# ----------------------------------------------------------------------
-
-# ----------------------------------------------------------------------
-# Usage:    ssh git at host D <subcommand> <argument>
-#
-# The whimsically named "D" command deletes repos ("D" is a counterpart to the
-# "C" permission which lets you create repos.  Which also means that, just
-# like "C", it only works for wild repos).
-#
-# There are two kinds of deletions: 'rm' removes a repo completely, while
-# 'trash' moves it to a trashcan which can be recovered later (upto a time
-# limit that your admin will tell you).
-#
-# The 'rm', 'lock', and 'unlock' subcommands:
-#     Initially, all repos are "locked" against 'rm'.  The correct sequence is
-#         ssh git at host D unlock repo
-#         ssh git at host D rm repo
-#     Since the initial condition is always locked, the "lock" command is
-#     rarely used but it is there if you want it.
-#
-# The 'trash', 'list-trash', and 'restore' subcommands:
-#     You can 'trash' a repo, which moves it to a special place:
-#         ssh git at host D trash repo
-#     You can then 'list-trash'
-#         ssh git at host D list-trash
-#     which prints something like
-#         repo/2012-04-11_05:58:51
-#     allowing you to restore by saying
-#         ssh git at host D restore repo/2012-04-11_05:58:51
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ "$1" = "-h" ] && usage
-[ "$1" != "list-trash" ] && [ -z "$2" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-cmd=$1
-repo=$2
-# ----------------------------------------------------------------------
-RB=`gitolite query-rc GL_REPO_BASE`;            cd $RB
-TRASH_CAN=`gitolite query-rc TRASH_CAN`;        tcan=Trash;                     TRASH_CAN=${TRASH_CAN:-$tcan}
-TRASH_SUFFIX=`gitolite query-rc TRASH_SUFFIX`;  tsuf=`date +%Y-%m-%d_%H:%M:%S`; TRASH_SUFFIX=${TRASH_SUFFIX:-$tsuf}
-# ----------------------------------------------------------------------
-
-owner_or_die() {
-    gitolite owns "$repo" || die You are not authorised
-}
-
-# ----------------------------------------------------------------------
-
-if [ "$cmd" = "rm" ]
-then
-
-    gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
-
-    owner_or_die
-    [ -f $repo.git/gl-rm-ok ] || die "'$repo' is locked!"
-    rm -rf $repo.git
-    echo "'$repo' is now gone!"
-
-elif [ "$cmd" = "lock" ]
-then
-
-    owner_or_die
-    rm -f $repo.git/gl-rm-ok
-    echo "'$repo' is now locked"
-
-elif [ "$cmd" = "unlock" ]
-then
-
-    gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
-
-    owner_or_die
-    touch $repo.git/gl-rm-ok
-    echo "'$repo' is now unlocked"
-
-elif [ "$cmd" = "trash" ]
-then
-
-    owner_or_die
-    mkdir -p $TRASH_CAN/$repo 2>/dev/null || die "failed creating directory in trashcan"
-    [ -d $TRASH_CAN/$repo/$TRASH_SUFFIX ] && die "try again in a few seconds..."
-    mv $repo.git $TRASH_CAN/$repo/$TRASH_SUFFIX
-    echo "'$repo' moved to trashcan"
-
-elif [ "$cmd" = "list-trash" ]
-then
-
-    cd $TRASH_CAN 2>/dev/null || exit 0
-    find . -name gl-creator | sort | while read t
-    do
-        owner=
-        owner=`cat "$t"`
-        [ "$owner" = "$GL_USER" ] && dirname $t
-    done | cut -c3-
-
-elif [ "$cmd" = "restore" ]
-then
-
-    owner=
-    owner=`cat $TRASH_CAN/$repo/gl-creator 2>/dev/null`
-    [ "$owner" = "$GL_USER" ] || die "'$repo' is not yours!"
-
-    cd $TRASH_CAN
-    realrepo=`dirname $repo`
-    [ -d $RB/$realrepo.git ] && die "'$realrepo' already exists"
-    mv $repo $RB/$realrepo.git
-    echo "'$repo' restored to '$realrepo'"
-
-else
-    die "unknown subcommand '$cmd'"
-fi
diff --git a/docker/gitolite/src/commands/access b/docker/gitolite/src/commands/access
deleted file mode 100755
index 1b16c72..0000000
--- a/docker/gitolite/src/commands/access
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/perl -s
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-our ( $q, $s, $h );    # quiet, show, help
-
-=for usage
-Usage:  gitolite access [-q|-s] <repo> <user> <perm> <ref>
-
-Print access rights for arguments given.  The string printed has the word
-DENIED in it if access was denied.  With '-q', returns only an exit code
-(shell truth, not perl truth -- 0 is success).  For '-s', see below.
-
-  - repo: mandatory
-  - user: mandatory
-  - perm: defauts to '+'.  Valid values: R, W, +, C, D, M
-  - ref:  defauts to 'any'.  See notes below
-
-Notes:
-  - ref: something like 'master', or 'refs/tags/v1.0', or even a VREF if you
-    know what they look like.
-
-    The 'any' ref is special -- it ignores deny rules, thus simulating
-    gitolite's behaviour during the pre-git access check (see 'deny-rules'
-    section in rules.html for details).
-
-  - batch mode: see src/triggers/post-compile/update-git-daemon-access-list
-    for a good example that shows how to test several repos in one invocation.
-    This is orders of magnitude faster than running the command multiple
-    times; you'll notice if you have more than a hundred or so repos.
-
-  - '-s' shows the rules (conf file name, line number, and rule) that were
-    considered and how they fared.
-=cut
-
-usage() if not @ARGV or $h;
-
-my ( $repo, $user, $aa, $ref ) = @ARGV;
-# default access is '+'
-$aa  ||= '+';
-# default ref is 'any'
-$ref ||= 'any';
-# fq the ref if needed
-$ref =~ s(^)(refs/heads/) if $ref and $ref ne 'any' and $ref !~ m(^(refs|VREF)/);
-_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ );
-_die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
-
-my $ret = '';
-
-if ( $repo ne '%' and $user ne '%' ) {
-    # single repo, single user; no STDIN
-    $ret = access( $repo, $user, $aa, $ref );
-
-    show($ret) if $s;
-
-    if ( $ret =~ /DENIED/ ) {
-        print "$ret\n" unless $q;
-        exit 1;
-    }
-
-    print "$ret\n" unless $q;
-    exit 0;
-}
-
-$repo = '' if $repo eq '%';
-$user = '' if $user eq '%';
-
-_die "'-q' and '-s' meaningless in pipe mode" if $q or $s;
- at ARGV = ();
-while (<>) {
-    my @in = split;
-    my $r  = $repo || shift @in;
-    my $u  = $user || shift @in;
-    $ret = access( $r, $u, $aa, $ref );
-    print "$r\t$u\t$ret\n";
-}
-
-sub show {
-    my $ret = shift;
-    die "repo already exists; ^C won't work\n" if $ret =~ /DENIED by existence/;
-
-    my $in = $rc{RULE_TRACE} or die "this should not happen! $ret";
-
-    print STDERR "legend:";
-    print STDERR "
-    d => skipped deny rule due to ref unknown or 'any',
-    r => skipped due to refex not matching,
-    p => skipped due to perm (W, +, etc) not matching,
-    D => explicitly denied,
-    A => explicitly allowed,
-    F => denied due to fallthru (no rules matched)
-
-";
-
-    my %rule_info = read_ri($in);    # get rule info data for all traced rules
-                                     # this means conf filename, line number, and content of the line
-
-    # the rule-trace info is a set of pairs of a number plus a string.  Only
-    # the last character in a string is valid (and has meanings shown above).
-    # At the end there may be a final 'f'
-    my @in = split ' ', $in;
-    while (@in) {
-        $in = shift @in;
-        if ( $in =~ /^\d+$/ ) {
-            my $res = shift @in or die "this should not happen either!";
-            my $m = chop($res);
-            printf "  %s %20s:%-6s %s\n", $m,
-                                          $rule_info{$in}{fn},
-                                          $rule_info{$in}{ln},
-                                          $rule_info{$in}{cl};
-        } elsif ( $in eq 'F' ) {
-            printf "  %s %20s\n", $in, "(fallthru)";
-        } else {
-            die "and finally, this also should not happen!";
-        }
-    }
-    print "\n";
-}
-
-sub read_ri {
-    my %rules = map { $_ => 1 } $_[0] =~ /(\d+)/g;
-    # contains a series of rule numbers, each of which we must search in
-    # $GL_ADMIN_BASE/.gitolite/conf/rule_info
-
-    my %rule_info;
-    for ( slurp( $ENV{GL_ADMIN_BASE} . "/conf/rule_info" ) ) {
-        my ( $r, $f, $l ) = split ' ', $_;
-        next unless $rules{$r};
-        $rule_info{$r}{fn} = $f;
-        $rule_info{$r}{ln} = $l;
-        $rule_info{$r}{cl} = conf_lines( $f, $l );
-
-        # a wee bit of optimisation, in case the rule_info file is huge and
-        # what we want is up near the beginning
-        delete $rules{$r};
-        last unless %rules;
-    }
-    return %rule_info;
-}
-
-{
-    my %conf_lines;
-
-    sub conf_lines {
-        my ( $file, $line ) = @_;
-        $line--;
-
-        unless ( $conf_lines{$file} ) {
-            $conf_lines{$file} = [ slurp( $ENV{GL_ADMIN_BASE} . "/conf/$file" ) ];
-            chomp( @{ $conf_lines{$file} } );
-        }
-        return $conf_lines{$file}[$line];
-    }
-}
diff --git a/docker/gitolite/src/commands/config b/docker/gitolite/src/commands/config
deleted file mode 100755
index b996066..0000000
--- a/docker/gitolite/src/commands/config
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/perl
-use 5.10.0;
-
-# ----------------------------------------------------------------------
-# gitolite command to allow "git config" on repos (with some restrictions)
-
-# (Not to be confused with the 'git-config' command, which is used only in
-# server-side scripts, not remotely.)
-
-# setup:
-#   1.  Enable the command by adding it to the COMMANDS section in the ENABLE
-#       list in the rc file.
-#
-#   2.  Specify configs allowed to be changed by the user.  This is a space
-#       separated regex list.  For example:
-
-#           repo ...
-#               ... (various rules) ...
-#               option user-configs = hook\..* foo.bar[0-9].*
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-use Gitolite::Common;
-
-# ----------------------------------------------------------------------
-# usage
-
-=for usage
-Usage:    ssh git at host config <repo> [git config options]
-
-Runs "git config" in the repo.  Only the following 3 syntaxes are supported
-(see 'man git-config'):
-
-          --add name value
-      --get-all name
-    --unset-all name
-         --list
-
-Your administrator should tell you what keys are allowed for the "name".
-=cut
-
-# ----------------------------------------------------------------------
-# arg checks
-
-my %nargs = qw(
-          --add 3
-      --get-all 2
-    --unset-all 2
-         --list 1
-     );
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-
-my $repo = shift;
-
-my ($op, $key, $val) = @ARGV;
-usage() unless $op and exists $nargs{$op} and @ARGV == $nargs{$op};
-
-# ----------------------------------------------------------------------
-# authorisation checks
-
-die "sorry, you are not authorised\n" unless
-    owns($repo)
-        or
-    ( ( $op eq '--get-all' or $op eq '--list' )
-        ? can_read($repo)
-        : ( can_write($repo) and option( $repo, 'writer-is-owner' ) )
-    );
-
-# ----------------------------------------------------------------------
-# key validity checks
-
-unless ($op eq '--list') {
-    my $user_configs = option( $repo, 'user-configs' );
-    # this is a space separated list of allowed config keys
-    my @validkeys = split( ' ', ( $user_configs || '' ) );
-    my @matched = grep { $key =~ /^$_$/i } @validkeys;
-    _die "config '$key' not allowed\n" if ( @matched < 1 );
-}
-
-# ----------------------------------------------------------------------
-# go!
-
-_chdir("$rc{GL_REPO_BASE}/$repo.git");
-_system( "git", "config", @ARGV );
diff --git a/docker/gitolite/src/commands/create b/docker/gitolite/src/commands/create
deleted file mode 100755
index d35c4a8..0000000
--- a/docker/gitolite/src/commands/create
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-
-# Usage:    ssh git at host create <repo>
-#
-# Create wild repo.
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ -z "$2" ] || usage
-[ "$1" = "-h" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-perms=$(gitolite git-config -r $1 gitolite-options.default.roles | sort | cut -f3 |
-    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$1/')
-echo "$perms" | $GL_BINDIR/commands/perms -c "$@"
diff --git a/docker/gitolite/src/commands/creator b/docker/gitolite/src/commands/creator
deleted file mode 100755
index 702df73..0000000
--- a/docker/gitolite/src/commands/creator
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-=for usage
-Usage:  gitolite creator [-n] <reponame> [<username>]
-
-Print the creator name for the repo.  A '-n' suppresses the newline.
-
-When an optional username is supplied, it checks if the user is the creator of
-the repo and returns an exit code (shell truth, 0 for success) instead of
-printing anything, which makes it possible to do this in shell:
-
-    if gitolite creator someRepo someUser
-    then
-        ...
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-my $nl = "\n";
-if ( $ARGV[0] eq '-n' ) {
-    $nl = '';
-    shift;
-}
-my $repo = shift;
-my $user = shift || '';
-
-my $creator = '';
-$creator = creator($repo) if not repo_missing($repo);
-if ($user) {
-    exit 0 if $creator eq $user;
-    exit 1;
-}
-return ( $creator eq $user ) if $user;
-print "$creator$nl";
diff --git a/docker/gitolite/src/commands/desc b/docker/gitolite/src/commands/desc
deleted file mode 100755
index 4a4bf20..0000000
--- a/docker/gitolite/src/commands/desc
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-=for usage
-Usage:    ssh git at host desc <repo>
-          ssh git at host desc <repo> <description string>
-
-Show or set description for repo.  You need to have write access to the repo
-and the 'writer-is-owner' option must be set for the repo, or it must be a
-user-created ('wild') repo and you must be the owner.
-=cut
-
-usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
-
-my $repo = shift;
-my $text = join( " ", @ARGV );
-my $file = 'description';
-
-#<<<
-_die "you are not authorized" unless
-    ( not $text and can_read($repo) )   or
-    (     $text and owns($repo) )       or
-    (     $text and can_write($repo)    and ( $rc{WRITER_CAN_UPDATE_DESC} or option( $repo, 'writer-is-owner' ) ) );
-#>>>
-
-$text
-  ? textfile( file => $file, repo => $repo, text => $text )
-  : print textfile( file => $file, repo => $repo );
-
-__END__
-
-kernel.org needs 'desc' to be available to people who have "RW" or above, not
-just the "creator".  In fact they need it for non-wild repos so there *is* no
-creator.  To accommodate this, we created the WRITER_CAN_UPDATE_DESC rc
-variable.
-
-However, that has turned out to be a bit of a blunt instrument for people with
-different types of wild repos -- they don't want to apply this to all of them.
-It seems easier to do this as an option, so you may have it for one set of
-"repo ..." and not have it for others.  And if you want it for the whole
-system you'd just put it under "repo @all".
-
-The new 'writer-is-owner' option is meant to cover desc, readme, and any other
-repo-specific text file, so it's also a blunt instrument, though in a
-different dimension :-)
diff --git a/docker/gitolite/src/commands/fork b/docker/gitolite/src/commands/fork
deleted file mode 100755
index 49994fc..0000000
--- a/docker/gitolite/src/commands/fork
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-
-# Usage:    ssh git at host fork <repo1> <repo2>
-#
-# Forks repo1 to repo2.  You must have read permissions on repo1, and create
-# ("C") permissions for repo2, which of course must not exist.
-#
-# A fork is functionally the same as cloning repo1 to a client and pushing it
-# to a new repo2.  It's just a little more efficient, not just in network
-# traffic but because it uses git clone's "-l" option to share the object
-# store also, so it is likely to be almost instantaneous, regardless of how
-# big the repo actually is.
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ "$1" = "-h" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-from=$1; shift
-to=$1; shift
-[ -z "$to" ] && usage
-
-gitolite access -q "$from" $GL_USER R any || die "'$from' does not exist or you are not allowed to read it"
-gitolite access -q "$to"   $GL_USER ^C any || die "'$to' already exists or you are not allowed to create it"
-
-# ----------------------------------------------------------------------
-# IMPORTANT NOTE: checking whether someone can create a repo is done as above.
-# However, make sure that the env var GL_USER is set, and that too to the same
-# value as arg-2 of the access command), otherwise it won't work.
-
-# Ideally, you'll leave such code to me.  There's a reason ^C is not listed in
-# the help message for 'gitolite access'.
-# ----------------------------------------------------------------------
-
-# clone $from to $to
-git clone --bare -l $GL_REPO_BASE/$from.git $GL_REPO_BASE/$to.git
-[ $? -ne 0 ] && exit 1
-
-echo "$from forked to $to" >&2
-
-# fix up creator, default role permissions (gl-perms), and hooks
-cd $GL_REPO_BASE/$to.git
-echo $GL_USER > gl-creator
-
-gitolite query-rc -q LOCAL_CODE && ln -sf `gitolite query-rc LOCAL_CODE`/hooks/common/* hooks
-ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
-
-# record where you came from
-echo "$from" > gl-forked-from
-
-# cache control, if rc says caching is on
-gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$to')";
-
-# trigger post_create
-gitolite trigger POST_CREATE $to $GL_USER fork
diff --git a/docker/gitolite/src/commands/git-annex-shell b/docker/gitolite/src/commands/git-annex-shell
deleted file mode 100755
index 572aba6..0000000
--- a/docker/gitolite/src/commands/git-annex-shell
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/perl
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-# This command requires unrestricted arguments, so add it to the ENABLE list
-# like this:
-#   'git-annex-shell ua',
-
-# This requires git-annex version 20111016 or newer. Older versions won't
-# be secure.
-
-use strict;
-use warnings;
-
-# ignore @ARGV and look at the original unmodified command
-my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
-
-# Expect commands like:
-#   git-annex-shell 'configlist' '/~/repo'
-#   git-annex-shell 'configlist' '/repo'
-#   git-annex-shell 'sendkey' '/~/repo' 'key'
-# The parameters are always single quoted, and the repo path is always
-# the second parameter.
-# Further parameters are not validated here (see below).
-die "bad git-annex-shell command: $cmd"
-  unless $cmd =~ m#^(git-annex-shell '\w+' ')/(?:\~/)?([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
-my $start = $1;
-my $repo  = $2;
-my $end   = $3;
-$repo =~ s/\.git$//;
-die "I dont like some of the characters in $repo\n" unless $repo =~ $Gitolite::Rc::REPONAME_PATT;
-die "I dont like absolute paths in $cmd\n" if $repo =~ /^\//;
-die "I dont like '..' paths in $cmd\n"     if $repo =~ /\.\./;
-
-# Modify $cmd, fixing up the path to the repo to include GL_REPO_BASE.
-my $newcmd = "$start$rc{GL_REPO_BASE}/$repo$end";
-
-# Rather than keeping track of which git-annex-shell commands
-# require write access and which are readonly, we tell it
-# when readonly access is needed.
-if ( can_write($repo) ) {
-} elsif ( can_read($repo) ) {
-    $ENV{GIT_ANNEX_SHELL_READONLY} = 1;
-} else {
-    die "$repo $ENV{GL_USER} DENIED\n";
-}
-# Further limit git-annex-shell to safe commands (avoid it passing
-# unknown commands on to git-shell)
-$ENV{GIT_ANNEX_SHELL_LIMITED} = 1;
-
-# Note that $newcmd does *not* get evaluated by the unix shell.
-# Instead it is passed as a single parameter to git-annex-shell for
-# it to parse and handle the command. This is why we do not need to
-# fully validate $cmd above.
-Gitolite::Common::gl_log( $ENV{SSH_ORIGINAL_COMMAND} );
-exec "git-annex-shell", "-c", $newcmd;
-
-__END__
-
-INSTRUCTIONS... (NEED TO BE VALIDATED BY SOMEONE WHO KNOWS GIT-ANNEX WELL).
-
-based on http://git-annex.branchable.com/tips/using_gitolite_with_git-annex/
-ONLY VARIATIONS FROM THAT PAGE ARE WRITTEN HERE.
-
-setup
-
-  * in the ENABLE list in the rc file, add an entry like this:
-        'git-annex-shell ua',
-
-That should be it; everything else should be as in that page.
diff --git a/docker/gitolite/src/commands/git-config b/docker/gitolite/src/commands/git-config
deleted file mode 100755
index 94211de..0000000
--- a/docker/gitolite/src/commands/git-config
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use Getopt::Long;
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-=for usage
-Usage:  gitolite git-config [-n] [-q] [-r] <repo> <key|pattern>
-
-Print git config keys and values for the given repo.  The key is either a full
-key, or, if '-r' is supplied, a regex that is applied to all available keys.
-
-    -q          exit code only (shell truth; 0 is success)
-    -n          suppress trailing newline when used as key (not pattern)
-    -r          treat key as regex pattern (unanchored)
-    -ev         print keys with empty values also (see below)
-
-Examples:
-    gitolite git-config repo gitweb.owner
-    gitolite git-config -q repo gitweb.owner
-    gitolite git-config -r repo gitweb
-
-Notes:
-
-1.  When the key is treated as a pattern, prints:
-
-        reponame<tab>key<tab>value<newline>
-
-    Otherwise the output is just the value.
-
-2.  By default, keys with empty values (specified as "" in the conf file) are
-    treated as non-existant.  Using '-ev' will print those keys also.  Note
-    that this only makes sense when the key is treated as a pattern, where
-    such keys are printed as:
-
-        reponame<tab>key<tab><newline>
-
-3.  Finally, see the advanced use section of 'gitolite access -h' -- you can
-    do something similar here also:
-
-        gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
-=cut
-
-usage() if not @ARGV;
-
-my ( $help, $nonl, $quiet, $regex, $ev ) = (0) x 5;
-GetOptions(
-    'n'  => \$nonl,
-    'q'  => \$quiet,
-    'r'  => \$regex,
-    'h'  => \$help,
-    'ev' => \$ev,
-) or usage();
-
-my ( $repo, $key ) = @ARGV;
-usage() unless $key;
-
-my $ret = '';
-
-if ( $repo ne '%' and $key ne '%' ) {
-    # single repo, single key; no STDIN
-    $key = "^\Q$key\E\$" unless $regex;
-
-    $ret = git_config( $repo, $key, $ev );
-
-    # if the key is not a regex, it should match at most one item
-    _die "found more than one entry for '$key'" if not $regex and scalar( keys %$ret ) > 1;
-
-    # unlike access, there's nothing to print if we don't find any matching keys
-    exit 1 unless %$ret;
-
-    if ($regex) {
-        map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
-    } else {
-        map { print $ret->{$_} . ( $nonl ? "" : "\n" ) } sort keys %$ret unless $quiet;
-    }
-    exit 0;
-}
-
-$repo = '' if $repo eq '%';
-$key  = '' if $key eq '%';
-
-_die "'-q' doesn't go with using a pipe" if $quiet;
- at ARGV = ();
-while (<>) {
-    my @in = split;
-    my $r  = $repo || shift @in;
-    my $k  = $key || shift @in;
-    $k = "^\Q$k\E\$" unless $regex;
-    $ret = git_config( $r, $k, $ev );
-    next unless %$ret;
-    map { print "$r\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret;
-}
diff --git a/docker/gitolite/src/commands/help b/docker/gitolite/src/commands/help
deleted file mode 100755
index cf54084..0000000
--- a/docker/gitolite/src/commands/help
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-=for usage
-Usage:  ssh git at host help       # via ssh
-        gitolite help           # directly on server command line
-
-Prints a list of custom commands available at this gitolite installation.
-
-Each command has its own help, accessed by passing it '-h' again.
-=cut
-
-usage() if @ARGV;
-
-print greeting();
-
-my $user = $ENV{GL_USER} || '';
-print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
-
-my %list = ( list_x( $ENV{GL_BINDIR} ), list_x( $rc{LOCAL_CODE} || '' ) );
-for ( sort keys %list ) {
-    print "\t$list{$_}" if $ENV{D};
-    print "\t$_\n" if not $user or $rc{COMMANDS}{$_};
-}
-
-print "\n";
-print "$rc{SITE_INFO}\n" if $rc{SITE_INFO};
-
-exit 0;
-
-# ------------------------------------------------------------------------------
-sub list_x {
-    my $d = shift;
-    return unless $d;
-    return unless -d "$d/commands";
-    _chdir "$d/commands";
-    return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f -o -type l|sort`;
-}
diff --git a/docker/gitolite/src/commands/htpasswd b/docker/gitolite/src/commands/htpasswd
deleted file mode 100755
index bbfacc7..0000000
--- a/docker/gitolite/src/commands/htpasswd
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-=for usage
-Usage:  ssh git at host htpasswd
-
-Sets your htpasswd, assuming your admin has enabled it.
-
-(Admins: You need to add HTPASSWD_FILE to the rc file, pointing to an
-existing, writable, but possibly an initially empty, file, as well as adding
-'htpasswd' to the ENABLE list).
-=cut
-
-# usage and sanity checks
-usage() if @ARGV and $ARGV[0] eq '-h';
-$ENV{GL_USER} or _die "GL_USER not set";
-my $htpasswd_file = $rc{HTPASSWD_FILE} || '';
-die "htpasswd not enabled\n" unless $htpasswd_file;
-die "$htpasswd_file doesn't exist or is not writable\n" unless -w $htpasswd_file;
-
-# prompt
-$|++;
-print <<EOFhtp;
-Please type in your new htpasswd at the prompt.  You only have to type it once.
-
-NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
-shoulder-surfing, and make sure you clear your screen as well as scrollback
-history after you're done (or close your terminal instance).
-
-EOFhtp
-print "new htpasswd: ";
-
-# get the password and run htpasswd
-my $password = <>;
-$password =~ s/[\n\r]*$//;
-die "empty passwords are not allowed\n" unless $password;
-my $res = system( "htpasswd", "-mb", $htpasswd_file, $ENV{GL_USER}, $password );
-die "htpasswd command seems to have failed with return code: $res.\n" if $res;
diff --git a/docker/gitolite/src/commands/info b/docker/gitolite/src/commands/info
deleted file mode 100755
index 5079cfa..0000000
--- a/docker/gitolite/src/commands/info
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use Getopt::Long;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-=for args
-Usage:  gitolite info [-lc] [-ld] [-json] [<repo name pattern>]
-
-List all existing repos you can access, as well as repo name patterns you can
-create repos from (if any).
-
-    '-lc'       lists creators as an additional field at the end.
-    '-ld'       lists description as an additional field at the end.
-    '-json'     produce JSON output instead of normal output
-
-The optional pattern is an unanchored regex that will limit the repos
-searched, in both cases.  It might speed up things a little if you have more
-than a few thousand repos.
-=cut
-
-# these are globals
-my ( $lc, $ld, $json, $patt ) = args();
-my %out;    # holds info to be json'd
-
-$ENV{GL_USER} or _die "GL_USER not set";
-if ($json) {
-    greeting(\%out);
-} else {
-    print greeting();
-}
-
-print_patterns();     # repos he can create for himself
-print_phy_repos();    # repos already created
-
-if ( $rc{SITE_INFO} ) {
-    $json
-      ? $out{SITE_INFO} = $rc{SITE_INFO}
-      : print "\n$rc{SITE_INFO}\n";
-}
-
-print JSON::to_json( \%out, { utf8 => 1, pretty => 1 } ) if $json;
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my ( $lc, $ld, $json, $patt ) = ( '', '', '', '' );
-    my $help = '';
-
-    GetOptions(
-        'lc'   => \$lc,
-        'ld'   => \$ld,
-        'json' => \$json,
-        'h'    => \$help,
-    ) or usage();
-
-    usage() if @ARGV > 1 or $help;
-    $patt = shift @ARGV || '.';
-
-    require JSON if $json;
-
-    return ( $lc, $ld, $json, $patt );
-}
-
-sub print_patterns {
-    my ( $repos, @aa );
-
-    my $lm = \&Gitolite::Conf::Load::list_members;
-
-    # find repo patterns only, call them with ^C flag included
-    @$repos = grep { !/$REPONAME_PATT/ } map { /^@/ ? @{ $lm->($_) } : $_ } @{ lister_dispatch('list-repos')->() };
-    @aa = qw(R W ^C);
-    listem( $repos, '', '', @aa );
-    # but squelch the 'lc' and 'ld' flags for these
-}
-
-sub print_phy_repos {
-    my ( $repos, @aa );
-
-    # now get the actual repos and get R or W only
-    _chdir( $rc{GL_REPO_BASE} );
-    $repos = list_phy_repos(1);
-    @aa    = qw(R W);
-    listem( $repos, $lc, $ld, @aa );
-}
-
-sub listem {
-    my ( $repos, $lc, $ld, @aa ) = @_;
-    my @list;
-    my $mlr = 0;    # max length of reponame
-    my $mlc = 0;    # ...and creator
-    for my $repo (@$repos) {
-        next unless $repo =~ /$patt/;
-        my $creator = '';
-        my $desc    = '';
-        my $perm    = '';
-        $creator = creator($repo) if $lc;
-
-        if ($ld) {
-            # use config value first, else 'description' file as second choice
-            my $k = 'gitweb.description';
-            my $d = "$ENV{GL_REPO_BASE}/$repo.git/description";
-            $desc = git_config( $repo, $k )->{$k} || '';
-            if ( !$desc and -r $d ) {
-                $desc = slurp($d);
-                chomp($desc);
-            }
-        }
-
-        for my $aa (@aa) {
-            my $ret = access( $repo, $ENV{GL_USER}, $aa, 'any' );
-            $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
-        }
-        $perm =~ s/\^//;
-        next unless $perm =~ /\S/;
-
-        if ($json) {
-            $out{repos}{$repo}{creator}     = $creator if $lc;
-            $out{repos}{$repo}{description} = $desc    if $ld;
-            $out{repos}{$repo}{perms}       = _hash($perm);
-        } else {
-            $mlr = length($repo) if ( $lc or $ld ) and $mlr < length($repo);
-            $mlc = length($creator) if $lc and $ld and $mlc < length($creator);
-            push @list, [ $perm, $repo, $creator, $desc ];
-        }
-    }
-    return if $json;
-
-    my $fmt = "%s\t%-${mlr}s\t%-${mlc}s\t%s\n";
-    map { s/\t\t/\t/; s/\s*$/\n/; print } map { sprintf $fmt, @$_ } @list;
-}
-
-sub _hash {
-    my $in = shift;
-    my %out = map { $_ => 1 } ( $in =~ /(\S)/g );
-    return \%out;
-}
diff --git a/docker/gitolite/src/commands/list-dangling-repos b/docker/gitolite/src/commands/list-dangling-repos
deleted file mode 100755
index 60a3592..0000000
--- a/docker/gitolite/src/commands/list-dangling-repos
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-=for usage
-Usage:  gitolite list-dangling-repos
-
-List all existing repos that no one can access remotely any more.  They could
-be normal repos that were taken out of "repo" statements in the conf file, or
-wildcard repos whose matching "wild" pattern was taken out or changed so it no
-longer matches.
-
-I would advise caution if you use this as a basis for deleting repos from the
-file system.  A bug in this program could cause you to lose important data!
-=cut
-
-usage() if @ARGV and $ARGV[0] eq '-h';
-
-# get the two lists we need.  %repos is the list of repos in "repo" statements
-# in the conf file.  %phy_repos is the list of actual repos on disk.  Our job
-# is to cull %phy_repos of all keys that have a matching key in %repos, where
-# "matching" means "string equal" or "regex match".
-my %repos = map { chomp; $_ => 1 } `gitolite list-repos`;
-for my $r ( grep /^@/, keys %repos ) {
-    map { chomp; $repos{$_} = 1; } `gitolite list-members $r`;
-}
-my %phy_repos = map { chomp; $_ => 1 } `gitolite list-phy-repos`;
-
-# Remove exact matches.  But for repo names like "gtk+", you could have
-# collapsed this into the next step (the regex match).
-for my $pr ( keys %phy_repos ) {
-    next unless exists $repos{$pr};
-    delete $repos{$pr};
-    delete $phy_repos{$pr};
-}
-
-# Remove regex matches.
-for my $pr ( keys %phy_repos ) {
-    my $matched = 0;
-    my $pr2     = Gitolite::Conf::Load::generic_name($pr);
-    for my $r ( keys %repos ) {
-        if ( $pr =~ /^$r$/ or $pr2 =~ /^$r$/ ) {
-            $matched = 1;
-            next;
-        }
-    }
-    delete $phy_repos{$pr} if $matched;
-}
-
-# what's left in %phy_repos are dangling repos.
-print join( "\n", sort keys %phy_repos ), "\n";
diff --git a/docker/gitolite/src/commands/lock b/docker/gitolite/src/commands/lock
deleted file mode 100755
index 70c2190..0000000
--- a/docker/gitolite/src/commands/lock
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use Getopt::Long;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-# gitolite command to lock and unlock (binary) files and deal with locks.
-
-=for usage
-Usage:  ssh git at host lock -l      <repo> <file>     # lock a file
-        ssh git at host lock -u      <repo> <file>     # unlock a file
-        ssh git at host lock --break <repo> <file>     # break someone else's lock
-        ssh git at host lock -ls     <repo>            # list locked files for repo
-
-See doc/locking.mkd for other details.
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-$ENV{GL_USER} or _die "GL_USER not set";
-
-my $op = '';
-$op = 'lock'   if $ARGV[0] eq '-l';
-$op = 'unlock' if $ARGV[0] eq '-u';
-$op = 'break'  if $ARGV[0] eq '--break';
-$op = 'list'   if $ARGV[0] eq '-ls';
-usage() if not $op;
-shift;
-
-my $repo = shift;
-_die "You are not authorised" if access( $repo, $ENV{GL_USER}, 'W', 'any' ) =~ /DENIED/;
-_die "You are not authorised" if $op eq 'break' and access( $repo, $ENV{GL_USER}, '+', 'any' ) =~ /DENIED/;
-
-my $file = shift || '';
-usage() if $op ne 'list' and not $file;
-
-_chdir( $ENV{GL_REPO_BASE} );
-_chdir("$repo.git");
-
-_die "aborting, file '$file' not found in any branch" if $file and not object_exists($file);
-
-my $ff = "gl-locks";
-
-if ( $op eq 'lock' ) {
-    f_lock( $repo, $file );
-} elsif ( $op eq 'unlock' ) {
-    f_unlock( $repo, $file );
-} elsif ( $op eq 'break' ) {
-    f_break( $repo, $file );
-} elsif ( $op eq 'list' ) {
-    f_list($repo);
-}
-
-# ----------------------------------------------------------------------
-# For a given path, return 1 if object exists in any branch, 0 if not.
-# This is to prevent locking invalid objects.
-
-sub object_exists {
-    my $file = shift;
-
-    my @branches = `git for-each-ref refs/heads '--format=%(refname)'`;
-    foreach my $b (@branches) {
-        chomp($b);
-        system("git cat-file -e $b:$file 2>/dev/null") or return 1;
-        # note that with system(), the return value is "shell truth", so
-        # you check for success with "or", not "and"
-    }
-    return 0;    # report object not found
-}
-
-# ----------------------------------------------------------------------
-# everything below assumes we have already chdir'd to "$repo.git".  Also, $ff
-# is used as a global.
-
-sub f_lock {
-    my ( $repo, $file ) = @_;
-
-    my %locks = get_locks();
-    _die "'$file' locked by '$locks{$file}{USER}' since " . localtime( $locks{$file}{TIME} ) if $locks{$file}{USER};
-    $locks{$file}{USER} = $ENV{GL_USER};
-    $locks{$file}{TIME} = time;
-    put_locks(%locks);
-}
-
-sub f_unlock {
-    my ( $repo, $file ) = @_;
-
-    my %locks = get_locks();
-    _die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file}{USER} || '' ) ne $ENV{GL_USER};
-    delete $locks{$file};
-    put_locks(%locks);
-}
-
-sub f_break {
-    my ( $repo, $file ) = @_;
-
-    my %locks = get_locks();
-    _die "'$file' was not locked" unless $locks{$file};
-    push @{ $locks{BREAKS} }, time . " $ENV{GL_USER} $locks{$file}{USER} $locks{$file}{TIME} $file";
-    delete $locks{$file};
-    put_locks(%locks);
-}
-
-sub f_list {
-    my $repo = shift;
-
-    my %locks = get_locks();
-    print "\n# locks held:\n\n";
-    map { print "$locks{$_}{USER}\t$_\t(" . scalar( localtime( $locks{$_}{TIME} ) ) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
-    print "\n# locks broken:\n\n";
-    for my $b ( @{ $locks{BREAKS} } ) {
-        my ( $when, $who, $whose, $how_old, $what ) = split ' ', $b;
-        print "$who\t$what\t(" . scalar( localtime($when) ) . ")\t(locked by $whose at " . scalar( localtime($how_old) ) . ")\n";
-    }
-}
-
-sub get_locks {
-    if ( -f $ff ) {
-        our %locks;
-
-        my $t = slurp($ff);
-        eval $t;
-        _die "do '$ff' failed with '$@', contact your administrator" if $@;
-
-        return %locks;
-    }
-    return ();
-}
-
-sub put_locks {
-    my %locks = @_;
-
-    use Data::Dumper;
-    $Data::Dumper::Indent   = 1;
-    $Data::Dumper::Sortkeys = 1;
-
-    my $dumped_data = Data::Dumper->Dump( [ \%locks ], [qw(*locks)] );
-    _print( $ff, $dumped_data );
-}
diff --git a/docker/gitolite/src/commands/mirror b/docker/gitolite/src/commands/mirror
deleted file mode 100755
index dbdc952..0000000
--- a/docker/gitolite/src/commands/mirror
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-my $tid;
-
-BEGIN {
-    $tid = $ENV{GL_TID} || 0;
-    delete $ENV{GL_TID};
-}
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-=for usage
-Usage 1:    gitolite mirror push <slave> <repo>
-Usage 2:    ssh git at master-server mirror push <slave> <repo>
-
-Forces a push of one repo to one slave.
-
-Usage 1 is directly on the master server.  Nothing is checked; if the slave
-accepts it, the push happens, even if the slave is not in any slaves
-option.  This is how you do delayed or lagged pushes to servers that do not
-need real-time updates or have bandwidth/connectivity issues.
-
-Usage 2 can be initiated by *any* user who has *any* gitolite access to the
-master server, but it checks that the slave is in one of the slaves options
-before doing the push.
-
-MIRROR STATUS: To find the status of the last mirror push to any slave, run
-the same command except with 'status' instead of 'push'.  With usage 1, you
-can use the special name "all" to get the status of all slaves for the given
-repo.  (Admins wishing to find the status of all slaves for ALL repos will
-have to script it using the output of "gitolite list-phy-repos".)
-
-SERVER LIST: 'gitolite mirror list master <reponame>' and 'gitolite mirror
-list slaves <reponame>' will show you the name of the master server, and list
-the slave servers, for the repo.  They only work on the server command line
-(any server), but not remotely (from a normal user).
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-
-_die "HOSTNAME not set" if not $rc{HOSTNAME};
-
-my ( $cmd, $host, $repo ) = @ARGV;
-$repo =~ s/\.git$//;
-usage() if not $repo;
-
-if ( $cmd eq 'push' ) {
-    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
-    # will die if host not in slaves for repo
-
-    trace( 1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started" );
-    _chdir( $rc{GL_REPO_BASE} );
-    _chdir("$repo.git");
-
-    if ( -f "gl-creator" ) {
-        # try to propagate the wild repo, including creator name and gl-perms
-        my $creator = `cat gl-creator`; chomp($creator);
-        trace( 1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null` );
-    }
-
-    my $errors = 0;
-    my $glss = '';
-    for (`git push --mirror $host:$repo 2>&1`) {
-        $errors = 1 if $?;
-        print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
-        $glss .= $_;
-        chomp;
-        if (/FATAL/) {
-            $errors = 1;
-            gl_log( 'mirror', $_ );
-        } else {
-            trace( 1, "mirror: $_" );
-        }
-    }
-    # save the mirror push status for this slave if the word 'fatal' is found,
-    # else remove the status file.  We don't store "success" output messages;
-    # you can always get those from the log files if you really need them.
-    if ( $glss =~ /fatal/i ) {
-        my $glss_prefix = Gitolite::Common::gen_ts() . "\t$ENV{GL_TID}\t";
-        $glss =~ s/^/$glss_prefix/gm;
-        _print("gl-slave-$host.status", $glss);
-    } else {
-        unlink "gl-slave-$host.status";
-    }
-
-    exit $errors;
-} elsif ($cmd eq 'status') {
-    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
-    # will die if host not in slaves for repo
-
-    _chdir( $rc{GL_REPO_BASE} );
-    _chdir("$repo.git");
-
-    $host = '*' if $host eq 'all';
-    map { print_status($_) } sort glob("gl-slave-$host.status");
-} else {
-    # strictly speaking, we could allow some of the possible commands remotely
-    # also, at least for admins.  However, these commands are mainly intended
-    # for server-side scripting so I don't care.
-    usage() if $ENV{GL_USER};
-
-    server_side_commands(@ARGV);
-}
-
-# ----------------------------------------------------------------------
-
-sub valid_slave {
-    my ( $host, $repo ) = @_;
-    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
-
-    my %list = repo_slaves($repo);
-    _die "'$host' not a valid slave for '$repo'" unless $list{$host};
-}
-
-sub repo_slaves {
-    my $repo = shift;
-
-    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
-    my %list = map { $_ => 1 } map { split } values %$ref;
-
-    return %list;
-}
-
-sub repo_master {
-    my $repo = shift;
-
-    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.master\$" );
-    my @list = map { split } values %$ref;
-    _die "'$repo' seems to have more than one master" if @list > 1;
-
-    return $list[0] || '';
-}
-
-sub print_status {
-    my $file = shift;
-    return unless -f $file;
-    my $slave = $1 if $file =~ /^gl-slave-(.+)\.status$/;
-    print "----------\n";
-    print "WARNING: previous mirror push to host '$slave' failed, status is:\n";
-    print slurp($file);
-    print "----------\n";
-}
-
-# ----------------------------------------------------------------------
-# server side commands.  Very little error checking.
-#   gitolite mirror list master <repo>
-#   gitolite mirror list slaves <repo>
-
-sub server_side_commands {
-    if ( $cmd eq 'list' ) {
-        if ( $host eq 'master' ) {
-            say repo_master($repo);
-        } elsif ( $host eq 'slaves' ) {
-            my %list = repo_slaves($repo);
-            say join( " ", sort keys %list );
-        } else {
-            _die "gitolite mirror list master|slaves <reponame>";
-        }
-    } else {
-        _die "invalid command";
-    }
-}
diff --git a/docker/gitolite/src/commands/motd b/docker/gitolite/src/commands/motd
deleted file mode 100755
index b56e99e..0000000
--- a/docker/gitolite/src/commands/motd
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-=for usage
-Usage:    ssh git at host motd <repo> rm
-          cat <filename> | ssh git at host motd <repo> set
-
-Remove or set the motd file for repo or the whole system.
-
-For a repo: you need to have write access to the repo and the
-'writer-is-owner' option must be set for the repo, or it must be a
-user-created ('wild') repo and you must be the owner.
-
-For the whole system: you need to be an admin (have write access to the
-gitolite-admin repo).  Use @all in place of the repo name.
-
-PLEASE NOTE that if you're using http mode, the motd will only appear for
-gitolite commands, not for normal git operations.  This in turn means that
-only the system wide motd can be seen; repo level motd's never show up.
-=cut
-
-usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
-
-my $repo = shift;
-my $op = shift || '';
-usage() if $op ne 'rm' and $op ne 'set';
-my $file = "gl-motd";
-
-#<<<
-_die "you are not authorized" unless
-    ( $repo eq '@all' and is_admin() )      or
-    ( $repo ne '@all' and owns($repo) )     or
-    ( $repo ne '@all' and can_write($repo)  and option( $repo, 'writer-is-owner' ) );
-#>>>
-
-my @out =
-  $repo eq '@all'
-  ? ( dir => $rc{GL_ADMIN_BASE} )
-  : ( repo => $repo );
-
-if ( $op eq 'rm' ) {
-    $repo eq '@all'
-      ? unlink "$rc{GL_ADMIN_BASE}/$file"
-      : unlink "$rc{GL_REPO_BASE}/$repo.git/$file";
-} elsif ( $op eq 'set' ) {
-    textfile( file => $file, @out, prompt => '' );
-} else {
-    print textfile( file => $file, @out, );
-}
diff --git a/docker/gitolite/src/commands/option b/docker/gitolite/src/commands/option
deleted file mode 100644
index de49aab..0000000
--- a/docker/gitolite/src/commands/option
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/usr/bin/perl
-
-# ----------------------------------------------------------------------
-# gitolite command to allow repo "owners" to set "options" on repos
-
-# This command can be run by a user to set "options" for any repo that she
-# owns.
-#
-# However, gitolite does *not* have the concept of an incremental "compile",
-# and options are only designed to be specified in the gitolite.conf file
-# (which a user should not be able to even see!).  Therefore, we allow one
-# specific file (conf/options.conf) to be manipulated by a remote user in a
-# *controlled* fashion, and this file is "include"d in the main gitolite.conf
-# file.
-
-# WARNINGS:
-#   1.  Runs "gitolite compile" at the end.  On really huge systems (where the
-#       sum total of the conf files is in the order of tens of thousands of
-#       lines) this may take a second or two :)
-#   2.  Since "options.conf" is not part of the admin repo, you may need to
-#       back it up separately, just like you currently back up gl-creator and
-#       gl-perms files from individual repos.
-#   3.  "options.conf" is formatted very strictly because it's not meant to be
-#       human edited.  If you edit it directly on the server, be careful.
-
-# Relevant gitolite doc links:
-#   "wild" repos and "owners"
-#       http://gitolite.com/gitolite/wild.html
-#       http://gitolite.com/gitolite/wild.html#specifying-owners
-#       http://gitolite.com/gitolite/wild.html#appendix-1-owner-and-creator
-#   gitolite "options"
-#       http://gitolite.com/gitolite/options.html
-#   the "include" statement
-#       http://gitolite.com/gitolite/conf.html#include
-
-# setup:
-#   1.  Enable the command by adding it to the ENABLE list in the rc file.
-#
-#   2.  Make sure your gitolite.conf has this line at the end:
-#
-#           include "options.conf"
-#
-#       then add/commit/push.
-#
-#       Do NOT add a file called "options.conf" to your gitolite-admin repo!
-#       This means every time you compile (push the admin repo) you will get a
-#       warning about the missing file.
-#
-#       You can either "touch ~/.gitolite/conf/options.conf" on the server, or
-#       take *any* wild repo and add *any* option to create it.
-#
-#   3.  Specify options allowed to be changed by the user.  For example:
-#
-#           repo foo/..*
-#               C   =   blah blah
-#               ...other rules...
-#               option user-options = hook\..* foo bar[0-9].*
-#
-#       Users can then set any of these options, but no others.
-
-# ----------------------------------------------------------------------
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-use Gitolite::Common;
-
-# ----------------------------------------------------------------------
-# usage and arg checks
-
-=for usage
-Usage:    ssh git at host option <repo> add <key> <val>
-          ssh git at host option <repo> del <key>
-          ssh git at host option <repo> list
-
-Add, delete, or list options for wild repos.  Keys must match one of the
-allowed patterns; your system administrator will tell you what they are.
-
-Doesn't check things like adding a key that already exists (simply overwrites
-without warning), deleting a key that doesn't, etc.
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-
-my $OPTIONS = "$ENV{HOME}/.gitolite/conf/options.conf";
-
-my $repo = shift;
-die "sorry, you are not authorised\n" unless owns($repo);
-
-my $op = shift; usage() unless $op =~ /^(add|del|list)$/;
-my $key = shift; usage() if not $key and $op ne 'list';
-my $val = shift; usage() if not $val and $op eq 'add';
-
-_print( $OPTIONS, "" ) unless -f $OPTIONS;    # avoid error on first run
-my $options = slurp($OPTIONS);
-
-# ----------------------------------------------------------------------
-# get 'list' out of the way first
-if ( $op eq 'list' ) {
-    print "$1\t$2\n" while $options =~ /^repo $repo\n    option (\S+) = (.*)/mg;
-    exit 0;
-}
-
-# ----------------------------------------------------------------------
-# that leaves 'add' or 'del'
-
-# NOTE: sanity check on characters in key and val not needed;
-# REMOTE_COMMAND_PATT is more restrictive than UNSAFE_PATT anyway!
-
-# check if the key is allowed
-my $user_options = option( $repo, 'user-options' );
-# this is a space separated list of allowed option keys
-my @validkeys = split( ' ', ( $user_options || '' ) );
-my @matched = grep { $key =~ /^$_$/i } @validkeys;
-_die "option '$key' not allowed\n" if ( @matched < 1 );
-
-# delete anyway
-$options =~ s/^repo $repo\n    option $key = .*\n//m;
-# then re-add if needed
-$options .= "repo $repo\n    option $key = $val\n" if $op eq 'add';
-
-# ----------------------------------------------------------------------
-# save and compile
-_print( $OPTIONS, $options );
-system("gitolite compile");
diff --git a/docker/gitolite/src/commands/owns b/docker/gitolite/src/commands/owns
deleted file mode 100755
index d1d8757..0000000
--- a/docker/gitolite/src/commands/owns
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-=for usage
-Usage:  gitolite owns <reponame>
-
-Checks if $GL_USER is an owner of the repo and returns an exit code (shell
-truth, 0 for success), which makes it possible to do this in shell:
-
-    if gitolite owns someRepo
-    then
-        ...
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-my $repo = shift;
-
-exit not owns($repo);
diff --git a/docker/gitolite/src/commands/perms b/docker/gitolite/src/commands/perms
deleted file mode 100755
index 30984bf..0000000
--- a/docker/gitolite/src/commands/perms
+++ /dev/null
@@ -1,190 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Easy;
-
-=for usage
-perms -- list or set permissions for user-created ("wild") repo.
-
-Usage summary:
-    ssh git at host perms <repo> -l
-        # list current permissions on repo
-    ssh git at host perms <repo> -lr
-        # list available roles and their access rights
-
-    ssh git at host perms <repo> + <rolename> <username>
-        # change permissions: add a user to a role
-    ssh git at host perms <repo> - <rolename> <username>
-        # change permissions: remove a user from a role
-
-Examples:
-    ssh git at host perms my/repo + READERS alice
-    ssh git at host perms my/repo + WRITERS bob
-
-----
-There is also a batch mode useful for scripting and bulk loading.  Do not
-combine this with the +/- mode above.  This mode also accepts an optional "-c"
-flag to create the repo if it does not already exist (assuming $GL_USER has
-permissions to create it).
-
-Examples:
-    cat copy-of-backed-up-gl-perms | ssh git at host perms <repo>
-    cat copy-of-backed-up-gl-perms | ssh git at host perms -c <repo>
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-
-$ENV{GL_USER} or _die "GL_USER not set";
-
-my $generic_error = "repo does not exist, or you are not authorised";
-
-if ( @ARGV >= 2 and $ARGV[1] eq '-l' ) {
-    getperms($ARGV[0]);    # doesn't return
-}
-
-# auto-create the repo if -c passed and repo doesn't exist
-if ( $ARGV[0] eq '-c' ) {
-    shift;
-    my $repo = $ARGV[0] or usage();
-    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
-
-    if ( not -d "$rc{GL_REPO_BASE}/$repo.git" ) {
-        my $ret = Gitolite::Conf::Load::access( $repo, $ENV{GL_USER}, '^C', 'any' );
-        _die $generic_error if $ret =~ /DENIED/;
-
-        require Gitolite::Conf::Store;
-        Gitolite::Conf::Store->import;
-        new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
-        gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
-    }
-}
-
-my $repo = shift;
-
-if ( @ARGV and $ARGV[0] eq '-lr' ) {
-    list_roles();
-    exit 0;
-} else {
-    setperms(@ARGV);
-}
-
-# cache control
-if ($rc{CACHE}) {
-    require Gitolite::Cache;
-    Gitolite::Cache::cache_control('flush', $repo);
-}
-
-_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
-
-# ----------------------------------------------------------------------
-
-sub getperms {
-    my $repo = shift;
-    _die $generic_error if not owns($repo);
-    my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
-
-    print slurp($pf) if -f $pf;
-
-    exit 0;
-}
-
-sub setperms {
-    _die $generic_error if not owns($repo);
-    my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
-
-    if ( not @_ ) {
-        # legacy mode; pipe data in
-        print STDERR "'batch' mode started, waiting for input (run with '-h' for details).\n";
-        print STDERR "Please enter 'cancel' to abort if you did not intend to do this.\n";
-        @ARGV = ();
-        my @a;
-        while (<>) {
-            _die "CANCELLED" if /^\s*cancel\s*$/i;
-            invalid_role($1) if /(\S+)/ and not $rc{ROLES}{$1};
-            push @a, $_;
-        }
-
-        _print( $pf, @a );
-        return;
-    }
-
-    _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if @_ != 3;
-    my ( $op, $role, $user ) = @_;
-    _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
-    _die "Invalid user '$user'"                                        if not $user =~ $USERNAME_PATT;
-
-    my $text = '';
-    my @text = slurp($pf) if -f $pf;
-
-    my $present = grep { $_ eq "$role $user\n" } @text;
-
-    if ( $op eq '-' ) {
-        if ( not $present ) {
-            _warn "'$role $user' was not present in file";
-        } else {
-            @text = grep { $_ ne "$role $user\n" } @text;
-            _print( $pf, @text );
-        }
-    } else {
-        invalid_role($role) unless grep { $_->[3] eq $role } load_roles();
-        if ($present) {
-            _warn "'$role $user' already present in file";
-        } else {
-            push @text, "$role $user\n";
-            @text = sort @text;
-            _print( $pf, @text );
-        }
-    }
-}
-
-my @rules;
-
-sub load_roles {
-    return @rules if @rules;
-
-    require Gitolite::Conf::Load;
-    Gitolite::Conf::Load::load($repo);
-
-    my %repos = %Gitolite::Conf::Load::repos;
-    my @repo_memberships = Gitolite::Conf::Load::memberships('repo', $repo);
-
-    for my $rp (@repo_memberships) {
-        my $hr = $repos{$rp};
-        for my $r ( keys %$hr ) {
-            next unless $r =~ s/^@//;
-            next unless $rc{ROLES}{$r};
-            map { $_->[3] = $r } @{ $hr->{"\@$r"} };
-            push @rules, @{ $hr->{"\@$r"} };
-        }
-    }
-    return @rules;
-}
-
-sub invalid_role {
-    my $role = shift;
-
-    print STDERR "Invalid role '$role'; valid roles for this repo:\n";
-    open(STDOUT, '>&', \*STDERR);   # make list_roles print to STDERR
-    list_roles();
-    exit 1;
-}
-
-sub list_roles {
-
-    my @rules = sort { $a->[0] <=> $b->[0] } load_roles();
-
-    for (@rules) {
-        $_->[2] =~ s(^refs/heads/)();
-        $_->[2] = '--any--' if $_->[2] eq 'refs/.*';
-    }
-
-    my $max = 0;
-    map { $max = $_ if $_ > $max } map { length($_->[2]) } @rules;
-    printf("\t%s\t%*s\t \t%s\n", "perm",  -$max, "ref",   "role");
-    printf("\t%s\t%*s\t \t%s\n", "----",  -$max, "---",   "----");
-    printf("\t%s\t%*s\t=\t%s\n", $_->[1], -$max, $_->[2], $_->[3]) for @rules;
-}
diff --git a/docker/gitolite/src/commands/print-default-rc b/docker/gitolite/src/commands/print-default-rc
deleted file mode 100755
index 79b88c1..0000000
--- a/docker/gitolite/src/commands/print-default-rc
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-
-print glrc('default-text');
diff --git a/docker/gitolite/src/commands/push b/docker/gitolite/src/commands/push
deleted file mode 100755
index f97f730..0000000
--- a/docker/gitolite/src/commands/push
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-export GL_BYPASS_ACCESS_CHECKS=1
-
-git push "$@"
diff --git a/docker/gitolite/src/commands/readme b/docker/gitolite/src/commands/readme
deleted file mode 100755
index cd9632f..0000000
--- a/docker/gitolite/src/commands/readme
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-# README.html files work similar to "description" files. For further
-# information see
-#   https://www.kernel.org/pub/software/scm/git/docs/gitweb.html
-# under "Per-repository gitweb configuration".
-
-=for usage
-Usage:    ssh git at host readme <repo>
-          ssh git at host readme <repo> rm
-          cat <filename> | ssh git at host readme <repo> set
-
-Show, remove or set the README.html file for repo.
-
-You need to have write access to the repo and the 'writer-is-owner' option
-must be set for the repo, or it must be a user-created ('wild') repo and you
-must be the owner.
-=cut
-
-usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
-
-my $repo = shift;
-my $op = shift || '';
-usage() if $op and $op ne 'rm' and $op ne 'set';
-my $file = 'README.html';
-
-#<<<
-_die "you are not authorized" unless
-    ( not $op and can_read($repo) )   or
-    (     $op and owns($repo) )       or
-    (     $op and can_write($repo)    and option( $repo, 'writer-is-owner' ) );
-#>>>
-
-if ( $op eq 'rm' ) {
-    unlink "$rc{GL_REPO_BASE}/$repo.git/$file";
-} elsif ( $op eq 'set' ) {
-    textfile( file => $file, repo => $repo, prompt => '' );
-} else {
-    print textfile( file => $file, repo => $repo );
-}
-
-__END__
-
-The WRITER_CAN_UPDATE_README option is gone now; it applies to all the repos
-in the system.  Much better to add 'option writer-is-owner = 1' to repos or
-repo groups that you want this to apply to.
-
-This option is meant to cover desc, readme, and any other repo-specific text
-file, so it's also a blunt instrument, though in a different dimension :-)
diff --git a/docker/gitolite/src/commands/rsync b/docker/gitolite/src/commands/rsync
deleted file mode 100755
index 1109ac4..0000000
--- a/docker/gitolite/src/commands/rsync
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-=for admins
-
-BUNDLE SUPPORT
-
-    (1) For each repo in gitolite.conf for which you want bundle support (or
-        '@all', if you wish), add the following line:
-
-            option bundle       =   1
-
-        Or you can say:
-
-            option bundle.ttl   =   <number>
-
-        A bundle file that is more than <number> seconds old (default value
-        86400, i.e., 1 day) is recreated on the next bundle request.  Increase
-        this if your repo is not terribly active.
-
-        Note: a bundle file is also deleted and recreated if it contains a ref
-        that was then either deleted or rewound in the repo.  This is checked
-        on every invocation.
-
-    (2) Add 'rsync' to the ENABLE list in the rc file
-
-
-GENERIC RSYNC SUPPORT
-
-    TBD
-
-=cut
-
-=for usage
-rsync helper for gitolite
-
-BUNDLE SUPPORT
-
-    Admins: see src/commands/rsync for setup instructions
-
-    Users:
-        rsync -P git at host:repo.bundle .
-            # downloads a file called "<basename of repo>.bundle"; repeat as
-            # needed till the whole thing is downloaded
-        git clone repo.bundle repo
-        cd repo
-        git remote set-url origin git at host:repo
-        git fetch origin    # and maybe git pull, etc. to freshen the clone
-
-GENERIC RSYNC SUPPORT
-
-    TBD
-
-=cut
-
-usage() if not @ARGV or $ARGV[0] eq '-h';
-
-# rsync driver program.  Several things can be done later, but for now it
-# drives just the 'bundle' transfer.
-
-if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^rsync --server --sender (-[-\w=.]+ )+\. (\S+)\.bundle$/ ) {
-
-    my $repo = $2;
-    $repo =~ s/\.git$//;
-
-    # all errors have the same message to avoid leaking info
-    can_read($repo) or _die "you are not authorised";
-    my %config = config( $repo, "gitolite-options.bundle" ) or _die "you are not authorised";
-
-    my $ttl = $config{'gitolite-options.bundle.ttl'} || 86400;    # in seconds (default 1 day)
-
-    my $bundle = bundle_create( $repo, $ttl );
-
-    $ENV{SSH_ORIGINAL_COMMAND} =~ s( \S+\.bundle)( $bundle);
-    trace( 1, "rsync bundle", $ENV{SSH_ORIGINAL_COMMAND} );
-    Gitolite::Common::_system( split ' ', $ENV{SSH_ORIGINAL_COMMAND} );
-    exit 0;
-}
-
-_warn "invalid rsync command '$ENV{SSH_ORIGINAL_COMMAND}'";
-usage();
-
-# ----------------------------------------------------------------------
-# helpers
-# ----------------------------------------------------------------------
-
-sub bundle_create {
-    my ( $repo, $ttl ) = @_;
-    my $bundle = "$repo.bundle";
-    $bundle =~ s(.*/)();
-    my $recreate = 0;
-
-    my ( %b, %r );
-    if ( -f $bundle ) {
-        %b = map { chomp; reverse split; } `git ls-remote --heads --tags $bundle`;
-        %r = map { chomp; reverse split; } `git ls-remote --heads --tags .`;
-
-        for my $ref ( sort keys %b ) {
-
-            my $mtime = ( stat $bundle )[9];
-            if ( time() - $mtime > $ttl ) {
-                trace( 1, "bundle too old" );
-                $recreate++;
-                last;
-            }
-
-            if ( not $r{$ref} ) {
-                trace( 1, "ref '$ref' deleted in repo" );
-                $recreate++;
-                last;
-            }
-
-            if ( $r{$ref} eq $b{$ref} ) {
-                # same on both sides; ignore
-                delete $r{$ref};
-                delete $b{$ref};
-                next;
-            }
-
-            `git rev-list --count --left-right $b{$ref}...$r{$ref}` =~ /^(\d+)\s+(\d+)$/ or _die "git too old";
-            if ($1) {
-                trace( 1, "ref '$ref' rewound in repo" );
-                $recreate++;
-                last;
-            }
-
-        }
-
-    } else {
-        trace( 1, "no bundle found" );
-        $recreate++;
-    }
-
-    return $bundle if not $recreate;
-
-    trace( 1, "creating bundle for '$repo'" );
-    -f $bundle and ( unlink $bundle or die "a horrible death" );
-    system("git bundle create $bundle --branches --tags >&2");
-
-    return $bundle;
-}
-
-sub trace {
-    Gitolite::Common::trace(@_);
-}
diff --git a/docker/gitolite/src/commands/sshkeys-lint b/docker/gitolite/src/commands/sshkeys-lint
deleted file mode 100755
index c7f0c81..0000000
--- a/docker/gitolite/src/commands/sshkeys-lint
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# complete rewrite of the sshkeys-lint program.  Usage has changed, see
-# usage() function or run without arguments.
-
-use Getopt::Long;
-my $admin = 0;
-my $quiet = 0;
-my $help  = 0;
-GetOptions( 'admin|a=s' => \$admin, 'quiet|q' => \$quiet, 'help|h' => \$help );
-
-use Data::Dumper;
-$Data::Dumper::Deepcopy = 1;
-$|++;
-
-my $in_gl_section = 0;
-my $warnings      = 0;
-my $KEYTYPE_REGEX = qr/\b(?:ssh-(?:rsa|dss|ed25519)|ecdsa-sha2-nistp(?:256|384|521))\b/;
-
-sub dbg {
-    use Data::Dumper;
-    for my $i (@_) {
-        print STDERR "DBG: " . Dumper($i);
-    }
-}
-
-sub msg {
-    my $warning = shift;
-    return if $quiet and not $warning;
-    $warnings++ if $warning;
-    print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_;
-}
-
-usage() if $help;
-
-our @pubkeyfiles = @ARGV; @ARGV = ();
-my $kd = "$ENV{HOME}/.gitolite/keydir";
-if ( not @pubkeyfiles ) {
-    chomp( @pubkeyfiles = `find $kd -type f -name "*.pub" | sort` );
-}
-
-if ( -t STDIN ) {
-    @ARGV = ("$ENV{HOME}/.ssh/authorized_keys");
-}
-
-# ------------------------------------------------------------------------
-
-my @authkeys;
-my %seen_fprints;
-my %pkf_by_fp;
-msg 0, "==== checking authkeys file:\n";
-fill_authkeys();    # uses up STDIN
-
-if ($admin) {
-    my $fp = fprint("$admin.pub");
-    my $fpu = ( $fp && $seen_fprints{$fp}{user} || 'no access' );
-    # dbg("fpu = $fpu, admin=$admin");
-    #<<<
-    die "\t\t*** FATAL ***\n" .
-        "$admin.pub maps to $fpu, not $admin.\n" .
-        "You will not be able to access gitolite with this key.\n" .
-        "Look for the 'ssh troubleshooting' link in http://gitolite.com/gitolite/ssh.html.\n"
-    if $fpu ne "user $admin";
-    #>>>
-}
-
-msg 0, "==== checking pubkeys:\n" if @pubkeyfiles;
-for my $pkf (@pubkeyfiles) {
-    # get the short name for the pubkey file
-    ( my $pkfsn = $pkf ) =~ s(^$kd/)();
-
-    my $fp = fprint($pkf);
-    next unless $fp;
-    msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
-    $pkf_by_fp{$fp} ||= $pkfsn;
-    my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
-    msg 0, "$pkfsn maps to $fpu\n";
-}
-
-if ($warnings) {
-    print "\n$warnings warnings found\n";
-}
-
-exit $warnings;
-
-# ------------------------------------------------------------------------
-sub fill_authkeys {
-    while (<>) {
-        my $seq = $.;
-        next if ak_comment($_);    # also sets/clears $in_gl_section global
-        my $fp   = fprint($_);
-        my $user = user($_);
-
-        check( $seq, $fp, $user );
-
-        $authkeys[$seq]{fprint}  = $fp;
-        $authkeys[$seq]{ustatus} = $user;
-    }
-}
-
-sub check {
-    my ( $seq, $fp, $user ) = @_;
-
-    msg 1, "line $seq, $user key found *outside* gitolite section!\n"
-      if $user =~ /^user / and not $in_gl_section;
-
-    msg 1, "line $seq, $user key found *inside* gitolite section!\n"
-      if $user !~ /^user / and $in_gl_section;
-
-    if ( $seen_fprints{$fp} ) {
-        #<<<
-        msg 1, "authkeys line $seq ($user) will be ignored by sshd; " .
-              "same key found on line " .
-              $seen_fprints{$fp}{seq} . " (" .
-              $seen_fprints{$fp}{user} . ")\n";
-        return;
-        #>>>
-    }
-
-    $seen_fprints{$fp}{seq}  = $seq;
-    $seen_fprints{$fp}{user} = $user;
-}
-
-sub user {
-    my $user = '';
-    $user ||= "user $1"         if /^command=.*gitolite-shell (.*?)"/;
-    $user ||= "unknown command" if /^command/;
-    $user ||= "shell access"    if /$KEYTYPE_REGEX/;
-
-    return $user;
-}
-
-sub ak_comment {
-    local $_ = shift;
-    $in_gl_section = 1 if /^# gitolite start/;
-    $in_gl_section = 0 if /^# gitolite end/;
-    die "gitosis?  what's that?\n" if /^#.*gitosis/;
-    return /^\s*(#|$)/;
-}
-
-sub fprint {
-    local $_ = shift;
-    my ( $fh, $tempfn, $in );
-    if ( /$KEYTYPE_REGEX/ ) {
-        # an actual key was passed.  Since ssh-keygen requires an actual file,
-        # make a temp file to take the data and pass on to ssh-keygen
-        s/^.* ($KEYTYPE_REGEX)/$1/;
-        use File::Temp qw(tempfile);
-        ( $fh, $tempfn ) = tempfile();
-        $in = $tempfn;
-        print $fh $_;
-        close $fh;
-    } else {
-        # a filename was passed
-        $in = $_;
-    }
-    # dbg("in = $in");
-    -f $in or die "file not found: $in\n";
-    open( $fh, "ssh-keygen -l -f $in |" ) or die "could not fork: $!\n";
-    my $fp = <$fh>;
-    # dbg("fp = $fp");
-    close $fh;
-    unlink $tempfn if $tempfn;
-    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-Za-z0-9+/]+));
-
-    return $1;
-}
-
-# ------------------------------------------------------------------------
-sub usage {
-    print <<EOF;
-
-Usage:  gitolite sshkeys-lint [-q] [optional list of pubkey filenames]
-        (optionally, STDIN can be a pipe or redirected from a file; see below)
-
-Look for potential problems in ssh keys.
-
-sshkeys-lint expects:
-  - the contents of an authorized_keys file via STDIN, otherwise it uses
-    \$HOME/.ssh/authorized_keys
-  - one or more pubkey filenames as arguments, otherwise it uses all the keys
-    found (recursively) in \$HOME/.gitolite/keydir
-
-The '-q' option will print only warnings instead of all mappings.
-
-Note that this runs ssh-keygen -l for each line in the authkeys file and each
-pubkey in the argument list, so be wary of running it on something huge.  This
-is meant for troubleshooting.
-
-EOF
-    exit 1;
-}
diff --git a/docker/gitolite/src/commands/sskm b/docker/gitolite/src/commands/sskm
deleted file mode 100755
index fd60233..0000000
--- a/docker/gitolite/src/commands/sskm
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-=for usage
-Usage for this command is not that simple.  Please read the full documentation
-in doc/sskm.mkd or online at http://gitolite.com/gitolite/sskm.html.
-=cut
-
-usage() if @ARGV and $ARGV[0] eq '-h';
-
-my $rb = $rc{GL_REPO_BASE};
-my $ab = $rc{GL_ADMIN_BASE};
-# get to the keydir
-_chdir("$ab/keydir");
-
-# save arguments for later
-my $operation = shift || 'list';
-my $keyid     = shift || '';
-# keyid must fit a very specific pattern
-$keyid and $keyid !~ /^@[-0-9a-z_]+$/i and die "invalid keyid $keyid\n";
-
-# get the actual userid and keytype
-my $gl_user = $ENV{GL_USER};
-my $keytype = '';
-$keytype = $1 if $gl_user =~ s/^zzz-marked-for-(...)-//;
-print STDERR "hello $gl_user, you are currently using "
-  . (
-    $keytype
-    ? "a key in the 'marked for $keytype' state\n"
-    : "a normal (\"active\") key\n"
-  );
-
-# ----
-# first collect the keys
-
-my ( @pubkeys, @marked_for_add, @marked_for_del );
-# get the list of pubkey files for this user, including pubkeys marked for
-# add/delete
-
-for my $pubkey (`find . -type f -name "*.pub" | sort`) {
-    chomp($pubkey);
-    $pubkey =~ s(^./)();    # artifact of the find command
-
-    my $user = $pubkey;
-    $user =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
-    $user =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
-
-    next unless $user eq $gl_user or $user =~ /^zzz-marked-for-...-$gl_user/;
-
-    if ( $user =~ m(^zzz-marked-for-add-) ) {
-        push @marked_for_add, $pubkey;
-    } elsif ( $user =~ m(^zzz-marked-for-del-) ) {
-        push @marked_for_del, $pubkey;
-    } else {
-        push @pubkeys, $pubkey;
-    }
-}
-
-# ----
-# list mode; just do it and exit
-sub print_keylist {
-    my ( $message, @list ) = @_;
-    return unless @list;
-    print "== $message ==\n";
-    my $count = 1;
-    for (@list) {
-        my $fp = fingerprint($_);
-        s/zzz-marked(\/|-for-...-)//g;
-        print $count++ . ": $fp : $_\n";
-    }
-}
-if ( $operation eq 'list' ) {
-    print "you have the following keys:\n";
-    print_keylist( "active keys",                          @pubkeys );
-    print_keylist( "keys marked for addition/replacement", @marked_for_add );
-    print_keylist( "keys marked for deletion",             @marked_for_del );
-    print "\n\n";
-    exit;
-}
-
-# ----
-# please see docs for details on how a user interacts with this
-
-if ( $keytype eq '' ) {
-    # user logging in with a normal key
-    die "valid operations: add, del, undo-add, confirm-del\n" unless $operation =~ /^(add|del|confirm-del|undo-add)$/;
-    if ( $operation eq 'add' ) {
-        print STDERR "please supply the new key on STDIN.  (I recommend you
-        don't try to do this interactively, but use a pipe)\n";
-        kf_add( $gl_user, $keyid, safe_stdin() );
-    } elsif ( $operation eq 'del' ) {
-        kf_del( $gl_user, $keyid );
-    } elsif ( $operation eq 'confirm-del' ) {
-        die "you dont have any keys marked for deletion\n" unless @marked_for_del;
-        kf_confirm_del( $gl_user, $keyid );
-    } elsif ( $operation eq 'undo-add' ) {
-        die "you dont have any keys marked for addition\n" unless @marked_for_add;
-        kf_undo_add( $gl_user, $keyid );
-    }
-} elsif ( $keytype eq 'del' ) {
-    # user is using a key that was marked for deletion.  The only possible use
-    # for this is that she changed her mind for some reason (maybe she marked
-    # the wrong key for deletion) or is not able to get her client-side sshd
-    # to stop using this key
-    die "valid operations: undo-del\n" unless $operation eq 'undo-del';
-
-    # reinstate the key
-    kf_undo_del( $gl_user, $keyid );
-} elsif ( $keytype eq 'add' ) {
-    die "valid operations: confirm-add\n" unless $operation eq 'confirm-add';
-    # user is trying to validate a key that has been previously marked for
-    # addition.  This isn't interactive, but it *could* be... if someone asked
-    kf_confirm_add( $gl_user, $keyid );
-}
-
-exit;
-
-# ----
-
-# make a temp clone and switch to it
-our $TEMPDIR;
-BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; }
-END { `/bin/rm -rf $TEMPDIR`; }
-
-sub cd_temp_clone {
-    chomp($TEMPDIR);
-    hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR" );
-    chdir($TEMPDIR);
-    my $hostname = `hostname`; chomp($hostname);
-    hushed_git( "config", "--get", "user.email" ) and hushed_git( "config", "user.email", $ENV{USER} . "@" . $hostname );
-    hushed_git( "config", "--get", "user.name" )  and hushed_git( "config", "user.name",  "$ENV{USER} on $hostname" );
-}
-
-sub fingerprint {
-    my $fp = `ssh-keygen -l -f $_[0]`;
-    die "does not seem to be a valid pubkey\n" unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+ )/i;
-    return $1;
-}
-
-sub safe_stdin {
-    # read one line from STDIN
-    my $data;
-    my $ret = read STDIN, $data, 4096;
-    # current pubkeys are approx 400 bytes so we go a little overboard
-    die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n" unless $ret;
-    die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
-    return $data;
-}
-
-sub hushed_git {
-    local (*STDOUT) = \*STDOUT;
-    local (*STDERR) = \*STDERR;
-    open( STDOUT, ">", "/dev/null" );
-    open( STDERR, ">", "/dev/null" );
-    system( "git", @_ );
-}
-
-sub highlander {
-    # there can be only one
-    my ( $keyid, $die_if_empty, @a ) = @_;
-    # too many?
-    if ( @a > 1 ) {
-        print STDERR "
-more than one key satisfies this condition, and I can't deal with that!
-The keys are:
-
-";
-        print STDERR "\t" . join( "\n\t", @a ), "\n\n";
-        exit 1;
-    }
-    # too few?
-    die "no keys with " . ( $keyid || "empty" ) . " keyid found\n" if $die_if_empty and not @a;
-
-    return @a;
-}
-
-sub kf_add {
-    my ( $gl_user, $keyid, $keymaterial ) = @_;
-
-    # add a new "marked for addition" key for $gl_user.
-    cd_temp_clone();
-    chdir("keydir");
-
-    mkdir("zzz-marked");
-    _print( "zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub", $keymaterial );
-    hushed_git( "add", "." ) and die "git add failed\n";
-    my $fp = fingerprint("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub");
-    hushed_git( "commit", "-m", "sskm: add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-sub kf_confirm_add {
-    my ( $gl_user, $keyid ) = @_;
-    # find entries in both @pubkeys and @marked_for_add whose basename matches $gl_user$keyid
-    my @pk  = highlander( $keyid, 0, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
-    my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
-
-    cd_temp_clone();
-    chdir("keydir");
-
-    my $fp = fingerprint( $mfa[0] );
-    if ( $pk[0] ) {
-        hushed_git( "mv", "-f", $mfa[0], $pk[0] );
-        hushed_git( "commit", "-m", "sskm: confirm-add (replace) $pk[0] ($fp)" ) and die "git commit failed\n";
-    } else {
-        hushed_git( "mv", "-f", $mfa[0], "$gl_user$keyid.pub" );
-        hushed_git( "commit", "-m", "sskm: confirm-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
-    }
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-sub kf_undo_add {
-    # XXX some code at start is shared with kf_confirm_add
-    my ( $gl_user, $keyid ) = @_;
-    my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
-
-    cd_temp_clone();
-    chdir("keydir");
-
-    my $fp = fingerprint( $mfa[0] );
-    hushed_git( "rm", $mfa[0] );
-    hushed_git( "commit", "-m", "sskm: undo-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-sub kf_del {
-    my ( $gl_user, $keyid ) = @_;
-
-    cd_temp_clone();
-    chdir("keydir");
-
-    mkdir("zzz-marked");
-    my @pk = highlander( $keyid, 1, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
-
-    my $fp = fingerprint( $pk[0] );
-    hushed_git( "mv", $pk[0], "zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub" ) and die "git mv failed\n";
-    hushed_git( "commit", "-m", "sskm: del $pk[0] ($fp)" ) and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-sub kf_confirm_del {
-    my ( $gl_user, $keyid ) = @_;
-    my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
-
-    cd_temp_clone();
-    chdir("keydir");
-
-    my $fp = fingerprint( $mfd[0] );
-    hushed_git( "rm", $mfd[0] );
-    hushed_git( "commit", "-m", "sskm: confirm-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
-
-sub kf_undo_del {
-    my ( $gl_user, $keyid ) = @_;
-
-    my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
-
-    print STDERR "
-You're undeleting a key that is currently marked for deletion.
-    Hit ENTER to undelete this key
-    Hit Ctrl-C to cancel the undelete
-Please see documentation for caveats on the undelete process as well as how to
-actually delete it.
-";
-    <>;    # yeay... always wanted to do that -- throw away user input!
-
-    cd_temp_clone();
-    chdir("keydir");
-
-    my $fp = fingerprint( $mfd[0] );
-    hushed_git( "mv", "-f", $mfd[0], "$gl_user$keyid.pub" );
-    hushed_git( "commit", "-m", "sskm: undo-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
-    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
-}
diff --git a/docker/gitolite/src/commands/sudo b/docker/gitolite/src/commands/sudo
deleted file mode 100755
index eeb0083..0000000
--- a/docker/gitolite/src/commands/sudo
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-# Usage:    ssh git at host sudo <user> <command> <arguments>
-#
-# Let super-user run commands as any other user.  "Super-user" is defined as
-# "have write access to the gitolite-admin repo".
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$2" ] && usage
-[ "$1" = "-h" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-gitolite access -q gitolite-admin $GL_USER W any || die "You are not authorised"
-
-user="$1"; shift
-cmd="$1"; shift
-
-# switch user
-GL_USER="$user"
-
-# figure out if the command is allowed from a remote user
-gitolite query-rc -q COMMANDS $cmd || die "Command '$cmd' not allowed"
-gitolite $cmd "$@"
diff --git a/docker/gitolite/src/commands/svnserve b/docker/gitolite/src/commands/svnserve
deleted file mode 100755
index 6e68acf..0000000
--- a/docker/gitolite/src/commands/svnserve
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-my $svnserve = $rc{SVNSERVE} || '';
-$svnserve ||= "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u";
-
-my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
-
-die "expecting 'svnserve -t', got '$cmd'\n" unless $cmd eq 'svnserve -t';
-
-$svnserve =~ s/%u/$ENV{GL_USER}/g;
-exec $svnserve;
-die "svnserve exec failed\n";
diff --git a/docker/gitolite/src/commands/symbolic-ref b/docker/gitolite/src/commands/symbolic-ref
deleted file mode 100755
index b65c792..0000000
--- a/docker/gitolite/src/commands/symbolic-ref
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-
-# Usage:    ssh git at host symbolic-ref <repo> <arguments to git-symbolic-ref>
-#
-# allow 'git symbolic-ref' over a gitolite connection
-
-# Security: remember all arguments to commands must match a very conservative
-# pattern.  Once that is assured, the symbolic-ref command has no security
-# related side-effects, so we don't check arguments at all.
-
-# Note: because of the restriction on allowed characters in arguments, you
-# can't supply an arbitrary string to the '-m' option.  The simplest
-# work-around is-to-just-use-join-up-words-like-this if you feel the need to
-# supply a "reason" string.  In any case this is useless by default; you'd
-# have to have core.logAllRefUpdates set for it to have any meaning.
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ "$1" = "-h" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-repo=$1; shift
-repo=${repo%.git}
-gitolite access -q "$repo" $GL_USER W any || die You are not authorised
-
-# change head
-cd $GL_REPO_BASE/$repo.git
-
-git symbolic-ref "$@"
diff --git a/docker/gitolite/src/commands/who-pushed b/docker/gitolite/src/commands/who-pushed
deleted file mode 100755
index fb37607..0000000
--- a/docker/gitolite/src/commands/who-pushed
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-usage($ARGV[1]) if $ARGV[1] and $ARGV[1] =~ /^[\w-]+$/ and $ARGV[0] eq '-h';
-
-( my $logdir = $ENV{GL_LOGFILE} ) =~ s(/[^/]+$)();
-
-# deal with migrate
-my %gl_log_lines_buffer;
-my $countr = 0;
-my $countl = 0;
-migrate(@ARGV) if $ARGV[0] eq '--migrate';   # won't return; exits right there
-
-# tip search?
-my $tip_search = 0;
-if ($ARGV[0] eq '--tip') {
-    shift;
-    $tip_search = 1;
-}
-
-# the normal who-pushed
-usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
-usage() if $ARGV[1] !~ /^[0-9a-f]+$/i;
-
-my $repo = shift;
-my $sha = shift; $sha =~ tr/A-F/a-f/;
-
-$ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" );
-
-# ----------------------------------------------------------------------
-
-my $repodir = "$ENV{GL_REPO_BASE}/$repo.git";
-chdir $repodir or die "repo '$repo' missing";
-
-my @logfiles = reverse glob("$logdir/*");
- at logfiles = ( "$repodir/gl-log" ) if -f "$repodir/gl-log";
-
-for my $logfile ( @logfiles ) {
-    @ARGV = ($logfile);
-    for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) {
-        chomp($line);
-        my @fields = split /\t/, $line;
-        my ( $ts, $pid, $who, $ref, $d_old, $new ) = @fields[ 0, 1, 4, 6, 7, 8 ];
-
-        # d_old is what you display
-        my $old = $d_old;
-        $old = ""       if $d_old eq ( "0" x 40 );
-        $old = "$old.." if $old;
-
-        if ($tip_search) {
-            print "$ts $pid $who $ref $d_old $new\n" if $new =~ /^$sha/;
-        } else {
-            system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
-        }
-    }
-}
-
-# ----------------------------------------------------------------------
-# migration
-
-sub migrate {
-    chdir $ENV{GL_REPO_BASE};
-    my @repos = `gitolite list-phy-repos`; chomp @repos;
-
-    my $count = scalar( grep { -f "$_.git/gl-log" } @repos );
-    if ( $count and ( $_[1] || '' ) ne '--force' ) {
-        say2 "$count repo(s) already have gl-log files.  To confirm overwriting, please re-run as:";
-        say2 "\tgitolite who-pushed --migrate --force";
-        say2 "see help ('-h', '-h logfiles', or '-h migrate') for details.";
-        exit 1;
-    }
-
-    foreach my $r (@repos) {
-        _print("$r.git/gl-log", '');
-    }
-
-    my %repo_exists = map { $_ => 1 } @repos;
-    @ARGV = sort ( glob("$logdir/*") );
-    while (<>) {
-        say2 "processed '$ARGV'" if eof(ARGV);
-        next unless /\tupdate\t/;
-        my @f = split /\t/;
-        my $repo = $f[3];
-        if ($repo =~ m(^/)) {
-            $repo =~ s/^$ENV{GL_REPO_BASE}\///;
-            $repo =~ s/\.git$//;
-        }
-
-        gen_gl_log($repo, $_) if $repo_exists{$repo};
-    }
-    flush_gl_log();
-
-    exit 0;
-}
-sub gen_gl_log {
-    my ($repo, $l) = @_;
-
-    $countr++ unless $gl_log_lines_buffer{$repo};    # new repo, not yet seen
-    $countl++;
-    $gl_log_lines_buffer{$repo} .= $l;
-
-    # once we have buffered log lines for about 100 repos, or about 10,000 log
-    # lines, we flush them
-    flush_gl_log() if $countr >= 100 or $countl >= 10_000;
-}
-sub flush_gl_log {
-    while (my ($r, $l) = each %gl_log_lines_buffer) {
-        open my $fh, ">>", "$r.git/gl-log" or _die "open flush_gl_log failed: $!";
-        print $fh $l;
-        close $fh;
-    }
-    %gl_log_lines_buffer = ();
-    say2 "flushed $countl lines to $countr repos...";
-    $countr = $countl = 0;
-}
-
-__END__
-
-=for usage
-uSAge:    ssh git at host who-pushed [--tip] <repo> <SHA>
-
-Determine who pushed the given commit.  The first few hex digits of the SHA
-should suffice.  If the '--tip' option is supplied, it'll only look for the
-SHA among "tip" commits (i.e., search the "new SHA"s, without running the
-expensive 'git rev-parse' for each push).
-
-Each line of the output contains the following fields: timestamp, a
-transaction ID, username, refname, and the old and new SHAs for the ref.
-
-Note on the "transaction ID" field: if looking at the log file doesn't help
-you figure out what its purpose is, please just ignore it.
-
-TO SEE ADDITIONAL HELP, run with options "-h logfiles" or "-h migrate".
-=cut
-
-=for logfiles
-There are 2 places that gitolite logs to, based on the value give to the
-LOG_DEST rc variable.  By default, log files go to ~/.gitolite/logs, but you
-can choose to send them to syslog instead (in which case 'who-pushed' will not
-work), or to both syslog and the normal log files.
-
-In addition, gitolite can also be told to log just the "update" records to a
-special "gl-log" file in the bare repo directory.  This makes 'who-pushed'
-**much** faster (thanks to milki for the problem *and* the simple solution).
-
-'who-pushed' will look for that special file first and use only that if it is
-found.  Otherwise it will look in the normal gitolite log files, which will of
-course be much slower.
-=cut
-
-=for migrate
-If you installed gitolite before v3.6.4, and you wish to use the new, more
-efficient logging that helps who-pushed run faster, you should first update
-the rc file (see http://gitolite.com/gitolite/rc.html for notes on that) to
-specify a suitable value for LOG_DEST.
-
-After that you should probably do a one-time generation of the repo-specific
-'gl-log' files from the normal log files.  This can only be done from the
-server command line, even if the 'who-pushed' command has been enabled for
-remote access.
-
-To do this, just run 'gitolite who-pushed --migrate'.  If some of your repos
-already had gl-log files, it will warn you, and tell you how to override.
-You're only supposed to to use this *once* after upgrading to v3.6.4 and
-setting LOG_DEST in the rc file anyway.
-=cut
-
diff --git a/docker/gitolite/src/commands/writable b/docker/gitolite/src/commands/writable
deleted file mode 100755
index 3e97f0b..0000000
--- a/docker/gitolite/src/commands/writable
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Easy;
-
-=for usage
-Usage: gitolite writable <reponame>|@all on|off|status
-
-Disable/re-enable pushes to all repos or named repo.  Useful to run
-non-git-aware backups and so on.
-
-'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
-'status' returns the current status as shell truth (i.e., exit code 0 for
-writable, 1 for not writable).
-
-With 'off', any subsequent text is taken to be the message to be shown to
-users when their pushes get rejected.  If it is not supplied, it will take it
-from STDIN; this allows longer messages.
-=cut
-
-usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
-usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off' and $ARGV[1] ne 'status';
-
-my $repo = shift;
-my $op   = shift;    # on|off|status
-
-if ( $repo eq '@all' ) {
-    _die "you are not authorized" if $ENV{GL_USER} and not is_admin();
-} else {
-    _die "you are not authorized" if $ENV{GL_USER} and not( owns($repo) or is_admin() or ( can_write($repo) and $op eq 'status' ) );
-}
-
-my $msg = join( " ", @ARGV );
-# try STDIN only if no msg found in args *and* it's an 'off' command
-if ( not $msg and $op eq 'off' ) {
-    say2 "...please type the message to be shown to users:";
-    $msg = join( "", <> );
-}
-
-my $sf = ".gitolite.down";
-my $rb = $ENV{GL_REPO_BASE};
-
-if ( $repo eq '@all' ) {
-    target( $ENV{HOME} );
-} else {
-    target("$rb/$repo.git");
-    target( $ENV{HOME} ) if $op eq 'status';
-}
-
-exit 0;
-
-sub target {
-    my $repodir = shift;
-    if ( $op eq 'status' ) {
-        exit 1 if -e "$repodir/$sf";
-    } elsif ( $op eq 'on' ) {
-        unlink "$repodir/$sf";
-    } elsif ( $op eq 'off' ) {
-        _print( "$repodir/$sf", $msg );
-    }
-}
diff --git a/docker/gitolite/src/gitolite b/docker/gitolite/src/gitolite
deleted file mode 100755
index 4a4cbf5..0000000
--- a/docker/gitolite/src/gitolite
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/perl
-
-# all gitolite CLI tools run as sub-commands of this command
-# ----------------------------------------------------------------------
-
-=for args
-Usage:  gitolite [sub-command] [options]
-
-The following built-in subcommands are available; they should all respond to
-'-h' if you want further details on each:
-
-    setup                       1st run: initial setup; all runs: hook fixups
-    compile                     compile gitolite.conf
-
-    query-rc                    get values of rc variables
-
-    list-groups                 list all group names in conf
-    list-users                  list all users/user groups in conf
-    list-repos                  list all repos/repo groups in conf
-    list-phy-repos              list all repos actually on disk
-    list-memberships            list all groups a name is a member of
-    list-members                list all members of a group
-
-Warnings:
-  - list-users is disk bound and could take a while on sites with 1000s of repos
-  - list-memberships does not check if the name is known; unknown names come
-    back with 2 answers: the name itself and '@all'
-
-In addition, running 'gitolite help' should give you a list of custom commands
-available.  They may or may not respond to '-h', depending on how they were
-written.
-=cut
-
-# ----------------------------------------------------------------------
-
-use FindBin;
-
-BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
-BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my ( $command, @args ) = @ARGV;
-gl_log( 'cli', 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE} and $$ == ( $ENV{GL_TID} || 0 );
-args();
-
-# the first two commands need options via @ARGV, as they have their own
-# GetOptions calls and older perls don't have 'GetOptionsFromArray'
-
-if ( $command eq 'setup' ) {
-    shift @ARGV;
-    require Gitolite::Setup;
-    Gitolite::Setup->import;
-    setup();
-
-} elsif ( $command eq 'query-rc' ) {
-    shift @ARGV;
-    query_rc();    # doesn't return
-
-# the rest don't need @ARGV per se
-
-} elsif ( $command eq 'compile' ) {
-    require Gitolite::Conf;
-    Gitolite::Conf->import;
-    compile(@args);
-
-} elsif ( $command eq 'trigger' ) {
-    trigger(@args);
-
-} elsif ( my $c = _which( "commands/$command", 'x' ) ) {
-    trace( 2, "attempting gitolite command $c" );
-    _system( $c, @args );
-
-} elsif ( $command eq 'list-phy-repos' ) {
-    _chdir( $rc{GL_REPO_BASE} );
-    print "$_\n" for ( @{ list_phy_repos(@args) } );
-
-} elsif ( $command =~ /^list-/ ) {
-    trace( 2, "attempting lister command $command" );
-    require Gitolite::Conf::Load;
-    Gitolite::Conf::Load->import;
-    my $fn = lister_dispatch($command);
-    print "$_\n" for ( @{ $fn->(@args) } );
-
-} else {
-    _die "unknown gitolite sub-command";
-}
-
-gl_log('END') if $$ == $ENV{GL_TID};
-
-exit 0;
-
-sub args {
-    usage() if not $command or $command eq '-h';
-}
-
-# ----------------------------------------------------------------------
diff --git a/docker/gitolite/src/gitolite-shell b/docker/gitolite/src/gitolite-shell
deleted file mode 100755
index d9ec01f..0000000
--- a/docker/gitolite/src/gitolite-shell
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/perl
-
-# gitolite shell, invoked from ~/.ssh/authorized_keys
-# ----------------------------------------------------------------------
-
-use FindBin;
-
-BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
-BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
-use lib $ENV{GL_LIBDIR};
-
-# set HOME
-BEGIN { $ENV{HOME} = $ENV{GITOLITE_HTTP_HOME} if $ENV{GITOLITE_HTTP_HOME}; }
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# the main() sub expects ssh-ish things; set them up...
-my $id = '';
-if ( exists $ENV{G3T_USER} ) {
-    $id = in_file();    # file:// masquerading as ssh:// for easy testing
-} elsif ( exists $ENV{SSH_CONNECTION} ) {
-    $id = in_ssh();
-} elsif ( exists $ENV{REQUEST_URI} ) {
-    $id = in_http();
-} else {
-    _die "who the *heck* are you?";
-}
-
-# sanity...
-my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-$soc =~ s/[\n\r]+/<<newline>>/g;
-_die "I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
-
-# allow gitolite-shell to be used as "$SHELL".  Experts only; no support, no docs
-if (@ARGV and $ARGV[0] eq '-c') {
-    shift;
-    $ARGV[0] =~ s/^$0 // or _die "unknown git/gitolite command: '$ARGV[0]'";
-}
-
-# the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed
-trigger('INPUT');
-
-main($id);
-
-gl_log('END') if $$ == $ENV{GL_TID};
-
-exit 0;
-
-# ----------------------------------------------------------------------
-
-sub in_file {
-    gl_log( 'file', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
-
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) {
-        print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
-        print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
-    }
-    return 'file';
-}
-
-sub in_http {
-    http_setup_die_handler();
-
-    _die "GITOLITE_HTTP_HOME not set" unless $ENV{GITOLITE_HTTP_HOME};
-
-    _die "fallback to DAV not supported" if $ENV{REQUEST_METHOD} eq 'PROPFIND';
-
-    # fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http,
-    # so the rest of the code stays the same (except the exec at the end).
-    http_simulate_ssh_connection();
-    $ENV{SSH_ORIGINAL_COMMAND} ||= '';
-
-    $ENV{REMOTE_USER} ||= $rc{HTTP_ANON_USER};
-    @ARGV = ( $ENV{REMOTE_USER} );
-
-    my $ip;
-    ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
-
-    gl_log( 'http', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
-
-    return 'http';
-}
-
-sub in_ssh {
-    my $ip;
-    ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
-
-    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
-
-    $ENV{SSH_ORIGINAL_COMMAND} ||= '';
-
-    return $ip;
-}
-
-# ----------------------------------------------------------------------
-
-# call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
-# has been setup (even if it's not actually coming via ssh).
-sub main {
-    my $id = shift;
-
-    # set up the user
-    my $user = $ENV{GL_USER} = shift @ARGV;
-
-    # set up the repo and the attempted access
-    my ( $verb, $repo ) = parse_soc();    # returns only for git commands
-    Gitolite::Conf::Load::sanity($repo, $REPONAME_PATT);
-    $ENV{GL_REPO} = $repo;
-    my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
-
-    # set up env vars from options set for this repo
-    env_options($repo);
-
-    # auto-create?
-    if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
-        require Gitolite::Conf::Store;
-        Gitolite::Conf::Store->import;
-        new_wild_repo( $repo, $user, $aa );
-        gl_log( 'create', $repo, $user, $aa );
-    }
-
-    # a ref of 'any' signifies that this is a pre-git check, where we don't
-    # yet know the ref that will be eventually pushed (and even that won't
-    # apply if it's a read operation).  See the matching code in access() for
-    # more information.
-    unless ( $ENV{GL_BYPASS_ACCESS_CHECKS} ) {
-        my $ret = access( $repo, $user, $aa, 'any' );
-        trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
-        _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
-
-        gl_log( "pre_git", $repo, $user, $aa, 'any', $ret );
-    }
-
-    trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
-    if ( $ENV{REQUEST_URI} ) {
-        _system( "git", "http-backend" );
-    } else {
-        my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
-        _system( "git", "shell", "-c", "$verb $repodir" );
-    }
-    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
-}
-
-# ----------------------------------------------------------------------
-
-sub parse_soc {
-    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-    $soc ||= 'info';
-
-    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    if ( $soc =~ m(^($git_commands) '?/?(.*?)(?:\.git(\d)?)?'?$) ) {
-        my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
-        $ENV{D} = $trace_level if $trace_level;
-        _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
-        trace( 2, "git command", $soc );
-        return ( $verb, $repo );
-    }
-
-    # after this we should not return; caller expects us to handle it all here
-    # and exit out
-
-    my @words = split ' ', $soc;
-    if ( $rc{COMMANDS}{ $words[0] } ) {
-        if ( $rc{COMMANDS}{ $words[0] } ne 'ua' ) {
-            _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
-            _die "no relative paths allowed anywhere!" if $soc =~ m(\.\./);
-        }
-        trace( 2, "gitolite command", $soc );
-        _system( "gitolite", @words );
-        exit 0;
-    }
-
-    _die "unknown git/gitolite command: '$soc'";
-}
-
-# ----------------------------------------------------------------------
-# helper functions for "in_http"
-
-sub http_setup_die_handler {
-
-    $SIG{__DIE__} = sub {
-        my $service = ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack' );
-        my $message = shift; chomp($message);
-        print STDERR "$message\n";
-
-        http_print_headers($service);
-
-        # format the service response, then the message.  With initial
-        # help from Ilari and then a more detailed email from Shawn...
-        $service = "# service=$service\n"; $message = "ERR $message\n";
-        $service = sprintf( "%04X", length($service) + 4 ) . "$service";    # no CRLF on this one
-        $message = sprintf( "%04X", length($message) + 4 ) . "$message";
-
-        print $service;
-        print "0000";                                                       # flush-pkt, apparently
-        print $message;
-        print STDERR $service;
-        print STDERR $message;
-        exit 0;                                                             # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
-      }
-}
-
-sub http_simulate_ssh_connection {
-    # these patterns indicate normal git usage; see "services[]" in
-    # http-backend.c for how I got that.  Also note that "info" is overloaded;
-    # git uses "info/refs...", while gitolite uses "info" or "info?...".  So
-    # there's a "/" after info in the list below
-    if ( $ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$)) ) {
-        my $repo = $1;
-        my $verb = ( $ENV{REQUEST_URI} =~ /git-receive-pack/ ) ? 'git-receive-pack' : 'git-upload-pack';
-        $ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
-    } else {
-        # this is one of our custom commands; could be anything really,
-        # because of the adc feature
-        my ($verb) = ( $ENV{PATH_INFO} =~ m(^/(\S+)) );
-        my $args = $ENV{QUERY_STRING};
-        $args =~ s/\+/ /g;
-        $args =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
-        $ENV{SSH_ORIGINAL_COMMAND} = $verb;
-        $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
-        http_print_headers();    # in preparation for the eventual output!
-
-        # we also need to pipe STDERR out via STDOUT, else the user doesn't see those messages!
-        open(STDERR, ">&STDOUT") or _die "Can't dup STDOUT: $!";
-    }
-    $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
-}
-
-my $http_headers_printed = 0;
-
-sub http_print_headers {
-    my ( $service, $code, $text ) = @_;
-
-    return if $http_headers_printed++;
-    $code ||= 200;
-    $text ||= "OK - gitolite";
-
-    $|++;
-    print "Status: $code $text\r\n";
-    print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
-    print "Pragma: no-cache\r\n";
-    print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
-    if ($service) {
-        print "Content-Type: application/x-$service-advertisement\r\n";
-    } else {
-        print "Content-Type: text/plain\r\n";
-    }
-    print "\r\n";
-}
diff --git a/docker/gitolite/src/lib/Gitolite/Cache.pm b/docker/gitolite/src/lib/Gitolite/Cache.pm
deleted file mode 100644
index 351a13e..0000000
--- a/docker/gitolite/src/lib/Gitolite/Cache.pm
+++ /dev/null
@@ -1,161 +0,0 @@
-package Gitolite::Cache;
-
-# cache stuff using an external database (redis)
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  cache_control
-  cache_wrap
-);
-
-use Exporter 'import';
-
-use Gitolite::Common;
-use Gitolite::Rc;
-use Storable qw(freeze thaw);
-use Redis;
-
-my $redis;
-
-my $redis_sock = "$ENV{HOME}/.redis-gitolite.sock";
-if ( -S $redis_sock ) {
-    _connect_redis();
-} else {
-    _start_redis();
-    _connect_redis();
-
-    # this redis db is a transient, caching only, db, so let's not
-    # accidentally use any stale data when if we're just starting up
-    cache_control('stop');
-    cache_control('start');
-}
-
-# ----------------------------------------------------------------------
-
-my %wrapped;
-my $ttl = ( $rc{CACHE_TTL} || ( $rc{GROUPLIST_PGM} ? 900 : 90000 ) );
-
-sub cache_control {
-    my $op = shift;
-    if ( $op eq 'stop' ) {
-        $redis->flushall();
-    } elsif ( $op eq 'start' ) {
-        $redis->set( 'cache-up', 1 );
-    } elsif ( $op eq 'flush' ) {
-        flush_repo(@_);
-    }
-}
-
-sub cache_wrap {
-    my $sub   = shift;
-    my $tname = $sub;    # this is what will show up in the trace output
-    trace( 3, "wrapping '$sub'" );
-    $sub = ( caller 1 )[0] . "::" . $sub if $sub !~ /::/;
-    return if $wrapped{$sub}++;    # in case somehow it gets called twice for the same sub!
-
-    # collect names of wrapped subs into a redis 'set'
-    $redis->sadd( "SUBWAY", $sub );    # subway?  yeah well they wrap subs don't they?
-
-    my $cref = eval '\&' . $sub;
-    my %opt  = @_;
-        # rest of the options come in as a hash.  'list' says this functions
-        # returns a list.  'ttl' is a number to override the default ttl for
-        # the cached value.
-
-    no strict 'refs';
-    no warnings 'redefine';
-    *{$sub} = sub {                    # the wrapper function
-        my $key = join( ", ", @_ );
-        trace( 2, "$tname.args", @_ );
-
-        if ( cache_up() and defined( my $val = $redis->get("$sub: $key") ) ) {
-            # cache is up and we got a hit, return value from cache
-            if ( $opt{list} ) {
-                trace( 2, "$tname.getl", @{ thaw($val) } );
-                return @{ thaw($val) };
-            } else {
-                trace( 2, "$tname.get", $val );
-                return $val;
-            }
-        } else {
-            # cache is down or we got a miss, compute
-            my ( $r, @r );
-            if ( $opt{list} ) {
-                @r = $cref->(@_);    # provide list context
-                trace( 2, "$tname.setl", @r );
-            } else {
-                $r = $cref->(@_);    # provide scalar context
-                trace( 2, "$tname.set", $r );
-            }
-
-            # store computed value in cache if cache is up
-            if ( cache_up() ) {
-                $redis->set( "$sub: $key", ( $opt{list} ? freeze( \@r ) : $r ) );
-                $redis->expire( "$sub: $key", $opt{ttl} || $ttl );
-                trace( 2, "$tname.ttl", ( $opt{ttl} || $ttl ) );
-            }
-
-            return @r if $opt{list};
-            return $r;
-        }
-    };
-    trace( 3, "wrapped '$sub'" );
-}
-
-sub cache_up {
-    return $redis->exists('cache-up');
-}
-
-sub flush_repo {
-    my $repo = shift;
-
-    my @wrapped = $redis->smembers("SUBWAY");
-    for my $func (@wrapped) {
-        # if we wrap any more functions, make sure they're functions where the
-        # first argument is 'repo'
-        my @keys = $redis->keys("$func: $repo, *");
-        $redis->del( @keys ) if @keys;
-    }
-}
-
-# ----------------------------------------------------------------------
-
-sub _start_redis {
-    my $conf = join( "", <DATA> );
-    $conf =~ s/%HOME/$ENV{HOME}/g;
-
-    open( REDIS, "|-", "/usr/sbin/redis-server", "-" ) or die "start redis server failed: $!";
-    print REDIS $conf;
-    close REDIS;
-
-    # give it a little time to come up
-    select( undef, undef, undef, 0.2 );
-}
-
-sub _connect_redis {
-    $redis = Redis->new( sock => $redis_sock, encoding => undef ) or die "redis new failed: $!";
-    $redis->ping or die "redis ping failed: $!";
-}
-
-1;
-
-__DATA__
-# resources
-maxmemory 50MB
-port 0
-unixsocket %HOME/.redis-gitolite.sock
-unixsocketperm 700
-timeout 0
-databases 1
-
-# daemon
-daemonize yes
-pidfile %HOME/.redis-gitolite.pid
-dbfilename %HOME/.redis-gitolite.rdb
-dir %HOME
-
-# feedback
-loglevel notice
-logfile %HOME/.redis-gitolite.log
-
-# we don't save
diff --git a/docker/gitolite/src/lib/Gitolite/Common.pm b/docker/gitolite/src/lib/Gitolite/Common.pm
deleted file mode 100644
index 5d6b749..0000000
--- a/docker/gitolite/src/lib/Gitolite/Common.pm
+++ /dev/null
@@ -1,365 +0,0 @@
-package Gitolite::Common;
-
-# common (non-gitolite-specific) functions
-# ----------------------------------------------------------------------
-
-#<<<
- at EXPORT = qw(
-  print2  dbg     _mkdir  _open   ln_sf     tsh_rc      sort_u
-  say     _warn   _chdir  _print            tsh_text    list_phy_repos
-  say2    _die    _system slurp             tsh_lines
-          trace           cleanup_conf_line tsh_try
-          usage                             tsh_run
-          gen_lfn
-          gl_log
-
-          dd
-          t_start
-          t_lap
-);
-#>>>
-use Exporter 'import';
-use File::Path qw(mkpath);
-use Carp qw(carp cluck croak confess);
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub print2 {
-    local $/ = "\n";
-    print STDERR @_;
-}
-
-sub say {
-    local $/ = "\n";
-    print @_, "\n";
-}
-
-sub say2 {
-    local $/ = "\n";
-    print STDERR @_, "\n";
-}
-
-sub trace {
-    gl_log( "\t" . join( ",", @_[ 1 .. $#_ ] ) ) if $_[0] <= 1 and defined $Gitolite::Rc::rc{LOG_EXTRA};
-
-    return unless defined( $ENV{D} );
-
-    my $level = shift; return if $ENV{D} < $level;
-    my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://;
-    if ( not $sub ) {
-        $sub = (caller)[1];
-        $sub =~ s(.*/(.*))(($1));
-    }
-    $sub .= ' ' x ( 31 - length($sub) );
-    say2 "$level\t$sub\t", join( "\t", @_ );
-}
-
-sub dbg {
-    use Data::Dumper;
-    return unless defined( $ENV{D} );
-    for my $i (@_) {
-        print STDERR "DBG: " . Dumper($i);
-    }
-}
-
-sub dd {
-    local $ENV{D} = 1;
-    dbg(@_);
-}
-
-{
-    my %start_times;
-
-    eval "require Time::HiRes";
-    # we just ignore any errors from this; nothing needs to be done as long as
-    # no code *calls* either of the next two functions.
-
-    sub t_start {
-        my $name = shift || 'default';
-        $start_times{$name} = [ Time::HiRes::gettimeofday() ];
-    }
-
-    sub t_lap {
-        my $name = shift || 'default';
-        return Time::HiRes::tv_interval( $start_times{$name} );
-    }
-}
-
-sub _warn {
-    gl_log( 'warn', @_ );
-    if ( $ENV{D} and $ENV{D} >= 3 ) {
-        cluck "WARNING: ", @_, "\n";
-    } elsif ( defined( $ENV{D} ) ) {
-        carp "WARNING: ", @_, "\n";
-    } else {
-        warn "WARNING: ", @_, "\n";
-    }
-}
-$SIG{__WARN__} = \&_warn;
-
-sub _die {
-    gl_log( 'die', @_ );
-    if ( $ENV{D} and $ENV{D} >= 3 ) {
-        confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
-    } elsif ( defined( $ENV{D} ) ) {
-        croak "FATAL: " . join( ",", @_ ) . "\n";
-    } else {
-        die "FATAL: " . join( ",", @_ ) . "\n";
-    }
-}
-$SIG{__DIE__} = \&_die;
-
-sub usage {
-    my $script = (caller)[1];
-    my $function = shift if @_ and $_[0] =~ /^[\w-]+$/;
-    $function ||= ( ( ( caller(1) )[3] ) || ( ( caller(0) )[3] ) );
-    $function =~ s/.*:://;
-    my $code = slurp($script);
-    $code =~ /^=for $function\b(.*?)^=cut/sm;
-    say( $1 ? $1 : "...no usage message for '$function' in $script" );
-    exit 1;
-}
-
-sub _mkdir {
-    # It's not an error if the directory exists, but it is an error if it
-    # doesn't exist and we can't create it. This includes not guaranteeing
-    # dead symlinks or if mkpath traversal is blocked by a file.
-    my $dir  = shift;
-    my $perm = shift;    # optional
-    return if -d $dir;
-    mkpath($dir);
-    chmod $perm, $dir if $perm;
-    return 1;
-}
-
-sub _chdir {
-    chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n";
-}
-
-sub _system {
-    # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
-    # exit with <rc of system()> if it applies, else just "exit 1".
-    trace( 1, 'system', @_ );
-    if ( system(@_) != 0 ) {
-        trace( 1, "system() failed", @_, "-> $?" );
-        if ( $? == -1 ) {
-            die "failed to execute: $!\n" if $ENV{D};
-        } elsif ( $? & 127 ) {
-            die "child died with signal " . ( $? & 127 ) . "\n" if $ENV{D};
-        } else {
-            die "child exited with value " . ( $? >> 8 ) . "\n" if $ENV{D};
-            exit( $? >> 8 );
-        }
-        exit 1;
-    }
-}
-
-sub _open {
-    open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n";
-    return $fh;
-}
-
-sub _print {
-    my ( $file, @text ) = @_;
-    my $fh = _open( ">", "$file.$$" );
-    print $fh @text;
-    close($fh) or _die "close $file failed: $! at ", (caller)[1], " line ", (caller)[2], "\n";
-    my $oldmode = ( ( stat $file )[2] );
-    rename "$file.$$", $file;
-    chmod $oldmode, $file if $oldmode;
-}
-
-sub slurp {
-    return unless defined wantarray;
-    local $/ = undef unless wantarray;
-    my $fh = _open( "<", $_[0] );
-    return <$fh>;
-}
-
-sub dos2unix {
-    # WARNING: when calling this, make sure you supply a list context
-    s/\r\n/\n/g for @_;
-    return @_;
-}
-
-sub ln_sf {
-    trace( 3, @_ );
-    my ( $srcdir, $glob, $dstdir ) = @_;
-    for my $hook ( glob("$srcdir/$glob") ) {
-        $hook =~ s/$srcdir\///;
-        unlink "$dstdir/$hook";
-        symlink "$srcdir/$hook", "$dstdir/$hook" or croak "could not symlink $srcdir/$hook to $dstdir\n";
-    }
-}
-
-sub sort_u {
-    my %uniq;
-    my $listref = shift;
-    return [] unless @{$listref};
-    undef @uniq{ @{$listref} };    # expect a listref
-    my @sort_u = sort keys %uniq;
-    return \@sort_u;
-}
-
-sub cleanup_conf_line {
-    my $line = shift;
-    return $line if $line =~ /^# \S+ \d+$/;
-
-    # kill comments, but take care of "#" inside *simple* strings
-    $line =~ s/^((".*?"|[^#"])*)#.*/$1/;
-    # normalise whitespace; keeps later regexes very simple
-    $line =~ s/=/ = /;
-    $line =~ s/\s+/ /g;
-    $line =~ s/^ //;
-    $line =~ s/ $//;
-    return $line;
-}
-
-{
-    my @phy_repos = ();
-
-    sub list_phy_repos {
-        # use cached value only if it exists *and* no arg was received (i.e.,
-        # receiving *any* arg invalidates cache)
-        return \@phy_repos if ( @phy_repos and not @_ );
-
-        my $cmd = 'find . ' . ($Gitolite::Rc::rc{REPO_SYMLINKS} || '') . ' -name "*.git" -prune';
-        for my $repo (`$cmd`) {
-            chomp($repo);
-            $repo =~ s/\.git$//;
-            $repo =~ s(^\./)();
-            push @phy_repos, $repo;
-        }
-        trace( 3, scalar(@phy_repos) . " physical repos found" );
-        return sort_u( \@phy_repos );
-    }
-}
-
-# generate a timestamp
-sub gen_ts {
-    my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
-    $y += 1900; $m++;    # usual adjustments
-    for ( $s, $min, $h, $d, $m ) {
-        $_ = "0$_" if $_ < 10;
-    }
-    my $ts = "$y-$m-$d.$h:$min:$s";
-
-    return $ts;
-}
-
-# generate a log file name
-sub gen_lfn {
-    my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
-    $y += 1900; $m++;    # usual adjustments
-    for ( $s, $min, $h, $d, $m ) {
-        $_ = "0$_" if $_ < 10;
-    }
-
-    my ($template) = shift;
-    # substitute template parameters and set the logfile name
-    $template =~ s/%y/$y/g;
-    $template =~ s/%m/$m/g;
-    $template =~ s/%d/$d/g;
-
-    return $template;
-}
-
-my $log_dest;
-my $syslog_opened = 0;
-END { closelog() if $syslog_opened; }
-sub gl_log {
-    # the log filename and the timestamp come from the environment.  If we get
-    # called even before they are set, we have no choice but to dump to STDERR
-    # (and probably call "logger").
-
-    # tab sep if there's more than one field
-    my $msg = join( "\t", @_ );
-    $msg =~ s/[\n\r]+/<<newline>>/g;
-
-    my $ts = gen_ts();
-    my $tid = $ENV{GL_TID} ||= $$;
-
-    $log_dest = $Gitolite::Rc::rc{LOG_DEST} || '' if not defined $log_dest;
-
-    # log (update records only) to "gl-log" in the bare repo dir; this is to
-    # make 'who-pushed' more efficient.  Since this is only for the update
-    # records, it is not a replacement for the other two types of logging.
-    if ($log_dest =~ /repo-log/ and $_[0] eq 'update') {
-        # if the log line is 'update', we're already in the bare repo dir
-        open my $lfh, ">>", "gl-log" or _die "open gl-log failed: $!";
-        print $lfh "$ts\t$tid\t$msg\n";
-        close $lfh;
-    }
-
-    # syslog
-    if ($log_dest =~ /syslog/) {            # log_dest *includes* syslog
-        if ($syslog_opened == 0) {
-            require Sys::Syslog;
-            Sys::Syslog->import(qw(:standard));
-
-            openlog("gitolite" . ( $ENV{GL_TID} ? "[$ENV{GL_TID}]" : "" ), "pid", "local0");
-            $syslog_opened = 1;
-        }
-
-        # gl_log is called either directly, or, if the rc variable LOG_EXTRA
-        # is set, from trace(1, ...).  The latter use is considered additional
-        # info for troubleshooting.  Trace prefixes a tab to the arguments
-        # before calling gl_log, to visually set off such lines in the log
-        # file.  Although syslog eats up that leading tab, we use it to decide
-        # the priority/level of the syslog message.
-        syslog( ( $msg =~ /^\t/ ? 'debug' : 'info' ), "%s", $msg);
-
-        return if $log_dest !~ /normal/;
-    }
-
-    my $fh;
-    logger_plus_stderr( "errors found before logging could be setup", "$msg" ) if not $ENV{GL_LOGFILE};
-    open my $lfh, ">>", $ENV{GL_LOGFILE}
-      or logger_plus_stderr( "errors found but logfile could not be created", "$ENV{GL_LOGFILE}: $!", "$msg" );
-    print $lfh "$ts\t$tid\t$msg\n";
-    close $lfh;
-}
-
-sub logger_plus_stderr {
-    open my $fh, "|-", "logger" or confess "it's really not my day is it...?\n";
-    for (@_) {
-        print STDERR "FATAL: $_\n";
-        print $fh "FATAL: $_\n";
-    }
-    exit 1;
-}
-
-# ----------------------------------------------------------------------
-
-# bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh)
-{
-    my ( $rc, $text );
-    sub tsh_rc   { return $rc   || 0; }
-    sub tsh_text { return $text || ''; }
-    sub tsh_lines { return split /\n/, $text; }
-
-    sub tsh_try {
-        my $cmd = shift; die "try: expects only one argument" if @_;
-        $text = `( $cmd ) 2>&1; printf RC=\$?`;
-        if ( $text =~ s/RC=(\d+)$// ) {
-            $rc = $1;
-            trace( 3, $text );
-            return ( not $rc );
-        }
-        die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
-    }
-
-    sub tsh_run {
-        open( my $fh, "-|", @_ ) or die "popen failed: $!";
-        local $/ = undef; $text = <$fh>;
-        close $fh; warn "pclose failed: $!" if $!;
-        $rc = ( $? >> 8 );
-        trace( 3, $text );
-        return $text;
-    }
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Conf.pm b/docker/gitolite/src/lib/Gitolite/Conf.pm
deleted file mode 100644
index ce7adca..0000000
--- a/docker/gitolite/src/lib/Gitolite/Conf.pm
+++ /dev/null
@@ -1,99 +0,0 @@
-package Gitolite::Conf;
-
-# explode/parse a conf file
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  compile
-  explode
-  parse
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Sugar;
-use Gitolite::Conf::Store;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub compile {
-    _die "'gitolite compile' does not take any arguments" if @_;
-
-    _chdir( $rc{GL_ADMIN_BASE} );
-    _chdir("conf");
-
-    parse( sugar('gitolite.conf') );
-
-    # the order matters; new repos should be created first, to give store a
-    # place to put the individual gl-conf files
-    new_repos();
-
-    # cache control
-    if ($rc{CACHE}) {
-        require Gitolite::Cache;
-        Gitolite::Cache->import(qw(cache_control));
-
-        cache_control('stop');
-    }
-
-    store();
-
-    if ($rc{CACHE}) {
-        cache_control('start');
-    }
-
-    for my $repo ( @{ $rc{NEW_REPOS_CREATED} } ) {
-        trigger( 'POST_CREATE', $repo );
-    }
-}
-
-sub parse {
-    my $lines = shift;
-    trace( 3, scalar(@$lines) . " lines incoming" );
-
-    my ( $fname, $lnum );
-    for my $line (@$lines) {
-        ( $fname, $lnum ) = ( $1, $2 ), next if $line =~ /^# (\S+) (\d+)$/;
-        # user or repo groups
-        if ( $line =~ /^(@\S+) = (.*)/ ) {
-            add_to_group( $1, split( ' ', $2 ) );
-        } elsif ( $line =~ /^repo (.*)/ ) {
-            set_repolist( split( ' ', $1 ) );
-        } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
-            my $perm  = $1;
-            my @refs  = parse_refs( $2 || '' );
-            my @users = parse_users($3);
-
-            for my $ref (@refs) {
-                for my $user (@users) {
-                    add_rule( $perm, $ref, $user, $fname, $lnum );
-                }
-            }
-        } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
-            my ( $key, $value ) = ( $1, $2 );
-            $value =~ s/^['"](.*)["']$/$1/;
-            my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
-            push @validkeys, "gitolite-options\\..*";
-            my @matched = grep { $key =~ /^$_$/i } @validkeys;
-            _die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
-            _die "bad config value '$value'" if $value =~ $UNSAFE_PATT;
-            while ( my ( $mk, $mv ) = each %{ $rc{SAFE_CONFIG} } ) {
-                $value =~ s/%$mk/$mv/g;
-            }
-            add_config( 1, $key, $value );
-        } elsif ( $line =~ /^subconf (\S+)$/ ) {
-            trace( 3, $line );
-            set_subconf($1);
-        } else {
-            _warn "syntax error, ignoring: '$line'";
-        }
-    }
-    parse_done();
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Conf/Explode.pm b/docker/gitolite/src/lib/Gitolite/Conf/Explode.pm
deleted file mode 100644
index cf89620..0000000
--- a/docker/gitolite/src/lib/Gitolite/Conf/Explode.pm
+++ /dev/null
@@ -1,118 +0,0 @@
-package Gitolite::Conf::Explode;
-
-# include/subconf processor
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  explode
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-# 'seen' for include/subconf files
-my %included = ();
-# 'seen' for group names on LHS
-my %prefixed_groupname = ();
-
-sub explode {
-    trace( 3, @_ );
-    my ( $file, $subconf, $out ) = @_;
-
-    # seed the 'seen' list if it's empty
-    $included{ device_inode("gitolite.conf") }++ unless %included;
-
-    my $fh = _open( "<", $file );
-    while (<$fh>) {
-        my $line = cleanup_conf_line($_);
-        next unless $line =~ /\S/;
-
-        # subst %HOSTNAME word if rc defines a hostname, else leave as is
-        $line =~ s/%HOSTNAME\b/$rc{HOSTNAME}/g if $rc{HOSTNAME};
-
-        $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
-
-        if ( $line =~ /^(include|subconf) (?:(\S+) )?(\S.+)$/ ) {
-            incsub( $1, $2, $3, $subconf, $out );
-        } else {
-            # normal line, send it to the callback function
-            push @{$out}, "# $file $.";
-            push @{$out}, $line;
-        }
-    }
-}
-
-sub incsub {
-    my $is_subconf = ( +shift eq 'subconf' );
-    my ( $new_subconf, $include_glob, $current_subconf, $out ) = @_;
-
-    _die "subconf '$current_subconf' attempting to run 'subconf'\n" if $is_subconf and $current_subconf ne 'master';
-
-    _die "invalid include/subconf file/glob '$include_glob'"
-      unless $include_glob =~ /^"(.+)"$/
-      or $include_glob =~ /^'(.+)'$/;
-    $include_glob = $1;
-
-    trace( 3, $is_subconf, $include_glob );
-
-    for my $file ( glob($include_glob) ) {
-        _warn("included file not found: '$file'"), next unless -f $file;
-        _die "invalid include/subconf filename '$file'" unless $file =~ m(([^/]+).conf$);
-        my $basename = $1;
-
-        next if already_included($file);
-
-        if ($is_subconf) {
-            push @{$out}, "subconf " . ( $new_subconf || $basename );
-            explode( $file, ( $new_subconf || $basename ), $out );
-            push @{$out}, "subconf $current_subconf";
-        } else {
-            explode( $file, $current_subconf, $out );
-        }
-    }
-}
-
-sub prefix_groupnames {
-    my ( $line, $subconf ) = @_;
-
-    my $lhs = '';
-    # save 'foo' if it's an '@foo = list' line
-    $lhs = $1 if $line =~ /^@(\S+) = /;
-    # prefix all @groups in the line
-    $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge;
-    # now prefix the LHS and store it if needed
-    if ($lhs) {
-        $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
-        $prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs";
-        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
-    }
-
-    return $line;
-}
-
-sub already_included {
-    my $file = shift;
-
-    my $file_id = device_inode($file);
-    return 0 unless $included{$file_id}++;
-
-    _warn("$file already included");
-    trace( 3, "$file already included" );
-    return 1;
-}
-
-sub device_inode {
-    my $file = shift;
-    trace( 3, $file, ( stat $file )[ 0, 1 ] );
-    return join( "/", ( stat $file )[ 0, 1 ] );
-}
-
-1;
-
diff --git a/docker/gitolite/src/lib/Gitolite/Conf/Load.pm b/docker/gitolite/src/lib/Gitolite/Conf/Load.pm
deleted file mode 100644
index 7728a5a..0000000
--- a/docker/gitolite/src/lib/Gitolite/Conf/Load.pm
+++ /dev/null
@@ -1,676 +0,0 @@
-package Gitolite::Conf::Load;
-
-# load conf data from stored files
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  load
-
-  access
-  git_config
-  env_options
-
-  option
-  repo_missing
-  creator
-
-  vrefs
-  lister_dispatch
-);
-
-use Exporter 'import';
-use Cwd;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-# our variables, because they get loaded by a 'do'
-our $data_version = '';
-our %repos;
-our %one_repo;
-our %groups;
-our %patterns;
-our %configs;
-our %one_config;
-our %split_conf;
-
-my $subconf = 'master';
-
-my %listers = (
-    'list-groups'      => \&list_groups,
-    'list-users'       => \&list_users,
-    'list-repos'       => \&list_repos,
-    'list-memberships' => \&list_memberships,
-    'list-members'     => \&list_members,
-);
-
-# helps maintain the "cache" in both "load_common" and "load_1"
-my $last_repo = '';
-
-# ----------------------------------------------------------------------
-
-{
-    my $loaded_repo = '';
-
-    sub load {
-        my $repo = shift or _die "load() needs a reponame";
-        trace( 3, "$repo" );
-        if ( $repo ne $loaded_repo ) {
-            load_common();
-            load_1($repo);
-            $loaded_repo = $repo;
-        }
-    }
-}
-
-sub access {
-    my ( $repo, $user, $aa, $ref ) = @_;
-    trace( 2, $repo, $user, $aa, $ref );
-    _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
-    sanity($repo);
-
-    my @rules;
-    my $deny_rules;
-
-    load($repo);
-    @rules = rules( $repo, $user );
-    $deny_rules = option( $repo, 'deny-rules' );
-
-    # sanity check the only piece the user can control
-    _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ m(^VREF/NAME/) or $ref =~ $REF_OR_FILENAME_PATT;
-    # apparently we can't always force sanity; at least what we *return*
-    # should be sane/safe.  This pattern is based on REF_OR_FILENAME_PATT.
-    ( my $safe_ref = $ref ) =~ s([^-0-9a-zA-Z._\@/+ :,])(.)g;
-    trace( 3, "safe_ref", $safe_ref ) if $ref ne $safe_ref;
-
-    # when a real repo doesn't exist, ^C is a pre-requisite for any other
-    # check to give valid results.
-    if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing($repo) ) {
-        my $iret = access( $repo, $user, '^C', $ref );
-        $iret =~ s/\^C/$aa/;
-        return $iret if $iret =~ /DENIED/;
-    }
-    # similarly, ^C must be denied if the repo exists
-    if ( $aa eq '^C' and not repo_missing($repo) ) {
-        trace( 2, "DENIED by existence" );
-        return "$aa $safe_ref $repo $user DENIED by existence";
-    }
-
-    trace( 3, scalar(@rules) . " rules found" );
-
-    $rc{RULE_TRACE} = '';
-    for my $r (@rules) {
-        $rc{RULE_TRACE} .= " " . $r->[0] . " ";
-
-        my $perm = $r->[1];
-        my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
-        trace( 3, "perm=$perm, refex=$refex" );
-
-        $rc{RULE_TRACE} .= "d";
-        # skip 'deny' rules if the ref is not (yet) known
-        next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
-
-        $rc{RULE_TRACE} .= "r";
-        # rule matches if ref matches or ref is any (see gitolite-shell)
-        next unless $ref =~ /^$refex/ or $ref eq 'any';
-
-        $rc{RULE_TRACE} .= "D";
-        trace( 2, "DENIED by $refex" ) if $perm eq '-';
-        return "$aa $safe_ref $repo $user DENIED by $refex" if $perm eq '-';
-
-        # For repo creation, perm will be C and aa will be "^C".  For branch
-        # access, $perm can be RW\+?(C|D|CD|DC)?M?, and $aa can be W, +, C or
-        # D, or any of these followed by "M".
-
-        # We need to turn $aa into a regex that can match a suitable $perm.
-        # This is trivially true for "^C", "W" and "D", but the others (+, C,
-        # M) need some tweaking.
-
-        # first, quote the '+':
-        ( my $aaq = $aa ) =~ s/\+/\\+/;
-        # if aa is just "C", the user is trying to create a *branch* (not a
-        # *repo*), so let's make the pattern clearer to reflect that.
-        $aaq = "RW.*C" if $aaq eq "C";
-        # if the aa is, say "WM", make this "W.*M" because the perm could be
-        # 'RW+M', 'RW+CDM' etc, and they are all valid:
-        $aaq =~ s/M/.*M/;
-
-        $rc{RULE_TRACE} .= "A";
-
-        # as far as *this* ref is concerned we're ok
-        return $refex if ( $perm =~ /$aaq/ );
-
-        $rc{RULE_TRACE} .= "p";
-    }
-    $rc{RULE_TRACE} .= " F";
-
-    trace( 2, "DENIED by fallthru" );
-    return "$aa $safe_ref $repo $user DENIED by fallthru";
-}
-
-# cache control
-if ($rc{CACHE}) {
-    require Gitolite::Cache;
-    Gitolite::Cache::cache_wrap('Gitolite::Conf::Load::access');
-}
-
-sub git_config {
-    my ( $repo, $key, $empty_values_OK ) = @_;
-    $key ||= '.';
-
-    if ( repo_missing($repo) ) {
-        load_common();
-    } else {
-        load($repo);
-    }
-
-    # read comments bottom up
-    my %ret =
-      # and take the second and third elements to make up your new hash
-      map { $_->[1] => $_->[2] }
-      # keep only the ones where the second element matches your key
-      grep { $_->[1] =~ qr($key) }
-      # sort this list of listrefs by the first element in each list ref'd to
-      sort { $a->[0] <=> $b->[0] }
-      # dereference it (into a list of listrefs)
-      map { @$_ }
-      # take the value of that entry
-      map { $configs{$_} }
-      # if it has an entry in %configs
-      grep { $configs{$_} }
-      # for each "repo" that represents us
-      memberships( 'repo', $repo );
-
-    # %configs looks like this (for each 'foo' that is in memberships())
-    # 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
-    # the first map gets you the value
-    #          [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
-    # the deref gets you
-    #            [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ]
-    # the sort rearranges it (in this case it's already sorted but anyway...)
-    # the grep gets you this, assuming the key is foo.bar (and "." is regex ".')
-    #            [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ]
-    # and the final map does this:
-    #                 'foo.bar'=>'repo'  ,      'foodbar'=>'repoD'
-
-    # now some of these will have an empty key; we need to delete them unless
-    # we're told empty values are OK
-    unless ($empty_values_OK) {
-        my ( $k, $v );
-        while ( ( $k, $v ) = each %ret ) {
-            delete $ret{$k} if not $v;
-        }
-    }
-
-    my ( $k, $v );
-    my $creator = creator($repo);
-    while ( ( $k, $v ) = each %ret ) {
-        $v =~ s/%GL_REPO/$repo/g;
-        $v =~ s/%GL_CREATOR/$creator/g if $creator;
-        $ret{$k} = $v;
-    }
-
-    map { trace( 3, "$_", "$ret{$_}" ) } ( sort keys %ret ) if $ENV{D};
-    return \%ret;
-}
-
-sub env_options {
-    return unless -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf-compiled.pm";
-    # prevent catch-22 during initial install
-
-    my $cwd = getcwd();
-
-    my $repo = shift;
-    map { delete $ENV{$_} } grep { /^GL_OPTION_/ } keys %ENV;
-    my $h = git_config( $repo, '^gitolite-options.ENV\.' );
-    while ( my ( $k, $v ) = each %$h ) {
-        next unless $k =~ /^gitolite-options.ENV\.(\w+)$/;
-        $ENV{ "GL_OPTION_" . $1 } = $v;
-    }
-
-    chdir($cwd);
-}
-
-sub option {
-    my ( $repo, $option ) = @_;
-    $option = "gitolite-options.$option";
-    my $ret = git_config( $repo, "^\Q$option\E\$" );
-    return '' unless %$ret;
-    return $ret->{$option};
-}
-
-sub sanity {
-    my ($repo, $patt) = @_;
-    $patt ||= $REPOPATT_PATT;
-
-    _die "invalid repo '$repo'" if not( $repo and $repo =~ $patt );
-    _die "'$repo' ends with a '/'"  if $repo =~ m(/$);
-    _die "'$repo' contains '..'"    if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
-    _die "'$repo' contains '.git/'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.git/);
-}
-
-sub repo_missing {
-    my $repo = shift;
-    sanity($repo);
-
-    return not -d "$rc{GL_REPO_BASE}/$repo.git";
-}
-
-# ----------------------------------------------------------------------
-
-sub load_common {
-
-    _chdir( $rc{GL_ADMIN_BASE} );
-
-    # we take an unusual approach to caching this function!
-    # (requires that first call to load_common is before first call to load_1)
-    if ( $last_repo and $split_conf{$last_repo} ) {
-        delete $repos{$last_repo};
-        delete $configs{$last_repo};
-        return;
-    }
-
-    my $cc = "conf/gitolite.conf-compiled.pm";
-
-    _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
-
-    if ( data_version_mismatch() ) {
-        _system("gitolite setup");
-        _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
-        _die "data version update failed; this is serious" if data_version_mismatch();
-    }
-}
-
-sub load_1 {
-    my $repo = shift;
-    return if $repo =~ /^\@/;
-    trace( 3, $repo );
-
-    if ( repo_missing($repo) ) {
-        trace( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
-        return;
-    }
-    _chdir("$rc{GL_REPO_BASE}/$repo.git");
-
-    if ( $repo eq $last_repo ) {
-        $repos{$repo} = $one_repo{$repo};
-        $configs{$repo} = $one_config{$repo} if $one_config{$repo};
-        return;
-    }
-
-    if ( -f "gl-conf" ) {
-        return if not $split_conf{$repo};
-
-        my $cc = "./gl-conf";
-        _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
-
-        $last_repo = $repo;
-        $repos{$repo} = $one_repo{$repo};
-        $configs{$repo} = $one_config{$repo} if $one_config{$repo};
-    } else {
-        _die "split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
-    }
-}
-
-{
-    my $lastrepo = '';
-    my $lastuser = '';
-    my @cached   = ();
-
-    sub rules {
-        my ( $repo, $user ) = @_;
-        trace( 3, $repo, $user );
-
-        return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
-
-        my @rules = ();
-
-        my @repos = memberships( 'repo', $repo );
-        my @users = memberships( 'user', $user, $repo );
-        trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
-
-        for my $r (@repos) {
-            for my $u (@users) {
-                push @rules, @{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
-            }
-        }
-
-        @rules = sort { $a->[0] <=> $b->[0] } @rules;
-
-        $lastrepo = $repo;
-        $lastuser = $user;
-        @cached   = @rules;
-
-        # however if the repo was missing, invalidate the cache
-        $lastrepo = '' if repo_missing($repo);
-
-        return @rules;
-    }
-
-    sub vrefs {
-        my ( $repo, $user ) = @_;
-        # fill the cache if needed
-        rules( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached );
-
-        my %seen;
-        my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
-        return @vrefs;
-    }
-}
-
-sub memberships {
-    trace( 3, @_ );
-    my ( $type, $base, $repo ) = @_;
-    $repo ||= '';
-    my @ret;
-    my $base2 = '';
-
-    @ret = ( $base, '@all' );
-
-    if ( $type eq 'repo' ) {
-        # first, if a repo, say, pub/sitaram/project, has a gl-creator file
-        # that says "sitaram", find memberships for pub/CREATOR/project also
-        $base2 = generic_name($base);
-
-        # second, you need to check in %repos also
-        for my $i ( keys %repos, keys %configs ) {
-            if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
-                push @ret, $i;
-            }
-        }
-    }
-
-    push @ret, @{ $groups{$base} } if exists $groups{$base};
-    push @ret, @{ $groups{$base2} } if $base2 and exists $groups{$base2};
-    for my $i ( keys %{ $patterns{groups} } ) {
-        if ( $base =~ /^$i$/ or $base2 and ( $base2 =~ /^$i$/ ) ) {
-            push @ret, @{ $groups{$i} };
-        }
-    }
-
-    push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
-
-    if ( $type eq 'user' and $repo and not repo_missing($repo) ) {
-        # find the roles this user has when accessing this repo and add those
-        # in as groupnames he is a member of.  You need the already existing
-        # memberships for this; see below this function for an example
-        push @ret, user_roles( $base, $repo, @ret );
-    }
-
-    @ret = @{ sort_u( \@ret ) };
-    trace( 3, sort @ret );
-    return @ret;
-}
-
-=for example
-
-conf/gitolite.conf:
-    @g1 = u1
-    @g2 = u1
-    # now user is a member of both g1 and g2
-
-gl-perms for repo being accessed:
-    READERS @g1
-
-This should result in @READERS being added to the memberships that u1 has
-(when accessing this repo).  So we send the current list (@g1, @g2) to
-user_roles(), otherwise it has to redo that logic.
-
-=cut
-
-sub data_version_mismatch {
-    return $data_version ne glrc('current-data-version');
-}
-
-sub user_roles {
-    my ( $user, $repo, @eg ) = @_;
-
-    # eg == existing groups (that user is already known to be a member of)
-    my %eg = map { $_ => 1 } @eg;
-
-    my %ret   = ();
-    my $f     = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
-    my @roles = ();
-    if ( -f $f ) {
-        my $fh = _open( "<", $f );
-        chomp( @roles = <$fh> );
-    }
-    push @roles, "CREATOR = " . creator($repo);
-    for (@roles) {
-        # READERS u3 u4 @g1
-        s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
-        next if /^#/;
-        next unless /\S/;
-        my ( $role, @members ) = split;
-        # role = READERS, members = u3, u4, @g1
-        if ( $role ne 'CREATOR' and not $rc{ROLES}{$role} ) {
-            _warn "role '$role' not allowed, ignoring";
-            next;
-        }
-        for my $m (@members) {
-            if ( $m !~ $USERNAME_PATT ) {
-                _warn "ignoring '$m' in perms line";
-                next;
-            }
-            # if user eq u3/u4, or is a member of @g1, he has role READERS
-            $ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
-        }
-    }
-
-    return keys %ret;
-}
-
-sub generic_name {
-    my $base  = shift;
-    my $base2 = '';
-    my $creator;
-
-    # get the creator name.  For not-yet-born repos this is $ENV{GL_USER},
-    # which should be set in all cases that we care about, viz., where we are
-    # checking ^C permissions before new_wild_repo(), and the info command.
-    # In particular, 'gitolite access' can't be used to check ^C perms on wild
-    # repos that contain "CREATOR" if GL_USER is not set.
-    $creator = creator($base);
-
-    $base2 = $base;
-    $base2 =~ s(\b$creator\b)(CREATOR) if $creator;
-    $base2 = '' if $base2 eq $base;    # if there was no change
-
-    return $base2;
-}
-
-sub creator {
-    my $repo = shift;
-    sanity($repo);
-
-    return ( $ENV{GL_USER} || '' ) if repo_missing($repo);
-    my $f       = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
-    my $creator = '';
-    chomp( $creator = slurp($f) ) if -f $f;
-    return $creator;
-}
-
-{
-    my %cache = ();
-
-    sub ext_grouplist {
-        my $user = shift;
-        my $pgm  = $rc{GROUPLIST_PGM};
-        return [] if not $pgm;
-
-        return $cache{$user} if $cache{$user};
-        my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
-        return ( $cache{$user} = \@extgroups );
-    }
-}
-
-# ----------------------------------------------------------------------
-# api functions
-# ----------------------------------------------------------------------
-
-sub lister_dispatch {
-    my $command = shift;
-
-    my $fn = $listers{$command} or _die "unknown gitolite sub-command";
-    return $fn;
-}
-
-=for list_groups
-Usage:  gitolite list-groups
-
-  - lists all group names in conf
-  - no options, no flags
-=cut
-
-sub list_groups {
-    usage() if @_;
-
-    load_common();
-
-    my @g = ();
-    while ( my ( $k, $v ) = each(%groups) ) {
-        push @g, @{$v};
-    }
-    return ( sort_u( \@g ) );
-}
-
-=for list_users
-Usage:  gitolite list-users [<repo name pattern>]
-
-List all users and groups explicitly named in a rule.  User names not
-mentioned in an access rule will not show up; you have to run 'list-members'
-on each group name yourself to see them.
-
-WARNING: may be slow if you have thousands of repos.  The optional repo name
-pattern is an unanchored regex; it can speed things up if you're interested
-only in users of a matching set of repos.  This is only an optimisation, not
-an actual access list; you will still have to pipe it to 'gitolite access'
-with appropriate arguments to get an actual access list.
-=cut
-
-sub list_users {
-    my $patt = shift || '.';
-    usage() if $patt eq '-h' or @_;
-    my $count = 0;
-    my $total = 0;
-
-    load_common();
-
-    my @u = map { keys %{$_} } values %repos;
-    $total = scalar( grep { /$patt/ } keys %split_conf );
-    warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
-    for my $one ( grep { /$patt/ } keys %split_conf ) {
-        load_1($one);
-        $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
-        push @u, map { keys %{$_} } values %one_repo;
-    }
-    print STDERR "\n" if $count >= 100;
-    return ( sort_u( \@u ) );
-}
-
-=for list_repos
-Usage:  gitolite list-repos
-
-  - lists all repos/repo groups in conf
-  - no options, no flags
-=cut
-
-sub list_repos {
-    usage() if @_;
-
-    load_common();
-
-    my @r = keys %repos;
-    push @r, keys %split_conf;
-
-    return ( sort_u( \@r ) );
-}
-
-=for list_memberships
-Usage:  gitolite list-memberships -u|-r <name>
-
-List all groups a name is a member of.  One of the flags '-u' or '-r' is
-mandatory, to specify if the name is a user or a repo.
-
-For users, the output includes the result from GROUPLIST_PGM, if it is
-defined.  For repos, the output includes any repo patterns that the repo name
-matches, as well as any groups that contain those patterns.
-=cut
-
-sub list_memberships {
-    require Getopt::Long;
-
-    my ( $user, $repo, $help );
-
-    Getopt::Long::GetOptionsFromArray(
-        \@_,
-        'user|u=s' => \$user,
-        'repo|r=s' => \$repo,
-        'help|h'   => \$help,
-    );
-    usage() if $help or ( not $user and not $repo );
-
-    load_common();
-    my @m;
-
-    if ( $user and $repo ) {
-        # unsupported/undocumented except via "in_role()" in Easy.pm
-        @m = memberships( 'user', $user, $repo );
-    } elsif ($user) {
-        @m = memberships( 'user', $user );
-    } elsif ($repo) {
-        @m = memberships( 'repo', $repo );
-    }
-
-    @m = grep { $_ ne '@all' and $_ ne ( $user || $repo ) } @m;
-    return ( sort_u( \@m ) );
-}
-
-=for list_members
-Usage:  gitolite list-members <group name>
-
-  - list all members of a group
-  - takes one group name
-=cut
-
-sub list_members {
-    usage() if @_ and $_[0] eq '-h' or not @_;
-
-    my $name = shift;
-
-    load_common();
-
-    my @m = ();
-    while ( my ( $k, $v ) = each(%groups) ) {
-        for my $g ( @{$v} ) {
-            push @m, $k if $g eq $name;
-        }
-    }
-
-    return ( sort_u( \@m ) );
-}
-
-# ----------------------------------------------------------------------
-
-{
-    my $start_time = 0;
-
-    sub timer {
-        unless ($start_time) {
-            $start_time = time();
-            return 0;
-        }
-        my $elapsed = shift;
-        return 0 if time() - $start_time < $elapsed;
-        $start_time = time();
-        return 1;
-    }
-}
-
-1;
-
diff --git a/docker/gitolite/src/lib/Gitolite/Conf/Store.pm b/docker/gitolite/src/lib/Gitolite/Conf/Store.pm
deleted file mode 100644
index 5568b3f..0000000
--- a/docker/gitolite/src/lib/Gitolite/Conf/Store.pm
+++ /dev/null
@@ -1,408 +0,0 @@
-package Gitolite::Conf::Store;
-
-# receive parsed conf data and store it
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  add_to_group
-  set_repolist
-  parse_refs
-  parse_users
-  add_rule
-  add_config
-  set_subconf
-
-  expand_list
-  new_repos
-  new_repo
-  new_wild_repo
-  hook_repos
-  store
-  parse_done
-);
-
-use Exporter 'import';
-use Data::Dumper;
-$Data::Dumper::Indent   = 1;
-$Data::Dumper::Sortkeys = 1;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Hooks::Update;
-use Gitolite::Hooks::PostUpdate;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my %repos;
-my %groups;
-my %configs;
-my %split_conf;
-
-my @repolist;    # current repo list; reset on each 'repo ...' line
-my $subconf = 'master';
-my $nextseq = 0;
-my %ignored;
-
-# ----------------------------------------------------------------------
-
-sub add_to_group {
-    my ( $lhs, @rhs ) = @_;
-    _die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT;
-    map { _die "bad expansion '$_'" unless $_ =~ $REPOPATT_PATT } @rhs;
-
-    # store the group association, but overload it to keep track of when
-    # the group was *first* created by using $subconf as the *value*
-    do { $groups{$lhs}{$_} ||= $subconf }
-      for ( expand_list(@rhs) );
-
-    # create the group hash even if empty
-    $groups{$lhs} = {} unless $groups{$lhs};
-}
-
-sub set_repolist {
-    my @in = @_;
-    @repolist = ();
-    # ...sanity checks
-    while (@in) {
-        $_ = shift @in;
-        if ( check_subconf_repo_disallowed( $subconf, $_ ) ) {
-            if ( exists $groups{$_} ) {
-                # groupname disallowed; try individual members now
-                ( my $g = $_ ) =~ s/^\@$subconf\./\@/;
-                _warn "expanding '$g'; this *may* slow down compilation";
-                unshift @in, keys %{ $groups{$_} };
-                next;
-            }
-            $ignored{$subconf}{$_} = 1;
-            next;
-        }
-
-        _warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
-        _die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
-
-        push @repolist, $_;
-    }
-}
-
-sub parse_refs {
-    my $refs = shift;
-    my @refs; @refs = split( ' ', $refs ) if $refs;
-    @refs = expand_list(@refs);
-
-    # if no ref is given, this PERM applies to all refs
-    @refs = qw(refs/.*) unless @refs;
-
-    # fully qualify refs that dont start with "refs/" or "VREF/";
-    # prefix them with "refs/heads/"
-    @refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs;
-
-    return @refs;
-}
-
-sub parse_users {
-    my $users = shift;
-    my @users = split ' ', $users;
-    do { _die "bad username '$_'" unless $_ =~ $USERNAME_PATT }
-      for @users;
-
-    return @users;
-}
-
-sub add_rule {
-    my ( $perm, $ref, $user, $fname, $lnum ) = @_;
-    _warn "doesn't make sense to supply a ref ('$ref') for 'R' rule"
-      if $perm eq 'R' and $ref ne 'refs/.*';
-    _warn "possible undeclared group '$user'"
-      if $user =~ /^@/
-      and not $groups{$user}
-      and not $rc{GROUPLIST_PGM}
-      and not special_group($user);
-    _die "bad ref '$ref'"   unless $ref =~ $REPOPATT_PATT;
-    _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
-
-    $nextseq++;
-    store_rule_info( $nextseq, $fname, $lnum );
-    for my $repo (@repolist) {
-        push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
-    }
-
-    sub special_group {
-        # ok perl doesn't really have lexical subs (at least not the older
-        # perls I want to support) but let's pretend...
-        my $g = shift;
-        $g =~ s/^\@//;
-        return 1 if $g eq 'all' or $g eq 'CREATOR';
-        return 1 if $rc{ROLES}{$g};
-        return 0;
-    }
-
-}
-
-sub add_config {
-    my ( $n, $key, $value ) = @_;
-
-    $nextseq++;
-    for my $repo (@repolist) {
-        push @{ $configs{$repo} }, [ $nextseq, $key, $value ];
-    }
-}
-
-sub set_subconf {
-    $subconf = shift;
-    _die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
-}
-
-# ----------------------------------------------------------------------
-
-sub expand_list {
-    my @list     = @_;
-    my @new_list = ();
-
-    for my $item (@list) {
-        if ( $item =~ /^@/ and $item ne '@all' )    # nested group
-        {
-            _die "undefined group '$item'" unless $groups{$item};
-            # add those names to the list
-            push @new_list, sort keys %{ $groups{$item} };
-        } else {
-            push @new_list, $item;
-        }
-    }
-
-    return @new_list;
-}
-
-sub new_repos {
-    trace(3);
-    _chdir( $rc{GL_REPO_BASE} );
-
-    # normal repos
-    my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } ( sort keys %repos, sort keys %configs );
-    # add in members of repo groups
-    map { push @repos, keys %{ $groups{$_} } } grep { /^@/ and $_ ne '@all' } keys %repos;
-
-    for my $repo ( @{ sort_u( \@repos ) } ) {
-        next unless $repo =~ $REPONAME_PATT;    # skip repo patterns
-        next if $repo =~ m(^\@|EXTCMD/);        # skip groups and fake repos
-
-        # use gl-conf as a sentinel
-        hook_1($repo) if -d "$repo.git" and not -f "$repo.git/gl-conf";
-
-        if ( not -d "$repo.git" ) {
-            push @{ $rc{NEW_REPOS_CREATED} }, $repo;
-            trigger( 'PRE_CREATE', $repo );
-            new_repo($repo);
-        }
-    }
-}
-
-sub new_repo {
-    my $repo = shift;
-    trace( 3, $repo );
-
-    _mkdir("$repo.git");
-    _chdir("$repo.git");
-    _system("git init --bare >&2");
-    _chdir( $rc{GL_REPO_BASE} );
-    hook_1($repo);
-}
-
-sub new_wild_repo {
-    my ( $repo, $user, $aa ) = @_;
-    _chdir( $rc{GL_REPO_BASE} );
-
-    trigger( 'PRE_CREATE', $repo, $user, $aa );
-    new_repo($repo);
-    _print( "$repo.git/gl-creator", $user );
-    trigger( 'POST_CREATE', $repo, $user, $aa );
-
-    _chdir( $rc{GL_ADMIN_BASE} );
-}
-
-sub hook_repos {
-    trace(3);
-
-    # all repos, all hooks
-    _chdir( $rc{GL_REPO_BASE} );
-    my $phy_repos = list_phy_repos(1);
-
-    for my $repo ( @{$phy_repos} ) {
-        hook_1($repo);
-    }
-}
-
-sub store {
-    trace(3);
-
-    # first write out the ones for the physical repos
-    _chdir( $rc{GL_REPO_BASE} );
-    my $phy_repos = list_phy_repos(1);
-
-    for my $repo ( @{$phy_repos} ) {
-        store_1($repo);
-    }
-
-    _chdir( $rc{GL_ADMIN_BASE} );
-    store_common();
-}
-
-sub parse_done {
-    for my $ig ( sort keys %ignored ) {
-        _warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
-    }
-
-    close_rule_info();
-}
-
-# ----------------------------------------------------------------------
-
-sub check_subconf_repo_disallowed {
-    # trying to set access for $repo (='foo')...
-    my ( $subconf, $repo ) = @_;
-    trace( 2, $subconf, $repo );
-
-    # processing the master config, not a subconf
-    return 0 if $subconf eq 'master';
-    # subconf is also called 'foo' (you're allowed to have a
-    # subconf that is only concerned with one repo)
-    return 0 if $subconf eq $repo;
-    # same thing in big-config-land; foo is just @foo now
-    return 0 if ( "\@$subconf" eq $repo );
-    my @matched = grep { $repo =~ /^$_$/ }
-      grep { $groups{"\@$subconf"}{$_} eq 'master' }
-      sort keys %{ $groups{"\@$subconf"} };
-    return 0 if @matched > 0;
-
-    trace( 2, "-> disallowed" );
-    return 1;
-}
-
-sub store_1 {
-    # warning: writes and *deletes* it from %repos and %configs
-    my ($repo) = shift;
-    trace( 3, $repo );
-    return unless ( $repos{$repo} or $configs{$repo} ) and -d "$repo.git";
-
-    my ( %one_repo, %one_config );
-
-    open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
-
-    my $dumped_data = '';
-    if ( $repos{$repo} ) {
-        $one_repo{$repo} = $repos{$repo};
-        delete $repos{$repo};
-        $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
-    }
-
-    if ( $configs{$repo} ) {
-        $one_config{$repo} = $configs{$repo};
-        delete $configs{$repo};
-        $dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] );
-    }
-
-    print $compiled_fh $dumped_data;
-    close $compiled_fh;
-
-    $split_conf{$repo} = 1;
-}
-
-sub store_common {
-    trace(3);
-    my $cc = "conf/gitolite.conf-compiled.pm";
-    my $compiled_fh = _open( ">", "$cc.new" );
-
-    my %patterns = ();
-
-    my $data_version = glrc('current-data-version');
-    trace( 3, "data_version = $data_version" );
-    print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
-
-    my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] );
-    $dumped_data .= Data::Dumper->Dump( [ \%configs ], [qw(*configs)] ) if %configs;
-
-    print $compiled_fh $dumped_data;
-
-    if (%groups) {
-        my %groups = %{ inside_out( \%groups ) };
-        $dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
-        print $compiled_fh $dumped_data;
-
-        # save patterns in %groups for faster handling of multiple repos, such
-        # as happens in the various POST_COMPILE scripts
-        for my $k ( keys %groups ) {
-            $patterns{groups}{$k} = 1 unless $k =~ $REPONAME_PATT;
-        }
-    }
-
-    print $compiled_fh Data::Dumper->Dump( [ \%patterns ], [qw(*patterns)] ) if %patterns;
-
-    print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
-
-    close $compiled_fh or _die "close compiled-conf failed: $!\n";
-    rename "$cc.new", $cc;
-}
-
-{
-    my $hook_reset = 0;
-
-    sub hook_1 {
-        my $repo = shift;
-        trace( 3, $repo );
-
-        # reset the gitolite supplied hooks, in case someone fiddled with
-        # them, but only once per run
-        if ( not $hook_reset ) {
-            _mkdir("$rc{GL_ADMIN_BASE}/hooks/common");
-            _mkdir("$rc{GL_ADMIN_BASE}/hooks/gitolite-admin");
-            _print( "$rc{GL_ADMIN_BASE}/hooks/common/update",              update_hook() );
-            _print( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update", post_update_hook() );
-            chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/common/update";
-            chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update";
-            $hook_reset++;
-        }
-
-        # propagate user-defined (custom) hooks to all repos
-        ln_sf( "$rc{LOCAL_CODE}/hooks/common", "*", "$repo.git/hooks" ) if $rc{LOCAL_CODE};
-
-        # override/propagate gitolite defined hooks for all repos
-        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
-        # override/propagate gitolite defined hooks for the admin repo
-        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
-    }
-}
-
-sub inside_out {
-    my $href = shift;
-    # input conf: @aa = bb cc <newline> @bb = @aa dd
-
-    my %ret = ();
-    while ( my ( $k, $v ) = each( %{$href} ) ) {
-        # $k is '@aa', $v is a href
-        for my $k2 ( keys %{$v} ) {
-            # $k2 is bb, then cc
-            push @{ $ret{$k2} }, $k;
-        }
-    }
-    return \%ret;
-    # %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]);
-}
-
-{
-    my $ri_fh = '';
-
-    sub store_rule_info {
-        $ri_fh = _open( ">", $rc{GL_ADMIN_BASE} . "/conf/rule_info" ) unless $ri_fh;
-        # $nextseq, $fname, $lnum
-        print $ri_fh join( "\t", @_ ) . "\n";
-    }
-
-    sub close_rule_info {
-        close $ri_fh or die "close rule_info file failed: $!";
-    }
-}
-
-1;
-
diff --git a/docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm b/docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm
deleted file mode 100644
index 986494b..0000000
--- a/docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm
+++ /dev/null
@@ -1,183 +0,0 @@
-# and now for something completely different...
-
-package SugarBox;
-
-sub run_sugar_script {
-    my ( $ss, $lref ) = @_;
-    do $ss if -r $ss;
-    $lref = sugar_script($lref);
-    return $lref;
-}
-
-# ----------------------------------------------------------------------
-
-package Gitolite::Conf::Sugar;
-
-# syntactic sugar for the conf file, including site-local macros
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  sugar
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Explode;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub sugar {
-    # gets a filename, returns a listref
-
-    my @lines = ();
-    explode( shift, 'master', \@lines );
-
-    my $lines;
-    $lines = \@lines;
-
-    # run through the sugar stack one by one
-
-    # first, user supplied sugar:
-    if ( exists $rc{SYNTACTIC_SUGAR} ) {
-        if ( ref( $rc{SYNTACTIC_SUGAR} ) ne 'ARRAY' ) {
-            _warn "bad syntax for specifying sugar scripts; see docs";
-        } else {
-            for my $s ( @{ $rc{SYNTACTIC_SUGAR} } ) {
-
-                # perl-ism; apart from keeping the full path separate from the
-                # simple name, this also protects %rc from change by implicit
-                # aliasing, which would happen if you touched $s itself
-                my $sfp = _which( "syntactic-sugar/$s", 'r' );
-
-                _warn("skipped sugar script '$s'"), next if not -r $sfp;
-                $lines = SugarBox::run_sugar_script( $sfp, $lines );
-                $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
-            }
-        }
-    }
-
-    # then our stuff:
-
-    $lines = rw_cdm($lines);
-    $lines = option($lines);       # must come after rw_cdm
-    $lines = owner_desc($lines);
-    $lines = name_vref($lines);
-    $lines = role_names($lines);
-
-    return $lines;
-}
-
-sub rw_cdm {
-    my $lines = shift;
-    my @ret;
-
-    # repo foo <...> RWC = ...
-    #   ->  option CREATE_IS_C = 1
-    # (and similarly DELETE_IS_D and MERGE_CHECK)
-    # but only once per repo of course
-
-    my %seen = ();
-    for my $line (@$lines) {
-        push @ret, $line;
-        if ( $line =~ /^repo / ) {
-            %seen = ();
-        } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
-            my $perms = $1;
-            push @ret, "option DELETE_IS_D = 1" if $perms =~ /D/     and not $seen{D}++;
-            push @ret, "option CREATE_IS_C = 1" if $perms =~ /RW.*C/ and not $seen{C}++;
-            push @ret, "option MERGE_CHECK = 1" if $perms =~ /M/     and not $seen{M}++;
-        }
-    }
-    return \@ret;
-}
-
-sub option {
-    my $lines = shift;
-    my @ret;
-
-    # option foo = bar
-    #   ->  config gitolite-options.foo = bar
-
-    for my $line (@$lines) {
-        if ( $line =~ /^option (\S+) = (\S.*)/ ) {
-            push @ret, "config gitolite-options.$1 = $2";
-        } else {
-            push @ret, $line;
-        }
-    }
-    return \@ret;
-}
-
-sub owner_desc {
-    my $lines = shift;
-    my @ret;
-
-    # owner = "owner name"
-    #   ->  config gitweb.owner = owner name
-    # desc = "some long description"
-    #   ->  config gitweb.description = some long description
-    # category = "whatever..."
-    #   ->  config gitweb.category = whatever...
-
-    for my $line (@$lines) {
-        if ( $line =~ /^desc = (\S.*)/ ) {
-            push @ret, "config gitweb.description = $1";
-        } elsif ( $line =~ /^owner = (\S.*)/ ) {
-            push @ret, "config gitweb.owner = $1";
-        } elsif ( $line =~ /^category = (\S.*)/ ) {
-            push @ret, "config gitweb.category = $1";
-        } else {
-            push @ret, $line;
-        }
-    }
-    return \@ret;
-}
-
-sub name_vref {
-    my $lines = shift;
-    my @ret;
-
-    # <perm> NAME/foo = <user>
-    #   ->  <perm> VREF/NAME/foo = <user>
-
-    for my $line (@$lines) {
-        if ( $line =~ /^(-|R\S+) \S.* = \S.*/ ) {
-            $line =~ s( NAME/)( VREF/NAME/)g;
-        }
-        push @ret, $line;
-    }
-    return \@ret;
-}
-
-sub role_names {
-    my $lines = shift;
-    my @ret;
-
-    # <perm> [<ref>] = <user list containing CREATOR|READERS|WRITERS>
-    #   ->  same but with "@" prepended to rolenames
-
-    for my $line (@$lines) {
-        if ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
-            my ( $p, $r ) = ( $1, $2 );
-            my $u = '';
-            for ( split ' ', $3 ) {
-                $_ = "\@$_" if $_ eq 'CREATOR' or $rc{ROLES}{$_};
-                $u .= " $_";
-            }
-            $r ||= '';
-            # mind the spaces (or play safe and run cleanup_conf_line again)
-            push @ret, cleanup_conf_line("$p $r = $u");
-        } else {
-            push @ret, $line;
-        }
-    }
-    return \@ret;
-}
-
-1;
-
diff --git a/docker/gitolite/src/lib/Gitolite/Easy.pm b/docker/gitolite/src/lib/Gitolite/Easy.pm
deleted file mode 100644
index 8f530f2..0000000
--- a/docker/gitolite/src/lib/Gitolite/Easy.pm
+++ /dev/null
@@ -1,240 +0,0 @@
-package Gitolite::Easy;
-
-# easy access to gitolite from external perl programs
-# ----------------------------------------------------------------------
-# most/all functions in this module test $ENV{GL_USER}'s rights and
-# permissions so it needs to be set.
-
-# "use"-ing this module
-# ----------------------------------------------------------------------
-# Using this module from within a gitolite trigger or command is easy; you
-# just need 'use lib $ENV{GL_LIBDIR};' before the 'use Gitolite::Easy;'.
-#
-# Using it from something completely outside gitolite requires a bit more
-# work.  First, run 'gitolite query-rc -a' to find the correct values for
-# GL_BINDIR and GL_LIBDIR in your installation.  Then use this code in your
-# external program, using the paths you just found:
-#
-#   BEGIN {
-#       $ENV{HOME} = "/home/git";   # or whatever is the hosting user's $HOME
-#       $ENV{GL_BINDIR} = "/full/path/to/gitolite/src";
-#       $ENV{GL_LIBDIR} = "/full/path/to/gitolite/src/lib";
-#   }
-#   use lib $ENV{GL_LIBDIR};
-#   use Gitolite::Easy;
-
-# API documentation
-# ----------------------------------------------------------------------
-# documentation for each function is at the top of the function.
-# Documentation is NOT in pod format; just read the source with a nice syntax
-# coloring text editor and you'll be happy enough.  (I do not like POD; please
-# don't send me patches for this aspect of the module).
-
-#<<<
- at EXPORT = qw(
-  is_admin
-  is_super_admin
-  in_group
-  in_role
-
-  owns
-  can_read
-  can_write
-
-  config
-
-  textfile
-
-  %rc
-  say
-  say2
-  _die
-  _warn
-  _print
-  usage
-
-  option
-);
-#>>>
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-my $user;
-
-# ----------------------------------------------------------------------
-
-# is_admin()
-
-# return true if $ENV{GL_USER} is set and has W perms to the admin repo
-
-# shell equivalent
-#   if gitolite access -q gitolite-admin $GL_USER W; then ...
-
-sub is_admin {
-    valid_user();
-    return not( access( 'gitolite-admin', $user, 'W', 'any' ) =~ /DENIED/ );
-}
-
-# is_super_admin()
-
-# (useful only if you are using delegation)
-
-# return true if $ENV{GL_USER} is set and has W perms to any file in the admin
-# repo
-
-# shell equivalent
-#   if gitolite access -q gitolite-admin $GL_USER W VREF/NAME/; then ...
-sub is_super_admin {
-    valid_user();
-    return not( access( 'gitolite-admin', $user, 'W', 'VREF/NAME/' ) =~ /DENIED/ );
-}
-
-# in_group()
-
-# return true if $ENV{GL_USER} is set and is in the given group
-
-# shell equivalent
-#   if gitolite list-memberships $GL_USER | grep -x $GROUPNAME >/dev/null; then ...
-sub in_group {
-    valid_user();
-    my $g = shift;
-    $g =~ s/^\@?/@/;
-
-    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships( '-u', $user ) };
-}
-
-# in_role()
-
-# return true if $ENV{GL_USER} is set and has the given role for the given repo
-
-# shell equivalent
-#   if gitolite list-memberships -u $GL_USER -r $GL_REPO | grep -x $ROLENAME >/dev/null; then ...
-sub in_role {
-    valid_user();
-    my $r = shift;
-    $r =~ s/^\@?/@/;
-    my $repo = shift;
-
-    return grep { $_ eq $r } @{ Gitolite::Conf::Load::list_memberships( "-u", $user, "-r", $repo ) };
-}
-
-# owns()
-
-# return true if $ENV{GL_USER} is set and is an OWNER of the given repo.
-
-# shell equivalent (assuming GL_USER is set)
-#   if gitolite owns $REPONAME; then ...
-sub owns {
-    valid_user();
-    my $r = shift;
-
-    # prevent unnecessary disclosure of repo existence info
-    return 0 if repo_missing($r);
-
-    return ( creator($r) eq $user or $rc{OWNER_ROLENAME} and in_role( $rc{OWNER_ROLENAME}, $r ) );
-}
-
-# can_read()
-# return true if $ENV{GL_USER} is set and can read the given repo
-
-# shell equivalent
-#   if gitolite access -q $REPONAME $GL_USER R; then ...
-sub can_read {
-    valid_user();
-    my $r = shift;
-    return not( access( $r, $user, 'R', 'any' ) =~ /DENIED/ );
-}
-
-# can_write()
-# return true if $ENV{GL_USER} is set and can write to the given repo.
-# Optional second argument can be '+' to check that instead of 'W'.  Optional
-# third argument can be a full ref name instead of 'any'.
-
-# shell equivalent
-#   if gitolite access -q $REPONAME $GL_USER W; then ...
-sub can_write {
-    valid_user();
-    my ( $r, $aa, $ref ) = @_;
-    $aa  ||= 'W';
-    $ref ||= 'any';
-    return not( access( $r, $user, $aa, $ref ) =~ /DENIED/ );
-}
-
-# config()
-# given a repo and a key, return a hash containing all the git config
-# variables for that repo where the section+key match the regex.  If none are
-# found, return an empty hash.  If you don't want it as a regex, use \Q
-# appropriately
-
-# shell equivalent
-#   foo=$(gitolite git-config -r $REPONAME foo\\.bar)
-sub config {
-    my $repo = shift;
-    my $key  = shift;
-
-    return () if repo_missing($repo);
-
-    my $ret = git_config( $repo, $key );
-    return %$ret;
-}
-
-# ----------------------------------------------------------------------
-
-# maintain a textfile; see comments in code for details, and calls in various
-# other programs (like 'motd', 'desc', and 'readme') for how to call
-sub textfile {
-    my %h = @_;
-    my $repodir;
-
-    # target file
-    _die "need file" unless $h{file};
-    _die "'$h{file}' contains a '/'" if $h{file} =~ m(/);
-    Gitolite::Conf::Load::sanity($h{file}, $REPONAME_PATT);
-
-    # target file's location.  This can come from one of two places: dir
-    # (which comes from our code, so does not need to be sanitised), or repo,
-    # which may come from the user
-    _die "need exactly one of repo or dir" unless $h{repo} xor $h{dir};
-    _die "'$h{dir}' does not exist" if $h{dir} and not -d $h{dir};
-    if ($h{repo}) {
-        Gitolite::Conf::Load::sanity($h{repo}, $REPONAME_PATT);
-        $h{dir} = "$rc{GL_REPO_BASE}/$h{repo}.git";
-        _die "repo '$h{repo}' does not exist" if not -d $h{dir};
-
-        my $umask = option( $h{repo}, 'umask' );
-        # note: using option() moves us to ADMIN_BASE, but we don't care here
-        umask oct($umask) if $umask;
-    }
-
-    # final full file name
-    my $f = "$h{dir}/$h{file}";
-
-    # operation
-    _die "can't have both prompt and text" if defined $h{prompt} and defined $h{text};
-    if (defined $h{prompt}) {
-        print STDERR $h{prompt};
-        my $t = join( "", <> );
-        _print($f, $t);
-    } elsif (defined $h{text}) {
-        _print($f, $h{text});
-    } else {
-        return slurp($f) if -f $f;
-    }
-
-    return '';
-}
-
-# ----------------------------------------------------------------------
-
-sub valid_user {
-    _die "GL_USER not set" unless exists $ENV{GL_USER};
-    $user = $ENV{GL_USER};
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm b/docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm
deleted file mode 100644
index 2eeefcc..0000000
--- a/docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ /dev/null
@@ -1,74 +0,0 @@
-package Gitolite::Hooks::PostUpdate;
-
-# everything to do with the post-update hook
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  post_update
-  post_update_hook
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub post_update {
-    trace( 3, 'post-up', @ARGV );
-    # this is the *real* post_update hook for gitolite
-
-    tsh_try("git ls-tree --name-only master");
-    _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/m;
-
-    my $hooks_changed = 0;
-    {
-        local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
-
-        tsh_try("git diff --name-only master");
-        $hooks_changed++ if tsh_text() =~ m(/hooks/common/);
-        # the leading slash ensure that this hooks/common directory is below
-        # some top level directory, not *at* the top.  That's LOCAL_CODE, and
-        # it's actual name could be anything but it doesn't matter to us.
-
-        tsh_try("git checkout -f --quiet master");
-    }
-    _system("gitolite compile");
-    _system("gitolite setup --hooks-only") if $hooks_changed;
-    _system("gitolite trigger POST_COMPILE");
-
-    exit 0;
-}
-
-{
-    my $text = '';
-
-    sub post_update_hook {
-        if ( not $text ) {
-            local $/ = undef;
-            $text = <DATA>;
-        }
-        return $text;
-    }
-}
-
-1;
-
-__DATA__
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Hooks::PostUpdate;
-
-# gitolite post-update hook (only for the admin repo)
-# ----------------------------------------------------------------------
-
-post_update();          # is not expected to return
-exit 1;                 # so if it does, something is wrong
diff --git a/docker/gitolite/src/lib/Gitolite/Hooks/Update.pm b/docker/gitolite/src/lib/Gitolite/Hooks/Update.pm
deleted file mode 100644
index 32cd6e0..0000000
--- a/docker/gitolite/src/lib/Gitolite/Hooks/Update.pm
+++ /dev/null
@@ -1,170 +0,0 @@
-package Gitolite::Hooks::Update;
-
-# everything to do with the update hook
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  update
-  update_hook
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub update {
-    # this is the *real* update hook for gitolite
-
-    bypass() if $ENV{GL_BYPASS_ACCESS_CHECKS};
-
-    my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
-
-    trace( 2, $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
-
-    my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
-    _die $ret if $ret =~ /DENIED/;
-
-    check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
-
-    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, $ret );
-    exit 0;
-}
-
-sub bypass {
-    require Cwd;
-    Cwd->import;
-    gl_log( 'update', getcwd(), '(' . ( $ENV{USER} || '?' ) . ')', 'bypass', @ARGV );
-    exit 0;
-}
-
-sub check_vrefs {
-    my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
-    my $name_seen = 0;
-    my $n_vrefs   = 0;
-    for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
-        $n_vrefs++;
-        if ( $vref =~ m(^VREF/NAME/) ) {
-            # this one is special; we process it right here, and only once
-            next if $name_seen++;
-
-            for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
-                check_vref( $aa, $ref );
-            }
-        } else {
-            my ( $dummy, $pgm, @args ) = split '/', $vref;
-            $pgm = _which( "VREF/$pgm", 'x' );
-            $pgm or _die "'$vref': helper program missing or unexecutable";
-
-            open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
-            while (<$fh>) {
-                # print non-vref lines and skip processing (for example,
-                # normal STDOUT by a normal update hook)
-                unless (m(^VREF/)) {
-                    print;
-                    next;
-                }
-                my ( $ref, $deny_message ) = split( ' ', $_, 2 );
-                check_vref( $aa, $ref, $deny_message );
-            }
-            close($fh) or _die $!
-              ? "Error closing sort pipe: $!"
-              : "$vref: helper program exit status $?";
-        }
-    }
-    return $n_vrefs;
-}
-
-sub check_vref {
-    my ( $aa, $ref, $deny_message ) = @_;
-
-    my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
-    if ( $ret =~ /by fallthru/ ) {
-        trace( 3, "remember, fallthru is success here!" );
-        return;
-    }
-    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
-    _die "$ret" . ( $deny_message ? "\n$deny_message" : '' ) if $ret =~ /DENIED/;
-}
-
-{
-    my $text = '';
-
-    sub update_hook {
-        if ( not $text ) {
-            local $/ = undef;
-            $text = <DATA>;
-        }
-        return $text;
-    }
-}
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my ( $ref, $oldsha, $newsha ) = @_;
-    my ( $oldtree, $newtree, $aa );
-
-    # this is special to git -- the hash of an empty tree
-    my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
-    $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
-    $newtree = $newsha eq '0' x 40 ? $empty : $newsha;
-
-    my $merge_base = '0' x 40;
-    # for branch create or delete, merge_base stays at '0'x40
-    chomp( $merge_base = `git merge-base $oldsha $newsha` )
-      unless $oldsha eq '0' x 40
-      or $newsha eq '0' x 40;
-
-    $aa = 'W';
-    # tag rewrite
-    $aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 );
-    # non-ff push to ref (including ref delete)
-    $aa = '+' if $oldsha ne $merge_base;
-
-    $aa = 'D' if ( option( $ENV{GL_REPO}, 'DELETE_IS_D' ) ) and $newsha eq '0' x 40;
-    $aa = 'C' if ( option( $ENV{GL_REPO}, 'CREATE_IS_C' ) ) and $oldsha eq '0' x 40;
-
-    # and now "M" commits.  All the other accesses (W, +, C, D) were mutually
-    # exclusive in some sense.  Sure a W could be a C or a + could be a D but
-    # that's by design.  A merge commit, however, could still be any of the
-    # others (except a "D").
-
-    # so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
-    # effect and this push contains a merge inside)
-
-    if ( option( $ENV{GL_REPO}, 'MERGE_CHECK' ) ) {
-        if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
-            _warn "ref create/delete ignored for purposes of merge-check\n";
-        } else {
-            $aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
-        }
-    }
-
-    return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
-}
-
-1;
-
-__DATA__
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Hooks::Update;
-
-# gitolite update hook
-# ----------------------------------------------------------------------
-
-update();               # is not expected to return
-exit 1;                 # so if it does, something is wrong
diff --git a/docker/gitolite/src/lib/Gitolite/Rc.pm b/docker/gitolite/src/lib/Gitolite/Rc.pm
deleted file mode 100644
index 9fd94b5..0000000
--- a/docker/gitolite/src/lib/Gitolite/Rc.pm
+++ /dev/null
@@ -1,684 +0,0 @@
-package Gitolite::Rc;
-
-# everything to do with 'rc'.  Also defines some 'constants'
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  %rc
-  glrc
-  query_rc
-  version
-  greeting
-  trigger
-  _which
-
-  $REMOTE_COMMAND_PATT
-  $REF_OR_FILENAME_PATT
-  $REPONAME_PATT
-  $REPOPATT_PATT
-  $USERNAME_PATT
-  $UNSAFE_PATT
-);
-
-use Exporter 'import';
-
-use Gitolite::Common;
-
-# ----------------------------------------------------------------------
-
-our %rc;
-our $non_core;
-
-# ----------------------------------------------------------------------
-
-# pre-populate some important rc keys
-# ----------------------------------------------------------------------
-
-$rc{GL_BINDIR} = $ENV{GL_BINDIR};
-$rc{GL_LIBDIR} = $ENV{GL_LIBDIR};
-
-# these keys could be overridden by the rc file later
-$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
-$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
-$rc{LOG_TEMPLATE}  = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
-
-# variables that should probably never be changed but someone will want to, I'll bet...
-# ----------------------------------------------------------------------
-
-#<<<
-$REMOTE_COMMAND_PATT  =                qr(^[-0-9a-zA-Z._\@/+ :,\%=]*$);
-$REF_OR_FILENAME_PATT =     qr(^[0-9a-zA-Z][-0-9a-zA-Z._\@/+ :,]*$);
-$REPONAME_PATT        =  qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@/+]*$);
-$REPOPATT_PATT        = qr(^\@?[[0-9a-zA-Z][-0-9a-zA-Z._\@/+\\^$|()[\]*?{},]*$);
-$USERNAME_PATT        =  qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@+]*$);
-
-$UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
-#>>>
-
-# ----------------------------------------------------------------------
-
-# find the rc file and 'do' it
-# ----------------------------------------------------------------------
-my $current_data_version = "3.2";
-
-my $rc = glrc('filename');
-if ( -r $rc and -s $rc ) {
-    do $rc or die $@;
-}
-if ( defined($GL_ADMINDIR) ) {
-    say2 "";
-    say2 "FATAL: '$rc' seems to be for older gitolite; please see\nhttp://gitolite.com/gitolite/migr.html";
-
-    exit 1;
-}
-
-# let values specified in rc file override our internal ones
-# ----------------------------------------------------------------------
- at rc{ keys %RC } = values %RC;
-
-# expand the non_core list into INPUT, PRE_GIT, etc using 'ENABLE' settings
-non_core_expand() if $rc{ENABLE};
-
-# add internal triggers
-# ----------------------------------------------------------------------
-
-# is the server/repo in a writable state (i.e., not down for maintenance etc)
-unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
-
-# (testing only) override the rc file silently
-# ----------------------------------------------------------------------
-# use an env var that is highly unlikely to appear in real life :)
-do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
-
-# setup some perl/rc/env vars, plus umask
-# ----------------------------------------------------------------------
-
-umask ( $rc{UMASK} || 0077 );
-
-unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
-
-$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}" unless $ENV{PATH} =~ /^$ENV{GL_BINDIR}:/;
-
-{
-    $rc{GL_TID} = $ENV{GL_TID} ||= $$;
-    # TID: loosely, transaction ID.  The first PID at the entry point passes
-    # it down to all its children so you can track each access, across all the
-    # various commands it spawns and actions it generates.
-
-    $rc{GL_LOGFILE} = $ENV{GL_LOGFILE} ||= gen_lfn( $rc{LOG_TEMPLATE} );
-}
-
-# these two are meant to help externally written commands (see
-# src/commands/writable for an example)
-$ENV{GL_REPO_BASE}  = $rc{GL_REPO_BASE};
-$ENV{GL_ADMIN_BASE} = $rc{GL_ADMIN_BASE};
-
-# ----------------------------------------------------------------------
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my $glrc_default_text = '';
-{
-    local $/ = undef;
-    $glrc_default_text = <DATA>;
-}
-
-# ----------------------------------------------------------------------
-
-sub non_core_expand {
-    my %enable;
-
-    for my $e ( @{ $rc{ENABLE} } ) {
-        my ( $name, $arg ) = split ' ', $e, 2;
-        # store args as the hash value for the name
-        $enable{$name} = $arg || '';
-
-        # for now, we pretend everything is a command, because commands
-        # are the only thing that the non_core list does not contain
-        $rc{COMMANDS}{$name} = $arg || 1;
-    }
-
-    # bring in additional non-core specs from the rc file, if given
-    if ( my $nc2 = $rc{NON_CORE} ) {
-        for ( $non_core, $nc2 ) {
-            # beat 'em into shape :)
-            s/#.*//g;
-            s/[ \t]+/ /g; s/^ //mg; s/ $//mg;
-            s/\n+/\n/g;
-        }
-
-        for ( split "\n", $nc2 ) {
-            next unless /\S/;
-            my ( $name, $where, $module, $before, $name2 ) = split ' ', $_;
-            if ( not $before ) {
-                $non_core .= "$name $where $module\n";
-                next;
-            }
-            die if $before ne 'before';
-            $non_core =~ s(^(?=$name2 $where( |$)))($name $where $module\n)m;
-        }
-    }
-
-    my @data = split "\n", $non_core || '';
-    for (@data) {
-        next if /^\s*(#|$)/;
-        my ( $name, $where, $module ) = split ' ', $_;
-
-        # if it appears here, it's not a command, so delete it.  At the end of
-        # this loop, what's left in $rc{COMMANDS} will be those names in the
-        # enable list that do not appear in the non_core list.
-        delete $rc{COMMANDS}{$name};
-
-        next unless exists $enable{$name};
-
-        # module to call is name if specified as "."
-        $module = $name if $module eq ".";
-
-        # module to call is "name::pre_git" or such if specified as "::"
-        ( $module = $name ) .= "::" . lc($where) if $module eq '::';
-
-        # append arguments, if supplied
-        $module .= " $enable{$name}" if $enable{$name};
-
-        push @{ $rc{$where} }, $module;
-    }
-
-    # finally, add in commands that were declared in the non-core list
-    map { /^(\S+)/; $rc{COMMANDS}{$1} = 1 } @{ $rc{COMMAND} };
-}
-
-# exported functions
-# ----------------------------------------------------------------------
-
-sub glrc {
-    my $cmd = shift;
-    if ( $cmd eq 'default-filename' ) {
-        return "$ENV{HOME}/.gitolite.rc";
-    } elsif ( $cmd eq 'default-text' ) {
-        return $glrc_default_text if $glrc_default_text;
-        _die "rc file default text not set; this should not happen!";
-    } elsif ( $cmd eq 'filename' ) {
-        # where is the rc file?
-
-        # search $HOME first
-        return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
-
-        return '';
-    } elsif ( $cmd eq 'current-data-version' ) {
-        return $current_data_version;
-    } else {
-        _die "unknown argument to glrc: '$cmd'";
-    }
-}
-
-my $all   = 0;
-my $dump  = 0;
-my $nonl  = 0;
-my $quiet = 0;
-
-sub query_rc {
-
-    my @vars = args();
-
-    no strict 'refs';
-
-    if ($all) {
-        for my $e ( sort keys %rc ) {
-            print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
-        }
-        exit 0;
-    }
-
-    if ($dump) {
-        require Data::Dumper;
-        $Data::Dumper::Sortkeys = 1;
-        print Data::Dumper::Dumper \%rc;
-        exit 0;
-    }
-
-    my $cv = \%rc;    # current "value"
-    while (@vars) {
-        my $v = shift @vars;
-
-        # dig into the rc hash, using each var as a component
-        if ( not ref($cv) ) {
-            _warn "unused arguments...";
-            last;
-        } elsif ( ref($cv) eq 'HASH' ) {
-            $cv = $cv->{$v} || '';
-        } elsif ( ref($cv) eq 'ARRAY' ) {
-            $cv = $cv->[$v] || '';
-        } else {
-            _die "dont know what to do with " . ref($cv) . " item in the rc file";
-        }
-    }
-
-    # we've run out of arguments so $cv is what we have.  If we're supposed to
-    # be quiet, we don't have to print anything so let's get that done first:
-    exit( $cv ? 0 : 1 ) if $quiet;    # shell truth
-
-    # print values (notice we ignore the '-n' option if it's a ref)
-    if ( ref($cv) eq 'HASH' ) {
-        print join( "\n", sort keys %$cv ), "\n" if %$cv;
-    } elsif ( ref($cv) eq 'ARRAY' ) {
-        print join( "\n", @$cv ), "\n" if @$cv;
-    } else {
-        print $cv . ( $nonl ? '' : "\n" ) if $cv;
-    }
-    exit( $cv ? 0 : 1 );              # shell truth
-}
-
-sub version {
-    my $version = '';
-    $version = '(unknown)';
-    for ("$ENV{GL_BINDIR}/VERSION") {
-        $version = slurp($_) if -r $_;
-    }
-    chomp($version);
-    return $version;
-}
-
-sub greeting {
-    my $json = shift;
-
-    chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
-    my $gv = substr( `git --version`, 12 );
-    my $gl_user = $ENV{GL_USER} || '';
-    $gl_user = " $gl_user" if $gl_user;
-
-    if ($json) {
-        $json->{GL_USER}          = $ENV{GL_USER};
-        $json->{USER}             = ( $ENV{USER} || "httpd" ) . "\@$hn";
-        $json->{gitolite_version} = version();
-        chomp( $json->{git_version} = $gv );    # this thing has a newline at the end
-        return;
-    }
-
-    # normal output
-    return "hello$gl_user, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
-}
-
-sub trigger {
-    my $rc_section = shift;
-
-    # if arg-2 (now arg-1, due to the 'shift' above) exists, it is a repo
-    # name, so setup env from options
-    require Gitolite::Conf::Load;
-    Gitolite::Conf::Load->import('env_options');
-    env_options( $_[0] ) if $_[0];
-
-    if ( exists $rc{$rc_section} ) {
-        if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
-            _die "'$rc_section' section in rc file is not a perl list";
-        } else {
-            for my $s ( @{ $rc{$rc_section} } ) {
-                my ( $pgm, @args ) = split ' ', $s;
-
-                if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
-
-                    require Gitolite::Triggers;
-                    trace( 2, 'trigger module', $module, $sub, @args, $rc_section, @_ );
-                    Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
-
-                } else {
-                    $pgm = _which( "triggers/$pgm", 'x' );
-
-                    _warn("skipped trigger '$s' (not found or not executable)"), next if not $pgm;
-                    trace( 2, 'trigger command', $s );
-                    _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
-                }
-            }
-        }
-        return;
-    }
-    trace( 3, "'$rc_section' not found in rc" );
-}
-
-sub _which {
-    # looks for a file in LOCAL_CODE or GL_BINDIR.  Returns whichever exists
-    # (LOCAL_CODE preferred if defined) or 0 if not found.
-    my $file = shift;
-    my $mode = shift;    # could be 'x' or 'r'
-
-    my @files = ("$rc{GL_BINDIR}/$file");
-    unshift @files, ("$rc{LOCAL_CODE}/$file") if $rc{LOCAL_CODE};
-
-    for my $f (@files) {
-        return $f if -x $f;
-        return $f if -r $f and $mode eq 'r';
-    }
-
-    return 0;
-}
-
-# ----------------------------------------------------------------------
-
-=for args
-Usage:  gitolite query-rc -a
-        gitolite query-rc -d
-        gitolite query-rc [-n] [-q] rc-variable
-
-    -a          print all variables and values (first level only)
-    -d          dump the entire rc structure
-    -n          do not append a newline if variable is scalar
-    -q          exit code only (shell truth; 0 is success)
-
-Query the rc hash.  Second and subsequent arguments dig deeper into the hash.
-The examples are for the default configuration; yours may be different.
-
-Single values:
-    gitolite query-rc GL_ADMIN_BASE     # prints "/home/git/.gitolite" or similar
-    gitolite query-rc UMASK             # prints "63" (that's 0077 in decimal!)
-
-Hashes:
-    gitolite query-rc COMMANDS
-        # prints "desc", "help", "info", "perms", "writable", one per line
-    gitolite query-rc COMMANDS help     # prints 1
-    gitolite query-rc -q COMMANDS help  # prints nothing; exit code is 0
-    gitolite query-rc COMMANDS fork     # prints nothing; exit code is 1
-
-Arrays (somewhat less useful):
-    gitolite query-rc POST_GIT          # prints nothing; exit code is 0
-    gitolite query-rc POST_COMPILE      # prints 4 lines
-    gitolite query-rc POST_COMPILE 0    # prints the first of those 4 lines
-
-Explore:
-    gitolite query-rc -a
-    # prints all first level variables and values, one per line.  Any that are
-    # listed as HASH or ARRAY can be explored further in subsequent commands.
-    gitolite query-rc -d                # dump the entire rc structure
-=cut
-
-sub args {
-    my $help = 0;
-
-    require Getopt::Long;
-    Getopt::Long::GetOptions(
-        'all|a'   => \$all,
-        'dump|d'  => \$dump,
-        'nonl|n'  => \$nonl,
-        'quiet|q' => \$quiet,
-        'help|h'  => \$help,
-    ) or usage();
-
-    _die("'-a' cannot be combined with other arguments or options; run with '-h' for usage") if $all and ( @ARGV or $dump or $nonl or $quiet );
-    usage() if not $all and not $dump and not @ARGV or $help;
-    return @ARGV;
-}
-
-# ----------------------------------------------------------------------
-
-BEGIN {
-    $non_core = "
-    # No user-servicable parts inside.  Warranty void if seal broken.  Refer
-    # servicing to authorised service center only.
-
-    continuation-lines      SYNTACTIC_SUGAR .
-    keysubdirs-as-groups    SYNTACTIC_SUGAR .
-    macros                  SYNTACTIC_SUGAR .
-    refex-expr              SYNTACTIC_SUGAR .
-
-    renice                  PRE_GIT         .
-
-    Kindergarten            INPUT           ::
-
-    CpuTime                 INPUT           ::
-    CpuTime                 POST_GIT        ::
-
-    Shell                   INPUT           ::
-
-    Alias                   INPUT           ::
-
-    Motd                    INPUT           ::
-    Motd                    PRE_GIT         ::
-    Motd                    COMMAND         motd
-
-    Mirroring               INPUT           ::
-    Mirroring               PRE_GIT         ::
-    Mirroring               POST_GIT        ::
-
-    refex-expr              ACCESS_2        RefexExpr::access_2
-
-    expand-deny-messages    ACCESS_1        .
-    expand-deny-messages    ACCESS_2        .
-
-    RepoUmask               PRE_GIT         ::
-    RepoUmask               POST_CREATE     ::
-
-    partial-copy            PRE_GIT         .
-
-    upstream                PRE_GIT         .
-
-    no-create-on-read       PRE_CREATE      AutoCreate::deny_R
-    no-auto-create          PRE_CREATE      AutoCreate::deny_RW
-
-    ssh-authkeys-split      POST_COMPILE    post-compile/ssh-authkeys-split
-    ssh-authkeys            POST_COMPILE    post-compile/ssh-authkeys
-    Shell                   POST_COMPILE    post-compile/ssh-authkeys-shell-users
-
-    set-default-roles       POST_CREATE     .
-
-    git-config              POST_COMPILE    post-compile/update-git-configs
-    git-config              POST_CREATE     post-compile/update-git-configs
-
-    gitweb                  POST_CREATE     post-compile/update-gitweb-access-list
-    gitweb                  POST_COMPILE    post-compile/update-gitweb-access-list
-
-    cgit                    POST_COMPILE    post-compile/update-description-file
-
-    daemon                  POST_CREATE     post-compile/update-git-daemon-access-list
-    daemon                  POST_COMPILE    post-compile/update-git-daemon-access-list
-
-    repo-specific-hooks     POST_COMPILE    .
-    repo-specific-hooks     POST_CREATE     .
-";
-}
-
-1;
-
-# ----------------------------------------------------------------------
-
-__DATA__
-# configuration variables for gitolite
-
-# This file is in perl syntax.  But you do NOT need to know perl to edit it --
-# just mind the commas, use single quotes unless you know what you're doing,
-# and make sure the brackets and braces stay matched up!
-
-# (Tip: perl allows a comma after the last item in a list also!)
-
-# HELP for commands can be had by running the command with "-h".
-
-# HELP for all the other FEATURES can be found in the documentation (look for
-# "list of non-core programs shipped with gitolite" in the master index) or
-# directly in the corresponding source file.
-
-%RC = (
-
-    # ------------------------------------------------------------------
-
-    # default umask gives you perms of '0700'; see the rc file docs for
-    # how/why you might change this
-    UMASK                           =>  0077,
-
-    # look for "git-config" in the documentation
-    GIT_CONFIG_KEYS                 =>  '',
-
-    # comment out if you don't need all the extra detail in the logfile
-    LOG_EXTRA                       =>  1,
-    # logging options
-    # 1. leave this section as is for 'normal' gitolite logging (default)
-    # 2. uncomment this line to log ONLY to syslog:
-    # LOG_DEST                      => 'syslog',
-    # 3. uncomment this line to log to syslog and the normal gitolite log:
-    # LOG_DEST                      => 'syslog,normal',
-    # 4. prefixing "repo-log," to any of the above will **also** log just the
-    #    update records to "gl-log" in the bare repo directory:
-    # LOG_DEST                      => 'repo-log,normal',
-    # LOG_DEST                      => 'repo-log,syslog',
-    # LOG_DEST                      => 'repo-log,syslog,normal',
-
-    # roles.  add more roles (like MANAGER, TESTER, ...) here.
-    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
-    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
-    ROLES => {
-        READERS                     =>  1,
-        WRITERS                     =>  1,
-    },
-
-    # enable caching (currently only Redis).  PLEASE RTFM BEFORE USING!!!
-    # CACHE                         =>  'Redis',
-
-    # ------------------------------------------------------------------
-
-    # rc variables used by various features
-
-    # the 'info' command prints this as additional info, if it is set
-        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
-
-    # the CpuTime feature uses these
-        # display user, system, and elapsed times to user after each git operation
-        # DISPLAY_CPU_TIME          =>  1,
-        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
-        # CPU_TIME_WARN_LIMIT       =>  0.1,
-
-    # the Mirroring feature needs this
-        # HOSTNAME                  =>  "foo",
-
-    # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
-        # CACHE_TTL                 =>  600,
-
-    # ------------------------------------------------------------------
-
-    # suggested locations for site-local gitolite code (see cust.html)
-
-        # this one is managed directly on the server
-        # LOCAL_CODE                =>  "$ENV{HOME}/local",
-
-        # or you can use this, which lets you put everything in a subdirectory
-        # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
-        # on this, see http://gitolite.com/gitolite/non-core.html#pushcode
-        # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
-
-    # ------------------------------------------------------------------
-
-    # List of commands and features to enable
-
-    ENABLE => [
-
-        # COMMANDS
-
-            # These are the commands enabled by default
-            'help',
-            'desc',
-            'info',
-            'perms',
-            'writable',
-
-            # Uncomment or add new commands here.
-            # 'create',
-            # 'fork',
-            # 'mirror',
-            # 'readme',
-            # 'sskm',
-            # 'D',
-
-        # These FEATURES are enabled by default.
-
-            # essential (unless you're using smart-http mode)
-            'ssh-authkeys',
-
-            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
-            'git-config',
-
-            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
-            'daemon',
-
-            # creates projects.list file; if you don't use gitweb, comment this out
-            'gitweb',
-
-        # These FEATURES are disabled by default; uncomment to enable.  If you
-        # need to add new ones, ask on the mailing list :-)
-
-        # user-visible behaviour
-
-            # prevent wild repos auto-create on fetch/clone
-            # 'no-create-on-read',
-            # no auto-create at all (don't forget to enable the 'create' command!)
-            # 'no-auto-create',
-
-            # access a repo by another (possibly legacy) name
-            # 'Alias',
-
-            # give some users direct shell access.  See documentation in
-            # sts.html for details on the following two choices.
-            # "Shell $ENV{HOME}/.gitolite.shell-users",
-            # 'Shell alice bob',
-
-            # set default roles from lines like 'option default.roles-1 = ...', etc.
-            # 'set-default-roles',
-
-            # show more detailed messages on deny
-            # 'expand-deny-messages',
-
-            # show a message of the day
-            # 'Motd',
-
-        # system admin stuff
-
-            # enable mirroring (don't forget to set the HOSTNAME too!)
-            # 'Mirroring',
-
-            # allow people to submit pub files with more than one key in them
-            # 'ssh-authkeys-split',
-
-            # selective read control hack
-            # 'partial-copy',
-
-            # manage local, gitolite-controlled, copies of read-only upstream repos
-            # 'upstream',
-
-            # updates 'description' file instead of 'gitweb.description' config item
-            # 'cgit',
-
-            # allow repo-specific hooks to be added
-            # 'repo-specific-hooks',
-
-        # performance, logging, monitoring...
-
-            # be nice
-            # 'renice 10',
-
-            # log CPU times (user, system, cumulative user, cumulative system)
-            # 'CpuTime',
-
-        # syntactic_sugar for gitolite.conf and included files
-
-            # allow backslash-escaped continuation lines in gitolite.conf
-            # 'continuation-lines',
-
-            # create implicit user groups from directory names in keydir/
-            # 'keysubdirs-as-groups',
-
-            # allow simple line-oriented macros
-            # 'macros',
-
-        # Kindergarten mode
-
-            # disallow various things that sensible people shouldn't be doing anyway
-            # 'Kindergarten',
-    ],
-
-);
-
-# ------------------------------------------------------------------------------
-# per perl rules, this should be the last line in such a file:
-1;
-
-# Local variables:
-# mode: perl
-# End:
-# vim: set syn=perl:
diff --git a/docker/gitolite/src/lib/Gitolite/Setup.pm b/docker/gitolite/src/lib/Gitolite/Setup.pm
deleted file mode 100644
index 43de5d9..0000000
--- a/docker/gitolite/src/lib/Gitolite/Setup.pm
+++ /dev/null
@@ -1,169 +0,0 @@
-package Gitolite::Setup;
-
-# implements 'gitolite setup'
-# ----------------------------------------------------------------------
-
-=for args
-Usage:  gitolite setup [<option>]
-
-Setup gitolite, compile conf, run the POST_COMPILE trigger (see rc file) and
-propagate hooks.
-
-    -a, --admin <name>          admin name
-    -pk, --pubkey <file>        pubkey file name
-    -ho, --hooks-only           skip other steps and just propagate hooks
-
-First run: either the pubkey or the admin name is *required*, depending on
-whether you're using ssh mode or http mode.
-
-Subsequent runs:
-
-  - Without options, 'gitolite setup' is a general "fix up everything" command
-    (for example, if you brought in repos from outside, or someone messed
-    around with the hooks, or you made an rc file change that affects access
-    rules, etc.)
-
-  - '-pk' can be used to replace the admin key; useful if you lost the admin's
-    private key but do have shell access to the server.
-
-  - '-ho' is mainly for scripting use.  Do not combine with other options.
-
-  - '-a' is ignored
-
-=cut
-
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  setup
-);
-
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Store;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub setup {
-    my ( $admin, $pubkey, $h_only, $argv ) = args();
-
-    unless ($h_only) {
-        setup_glrc();
-        setup_gladmin( $admin, $pubkey, $argv );
-
-        _system("gitolite compile");
-        _system("gitolite trigger POST_COMPILE");
-    }
-
-    hook_repos();    # all of them, just to be sure
-}
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my $admin  = '';
-    my $pubkey = '';
-    my $h_only = 0;
-    my $help   = 0;
-    my $argv   = join( " ", @ARGV );
-
-    require Getopt::Long;
-    Getopt::Long::GetOptions(
-        'admin|a=s'     => \$admin,
-        'pubkey|pk=s'   => \$pubkey,
-        'hooks-only|ho' => \$h_only,
-        'help|h'        => \$help,
-    ) or usage();
-
-    usage() if $help or ( $pubkey and $admin );
-    usage() if $h_only and ( $admin or $pubkey );
-
-    if ($pubkey) {
-        $pubkey =~ /\.pub$/                 or _die "'$pubkey' name does not end in .pub";
-        tsh_try("cat $pubkey")              or _die "'$pubkey' not a readable file";
-        tsh_lines() == 1                    or _die "'$pubkey' must have exactly one line";
-        tsh_try("ssh-keygen -l -f $pubkey") or _die "'$pubkey' does not seem to be a valid ssh pubkey file";
-
-        $admin = $pubkey;
-        # next 2 lines duplicated from args() in ssh-authkeys
-        $admin =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
-        $admin =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
-        $pubkey =~ /\@/ and print STDERR "NOTE: the admin username is '$admin'\n";
-
-    }
-
-    return ( $admin || '', $pubkey || '', $h_only || 0, $argv );
-}
-
-sub setup_glrc {
-    _print( glrc('default-filename'), glrc('default-text') ) if not glrc('filename');
-}
-
-sub setup_gladmin {
-    my ( $admin, $pubkey, $argv ) = @_;
-    _die "'-pk' or '-a' required; see 'gitolite setup -h' for more"
-      if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
-
-    # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
-    # $rc{GL_REPO_BASE}/gitolite-admin.git
-
-    # grab the pubkey content before we chdir() away
-    my $pubkey_content = '';
-    $pubkey_content = slurp($pubkey) if $pubkey;
-
-    # set up the admin files in admin-base
-
-    _mkdir( $rc{GL_ADMIN_BASE} );
-    _chdir( $rc{GL_ADMIN_BASE} );
-
-    _mkdir("conf");
-    _mkdir("logs");
-    my $conf;
-    {
-        local $/ = undef;
-        $conf = <DATA>;
-    }
-    $conf =~ s/%ADMIN/$admin/g;
-
-    _print( "conf/gitolite.conf", $conf ) if not -f "conf/gitolite.conf";
-
-    if ($pubkey) {
-        _mkdir("keydir");
-        _print( "keydir/$admin.pub", $pubkey_content );
-    }
-
-    # set up the admin repo in repo-base
-
-    _chdir();
-    _mkdir( $rc{GL_REPO_BASE} );
-    _chdir( $rc{GL_REPO_BASE} );
-
-    new_repo("gitolite-admin") if not -d "gitolite-admin.git";
-
-    # commit the admin files to the admin repo
-
-    $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
-    _chdir("$rc{GL_REPO_BASE}/gitolite-admin.git");
-    _system("git add conf/gitolite.conf");
-    _system("git add keydir") if $pubkey;
-    tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
-    tsh_try("git config --get user.name")  or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
-    tsh_try("git diff --cached --quiet")
-      or tsh_try("git commit -am 'gitolite setup $argv'")
-      or _die "setup failed to commit to the admin repo";
-    delete $ENV{GIT_WORK_TREE};
-}
-
-1;
-
-__DATA__
-repo gitolite-admin
-    RW+     =   %ADMIN
-
-repo testing
-    RW+     =   @all
diff --git a/docker/gitolite/src/lib/Gitolite/Test.pm b/docker/gitolite/src/lib/Gitolite/Test.pm
deleted file mode 100644
index 904abbf..0000000
--- a/docker/gitolite/src/lib/Gitolite/Test.pm
+++ /dev/null
@@ -1,122 +0,0 @@
-package Gitolite::Test;
-
-# functions for the test code to use
-# ----------------------------------------------------------------------
-
-#<<<
- at EXPORT = qw(
-  try
-  put
-  text
-  lines
-  dump
-  confreset
-  confadd
-  cmp
-  md5sum
-);
-#>>>
-use Exporter 'import';
-use File::Path qw(mkpath);
-use Carp qw(carp cluck croak confess);
-use Digest::MD5 qw(md5_hex);
-
-use Gitolite::Common;
-
-BEGIN {
-    require Gitolite::Test::Tsh;
-    *{'try'}   = \&Tsh::try;
-    *{'put'}   = \&Tsh::put;
-    *{'text'}  = \&Tsh::text;
-    *{'lines'} = \&Tsh::lines;
-    *{'cmp'}   = \&Tsh::cmp;
-}
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-# make sure the user is ready for it
-if ( not $ENV{GITOLITE_TEST} or $ENV{GITOLITE_TEST} ne 'y' ) {
-    print "Bail out! See t/README for information on how to run the tests.\n";
-    exit 255;
-}
-
-# required preamble for all tests
-try "
-    DEF gsh = /TRACE: gsh.SOC=/
-    DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/
-
-    DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone;
-    DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/
-    DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
-
-    DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file:///gitolite-admin/
-    DEF CHECK_SETUP = CS_1; git log; ok; /fa7564c1b903ea3dce49314753f25b34b9e0cea0/
-
-    DEF CLONE = glt clone %1 file:///%2
-    DEF PUSH  = glt push %1 origin
-
-    # clean install
-    mkdir -p $ENV{HOME}/bin
-    ln -sf $ENV{PWD}/t/glt ~/bin
-    ./install -ln
-    cd; rm -vrf .gito* repositories
-    git config --file $ENV{HOME}/.gitconfig.local user.name \"gitolite tester\"
-    git config --file $ENV{HOME}/.gitconfig.local user.email \"tester\@example.com\"
-    git config --global                           include.path \"~/.gitconfig.local\"
-
-    # setup
-    gitolite setup -a admin
-
-    # clone admin repo
-    cd tsh_tempdir
-    glt clone admin --progress file:///gitolite-admin
-    cd gitolite-admin
-" or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
-
-sub dump {
-    use Data::Dumper;
-    for my $i (@_) {
-        print STDERR "DBG: " . Dumper($i);
-    }
-}
-
-sub _confargs {
-    return @_ if ( $_[1] );
-    return 'gitolite.conf', $_[0];
-}
-
-sub confreset {
-    chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
-    system( "rm", "-rf", "conf" );
-    mkdir("conf");
-    system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga");
-    system("mv ~/repositories/testing.git        ~/repositories/.te");
-    system("find ~/repositories -name '*.git' |xargs rm -rf");
-    system("mv ~/repositories/.ga ~/repositories/gitolite-admin.git");
-    system("mv ~/repositories/.te ~/repositories/testing.git       ");
-    put "|cut -c9- > conf/gitolite.conf", '
-        repo    gitolite-admin
-            RW+     =   admin
-        repo    testing
-            RW+     =   @all
-';
-}
-
-sub confadd {
-    chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
-    my ( $file, $string ) = _confargs(@_);
-    put "|cat >> conf/$file", $string;
-}
-
-sub md5sum {
-    my $out = '';
-    for my $file (@_) {
-        $out .= md5_hex( slurp($file) ) . "  $file\n";
-    }
-    return $out;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Test/Tsh.pm b/docker/gitolite/src/lib/Gitolite/Test/Tsh.pm
deleted file mode 100644
index 670178f..0000000
--- a/docker/gitolite/src/lib/Gitolite/Test/Tsh.pm
+++ /dev/null
@@ -1,643 +0,0 @@
-#!/usr/bin/perl
-use 5.10.0;
-
-# Tsh -- non interactive Testing SHell in perl
-
-# TODO items:
-# - allow an RC file to be used to add basic and extended commands
-# - convert internal defaults to additions to the RC file
-# - implement shell commands as you go
-# - solve the "pass/fail" inconsistency between shell and perl
-# - solve the pipes problem (use 'overload'?)
-
-# ----------------------------------------------------------------------
-# modules
-
-package Tsh;
-
-use Exporter 'import';
- at EXPORT = qw(
-  try run cmp AUTOLOAD
-  rc error_count text lines error_list put
-  cd tsh_tempdir
-
-  $HOME $PWD $USER
-);
- at EXPORT_OK = qw();
-
-use Env qw(@PATH HOME PWD USER TSH_VERBOSE);
-# other candidates:
-# GL_ADMINDIR GL_BINDIR GL_RC GL_REPO_BASE_ABS GL_REPO GL_USER
-
-use strict;
-use warnings;
-
-use Text::Tabs;    # only used for formatting the usage() message
-use Text::ParseWords;
-
-use File::Temp qw(tempdir);
-END { chdir( $ENV{HOME} ); }
-# we need this END handler *after* the 'use File::Temp' above.  Without
-# this, if $PWD at exit was $tempdir, you get errors like "cannot remove
-# path when cwd is [...] at /usr/share/perl5/File/Temp.pm line 902".
-
-use Data::Dumper;
-
-# ----------------------------------------------------------------------
-# globals
-
-my $rc;      # return code from backticked (external) programs
-my $text;    # STDOUT+STDERR of backticked (external) programs
-my $lec;     # the last external command (the rc and text are from this)
-my $cmd;     # the current command
-
-my $testnum;     # current test number, for info in TAP output
-my $testname;    # current test name, for error info to user
-my $line;        # current line number and text
-
-my $err_count;   # count of test failures
-my @errors_in;   # list of testnames that errored
-
-my $tick;        # timestamp for git commits
-
-my %autoloaded;
-my $tempdir = '';
-
-# ----------------------------------------------------------------------
-# setup
-
-# unbuffer STDOUT and STDERR
-select(STDERR); $|++;
-select(STDOUT); $|++;
-
-# set the timestamp (needed only under harness)
-test_tick() if $ENV{HARNESS_ACTIVE};
-
-# ----------------------------------------------------------------------
-# this is for one-liner access from outside, using @ARGV, as in:
-#   perl -MTsh -e 'tsh()' 'tsh command list'
-# or via STDIN
-#   perl -MTsh -e 'tsh()' < file-containing-tsh-commands
-# NOTE: it **exits**!
-
-sub tsh {
-    my @lines;
-
-    if (@ARGV) {
-        # simple, single argument which is a readable filename
-        if ( @ARGV == 1 and $ARGV[0] !~ /\s/ and -r $ARGV[0] ) {
-            # take the contents of the file
-            @lines = <>;
-        } else {
-            # more than one argument *or* not readable filename
-            # just take the arguments themselves as the command list
-            @lines = @ARGV;
-            @ARGV  = ();
-        }
-    } else {
-        # no arguments given, take STDIN
-        usage() if -t;
-        @lines = <>;
-    }
-
-    # and process them
-    try(@lines);
-
-    # print error summary by default
-    if ( not defined $TSH_VERBOSE ) {
-        say STDERR "$err_count error(s)" if $err_count;
-    }
-
-    exit $err_count;
-}
-
-# these two get called with series of tsh commands, while the autoload,
-# (later) handles single commands
-
-sub try {
-    $line = $rc = $err_count = 0;
-    @errors_in = ();
-
-    # break up multiline arguments into separate lines
-    my @lines = map { split /\n/ } @_;
-
-    # and process them
-    rc_lines(@lines);
-
-    # bump err_count if the last command had a non-0 rc (that was apparently not checked).
-    $err_count++ if $rc;
-
-    # finish up...
-    dbg( 1, "$err_count error(s)" ) if $err_count;
-    return ( not $err_count );
-}
-
-# run() differs from try() in that
-#   -   uses open(), not backticks
-#   -   takes only one command, not tsh-things like ok, /patt/ etc
-#   -   -   if you pass it an array it uses the list form!
-
-sub run {
-    open( my $fh, "-|", @_ ) or die "tell sitaram $!";
-    local $/ = undef; $text = <$fh>;
-    close $fh; warn "tell sitaram $!" if $!;
-    $rc = ( $? >> 8 );
-    return $text;
-}
-
-sub put {
-    my ( $file, $data ) = @_;
-    die "probable quoting error in arguments to put: $file\n" if $file =~ /^\s*['"]/;
-    my $mode = ">";
-    $mode = "|-" if $file =~ s/^\s*\|\s*//;
-
-    $rc = 0;
-    my $fh;
-    open( $fh, $mode, $file )
-      and print $fh $data
-      and close $fh
-      and return 1;
-
-    $rc = 1;
-    dbg( 1, "put $file: $!" );
-    return '';
-}
-
-# ----------------------------------------------------------------------
-# TODO: AUTOLOAD and exportable convenience subs for common shell commands
-
-sub cd {
-    my $dir = shift || '';
-    _cd($dir);
-    dbg( 1, "cd $dir: $!" ) if $rc;
-    return ( not $rc );
-}
-
-# this is classic AUTOLOAD, almost from the perlsub manpage.  Although, if
-# instead of `ls('bin');` you want to be able to say `ls 'bin';` you will need
-# to predeclare ls, with `sub ls;`.
-sub AUTOLOAD {
-    my $program = $Tsh::AUTOLOAD;
-    dbg( 4, "program = $program, arg=$_[0]" );
-    $program =~ s/.*:://;
-    $autoloaded{$program}++;
-
-    die "tsh's autoload support expects only one arg\n" if @_ > 1;
-    _sh("$program $_[0]");
-    return ( not $rc );    # perl truth
-}
-
-# ----------------------------------------------------------------------
-# exportable service subs
-
-sub rc {
-    return $rc || 0;
-}
-
-sub text {
-    return $text || '';
-}
-
-sub lines {
-    return split /\n/, $text;
-}
-
-sub error_count {
-    return $err_count;
-}
-
-sub error_list {
-    return (
-        wantarray
-        ? @errors_in
-        : join( "\n", @errors_in )
-    );
-}
-
-sub tsh_tempdir {
-    # create tempdir if not already done
-    $tempdir = tempdir( "tsh_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 ) unless $tempdir;
-    # XXX TODO that 'UNLINK' doesn't work for Ctrl_C
-
-    return $tempdir;
-}
-
-# ----------------------------------------------------------------------
-# internal (non-exportable) service subs
-
-sub print_plan {
-    return unless $ENV{HARNESS_ACTIVE};
-    local $_ = shift;
-    say "1..$_";
-}
-
-sub rc_lines {
-    my @lines = @_;
-
-    while (@lines) {
-        local $_ = shift @lines;
-        chomp; $_ = trim_ws($_);
-
-        $line++;
-
-        # this also sets $testname
-        next if is_comment_or_empty($_);
-
-        dbg( 2, "L: $_" );
-        $line .= ": $_";    # save line for printing with 'FAIL:'
-
-        # a DEF has to be on a line by itself
-        if (/^DEF\s+([-.\w]+)\s*=\s*(\S.*)$/) {
-            def( $1, $2 );
-            next;
-        }
-
-        my @cmds = cmds($_);
-
-        # process each command
-        # (note: some of the commands may put stuff back into @lines)
-        while (@cmds) {
-            # this needs to be the 'global' one, since fail() prints it
-            $cmd = shift @cmds;
-
-            # is the current command a "testing" command?
-            my $testing_cmd = (
-                   $cmd =~ m(^ok(?:\s+or\s+(.*))?$)
-                or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$)
-                or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$)
-                or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$)
-            );
-
-            # warn if the previous command failed but rc is not being checked
-            if ( $rc and not $testing_cmd ) {
-                dbg( 1, "rc: $rc from cmd prior to '$cmd'\n" );
-                # count this as a failure, for exit status purposes
-                $err_count++;
-                # and reset the rc, otherwise for example 'ls foo; tt; tt; tt'
-                # will tell you there are 3 errors!
-                $rc = 0;
-                push @errors_in, $testname if $testname;
-            }
-
-            # prepare to run the command
-            dbg( 3, "C: $cmd" );
-            if ( def($cmd) ) {
-                # expand macro and replace head of @cmds (unshift)
-                dbg( 2, "DEF: $cmd" );
-                unshift @cmds, cmds( def($cmd) );
-            } else {
-                parse($cmd);
-            }
-            # reset rc if checking is done
-            $rc = 0 if $testing_cmd;
-            # assumes you will (a) never have *both* 'ok' and '!ok' after
-            # an action command, and (b) one of them will come immediately
-            # after the action command, with /patt/ only after it.
-        }
-    }
-}
-
-sub def {
-    my ( $cmd, $list ) = @_;
-    state %def;
-    %def = read_rc_file() unless %def;
-
-    if ($list) {
-        # set mode
-        die "attempt to redefine macro $cmd\n" if $def{$cmd};
-        $def{$cmd} = $list;
-        return;
-    }
-
-    # get mode: split the $cmd at spaces, see if there is a definition
-    # available, substitute any %1, %2, etc., in it and send it back
-    my ( $c, @d ) = shellwords($cmd);
-    my $e;    # the expanded value
-    if ( $e = $def{$c} ) {    # starting value
-        for my $i ( 1 .. 9 ) {
-            last unless $e =~ /%$i/;                              # no more %N's (we assume sanity)
-            die "$def{$c} requires more arguments\n" unless @d;
-            my $f = shift @d;                                     # get the next datum
-            $e =~ s/%$i/$f/g;                                     # and substitute %N all over
-        }
-        return join( " ", $e, @d );                               # join up any remaining data
-    }
-    return '';
-}
-
-sub _cd {
-    my $dir = shift || $HOME;
-    # a directory name of 'tsh_tempdir' is special
-    $dir = tsh_tempdir() if $dir eq 'tsh_tempdir';
-    $rc = 0;
-    chdir($dir) or $rc = 1;
-}
-
-sub _sh {
-    my $cmd = shift;
-    # TODO: switch to IPC::Open3 or something...?
-
-    dbg( 4, "  running: ( $cmd ) 2>&1" );
-    $text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
-    $lec  = $cmd;
-    dbg( 4, "  results:\n$text" );
-
-    if ( $text =~ /RC=(\d+)$/ ) {
-        $rc = $1;
-        $text =~ s/RC=\d+$//;
-    } else {
-        die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
-    }
-}
-
-sub _perl {
-    my $perl = shift;
-    local $_;
-    $_ = $text;
-
-    dbg( 4, "  eval: $perl" );
-    my $evrc = eval $perl;
-
-    if ($@) {
-        $rc = 1;    # shell truth
-        dbg( 1, $@ );
-        # leave $text unchanged
-    } else {
-        $rc = not $evrc;
-        # $rc is always shell truth, so we need to cover the case where
-        # there was no error but it still returned a perl false
-        $text = $_;
-    }
-    dbg( 4, "  eval-rc=$evrc, results:\n$text" );
-}
-
-sub parse {
-    my $cmd = shift;
-
-    if ( $cmd =~ /^sh (.*)/ ) {
-
-        _sh($1);
-
-    } elsif ( $cmd =~ /^perl (.*)/ ) {
-
-        _perl($1);
-
-    } elsif ( $cmd eq 'tt' or $cmd eq 'test-tick' ) {
-
-        test_tick();
-
-    } elsif ( $cmd =~ /^plan ?(\d+)$/ ) {
-
-        print_plan($1);
-
-    } elsif ( $cmd =~ /^cd ?(\S*)$/ ) {
-
-        _cd($1);
-
-    } elsif ( $cmd =~ /^ENV (\w+)=['"]?(.+?)['"]?$/ ) {
-
-        $ENV{$1} = $2;
-
-    } elsif ( $cmd =~ /^(?:tc|test-commit)\s+(\S.*)$/ ) {
-
-        # this is the only "git special" really; the default expansions are
-        # just that -- defaults.  But this one is hardwired!
-        dummy_commits($1);
-
-    } elsif ( $cmd =~ '^put(?:\s+(\S.*))?$' ) {
-
-        if ($1) {
-            put( $1, $text );
-        } else {
-            print $text if defined $text;
-        }
-
-    } elsif ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) ) {
-
-        $rc ? fail( "ok, rc=$rc from $lec", $1 || '' ) : ok();
-
-    } elsif ( $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) ) {
-
-        $rc ? ok() : fail( "!ok, rc=0 from $lec", $1 || '' );
-
-    } elsif ( $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) ) {
-
-        expect( $1, $2 );
-
-    } elsif ( $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ) {
-
-        not_expect( $1, $2 );
-
-    } else {
-
-        _sh($cmd);
-
-    }
-}
-
-# currently unused
-sub executable {
-    my $cmd = shift;
-    # path supplied
-    $cmd =~ m(/) and -x $cmd and return 1;
-    # barename; look up in $PATH
-    for my $p (@PATH) {
-        -x "$p/$cmd" and return 1;
-    }
-    return 0;
-}
-
-sub ok {
-    $testnum++;
-    say "ok ($testnum)" if $ENV{HARNESS_ACTIVE};
-}
-
-sub fail {
-    $testnum++;
-    say "not ok ($testnum)" if $ENV{HARNESS_ACTIVE};
-
-    my $die = 0;
-    my ( $msg1, $msg2 ) = @_;
-    if ($msg2) {
-        # if arg2 is non-empty, print it regardless of debug level
-        $die = 1 if $msg2 =~ s/^die //;
-        say STDERR "# $msg2";
-    }
-
-    local $TSH_VERBOSE = 1 if $ENV{TSH_ERREXIT};
-    dbg( 1, "FAIL: $msg1", $testname || '', "test number $testnum", "L: $line", "results:\n$text" );
-
-    # count the error and add the testname to the list if it is set
-    $err_count++;
-    push @errors_in, $testname if $testname;
-
-    return unless $die or $ENV{TSH_ERREXIT};
-    dbg( 1, "exiting at cmd $cmd\n" );
-
-    exit( $rc || 74 );
-}
-
-sub cmp {
-    # compare input string with second input string or text()
-    my $in = shift;
-    my $text = ( @_ ? +shift : text() );
-
-    if ( $text eq $in ) {
-        ok();
-    } else {
-        fail( 'cmp failed', '' );
-        dbg( 4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n" );
-    }
-}
-
-sub expect {
-    my ( $patt, $msg ) = @_;
-    $msg =~ s/^\s+// if $msg;
-    my $sm;
-    if ( $sm = sm($patt) ) {
-        dbg( 4, "  M: $sm" );
-        ok();
-    } else {
-        fail( "/$patt/", $msg || '' );
-    }
-}
-
-sub not_expect {
-    my ( $patt, $msg ) = @_;
-    $msg =~ s/^\s+// if $msg;
-    my $sm;
-    if ( $sm = sm($patt) ) {
-        dbg( 4, "  M: $sm" );
-        fail( "!/$patt/", $msg || '' );
-    } else {
-        ok();
-    }
-}
-
-sub sm {
-    # smart match?  for now we just do regex match
-    my $patt = shift;
-
-    return ( $text =~ qr($patt) ? $& : "" );
-}
-
-sub trim_ws {
-    local $_ = shift;
-    s/^\s+//; s/\s+$//;
-    return $_;
-}
-
-sub is_comment_or_empty {
-    local $_ = shift;
-    chomp; $_ = trim_ws($_);
-    if (/^##\s(.*)/) {
-        $testname = $1;
-        say "# $1";
-    }
-    return ( /^#/ or /^$/ );
-}
-
-sub cmds {
-    local $_ = shift;
-    chomp; $_ = trim_ws($_);
-
-    # split on unescaped ';'s, then unescape the ';' in the results
-    my @cmds = map { s/\\;/;/g; $_ } split /(?<!\\);/;
-    @cmds = grep { $_ = trim_ws($_); /\S/; } @cmds;
-    return @cmds;
-}
-
-sub dbg {
-    return unless $TSH_VERBOSE;
-    my $level = shift;
-    return unless $TSH_VERBOSE >= $level;
-    my $all = join( "\n", grep( /./, @_ ) );
-    chomp($all);
-    $all =~ s/\n/\n\t/g;
-    say STDERR "# $all";
-}
-
-sub ddump {
-    for my $i (@_) {
-        print STDERR "DBG: " . Dumper($i);
-    }
-}
-
-sub usage {
-    # TODO
-    print "Please see documentation at:
-
-        https://github.com/sitaramc/tsh/blob/master/README.mkd
-
-Meanwhile, here are your local 'macro' definitions:
-
-";
-    my %m = read_rc_file();
-    my @m = map { "$_\t$m{$_}\n" } sort keys %m;
-    $tabstop = 16;
-    print join( "", expand(@m) );
-    exit 1;
-}
-
-# ----------------------------------------------------------------------
-# git-specific internal service subs
-
-sub dummy_commits {
-    for my $f ( split ' ', shift ) {
-        if ( $f eq 'tt' or $f eq 'test-tick' ) {
-            test_tick();
-            next;
-        }
-        my $ts = ( $tick ? gmtime( $tick + 19800 ) : gmtime() );
-        _sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
-    }
-}
-
-sub test_tick {
-    unless ( $ENV{HARNESS_ACTIVE} ) {
-        sleep 1;
-        return;
-    }
-    $tick += 60 if $tick;
-    $tick ||= 1310000000;
-    $ENV{GIT_COMMITTER_DATE} = "$tick +0530";
-    $ENV{GIT_AUTHOR_DATE}    = "$tick +0530";
-}
-
-# ----------------------------------------------------------------------
-# the internal macros, for easy reference and reading
-
-sub read_rc_file {
-    my $rcfile = "$HOME/.tshrc";
-    my $rctext;
-    if ( -r $rcfile ) {
-        local $/ = undef;
-        open( my $rcfh, "<", $rcfile ) or die "this should not happen: $!\n";
-        $rctext = <$rcfh>;
-    } else {
-        # this is the default "rc" content
-        $rctext = "
-            add         =   git add
-            branch      =   git branch
-            clone       =   git clone
-            checkout    =   git checkout
-            commit      =   git commit
-            fetch       =   git fetch
-            init        =   git init
-            push        =   git push
-            reset       =   git reset
-            tag         =   git tag
-
-            empty       =   git commit --allow-empty -m empty
-            push-om     =   git push origin master
-            reset-h     =   git reset --hard
-            reset-hu    =   git reset --hard \@{u}
-        "
-    }
-
-    # ignore everything except lines of the form "aa = bb cc dd"
-    my %commands = ( $rctext =~ /^\s*([-.\w]+)\s*=\s*(\S.*)$/gm );
-    return %commands;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers.pm b/docker/gitolite/src/lib/Gitolite/Triggers.pm
deleted file mode 100644
index 16e8aa6..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package Gitolite::Triggers;
-
-# load and run triggered modules
-# ----------------------------------------------------------------------
-
-#<<<
- at EXPORT = qw(
-);
-#>>>
-use Exporter 'import';
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-sub run {
-    my ( $module, $sub, @args ) = @_;
-    $module = "Gitolite::Triggers::$module" if $module !~ /^Gitolite::/;
-
-    eval "require $module";
-    _die "$@" if $@;
-    my $subref;
-    eval "\$subref = \\\&$module" . "::" . "$sub";
-    _die "module '$module' does not exist or does not have sub '$sub'" unless ref($subref) eq 'CODE';
-
-    $subref->(@args);
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm
deleted file mode 100644
index 1fa24bb..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm
+++ /dev/null
@@ -1,128 +0,0 @@
-package Gitolite::Triggers::Alias;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# aliasing a repo to another
-# ----------------------------------------------------------------------
-
-=for usage
-
-Why:
-
-    We had an existing repo "foo" that lots of people use.  We wanted to
-    rename it to "foo/code", so that related repos "foo/upstream" and
-    "foo/docs" (both containing stuff we did not want to put in "foo") could
-    also be made and then the whole thing would be structured nicely.
-
-    At the same time we did not want to *force* all the users to change the
-    name.  At least git operations should still work with the old name,
-    although it is OK for "info" and other "commands" to display/require the
-    proper name (i.e., the new name).
-
-How:
-
-  * uncomment the line "Alias" in the "user-visible behaviour" section in the
-    rc file
-
-  * add a new variable REPO_ALIASES to the rc file, with entries like:
-
-        REPO_ALIASES                =>
-            {
-                # if you need a more aggressive warning message than the default
-                WARNING             => "Please change your URLs to use '%new'; '%old' will not work after XXXX-XX-XX",
-
-                # prefix mapping section
-                PREFIX_MAPS         =>  {
-                    # note: NO leading slash in keys or values below
-                    'var/lib/git/'  =>  '',
-                    'var/opt/git/'  =>  'opt/',
-                },
-
-                # individual repo mapping section
-                'foo'               =>  'foo/code',
-
-                # force users to change their URLs
-                'bar'               =>  '301/bar/code',
-                    # a target repo starting with "301/" won't actually work;
-                    # it will just produce an error message pointing the user
-                    # to the new name.  This allows admins to force users to
-                    # fix their URLs.
-            },
-
-    If a prefix map is supplied, each key is checked (in *undefined* order),
-    and the *first* key which matches the prefix of the repo will be applied.
-    If more than one key matches (for example if you specify '/abc/def' as one
-    key, and '/abc' as another), it is undefined which will get picked up.
-
-    The result of this, (or the original repo name if no map was found), will
-    then be subject to the individual repo mappings.  Since these are full
-    repo names, there is no possibility of multiple matches.
-
-Notes:
-
-  * only git operations (clone/fetch/push) are alias aware.  Nothing else in
-    gitolite, such as all the gitolite commands etc., are alias-aware and will
-    always use/require the proper repo name.
-
-  * http mode has not been tested and will not be.  If someone has the time to
-    test it and make it work please let me know.
-
-  * funnily enough, this even works with mirroring!  That is, a master can
-    push a repo "foo" to a slave per its configuration, while the slave thinks
-    it is getting repo "bar" from the master per its configuration.
-
-    Just make sure to put the Alias::input line *before* the Mirroring::input
-    line in the rc file on the slave.
-
-    However, it will probably not work with redirected pushes unless you setup
-    the opposite alias ("bar" -> "foo") on master.
-=cut
-
-sub input {
-    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    my $user = $ARGV[0] || '@all';    # user name is undocumented for now
-
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '\/?(\S+)'$/ ) {
-        my $repo = $1;
-        ( my $norm = $repo ) =~ s/\.git$//;    # normalised repo name
-
-        my $target = $norm;
-
-        # prefix maps first
-        my $pm = $rc{REPO_ALIASES}{PREFIX_MAPS} || {};
-        while (my($k, $v) = each %$pm) {
-            last if $target =~ s/^$k/$v/;
-            # no /i, /g, etc. by design
-        }
-
-        # individual repo map next
-        $target = $rc{REPO_ALIASES}{$target} || $target;
-
-        # undocumented; don't use without discussing on mailing list
-        $target = $target->{$user} if ref($target) eq 'HASH';
-
-        # if the repo name finally maps to empty, we bail, with no changes
-        return unless $target;
-
-        # we're done.  Did we actually change anything?
-        return if $norm eq $target;
-
-        # if the new name starts with "301/", inform and abort
-        _die "please use '$target' instead of '$norm'" if $target =~ s(^301/)();
-        # otherwise print a warning and continue with the new name
-        my $wm = $rc{REPO_ALIASES}{WARNING} || "'%old' is an alias for '%new'";
-        $wm =~ s/%new/$target/g;
-        $wm =~ s/%old/$norm/g;
-        _warn $wm;
-
-        $ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/;
-    }
-
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm b/docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm
deleted file mode 100644
index e1d977a..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm
+++ /dev/null
@@ -1,24 +0,0 @@
-package Gitolite::Triggers::AutoCreate;
-
-use strict;
-use warnings;
-
-# perl trigger set for stuff to do with auto-creating repos
-# ----------------------------------------------------------------------
-
-# to deny auto-create on read access, uncomment 'no-create-on-read' in the
-# ENABLE list in the rc file
-sub deny_R {
-    die "autocreate denied\n" if $_[3] and $_[3] eq 'R';
-    return;
-}
-
-# to deny auto-create on read *and* write, uncomment 'no-auto-create' in the
-# ENABLE list in the rc file.  This means you can only create wild repos using
-# the 'create' command, (which needs to be enabled in the ENABLE list).
-sub deny_RW {
-    die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' );
-    return;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm b/docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm
deleted file mode 100644
index 74b4217..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm
+++ /dev/null
@@ -1,52 +0,0 @@
-package Gitolite::Triggers::CpuTime;
-
-use Time::HiRes;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# cpu and elapsed times for gitolite+git operations
-# ----------------------------------------------------------------------
-# uncomment the appropriate lines in the rc file to enable this
-
-# Ideally, you will (a) write your own code with a different filename so later
-# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
-# to the rc file, and (c) change your rc file to call your program instead.
-
-# ----------------------------------------------------------------------
-my $start_time;
-
-sub input {
-    _warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$;
-    $start_time = [ Time::HiRes::gettimeofday() ];
-}
-
-sub post_git {
-    _warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$;
-
-    my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_;
-    my ( $utime, $stime, $cutime, $cstime ) = times();
-    my $elapsed = Time::HiRes::tv_interval($start_time);
-
-    gl_log( 'times', $utime, $stime, $cutime, $cstime, $elapsed );
-
-    # now do whatever you want with the data; the following is just an example.
-
-    if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
-        my $total = $utime + $cutime + $stime + $cstime;
-        # some code to send an email or whatever...
-        say2 "limit = $limit, actual = $total" if $total > $limit;
-    }
-
-    if ( $rc{DISPLAY_CPU_TIME} ) {
-        say2 "perf stats for $verb on repo '$repo':";
-        say2 "  user CPU time: " . ( $utime + $cutime );
-        say2 "  sys  CPU time: " . ( $stime + $cstime );
-        say2 "   elapsed time: " . $elapsed;
-    }
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm
deleted file mode 100755
index 6274c3d..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm
+++ /dev/null
@@ -1,99 +0,0 @@
-package Gitolite::Triggers::Kindergarten;
-
-# http://www.great-quotes.com/quote/424177
-#   "Doctor, it hurts when I do this."
-#   "Then don't do that!"
-
-# Prevent various things that sensible people shouldn't be doing anyway. List
-# of things it prevents is at the end of the program.
-
-# If you were forced to enable this module because someone is *constantly*
-# doing things that need to be caught, consider getting rid of that person.
-# Because, really, who knows what *else* he/she is doing that can't be caught
-# with some clever bit of code?
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-my %active;
-sub active {
-    # in rc, you either see just 'Kindergarten' to activate all features, or
-    # 'Kindergarten U0 CREATOR' (i.e., a space sep list of features after the
-    # word Kindergarten) to activate only those named features.
-
-    # no features specifically activated; implies all of them are active
-    return 1 if not %active;
-    # else check if this specific feature is active
-    return 1 if $active{ +shift };
-
-    return 0;
-}
-
-my ( $verb, $repo, $cmd, $args );
-sub input {
-    # get the features to be activated, if supplied
-    while ( $_[0] ne 'INPUT' ) {
-        $active{ +shift } = 1;
-    }
-
-    # generally fill up variables you might use later
-    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /($git_commands) '\/?(\S+)'$/ ) {
-        $verb = $1;
-        $repo = $2;
-    } elsif ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^(\S+) (.*)$/ ) {
-        $cmd  = $1;
-        $args = $2;
-    }
-
-    prevent_CREATOR($repo) if active('CREATOR') and $verb;
-    prevent_0(@ARGV)       if active('U0')      and @ARGV;
-}
-
-sub prevent_CREATOR {
-    my $repo = shift;
-    _die "'CREATOR' not allowed as part of reponame" if $repo =~ /\bCREATOR\b/;
-}
-
-sub prevent_0 {
-    my $user = shift;
-    _die "'0' is not a valid username" if $user eq '0';
-}
-
-1;
-
-__END__
-
-CREATOR
-
-    prevent literal 'CREATOR' from being part of a repo name
-
-    a quirk deep inside gitolite would let this config
-
-        repo foo/CREATOR/..*
-            C   =   ...
-
-    allow the creation of repos like "foo/CREATOR/bar", i.e., the word CREATOR is
-    literally used.
-
-    I consider this a totally pathological situation to check for.  The worst that
-    can happen is someone ends up cluttering the server with useless repos.
-
-    One solution could be to prevent this only for wild repos, but I can't be
-    bothered to fine tune this, so this module prevents even normal repos from
-    having the literal CREATOR in them.
-
-    See https://groups.google.com/forum/#!topic/gitolite/cS34Vxix0Us for more.
-
-U0
-
-    prevent a user from being called literal '0'
-
-    Ideally we should prevent keydir/0.pub (or variants) from being created,
-    but for "Then don't do that" purposes it's enough to prevent the user from
-    logging in.
-
-    See https://groups.google.com/forum/#!topic/gitolite/F1IBenuSTZo for more.
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm
deleted file mode 100644
index c88fc92..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm
+++ /dev/null
@@ -1,253 +0,0 @@
-package Gitolite::Triggers::Mirroring;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-my $hn           = $rc{HOSTNAME};
-
-my ( $mode, $master, %slaves, %trusted_slaves );
-
-# ----------------------------------------------------------------------
-
-sub input {
-    unless ( $ARGV[0] =~ /^server-(\S+)$/ ) {
-        _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
-        return;
-    }
-
-    # note: we treat %rc as our own internal "poor man's %ENV"
-    $rc{FROM_SERVER} = $1;
-    trace( 3, "from_server: $1" );
-    my $sender = $rc{FROM_SERVER} || '';
-
-    # custom peer-to-peer commands.  At present the only one is 'perms -c',
-    # sent from a mirror command
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/ ) {
-        $ENV{GL_USER} = $1;
-
-        my $repo = $2;
-        details($repo);
-        _die "$hn: '$repo' is local"                        if $mode eq 'local';
-        _die "$hn: '$repo' is native"                       if $mode eq 'master';
-        _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
-
-        # this expects valid perms content on STDIN
-        _system("gitolite perms -c $repo");
-
-        # we're done.  Yes, really...
-        exit 0;
-    }
-
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
-        # my ($user, $newsoc, $repo) = ($1, $2, $3);
-        $ENV{SSH_ORIGINAL_COMMAND} = $2;
-        @ARGV                      = ($1);
-        $rc{REDIRECTED_PUSH}       = 1;
-        trace( 3, "redirected_push for user $1" );
-    } else {
-        # master -> slave push, no access checks needed
-        $ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
-    }
-}
-
-# ----------------------------------------------------------------------
-
-sub pre_git {
-    return unless $hn;
-    # nothing, and I mean NOTHING, happens if HOSTNAME is not set
-    trace( 3, "pre_git() on $hn" );
-
-    my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
-
-    my $sender = $rc{FROM_SERVER} || '';
-    $user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
-
-    # ------------------------------------------------------------------
-    # now you know the repo, get its mirroring details
-    details($repo);
-
-    # print mirror status if at least one slave status file is present
-    print_status( $repo ) if not $rc{HUSH_MIRROR_STATUS} and $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-slave-*.status");
-
-    # we don't deal with any reads.  Note that for pre-git this check must
-    # happen *after* getting details, to give mode() a chance to die on "known
-    # unknown" repos (repos that are in the config, but mirror settings
-    # exclude this host from both the master and slave lists)
-    return if $aa eq 'R';
-
-    trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
-
-    # ------------------------------------------------------------------
-    # case 1: we're master or slave, normal user pushing to us
-    if ( $user and not $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 1, user push" );
-        return if $mode eq 'local' or $mode eq 'master';
-        if ( $trusted_slaves{$hn} ) {
-            trace( 1, "redirect to $master" );
-            exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
-        } else {
-            _die "$hn: pushing '$repo' to slave '$hn' not allowed";
-        }
-    }
-
-    # ------------------------------------------------------------------
-    # case 2: we're slave, master pushing to us
-    if ( $sender and not $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 2, master push" );
-        _die "$hn: '$repo' is local"                        if $mode eq 'local';
-        _die "$hn: '$repo' is native"                       if $mode eq 'master';
-        _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
-        return;
-    }
-
-    # ------------------------------------------------------------------
-    # case 3: we're master, slave sending a redirected push to us
-    if ( $sender and $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 2, slave redirect" );
-        _die "$hn: '$repo' is local"                           if $mode eq 'local';
-        _die "$hn: '$repo' is not native"                      if $mode eq 'slave';
-        _die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
-        _die "$hn: redirection not allowed from '$sender'"     if not $trusted_slaves{$sender};
-        return;
-    }
-
-    _die "$hn: should not reach this line";
-
-}
-
-# ----------------------------------------------------------------------
-
-sub post_git {
-    return unless $hn;
-    # nothing, and I mean NOTHING, happens if HOSTNAME is not set
-    trace( 1, "post_git() on $hn" );
-
-    my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
-    # we don't deal with any reads
-    return if $aa eq 'R';
-
-    my $sender = $rc{FROM_SERVER} || '';
-    $user = '' if $sender;
-
-    # ------------------------------------------------------------------
-    # now you know the repo, get its mirroring details
-    details($repo);
-
-    trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
-
-    # ------------------------------------------------------------------
-    # case 1: we're master or slave, normal user pushing to us
-    if ( $user and not $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 1, user push" );
-        return if $mode eq 'local';
-        # slave was eliminated earlier anyway, so that leaves 'master'
-
-        # find all slaves and push to each of them
-        push_to_slaves($repo);
-
-        return;
-    }
-
-    # ------------------------------------------------------------------
-    # case 2: we're slave, master pushing to us
-    if ( $sender and not $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 2, master push" );
-        # nothing to do
-        return;
-    }
-
-    # ------------------------------------------------------------------
-    # case 3: we're master, slave sending a redirected push to us
-    if ( $sender and $rc{REDIRECTED_PUSH} ) {
-        trace( 3, "case 2, slave redirect" );
-
-        # find all slaves and push to each of them
-        push_to_slaves($repo);
-
-        return;
-    }
-}
-
-{
-    my $lastrepo = '';
-
-    sub details {
-        my $repo = shift;
-        return if $lastrepo eq $repo;
-
-        $master         = master($repo);
-        %slaves         = slaves($repo);
-        $mode           = mode($repo);
-        %trusted_slaves = trusted_slaves($repo);
-        trace( 3, $master, $mode, join( ",", sort keys %slaves ), join( ",", sort keys %trusted_slaves ) );
-    }
-
-    sub master {
-        return option( +shift, 'mirror.master' );
-    }
-
-    sub slaves {
-        my $repo = shift;
-
-        my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
-        my %out = map { $_ => 'async' } map { split } values %$ref;
-
-        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.sync.*" );
-        map { $out{$_} = 'sync' } map { split } values %$ref;
-
-        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.nosync.*" );
-        map { $out{$_} = 'nosync' } map { split } values %$ref;
-
-        return %out;
-    }
-
-    sub trusted_slaves {
-        my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
-        # the list of trusted slaves (where we accept redirected pushes from)
-        # is either explicitly given...
-        my @out = map { split } values %$ref;
-        my %out = map { $_ => 1 } @out;
-        # ...or it's all the slaves mentioned if the list is just a "all"
-        %out = %slaves if ( @out == 1 and $out[0] eq 'all' );
-        return %out;
-    }
-
-    sub mode {
-        my $repo = shift;
-        return 'local'  if not $hn;
-        return 'master' if $master eq $hn;
-        return 'slave'  if $slaves{$hn};
-        return 'local'  if not $master and not %slaves;
-        _die "$hn: '$repo' is mirrored but not here";
-    }
-}
-
-sub push_to_slaves {
-    my $repo = shift;
-
-    my $u = $ENV{GL_USER};
-    delete $ENV{GL_USER};    # why?  see src/commands/mirror
-
-    for my $s ( sort keys %slaves ) {
-        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $slaves{$s} eq 'async';
-        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1")   if $slaves{$s} eq 'sync';
-        _warn "manual mirror push pending for '$s'"                          if $slaves{$s} eq 'nosync';
-    }
-
-    $ENV{GL_USER} = $u;
-}
-
-sub print_status {
-    my $repo = shift;
-    my $u = $ENV{GL_USER};
-    delete $ENV{GL_USER};
-    system("gitolite mirror status all $repo >&2");
-    $ENV{GL_USER} = $u;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm
deleted file mode 100644
index 6de80a2..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm
+++ /dev/null
@@ -1,29 +0,0 @@
-package Gitolite::Triggers::Motd;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# print a message of the day to STDERR
-# ----------------------------------------------------------------------
-
-my $file = "gl-motd";
-
-sub input {
-    # at present, we print it for every single interaction with gitolite.  We
-    # may want to change that later; if we do, get code from Kindergarten.pm
-    # to get the gitcmd+repo or cmd+args so you can filter on them
-
-    my $f = "$rc{GL_ADMIN_BASE}/$file";
-    print STDERR slurp($f) if -f $f;
-}
-
-sub pre_git {
-    my $repo = $_[1];
-    my $f    = "$rc{GL_REPO_BASE}/$repo.git/$file";
-    print STDERR slurp($f) if -f $f;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm b/docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm
deleted file mode 100644
index e913665..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm
+++ /dev/null
@@ -1,80 +0,0 @@
-package Gitolite::Triggers::RefexExpr;
-use strict;
-use warnings;
-
-# track refexes passed and evaluate expressions on them
-# ----------------------------------------------------------------------
-# see src/VREF/refex-expr for instructions and WARNINGS!
-
-use Gitolite::Easy;
-
-my %passed;
-my %rules;
-my $init_done = 0;
-
-sub access_2 {
-    # get out quick for repos that don't have any rules
-    return if $init_done and not %rules;
-
-    # but we don't really know that the first time, heh!
-    if ( not $init_done ) {
-        my $repo = $_[1];
-        init($repo);
-        return unless %rules;
-    }
-
-    my $refex = $_[5];
-    return if $refex =~ /DENIED/;
-
-    $passed{$refex}++;
-
-    # evaluate the rules each time; it's not very expensive
-    for my $k ( sort keys %rules ) {
-        $ENV{ "GL_REFEX_EXPR_" . $k } = eval_rule( $rules{$k} );
-    }
-}
-
-sub eval_rule {
-    my $rule = shift;
-
-    my $e;
-    $e = join " ", map { convert($_) } split ' ', $rule;
-
-    my $ret = eval $e;
-    _die "eval '$e' -> '$@'" if $@;
-    Gitolite::Common::trace( 1, "RefexExpr", "'$rule' -> '$e' -> '$ret'" );
-
-    return "'$rule' -> '$e'" if $ret;
-}
-
-my %constant;
-%constant = map { $_ => $_ } qw(1 not and or xor + - ==);
-$constant{'-lt'} = '<';
-$constant{'-gt'} = '>';
-$constant{'-eq'} = '==';
-$constant{'-le'} = '<=';
-$constant{'-ge'} = '>=';
-$constant{'-ne'} = '!=';
-
-sub convert {
-    my $i = shift;
-    return $i if $i =~ /^-?\d+$/;
-    return $constant{$i} || $passed{$i} || $passed{"refs/heads/$i"} || 0;
-}
-
-# called only once
-sub init {
-    $init_done = 1;
-    my $repo = shift;
-
-    # find all the rule expressions
-    my %t = config( $repo, "^gitolite-options\\.refex-expr\\." );
-    my ( $k, $v );
-    # get rid of the cruft and store just the rule name as the key
-    while ( ( $k, $v ) = each %t ) {
-        $k =~ s/^gitolite-options\.refex-expr\.//;
-        $rules{$k} = $v;
-    }
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm b/docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm
deleted file mode 100644
index 109cb31..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm
+++ /dev/null
@@ -1,58 +0,0 @@
-package Gitolite::Triggers::RepoUmask;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# setting a repo specific umask
-# ----------------------------------------------------------------------
-# this is for people who are too paranoid to trust e.g., gitweb's repo
-# exclusion logic, but not paranoid enough to put it on a different server
-
-=for usage
-
-  * In the rc file, add the line
-        'RepoUmask',
-    somewhere in the ENABLE list
-
-  * For each repo that is to get a different umask than the default, add a
-    line like this:
-
-        option umask = 0027
-
-=cut
-
-# sadly option/config values are not available at pre_create time for normal
-# repos.  So we have to do a one-time fixup in a post_create trigger.
-sub post_create {
-    my $repo = $_[1];
-
-    my $umask = option( $repo, 'umask' );
-    _chdir( $rc{GL_REPO_BASE} );    # because using option() moves us to ADMIN_BASE!
-
-    return unless $umask;
-
-    # unlike the one in the rc file, this is a string
-    $umask = oct($umask);
-    my $mode = "0" . sprintf( "%o", $umask ^ 0777 );
-
-    system("chmod -R $mode $repo.git >&2");
-    system("find $repo.git -type f -exec chmod a-x '{}' \\;");
-}
-
-sub pre_git {
-    my $repo = $_[1];
-
-    my $umask = option( $repo, 'umask' );
-    _chdir( $rc{GL_REPO_BASE} );    # because using option() moves us to ADMIN_BASE!
-
-    return unless $umask;
-
-    # unlike the one in the rc file, this is a string
-    umask oct($umask);
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm
deleted file mode 100644
index a2c5c0d..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm
+++ /dev/null
@@ -1,66 +0,0 @@
-package Gitolite::Triggers::Shell;
-
-# usage notes: uncomment 'Shell' in the ENABLE list in the rc file.
-
-# documentation is in the ssh troubleshooting and tips document, under the
-# section "giving shell access to gitolite users"
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-# fedora likes to do things that are a little off the beaten track, compared
-# to typical gitolite usage:
-# - every user has their own login
-# - the forced command may not get the username as an argument.  If it does
-#   not, the gitolite user name is $USER (the unix user name)
-# - and finally, if the first argument to the forced command is '-s', and
-#   $SSH_ORIGINAL_COMMAND is empty or runs a non-git/gitolite command, then
-#   the user gets a shell
-
-sub input {
-    my $shell_allowed = 0;
-    if ( @ARGV and $ARGV[0] eq '-s' ) {
-        shift @ARGV;
-        $shell_allowed++;
-    }
-
-    @ARGV = ( $ENV{USER} ) unless @ARGV;
-
-    return unless $shell_allowed;
-
-    # now determine if this was intended as a shell command or git/gitolite
-    # command
-
-    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-
-    # no command, just 'ssh alice at host'; doesn't return ('exec's out)
-    shell_out() if $shell_allowed and not $soc;
-
-    return if git_gitolite_command($soc);
-
-    gl_log( 'shell', $ENV{SHELL}, "-c", $soc );
-    exec $ENV{SHELL}, "-c", $soc;
-}
-
-sub shell_out {
-    my $shell = $ENV{SHELL};
-    $shell =~ s/.*\//-/;    # change "/bin/bash" to "-bash"
-    gl_log( 'shell', $shell );
-    exec { $ENV{SHELL} } $shell;
-}
-
-# some duplication with gitolite-shell, factor it out later, if it works fine
-# for fedora and they like it.
-sub git_gitolite_command {
-    my $soc = shift;
-
-    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    return 1 if $soc =~ /^($git_commands) /;
-
-    my @words = split ' ', $soc;
-    return 1 if $rc{COMMANDS}{ $words[0] };
-
-    return 0;
-}
-
-1;
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm b/docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm
deleted file mode 100644
index 8cf0e8d..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm
+++ /dev/null
@@ -1,97 +0,0 @@
-package Gitolite::Triggers::TProxy;
-
-# ----------------------------------------------------------------------
-# transparent proxy for git repos, hosted on a gitolite server
-
-# ----------------------------------------------------------------------
-# WHAT
-
-#   1.  user runs a git command (clone, fetch, push) against a gitolite
-#       server.
-#   2.  if that server has the repo, it will serve it up.  Else it will
-#       *transparently* forward the git operation to a designated upstream
-#       server.  The user does not have to do anything, and in fact may not
-#       even know this has happened.
-
-# can be combined with, but does not *require*, gitolite mirroring.
-
-# ----------------------------------------------------------------------
-# SECURITY
-#
-#   1.  Most of the issues that apply to "redirected push" in mirroring.html
-#       also apply here.  In particular, you had best make sure the two
-#       servers use the same authentication data (i.e., "alice" here should be
-#       "alice" there!)
-#
-#   2.  Also, do not add keys for servers you don't trust!
-
-# ----------------------------------------------------------------------
-# HOW
-
-# on transparent proxy server (the one that is doing the redirect):
-#   1.  add
-#           INPUT => ['TProxy::input'],
-#       just before the ENABLE list in the rc file
-#   2.  add an RC variable to tell gitolite where to go; this is also just
-#       before the ENABLE list:
-#           TPROXY_FORWARDS_TO => 'git at upstream',
-
-# on upstream server (the one redirected TO):
-#   1.  add
-#           INPUT => ['TProxy::input'],
-#       just before the ENABLE list in the rc file
-#   2.  add the pubkey of the proxy server (the one that will be redirecting
-#       to us) to this server's gitolite-admin "keydir" as
-#       "server-<something>.pub", and push the change.
-
-# to use in combination with gitolite mirroring
-#   1.  just follow the same instructions as above.  Server names and
-#       corresponding pub keys would already be set ok so step 2 in the
-#       upstream server setup (above) will not be needed.
-#   2.  needless to say, **don't** declare the repos you want to be
-#       transparently proxied in the gitolite.conf for the slave.
-
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-
-# ----------------------------------------------------------------------
-
-sub input {
-    # are we the upstream, getting something from a tproxy server?
-    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    if ( $ARGV[0] =~ /^server-/ and $soc =~ /^TPROXY_FOR=(\S+) SOC=(($git_commands) '\S+')$/ ) {
-        @ARGV = ($1);
-        # you better make sure you read the security warnings up there!
-
-        $ENV{SSH_ORIGINAL_COMMAND} = $2;
-        delete $ENV{GL_BYPASS_ACCESS_CHECKS};
-        # just in case we somehow end up running before Mirroring::input!
-
-        return;
-    }
-
-    # well we're not upstream; are we a tproxy?
-    return unless $rc{TPROXY_FORWARDS_TO};
-
-    # is it a normal git command?
-    return unless $ENV{SSH_ORIGINAL_COMMAND} =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$);
-
-    # ...get the repo name from $ENV{SSH_ORIGINAL_COMMAND}
-    my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
-    $ENV{D} = $trace_level if $trace_level;
-    _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
-
-    # nothing to do if the repo exists locally
-    return if -d "$ENV{GL_REPO_BASE}/$repo.git";
-
-    my $user = shift @ARGV;
-    # redirect to upstream
-    exec( "ssh", $rc{TPROXY_FORWARDS_TO}, "TPROXY_FOR=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
-}
diff --git a/docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm b/docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm
deleted file mode 100644
index ed86e12..0000000
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm
+++ /dev/null
@@ -1,17 +0,0 @@
-package Gitolite::Triggers::Writable;
-
-use Gitolite::Rc;
-use Gitolite::Common;
-
-sub access_1 {
-    my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ];
-    return if $aa eq 'R' or $result =~ /DENIED/;
-
-    for my $f ( "$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down" ) {
-        next unless -f $f;
-        _die slurp($f) if -s $f;
-        _die "sorry, writes are currently disabled (no more info available)\n";
-    }
-}
-
-1;
diff --git a/docker/gitolite/src/syntactic-sugar/continuation-lines b/docker/gitolite/src/syntactic-sugar/continuation-lines
deleted file mode 100644
index d63475f..0000000
--- a/docker/gitolite/src/syntactic-sugar/continuation-lines
+++ /dev/null
@@ -1,36 +0,0 @@
-# vim: syn=perl:
-
-# "sugar script" (syntactic sugar helper) for gitolite3
-
-# Enabling this script in the rc file allows you to use back-slash escaped
-# continuation lines, like in C or shell etc.
-
-# This script also serves as an example "sugar script" if you want to write
-# your own (and maybe send them to me).  A "sugar script" in gitolite will be
-# executed via a perl 'do' and is expected to contain one function called
-# 'sugar_script'.  This function should take a listref and return a listref.
-# Each item in the list is one line.  There are NO newlines; g3 kills them off
-# fairly early in the process.
-
-# If you're not familiar with perl please do not try this.  Ask me to write
-# you a sugar script instead.
-
-sub sugar_script {
-    my $lines = shift;
-
-    my @out  = ();
-    my $keep = '';
-    for my $l (@$lines) {
-        # skip RULE_INFO lines if in continuation mode
-        next if $keep and $l =~ /^ *#/;
-        if ( $l =~ s/\\$// ) {
-            $keep .= $l;
-        } else {
-            $l = $keep . $l if $keep;
-            $keep = '';
-            push @out, $l;
-        }
-    }
-
-    return \@out;
-}
diff --git a/docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups b/docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups
deleted file mode 100644
index 0a3a9ae..0000000
--- a/docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups
+++ /dev/null
@@ -1,32 +0,0 @@
-# vim: syn=perl:
-
-# "sugar script" (syntactic sugar helper) for gitolite3
-
-# Enabling this script in the rc file allows you to use subdirectories in
-# keydir as group names.  The last component other than keydir itself will be
-# taken as the group name.
-
-sub sugar_script {
-    Gitolite::Common::trace( 2, "running 'keysubdirs-as-groups' sugar script..." );
-    my $lines = shift;
-
-    my @out = @{$lines};
-    unshift @out, groupnames();
-
-    return \@out;
-}
-
-sub groupnames {
-    my @out     = ();
-    my %members = ();
-    for my $pk (`find ../keydir/ -name "*.pub"`) {
-        next unless $pk =~ m(.*/([^/]+)/([^/]+?)(?:@[^./]+)?\.pub$);
-        next if $1 eq 'keydir';
-        $members{$1} .= " $2";
-    }
-    for my $m ( sort keys %members ) {
-        push @out, "\@$m =" . $members{$m};
-    }
-
-    return @out;
-}
diff --git a/docker/gitolite/src/syntactic-sugar/macros b/docker/gitolite/src/syntactic-sugar/macros
deleted file mode 100644
index a3493a4..0000000
--- a/docker/gitolite/src/syntactic-sugar/macros
+++ /dev/null
@@ -1,82 +0,0 @@
-# vim: syn=perl:
-
-# "sugar script" (syntactic sugar helper) for gitolite3
-
-# simple line-wise macro processor
-# ----------------------------------------------------------------------
-# see documentation at the end of this script
-
-my %macro;
-
-sub sugar_script {
-    my $lines = shift;
-    my @out   = ();
-
-    my $l = join( "\n", @$lines );
-    while ( $l =~ s/^macro (\w+)\b(.*?)\nend//ms ) {
-        $macro{$1} = $2;
-    }
-
-    $l =~ s/^((\w+)\b.*)/$macro{$2} ? expand($1) : $1/gem;
-
-    $lines = [ split "\n", $l ];
-    return $lines;
-}
-
-sub expand {
-    my $l = shift;
-    my ( $word, @arg );
-
-    eval "require Text::ParseWords";
-    if ($@) {
-        ( $word, @arg ) = split ' ', $l;
-    } else {
-        ( $word, @arg ) = Text::ParseWords::shellwords($l);
-    }
-    my $v = $macro{$word};
-    $v =~ s/%(\d+)/$arg[$1-1] or die "macro '$word' needs $1 arguments at '$l'\n"/gem;
-    return $v;
-}
-
-__END__
-
-Documentation is mostly by example.
-
-Setup:
-
-  * uncomment the line
-        'macros',
-    in the ENABLE list in ~/.gitolite.rc
-
-Notes on macro definition:
-
-  * the keywords 'macro' and 'end' should start on a new line
-  * the first word after 'macro' is the name of the macro, and the rest, until
-    the 'end', is the body
-
-Notes on macro use:
-
-  * the macro name should be the first word on a line
-  * the rest of the line is used as arguments to the macro
-
-Example:
-
-    if your conf contains:
-
-        macro foo repo aa-%1
-            RW  =   u1 %2
-            R   =   u2
-        end
-
-        foo 1 alice
-        foo 2 bob
-
-    this will effectively turn into
-
-        repo aa-1
-            RW  =   u1 alice
-            R   =   u2
-
-        repo aa-2
-            RW  =   u1 bob
-            R   =   u2
diff --git a/docker/gitolite/src/syntactic-sugar/refex-expr b/docker/gitolite/src/syntactic-sugar/refex-expr
deleted file mode 100644
index f9e7706..0000000
--- a/docker/gitolite/src/syntactic-sugar/refex-expr
+++ /dev/null
@@ -1,35 +0,0 @@
-# vim: syn=perl:
-
-# "sugar script" (syntactic sugar helper) for gitolite3
-# ----------------------------------------------------------------------
-# see src/VREF/refex-expr for instructions and WARNINGS!
-
-my $perm = qr(-|R|RW\+?C?D?M?);
-
-my $seq = 1;
-
-sub sugar_script {
-    my $lines = shift;
-
-    # my @out  = ();
-    for my $l (@$lines) {
-        push @out, $l;
-
-        # quick check
-        next unless $l =~ /^($perm) /;
-        # more detailed check
-        next unless $l =~ /^($perm) (\S.*) = (\S.*)$/;
-        my ( $perm, $refexes, $users ) = ( $1, $2, $3 );
-        next unless $refexes =~ / (and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne) /;
-
-        print STDERR ">>>> $l\n";
-        pop @out;    # we need to replace that last line
-
-        push @out, "option refex-expr.sugar$seq = $refexes";
-        push @out, "$perm VREF/refex-expr/sugar$seq = $users";
-
-        $seq++;
-    }
-
-    return \@out;
-}
diff --git a/docker/gitolite/src/triggers/bg b/docker/gitolite/src/triggers/bg
deleted file mode 100755
index 3c66500..0000000
--- a/docker/gitolite/src/triggers/bg
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-# quick and dirty program to background any of the triggers programs that are
-# taking too long.  To use, just replace a line like
-#       'post-compile/update-gitweb-access-list',
-# with
-#       'bg post-compile/update-gitweb-access-list',
-
-# We dump output to a file in the log directory but please keep in mind this
-# is not a "log" so much as a redirection of the entire output.
-
-echo `date` $GL_TID "$0: $@" >> $GL_LOGFILE.bg
-
-path=${0%/*}
-script=$path/$1; shift
-
-( ( $script "$@" < /dev/null >> $GL_LOGFILE.bg 2>&1 & ) )
diff --git a/docker/gitolite/src/triggers/expand-deny-messages b/docker/gitolite/src/triggers/expand-deny-messages
deleted file mode 100755
index a8b2289..0000000
--- a/docker/gitolite/src/triggers/expand-deny-messages
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# program name: expand-deny-messages
-
-# DOCUMENTATION IS AT THE BOTTOM OF THIS FILE; PLEASE READ
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-my %attempted_access = (
-    # see triggers.html
-    'ACCESS_1' => {
-        'R' => 'Repo read',
-        'W' => 'Repo write',
-    },
-    'ACCESS_2' => {
-        'W' => "Fast forward push",
-        '+' => "Rewind push branch or overwrite tag",
-        'C' => "Create ref",
-        'D' => "Delete ref",
-    }
-);
-
-# env var to disable is set?
-exit 0 if $ENV{GL_OPTION_EDM_DISABLE};
-
-# argument 1
-my $a12 = shift;    # ACCESS_1 or ACCESS_2
-exit 0 if $a12 !~ /^ACCESS_[12]$/;    # shouldn't happen; error in rc file?
-
-# the rest of the arguments
-my ( $repo, $user, $aa, $ref, $msg, $oldsha, $newsha ) = @ARGV;
-
-# we're only interested in deny messages
-exit 0 if $msg !~ /DENIED/;
-
-print STDERR "\nFATAL -- ACCESS DENIED\n";
-
-_info( "Repo",      $repo );
-_info( "User",      $user );
-_info( "Stage",     ( $a12 eq 'ACCESS_1' ? "Before git was called" : "From git's update hook" ) );
-_info( "Ref",       _ref($ref) ) if $a12 eq 'ACCESS_2';
-_info( "Operation", _op( $a12, $aa, $oldsha, $newsha ) );
-
-if ( $ref =~ m((^VREF/[^/]+)) ) {
-    my $vref      = $1;
-    my $vref_text = slurp( _which( $vref, 'x' ) );
-    my $etag      = '(?:help|explain|explanation)';
-    $vref_text =~ m(^\s*# $etag.start\n(.*)^\s*# $etag.end\n)sm
-      and print STDERR "Explanation for $vref:\n$1";
-}
-
-print STDERR "\n";
-print STDERR "$ENV{GL_OPTION_EDM_EXTRA_INFO}\n\n" if $ENV{GL_OPTION_EDM_EXTRA_INFO};
-
-# ------------------------------------------------------------------------
-
-sub _ref {
-    my $r = shift;
-    return "VREF '$r'"   if $r =~ s(^VREF/)();
-    return "Branch '$r'" if $r =~ s(^refs/heads/)();
-    return "Tag '$r'"    if $r =~ s(^refs/tags/)();
-    return "Non-standard ref '$r'";
-}
-
-sub _info {
-    printf STDERR "%-14s  %-60s\n", @_;
-}
-
-sub _op {
-    my ( $a12, $aa, $oldsha, $newsha ) = @_;
-
-    # first remove the M part and save the text for later addition if needed
-    my $merge = ( $aa =~ s/M// ? " with merge commit" : "" );
-
-    # next, the attempted access is modified to reflect the actual operation being
-    # attempted.  NOTE: this no longer necessarily reflects what the gitolite log
-    # file stores; it's more granular and truly distinguishes a branch create from
-    # an ff push, etc.  Could help when user typos a branch name I suppose
-    $aa = 'C' if $oldsha and $oldsha eq '0' x 40;
-    $aa = 'D' if $newsha and $newsha eq '0' x 40;
-
-    # then we map it, add merge text if any
-    my $op = $attempted_access{$a12}{$aa} || "Unknown operation '$aa'";
-    $op .= $merge;
-
-    return $op;
-}
-
-__END__
-
-ENABLING THE FEATURE
---------------------
-
-To enable this feature, uncomment the line in the rc file if your gitolite was
-installed recently enough.  Otherwise you will need to add these lines to the
-end of your rc file, just before the "%RC" block ends:
-
-    ACCESS_1 => [
-        'expand-deny-messages',
-    ],
-
-    ACCESS_2 => [
-        'expand-deny-messages',
-    ],
-
-Please don't miss the trailing commas!
-
-DISABLING IT FOR SPECIFIC REPOS
--------------------------------
-
-Once it is enabled at the rc file level, if you wish to disable it for
-specific repositories just add a line like this to those repos:
-
-        option ENV.EDM_DISABLE = 1
-
-Or you can also disable it for all repos, then enable it for some:
-
-    repo @all
-        option ENV.EDM_DISABLE = 1
-
-    # ... then later ...
-
-    repo foo bar @baz
-        option ENV.EDM_DISABLE = 0
-
-(options.html[1] and pages linked from it will explain how that works).
-
-[1]: http://gitolite.com/gitolite/options.html
-
-SUPPLYING EXTRA INFORMATION
----------------------------
-
-You can also supply some extra information to be printed, by adding a line
-like this to each repository in the gitolite.conf file:
-
-        option ENV.EDM_EXTRA_INFO = "please contact alice at example.com"
-
-You could of course add it under a "repo @all" section if you like.
-
-SUPPLYING EXTRA INFORMATION FOR VREFs
--------------------------------------
-
-If you have VREFs that do funky things and you want to **lecture** your users
-when they screw up, add something like the following to your VREF code.
-
-    # help start
-
-    Some help text.
-
-    Some more help text.  This can be
-    multi-line.
-
-    (etc etc etc)
-
-    # help end
-
-Then everything between the "# help start" line and the "# help end" line will
-get printed if a users falls afoul of this VREF.  If any of the lines shown
-are not valid syntax for your language, figure out some way to put the whole
-thing in a comment block.  Here a C example:
-
-    /*
-    # help start
-    line 1
-    line 2
-    ...
-    last line
-    # help end
-    */
-
-Even if your language does not support multi-line comments like C does, there
-may be other ways to specify those lines.  Here's an example in shell:
-
-    cat << EOF > /dev/null
-    # help start
-    line 1
-    line 2
-    ...
-    last line
-    # help end
-    EOF
diff --git a/docker/gitolite/src/triggers/partial-copy b/docker/gitolite/src/triggers/partial-copy
deleted file mode 100755
index 79b4d48..0000000
--- a/docker/gitolite/src/triggers/partial-copy
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-# this is a wee bit expensive in terms of forks etc., compared to doing it in
-# perl, but I wanted to show how *easy* it actually is now.  And really,
-# you'll only notice if you access this repo like a hundred times a minute or
-# something so don't sweat it.
-
-# given a repo and a user, check if option('partialCopyOf') is set, and if so,
-# fetch all allowed branches from there.
-
-die() { echo "$@" >&2; exit 1; }
-
-# make sure we're being called from the pre_git trigger
-[ "$1" = "PRE_GIT" ] || die I must be called from PRE_GIT, not "$1"
-shift
-
-repo=$1
-user=$2
-main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
-[ -z "$main" ] && exit 0
-
-# "we", "our repo"  =>  the partial copy
-# "main", "pco"     =>  the one which we are a "partial copy of"
-
-cd $GL_REPO_BASE/$main.git
-
-for ref in `git for-each-ref refs/heads '--format=%(refname)'`
-do
-    cd $GL_REPO_BASE/$repo.git
-
-    gitolite access -q $repo $user R $ref &&
-    git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
-done
-
-export GL_BYPASS_ACCESS_CHECKS=1
-
-# remove all refs not in main or accessible
-cd $GL_REPO_BASE/$repo.git
-
-for ref in `git for-each-ref refs/heads refs/tags '--format=%(refname)'`
-do
-    cd $GL_REPO_BASE/$main.git
-
-    if git show-ref --verify --quiet $ref &&
-       gitolite access -q $repo $user R $ref ; then
-        # ref is present in main and accessible in repo
-        continue
-    fi
-
-    git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
-done
-
-# remove all tags no longer reachable
-cd $GL_REPO_BASE/$repo.git
-
-for ref in `git for-each-ref refs/tags '--format=%(refname)'`
-do
-    SHA=`git rev-list -1 $ref`
-    for branch in `git for-each-ref refs/heads '--format=%(refname)'`
-    do
-       if [ "`git merge-base $SHA $branch`" = "$SHA" ]; then
-           # tag is reachable in current branch, continue higher loop
-           continue 2
-       fi
-    done
-    git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
-done
-
-exit 0
diff --git a/docker/gitolite/src/triggers/post-compile/ssh-authkeys b/docker/gitolite/src/triggers/post-compile/ssh-authkeys
deleted file mode 100755
index d5f5d8b..0000000
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use File::Temp qw(tempfile);
-use Getopt::Long;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-$|++;
-
-# best called via 'gitolite trigger POST_COMPILE'; other modes at your own
-# risk, especially if the rc file specifies arguments for it.  (That is also
-# why it doesn't respond to "-h" like most gitolite commands do).
-
-# option procesing
-# ----------------------------------------------------------------------
-
-# currently has one option:
-#   -kfn, --key-file-name        adds the keyfilename as a second argument
-
-my $kfn = '';
-GetOptions( 'key-file-name|kfn' => \$kfn, );
-
-tsh_try("sestatus");
-my $selinux = ( tsh_text() =~ /enabled/ );
-
-my $ab = $rc{GL_ADMIN_BASE};
-trace( 1, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
-my $akdir        = "$ENV{HOME}/.ssh";
-my $akfile       = "$ENV{HOME}/.ssh/authorized_keys";
-my $glshell      = $rc{GL_BINDIR} . "/gitolite-shell";
-my $auth_options = auth_options();
-
-sanity();
-
-# ----------------------------------------------------------------------
-
-_chdir($ab);
-
-# old data
-my $old_ak = slurp($akfile);
-my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
-chomp(@non_gl);
-my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
-
-# pubkey files
-chomp( my @pubkeys = `find keydir/ -type f -name "*.pub" | sort` );
-my @gl_keys = ();
-for my $f (@pubkeys) {
-    my $fp = fp($f);
-    if ( $seen{$fp} ) {
-        _warn "$f duplicates $seen{$fp}, sshd will ignore it";
-    } else {
-        $seen{$fp} = $f;
-    }
-    push @gl_keys, grep { /./ } optionise($f);
-}
-
-# dump it out
-my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
-
-my $ak = slurp($akfile);
-_die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
-_print( $akfile, $out );
-
-_warn "you have no keys left; I hope you intended to do that!" unless @gl_keys;
-
-# ----------------------------------------------------------------------
-
-sub sanity {
-    _die "'$glshell' not found; this should NOT happen..."                if not -f $glshell;
-    _die "'$glshell' found but not readable; this should NOT happen..."   if not -r $glshell;
-    _die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;
-
-    my $n = "    (this is normal on a brand new install)";
-    _warn "$akdir missing; creating a new one\n$n"  if not -d $akdir;
-    _warn "$akfile missing; creating a new one\n$n" if not -f $akfile;
-
-    _mkdir( $akdir, 0700 ) if not -d $akdir;
-    if ( not -f $akfile ) {
-        _print( $akfile, "" );
-        chmod 0600, $akfile;
-    }
-}
-
-sub auth_options {
-    my $auth_options = $rc{AUTH_OPTIONS};
-    $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
-
-    return $auth_options;
-}
-
-sub fp {
-    # input: see below
-    # output: a (list of) FPs
-    my $in = shift || '';
-    if ( $in =~ /\.pub$/ ) {
-        # single pubkey file
-        _die "bad pubkey file '$in'" unless $in =~ $REPONAME_PATT;
-        return fp_file($in);
-    } elsif ( -f $in ) {
-        # an authkeys file
-        return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
-    } else {
-        # one or more actual keys
-        return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ );
-    }
-}
-
-sub fp_file {
-    return $selinux++ if $selinux;    # return a unique "fingerprint" to prevent noise
-    my $f  = shift;
-    my $fp = `ssh-keygen -l -f '$f'`;
-    chomp($fp);
-    _die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-ZA-z0-9+/]+));
-    $fp = $1;
-    return $fp;
-}
-
-sub fp_line {
-    my ( $fh, $fn ) = tempfile();
-    print $fh shift() . "\n";
-    close $fh;
-    my $fp = fp_file($fn);
-    unlink $fn;
-    return $fp;
-}
-
-sub optionise {
-    my $f = shift;
-
-    my $user = $f;
-    $user =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
-    $user =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
-
-    my @line = slurp($f);
-    if ( @line != 1 ) {
-        _warn "$f does not contain exactly 1 line; ignoring";
-        return '';
-    }
-    chomp(@line);
-    return "command=\"$glshell $user" . ( $kfn ? " $f" : "" ) . "\",$auth_options $line[0]";
-}
-
diff --git a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users b/docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users
deleted file mode 100755
index 2dd6643..0000000
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-$|++;
-
-my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
-
-# ----------------------------------------------------------------------
-
-my $aktext = slurp($akfile);
-
-for my $su ( shell_users() ) {
-    $aktext =~ s(/gitolite-shell $su([" ].*?),no-pty )(/gitolite-shell -s $su$1 )g;
-}
-
-_print( $akfile, $aktext );
-
-# two methods to specify list of shell-capable users.  (1) list of usernames
-# as arguments to 'Shell' in rc file, (2) list of usernames in a plain text
-# file whose name is the first argument to 'Shell' in the rc file.  Or both!
-sub shell_users {
-    my ($sufile, @ret);
-
-    # backward compat for 3.6 and below.  This code will be removed in 3.7.
-    # Also, the variable is ignored if you end up using the new variant (i.e.,
-    # put a file name on the 'Shell' line itself).
-    $sufile = $rc{SHELL_USERS_LIST} if $rc{SHELL_USERS_LIST} and -r $rc{SHELL_USERS_LIST};
-
-    $sufile = shift @ARGV if @ARGV and -r $ARGV[0];
-
-    if ($sufile) {
-        @ret = grep { not /^#/ } slurp($sufile);
-        chomp(@ret);
-    }
-
-    for my $u (@ARGV) {
-        # arguments placed in the rc file appear before the trigger name
-        last if $u eq 'POST_COMPILE';
-
-        push @ret, $u;
-        # no sanity checking, since the rc file can only be created by someone
-        # who already has shell access
-    }
-    _die "'Shell': enabled but no usernames supplied" unless @ret;
-    return @ret;
-}
diff --git a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-split b/docker/gitolite/src/triggers/post-compile/ssh-authkeys-split
deleted file mode 100755
index d96d2e9..0000000
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-split
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/bash
-
-#   split multi-key files into separate keys like ssh-authkeys likes
-
-# WHY
-# ---
-#
-# Yeah I wonder that too, when it's so much more maintainable to keep the damn
-# keys as sitaram at home.pub and sitaram at work.pub or such.  But there's no
-# accounting for tastes, and some old fogies apparently want to put all of a
-# user's keys into a single ".pub" file.
-
-# WARNINGS AND CAVEATS
-# --------------------
-#
-# - assumes no "@" sign in basenames of any multi-key files (single line file
-#   may still have them)
-
-# - assumes you don't have a subdir in keydir called "__split_keys__"
-
-# - God help you if you try to throw in a putty key in there.
-
-# - RUNNING "GITOLITE SETUP" WILL LOSE ALL THESE KEYS.  So if you ever do
-#   that, you will then need to make a dummy push to the admin repo to add
-#   them back.  If all your **admin** keys were in split keys, then you lost
-#   remote access.  If that happens, log on to the server using "su - git" or
-#   such, then use the methods described in the "bypassing gitolite" section
-#   in "emergencies.html" instead of a remote push.
-
-# SUPPORT
-# -------
-#
-# NONE.  Mainly because I **know** someone will throw in a putty key.  I just
-# know it.
-
-# USAGE
-# -----
-#
-# to enable, uncomment the 'ssh-authkeys-split' line in the ENABLE list in the
-# rc file.
-
-cd $GL_ADMIN_BASE/keydir
-
-rm -rf __split_keys__
-mkdir __split_keys__
-export SKD=$PWD/__split_keys__
-
-find . -type f -name "*.pub" | while read k
-do
-    # do we need to split?
-    lines=`wc -l < $k`
-    [ "$lines" = "1" ] && continue
-
-    # is it sane to split?
-    base=`basename $k .pub`
-    echo $base | grep '@' >/dev/null && continue
-
-    # ok do it
-    seq=1
-    while read line
-    do
-        echo "$line" > $SKD/$base@$seq.pub
-        (( seq++ ))
-    done < $k
-
-    # now delete the original file
-    rm $k
-done
diff --git a/docker/gitolite/src/triggers/post-compile/update-description-file b/docker/gitolite/src/triggers/post-compile/update-description-file
deleted file mode 100755
index e5b7c6a..0000000
--- a/docker/gitolite/src/triggers/post-compile/update-description-file
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-# For normal (not "wild") repos, gitolite v3 sets 'gitweb.description' instead
-# of putting the text in the "description" file.  This is easier because it
-# just goes with the flow of setting config variables; nothing special needs
-# to be done for the description.
-
-# But this only works for gitweb, not for cgit.  Cgit users must uncomment the
-# 'cgit' line in the ENABLE list in the rc file (which has the effect of
-# adding this program to the POST_COMPILE trigger list).
-
-cd $GL_REPO_BASE
-gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne '
-            my @F = split /\t/,$_,3;
-            textfile( file => "description", repo => $F[0], text => $F[2] );
-    '
diff --git a/docker/gitolite/src/triggers/post-compile/update-git-configs b/docker/gitolite/src/triggers/post-compile/update-git-configs
deleted file mode 100755
index a58a85d..0000000
--- a/docker/gitolite/src/triggers/post-compile/update-git-configs
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/perl
-
-# update git-config entries in each repo
-# ----------------------------------------------------------------------
-
-use FindBin;
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-my $RB = $rc{GL_REPO_BASE};
-_chdir($RB);
-
-# ----------------------------------------------------------------------
-# skip if arg-0 is POST_CREATE and no arg-2 (user name) exists; this means
-# it's been triggered by a *normal* (not "wild") repo creation, which in turn
-# means a POST_COMPILE should be following so there's no need to waste time
-# running this once for each new repo
-exit 0 if @ARGV and $ARGV[0] eq 'POST_CREATE' and not $ARGV[2];
-
-# ----------------------------------------------------------------------
-# if called from POST_CREATE, we have only a single repo to worry about
-if ( @ARGV and $ARGV[0] eq 'POST_CREATE' ) {
-    my $repo = $ARGV[1];
-    fixup_config($repo);
-
-    exit 0;
-}
-
-# ----------------------------------------------------------------------
-# else it's all repos (i.e., called from POST_COMPILE)
-
-my $lpr = list_phy_repos();
-
-for my $pr (@$lpr) {
-    fixup_config($pr);
-}
-
-sub fixup_config {
-    my $pr      = shift;
-    my $creator = creator($pr);
-
-    my $gc = git_config( $pr, '.', 1 );
-    while ( my ( $key, $value ) = each( %{$gc} ) ) {
-        next if $key =~ /^gitolite-options\./;
-        if ( $value ne "" ) {
-            system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
-        } else {
-            system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );
-        }
-    }
-}
diff --git a/docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list b/docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list
deleted file mode 100755
index 446b0da..0000000
--- a/docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/perl
-
-# update git-daemon-export-ok files in each repo
-# ----------------------------------------------------------------------
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Easy;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-# skip if arg-0 is POST_CREATE and no arg-2 (user name) exists; this means
-# it's been triggered by a *normal* (not "wild") repo creation, which in turn
-# means a POST_COMPILE should be following so there's no need to waste time
-# running this once for each new repo
-exit 0 if @ARGV and $ARGV[0] eq 'POST_CREATE' and not $ARGV[2];
-
-my $EO = "git-daemon-export-ok";
-my $RB = $rc{GL_REPO_BASE};
-
-for my $d (`gitolite list-phy-repos | gitolite access % daemon R any`) {
-    my @F = split "\t", $d;
-    if ($F[2] =~ /DENIED/) {
-        unlink "$RB/$F[0].git/$EO";
-    } else {
-        textfile( file => $EO, repo => $F[0], text => "" );
-    }
-}
-
-# As a quick recap, the gitolite output looks somewhat like this:
-
-#   bar^Idaemon^IR any bar daemon DENIED by fallthru$
-#   foo^Idaemon^Irefs/.*$
-#   fubar^Idaemon^Irefs/.*$
-#   gitolite-admin^Idaemon^IR any gitolite-admin daemon DENIED by fallthru$
-#   testing^Idaemon^Irefs/.*$
-
-# where I've typed "^I" to denote a tab.
diff --git a/docker/gitolite/src/triggers/post-compile/update-gitweb-access-list b/docker/gitolite/src/triggers/post-compile/update-gitweb-access-list
deleted file mode 100755
index 937226b..0000000
--- a/docker/gitolite/src/triggers/post-compile/update-gitweb-access-list
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/sh
-
-# this is literally the simplest gitweb update possible.  You are free to add
-# whatever you want and contribute it back, as long as it is upward
-# compatible.
-
-# ----------------------------------------------------------------------
-# delete the 'description' file that 'git init' created if this is run from
-# the post-create trigger.  However, note that POST_CREATE is also called from
-# perms (since POST_CREATE doubles as eqvt of POST_COMPILE to propagate ad hoc
-# permissions changes for wild repos) and then you should not delete it.
-[ "$1" = "POST_CREATE" ] && [ "$4" != "perms" ] && rm -f $GL_REPO_BASE/$2.git/description 2>/dev/null
-
-# ----------------------------------------------------------------------
-# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
-# it's been triggered by a *normal* (not "wild") repo creation, which in turn
-# means a POST_COMPILE should be following so there's no need to waste time
-# running this once for each new repo
-[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
-
-plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
-[ -z "$plf" ] && plf=$HOME/projects.list
-# since mktemp does not honor umask, we just use it to generate a temp
-# filename (note: 'mktemp -u' on some systems, this gets close enough)
-tmpfile=`mktemp $plf.tmp_XXXXXXXX`
-rm -f $tmpfile;
-
-(
-    gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
-    gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
-) |
-    cut -f1 | sort -u | sed -e 's/$/.git/' > $tmpfile
-
-[ -f $plf ] && perl -e "chmod ( ( (stat('$plf'))[2] & 07777 ), '$tmpfile')"
-mv $tmpfile $plf
diff --git a/docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options b/docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options
deleted file mode 100755
index 9b499b2..0000000
--- a/docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/sh
-
-# Update git-daemon and gitweb access using 'option' lines instead of special
-# usernames.
-
-# To use:
-
-# * enable this combined updater in the rc file by removing the other two
-#   update-*-access-list entries and inserting this one instead.  (This would
-#   be in the POST_CREATE and POST_COMPILE lists).
-
-# * the add option lines in the conf file, like this:
-#
-#       repo foo @bar
-#           option daemon = 1
-#           option gitweb = 1
-
-# Note: don't forget that gitweb can also be enabled by actual config
-# variables (gitweb.owner, gitweb.description, gitweb.category)
-
-# This is useful for people who don't like '@all' to be literally *all* users,
-# including gitweb and daemon, and can't/won't use deny-rules properly.
-
-# ----------------------------------------------------------------------
-# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
-# it's been triggered by a *normal* (not "wild") repo creation, which in turn
-# means a POST_COMPILE should be following so there's no need to waste time
-# running this once for each new repo
-[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
-
-# first do the gitweb stuff
-
-plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
-[ -z "$plf" ] && plf=$HOME/projects.list
-
-(
-    gitolite list-phy-repos | gitolite git-config % gitolite-options.gitweb
-    gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
-) |
-    cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
-
-# now deal with git-daemon
-
-EO=git-daemon-export-ok
-RB=`gitolite query-rc GL_REPO_BASE`
-export EO RB
-
-export tmp=$(mktemp -d)
-trap "rm -rf $tmp" 0
-
-gitolite list-phy-repos | sort | tee $tmp/all | gitolite git-config % gitolite-options.daemon | cut -f1 > $tmp/daemon
-
-comm -23 $tmp/all $tmp/daemon | perl -lne 'unlink "$ENV{RB}/$_.git/$ENV{EO}"'
-cat               $tmp/daemon | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne 'textfile( file => $ENV{EO}, repo => $_, text => "");'
diff --git a/docker/gitolite/src/triggers/renice b/docker/gitolite/src/triggers/renice
deleted file mode 100755
index ba0b726..0000000
--- a/docker/gitolite/src/triggers/renice
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-n=$1
-[ "$n" = "PRE_GIT" ] && n=10
-renice -n $n $GL_TID >/dev/null
diff --git a/docker/gitolite/src/triggers/repo-specific-hooks b/docker/gitolite/src/triggers/repo-specific-hooks
deleted file mode 100755
index 5d52a47..0000000
--- a/docker/gitolite/src/triggers/repo-specific-hooks
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# setup repo-specific hooks
-
-use lib $ENV{GL_LIBDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-_die "repo-specific-hooks: LOCAL_CODE not defined in rc" unless $rc{LOCAL_CODE};
-_die "repo-specific-hooks: '$rc{LOCAL_CODE}/hooks/repo-specific' does not exist or is not a directory" unless -d "$rc{LOCAL_CODE}/hooks/repo-specific";
-
-_chdir( $ENV{GL_REPO_BASE} );
-
- at ARGV = ("gitolite list-phy-repos | gitolite git-config -ev -r % gitolite-options\\.hook\\. |");
-
-my $driver = "$rc{LOCAL_CODE}/hooks/multi-hook-driver";
-# Hook Driver
-{
-    local $/ = undef;
-    my $hook_text = <DATA>;
-    _print( $driver, $hook_text );
-    chmod 0755, $driver;
-}
-
-while (<>) {
-    chomp;
-    my ( $repo, $hook, $codes ) = split /\t/, $_;
-    $codes ||= '';
-
-    # we don't allow fiddling with the admin repo
-    if ( $repo eq 'gitolite-admin' ) {
-        _warn "repo-specific-hooks: ignoring attempts to set hooks for the admin repo";
-        next;
-    }
-
-    # get the hook name
-    $hook =~ s/^gitolite-options\.hook\.//;
-
-    unless ( $hook =~ /^(pre-receive|post-receive|post-update)$/ ) {
-        _warn "repo-specific-hooks: '$hook' is not allowed, ignoring";
-        _warn "    (only pre-receive, post-receive, and post-update are allowed)";
-        next;
-    }
-
-    my @codes = split /\s+/, $codes;
-
-    my $dst = "$repo.git/hooks/$hook";
-    unlink( glob("$dst.*") );
-
-    my $counter = "h00";
-    foreach my $code (@codes) {
-        if ( $code =~ m(^/|\.\.) ) {
-            _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
-            next;
-        }
-
-        my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
-        my $dst = "$repo.git/hooks/$hook.$counter-$code";
-        unless ( -x $src ) {
-            _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
-            next;
-        }
-        unlink $dst;
-        symlink $src, $dst or _warn "could not symlink '$src' to '$dst'";
-        $counter++;
-
-        # no sanity checks for multiple overwrites of the same hook
-    }
-
-    unlink $dst;
-    symlink $driver, $dst or die "could not symlink '$driver' to '$dst'";
-}
-
-__DATA__
-#/bin/sh
-
-# Determine what input the hook needs
-# post-update takes args, pre/post-receive take stdin
-type=args
-stdin=''
-[ $0 != hooks/post-update ] && {
-    type=stdin
-    stdin=`cat`
-}
-
-for h in $0.*; do
-    [ -x $h ] || continue
-    if [ $type = args ]
-    then
-        $h $@
-    else
-        echo "$stdin" | $h
-    fi
-done
diff --git a/docker/gitolite/src/triggers/set-default-roles b/docker/gitolite/src/triggers/set-default-roles
deleted file mode 100755
index 18ac28b..0000000
--- a/docker/gitolite/src/triggers/set-default-roles
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-# POST_CREATE trigger to set up default set of perms for a new wild repo
-
-# ----------------------------------------------------------------------
-# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists (i.e., it's not
-# a wild repo)
-[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
-[ "$4" = "R" ] || [ "$4" = "W" ] || [ "$4" = "perms-c" ] || [ "$4" = "fork" ] || exit 0
-
-die() { echo "$@" >&2; exit 1; }
-
-cd $GL_REPO_BASE/$2.git || die "could not cd to $GL_REPO_BASE/$2.git"
-gitolite git-config -r $2 gitolite-options.default.roles | sort | cut -f3 |
-    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$2/' > gl-perms
-
-# cache control, if rc says caching is on
-gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$2')";
-
-exit 0
diff --git a/docker/gitolite/src/triggers/upstream b/docker/gitolite/src/triggers/upstream
deleted file mode 100755
index c64e2f2..0000000
--- a/docker/gitolite/src/triggers/upstream
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/sh
-
-# manage local, gitolite-controlled, copies of read-only upstream repos.
-
-repo=$2
-
-url=$(gitolite git-config $repo gitolite-options.upstream.url)
-[ -z "$url" ] && exit 0     # exit if no url was specified
-
-cd $GL_REPO_BASE/$repo.git || exit 1
-
-[ "$1" != "fetch" ] && {
-    nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
-    [ -n "$nice" ] && find FETCH_HEAD -mmin -$nice 2>/dev/null | grep . >/dev/null && exit 0
-}
-
-git fetch -q "$url" '+refs/*:refs/*'
-
-# ----------------------------------------------------------------------
-
-# FEATURES:
-# * invokes upstream fetch on each local fetch
-#   (unless the optional 'nice' setting is enabled)
-# * can force a fetch (ignoring 'nice' value) from server CLI
-
-# INSTRUCTIONS:
-#
-# * uncomment 'upstream' in the ENABLE list in the rc file.
-# * add option lines to conf file.  For example:
-#
-#       repo git
-#           R                       =   @all
-#           RW+ my-company/         =   @developers
-#
-#           option  upstream.url    =   git://git.kernel.org/pub/scm/git/git.git
-#           option  upstream.nice   =   120
-#
-# * to force a fetch on the server shell (or via cron), run this command:
-#       gitolite ../triggers/upstream fetch reponame
-
-# ADDITIONAL NOTES:
-# * restrict local pushes to a namespace that the upstream won't use
-#   (otherwise the next fetch will wipe them out)
-# * if the upstream URL changes, just change the conf and push admin repo
-# * the 'nice' setting is in minutes and is optional; it is the minimum
-#   elapsed time between 2 upstream fetches.
-
-# USAGE EXAMPLE:
-#
-# Let's say you want to keep a read-only local mirror of all your github repos
-# on your local gitolite installation.  Assuming your github usernames are the
-# same as your local usernames, and you have updated GIT_CONFIG_KEYS in the rc
-# file to allow 'config' lines, you can do this:
-#
-#   repo github/CREATOR/..*
-#       C   = @all
-#       R   = @all
-#       option upstream.url                     =   git://github.com/%GL_REPO.git
-#       option upstream.nice                    =   120
-#       config url.git://github.com/.insteadOf  =   git://github.com/github/
-#
-# Now you can make local, read-only, clones of all your github repos with
-#
-#   git ls-remote gitolite:github/sitaramc/gitolite
-#   git ls-remote gitolite:github/sitaramc/hap
-#   (etc)
-#
-# and if milki were also a user on this gitolite instance, then
-#
-#   git ls-remote gitolite:github/milki/xclip
-#   git ls-remote gitolite:github/milki/ircblogger
-#   (etc)
diff --git a/docker/gitolite/t/0-me-first.t b/docker/gitolite/t/0-me-first.t
deleted file mode 100755
index 8c9d12b..0000000
--- a/docker/gitolite/t/0-me-first.t
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-
-# initial smoke tests
-# ----------------------------------------------------------------------
-
-try "plan 71";
-
-# basic push admin repo
-confreset;confadd '
-    repo aa
-        RW+     =   u1
-        RW      =   u2 u3
-
-    repo cc/..*
-        C       =   u4
-        RW+     =   CREATOR u5
-        R       =   READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-                                            /Initialized empty Git repository in .*/aa.git//
-
-    # basic clone
-    cd ..
-    glt clone u1 file:///aa u1aa;    ok;     /Cloning into 'u1aa'.../
-                                            /warning: You appear to have cloned an empty repository/
-    [ -d u1aa ];                    ok
-
-    # basic clone deny
-    glt clone u4 file:///aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
-    [ -d u4aa ];                    !ok
-
-    # basic push
-    cd u1aa;                        ok
-    tc z-507;                       ok;     /master .root-commit. 7cf7624. z-507/
-    glt push u1 origin HEAD;        ok;     /To file:///aa/
-                                            /\\[new branch\\] *HEAD -> master/
-
-    # basic rewind
-    tc o-866 o-867 o-868;           ok;     /master 2d066fb. o-868/
-    glt push u1 origin HEAD;        ok;     /7cf7624..2d066fb  HEAD -> master/
-    git reset --hard HEAD^;         ok;     /HEAD is now at 8b1456b o-867/
-    tc x-967;                       ok;     /master 284951d. x-967/
-    glt push u1 -f origin HEAD;     ok;     /\\+ 2d066fb...284951d HEAD -> master \\(forced update\\)/
-
-    # log file
-    cat \$(gitolite query-rc GL_LOGFILE);
-                                    ok;     /\tupdate\t/
-                                            /aa\tu1\t\\+\trefs/heads/master/
-                                            /2d066fb4860c29cf321170c17695c6883f3d50e8/
-                                            /284951dfa11d58f99ab76b9f4e4c1ad2f2461236/
-
-    # basic rewind deny
-    cd ..
-    glt clone u2 file:///aa u2aa;    ok;     /Cloning into 'u2aa'.../
-    cd u2aa;                        ok
-    tc g-776 g-777 g-778;           ok;     /master 9cbc181. g-778/
-    glt push u2 origin HEAD;        ok;     /284951d..9cbc181  HEAD -> master/
-    git reset --hard HEAD^;         ok;     /HEAD is now at 2edf7fc g-777/
-    tc d-485;                       ok;     /master 1c01d32. d-485/
-    glt push u2 -f origin HEAD;     !ok;    reject
-                                            /\\+ refs/heads/master aa u2 DENIED by fallthru/
-
-    # non-existent repos etc
-    glt ls-remote u4 file:///bb;    !ok;    /DENIED by fallthru/
-    glt ls-remote u4 file:///cc/1;  ok;     /Initialized empty/
-    glt ls-remote u5 file:///cc/1;  ok;     perl s/TRACE.*//g; !/\\S/
-    glt ls-remote u5 file:///cc/2;  !ok;    /DENIED by fallthru/
-    glt ls-remote u6 file:///cc/2;  !ok;    /DENIED by fallthru/
-
-    # command
-    glt perms u4 -c cc/bar/baz/frob + READERS u2;
-                                    ok;     /Initialized empty .*cc/bar/baz/frob.git/
-
-    # path traversal
-    glt ls-remote u4 file:///cc/dd/../ee
-                                    !ok;    /FATAL: 'cc/dd/\\.\\./ee' contains '\\.\\.'/
-    glt ls-remote u5 file:///cc/../../../../../..$rb/gitolite-admin
-                                    !ok;    /FATAL: 'cc/../../../../../..$rb/gitolite-admin' contains '\\.\\.'/
-
-    glt perms u4 -c cc/bar/baz/../frob + READERS u2
-                                    !ok;    /FATAL: no relative paths allowed anywhere!/
-
-";
diff --git a/docker/gitolite/t/C-vs-C.t b/docker/gitolite/t/C-vs-C.t
deleted file mode 100644
index fee5cc4..0000000
--- a/docker/gitolite/t/C-vs-C.t
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# the commit message in which this test is introduced should have details, but
-# briefly, this test makes sure that access() does not get confused by
-# repo-create permissions being allowed, when looking for branch-create
-# permissions.
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# branch permissions test
-# ----------------------------------------------------------------------
-
-try "plan 25";
-
-confreset;confadd '
-    repo foo/..*
-        C       =   @all
-        RW+CD   =   CREATOR
-        RW      =   u2
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..;                          ok
-    glt clone u1 file:///foo/aa;    ok
-    cd aa;                          ok
-    tc l-1;                         ok;     /master/
-    glt push u1 origin master:m1;   ok;     /To file:///foo/aa/
-                                            /\\* \\[new branch\\]      master -> m1/
-
-    tc l-2;                         ok;     /master/
-    glt push u2 origin master:m2;   !ok;    /FATAL: C/
-                                            /DENIED by fallthru/
-    glt push u2 origin master:m1;   ok;     /To file:///foo/aa/
-                                            /8cd302a..29b8683/
-                                            /master -> m1/
-";
diff --git a/docker/gitolite/t/README b/docker/gitolite/t/README
deleted file mode 100644
index 4ecdfb7..0000000
--- a/docker/gitolite/t/README
+++ /dev/null
@@ -1,14 +0,0 @@
-
-============================================
-WARNING: THE TEST SUITE DELETES STUFF FIRST!
-============================================
-
-Please run the tests ONLY on a userid where it's ok to LOSE DATA.
-
-On such a userid, clone gitolite then run this command in the clone:
-
-    GITOLITE_TEST=y prove
-
-http://gitolite.com/gitolite/testing.html has more details.  Alternatively,
-http://gitolite.com/gitolite/req.html#trying will help you try out gitolite if
-you want to play with gitolite safely.
diff --git a/docker/gitolite/t/access.t b/docker/gitolite/t/access.t
deleted file mode 100755
index 34e015f..0000000
--- a/docker/gitolite/t/access.t
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# test 'gitolite access'
-# ----------------------------------------------------------------------
-
-try "plan 208";
-
-confreset;confadd '
-    @admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    repo testing
-        RW+     =   @all
-
-    @g1 = t1
-    repo @g1
-        R       =   u2
-        RW      =   u3
-        RW+     =   u4
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-    gitolite access -q t1 u1;   !ok;    !/./
-    gitolite access -q t1 u1 R; !ok;    !/./
-    gitolite access -q t1 u1 W; !ok;    !/./
-    gitolite access -q t1 u1 +; !ok;    !/./
-    gitolite access -q t1 u2;   !ok;    !/./
-    gitolite access -q t1 u2 R; ok;     !/./
-    gitolite access -q t1 u2 W; !ok;    !/./
-    gitolite access -q t1 u2 +; !ok;    !/./
-    gitolite access -q t1 u3;   !ok;    !/./
-    gitolite access -q t1 u3 R; ok;     !/./
-    gitolite access -q t1 u3 W; ok;     !/./
-    gitolite access -q t1 u3 +; !ok;    !/./
-    gitolite access -q t1 u4;   ok;     !/./
-    gitolite access -q t1 u4 R; ok;     !/./
-    gitolite access -q t1 u4 W; ok;     !/./
-    gitolite access -q t1 u4 +; ok;     !/./
-
-    gitolite access t1 u1;      !ok;    /\\+ any t1 u1 DENIED by fallthru/
-    gitolite access t1 u1 R;    !ok;    /R any t1 u1 DENIED by fallthru/
-    gitolite access t1 u1 W;    !ok;    /W any t1 u1 DENIED by fallthru/
-    gitolite access t1 u1 +;    !ok;    /\\+ any t1 u1 DENIED by fallthru/
-    gitolite access t1 u2;      !ok;    /\\+ any t1 u2 DENIED by fallthru/
-    gitolite access t1 u2 R;    ok;     /refs/\.\*/
-    gitolite access t1 u2 W;    !ok;    /W any t1 u2 DENIED by fallthru/
-    gitolite access t1 u2 +;    !ok;    /\\+ any t1 u2 DENIED by fallthru/
-    gitolite access t1 u3;      !ok;    /\\+ any t1 u3 DENIED by fallthru/
-    gitolite access t1 u3 R;    ok;     /refs/\.\*/
-    gitolite access t1 u3 W;    ok;     /refs/\.\*/
-    gitolite access t1 u3 +;    !ok;    /\\+ any t1 u3 DENIED by fallthru/
-    gitolite access t1 u4;      ok;     /refs/\.\*/
-    gitolite access t1 u4 R;    ok;     /refs/\.\*/
-    gitolite access t1 u4 W;    ok;     /refs/\.\*/
-    gitolite access t1 u4 +;    ok;     /refs/\.\*/
-
-";
-
-confreset;confadd '
-    @admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    @g1 = u1
-    @g2 = u2
-    @g3 = u3
-    @gaa = aa
-    repo @gaa
-        RW+                 =   @g1
-        RW                  =   @g2
-        RW+     master      =   @g3
-        RW      master      =   u4
-        -       master      =   u5
-        RW+     dev         =   u5
-        RW                  =   u5
-';
-
-try "ADMIN_PUSH set2; !/FATAL/" or die text();
-
-try "
-    gitolite access \@gaa \@g1 + any ;                  ok;     /refs/.*/; !/DENIED/
-    gitolite access aa \@g1 + refs/heads/master ;       ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa \@g1 + refs/heads/next ;      ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa \@g1 W refs/heads/next ;      ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa u1 + refs/heads/dev ;         ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa u1 + refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
-    gitolite access aa u1 W refs/heads/next ;           ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa \@g2 + refs/heads/master ;    !ok;    /\\+ refs/heads/master \@gaa \@g2 DENIED by fallthru/
-    gitolite access \@gaa \@g2 + refs/heads/next ;      !ok;    /\\+ refs/heads/next \@gaa \@g2 DENIED by fallthru/
-    gitolite access aa \@g2 W refs/heads/master ;       ok;     /refs/.*/; !/DENIED/
-    gitolite access aa u2 + any ;                       !ok;    /\\+ any aa u2 DENIED by fallthru/
-    gitolite access \@gaa u2 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u2 DENIED by fallthru/
-    gitolite access \@gaa u2 W refs/heads/master ;      ok;     /refs/.*/; !/DENIED/
-    gitolite access \@gaa \@g3 + refs/heads/master ;    ok;     /refs/heads/master/; !/DENIED/
-    gitolite access \@gaa \@g3 W refs/heads/next ;      !ok;    /W refs/heads/next \@gaa \@g3 DENIED by fallthru/
-    gitolite access \@gaa \@g3 W refs/heads/dev ;       !ok;    /W refs/heads/dev \@gaa \@g3 DENIED by fallthru/
-    gitolite access aa u3 + refs/heads/dev ;            !ok;    /\\+ refs/heads/dev aa u3 DENIED by fallthru/
-    gitolite access aa u3 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u3 DENIED by fallthru/
-    gitolite access \@gaa u4 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u4 DENIED by fallthru/
-    gitolite access \@gaa u4 W refs/heads/master ;      ok;     /refs/heads/master/; !/DENIED/
-    gitolite access aa u4 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u4 DENIED by fallthru/
-    gitolite access \@gaa u4 W refs/heads/next ;        !ok;    /W refs/heads/next \@gaa u4 DENIED by fallthru/
-    gitolite access \@gaa u5 R any ;                    ok;     /refs/heads/dev/; !/DENIED/
-    gitolite access aa u5 R any ;                       ok;     /refs/heads/dev/; !/DENIED/
-    gitolite access \@gaa u5 + refs/heads/dev ;         ok;     /refs/heads/dev/; !/DENIED/
-    gitolite access \@gaa u5 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u5 DENIED by refs/heads/master/
-    gitolite access aa u5 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u5 DENIED by fallthru/
-    gitolite access \@gaa u5 R refs/heads/dev ;         ok;     /refs/heads/dev/; !/DENIED/
-    gitolite access \@gaa u5 R refs/heads/master ;      !ok;    /R refs/heads/master \@gaa u5 DENIED by refs/heads/master/
-    gitolite access \@gaa u5 R refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
-    gitolite access aa u5 W refs/heads/dev ;            ok;     /refs/heads/dev/; !/DENIED/
-    gitolite access aa u5 W refs/heads/master ;         !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
-    gitolite access \@gaa u5 W refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
-";
-
-confreset;confadd '
-    @admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    @gr1 = r1
-    repo @gr1
-        RW  refs/heads/v[0-9]   = u1
-        RW  refs/heads          = tester
-
-    @gr2 = r2
-    repo @gr2
-        RW  refs/heads/v[0-9]   = u1
-        -   refs/heads/v[0-9]   = tester
-        RW  refs/heads          = tester
-';
-
-try "ADMIN_PUSH set3; !/FATAL/" or die text();
-
-try "
-    gitolite access \@gr2 tester W refs/heads/v1;       !ok;    /W refs/heads/v1 \@gr2 tester DENIED by refs/heads/v\\[0-9\\]/
-    gitolite access \@gr1 tester W refs/heads/v1;       ok;     /refs/heads/; !/DENIED/
-    gitolite access r1 tester W refs/heads/v1;          ok;     /refs/heads/; !/DENIED/
-    gitolite access r2 tester W refs/heads/v1;          !ok;    /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
-    gitolite access r2 tester W refs/heads/va;          ok;     /refs/heads/; !/DENIED/
-";
-
-confreset;confadd '
-    repo foo
-        RW+ =   u1
-
-    @gr1 = foo bar
-
-    repo @gr1
-        RW  =   u2
-        R   =   u3
-
-    repo @all
-        R   =   gitweb
-';
-
-try "ADMIN_PUSH set4; !/FATAL/" or die text();
-
-try "
-    gitolite access foo u1 +;           ok
-    gitolite access foo u2 +;           !ok
-    gitolite access foo u3 +;           !ok
-    gitolite access foo u4 +;           !ok
-    gitolite access foo gitweb +;       !ok
-
-    gitolite access foo u1 W;           ok
-    gitolite access foo u2 W;           ok
-    gitolite access foo u3 W;           !ok
-    gitolite access foo u4 W;           !ok
-    gitolite access foo gitweb W;       !ok
-
-    gitolite access foo u1 R;           ok
-    gitolite access foo u2 R;           ok
-    gitolite access foo u3 R;           ok
-    gitolite access foo u4 R;           !ok
-    gitolite access foo gitweb R;       ok
-";
diff --git a/docker/gitolite/t/all-yall.t b/docker/gitolite/t/all-yall.t
deleted file mode 100755
index 901b1c2..0000000
--- a/docker/gitolite/t/all-yall.t
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# could anything be clearer than "all y'all"?
-# ----------------------------------------------------------------------
-
-try "plan 26";
-
-confreset;confadd '
-    repo @all
-        R   =   @all
-    repo foo
-        RW+ =   u1
-    repo bar
-        RW+ =   u2
-    repo dev/..*
-        C   =   u3 u4
-        RW+ =   CREATOR
-';
-
-try "
-    rm $ENV{HOME}/projects.list
-";
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    glt ls-remote u1 file:///dev/wild1
-                /FATAL: R any dev/wild1 u1 DENIED by fallthru/
-
-    glt clone u3 file:///dev/wild1
-                /Cloning into 'wild1'.../
-                /Initialized empty Git repository in .*/dev/wild1.git//
-                /warning: You appear to have cloned an empty repository./
-
-    cd wild1
-    tc n-855 n-856
-    glt push u3 origin master:wild1
-                /To file:///dev/wild1/
-                /\\* \\[new branch\\]      master -> wild1/
-    glt push u1 file:///foo master:br-foo
-                /To file:///foo/
-                /\\* \\[new branch\\]      master -> br-foo/
-    glt push u2 file:///bar master:br-bar
-                /To file:///bar/
-                /\\* \\[new branch\\]      master -> br-bar/
-
-    glt ls-remote u6 file:///foo
-                /refs/heads/br-foo/
-
-    glt ls-remote u6 file:///bar
-                /refs/heads/br-bar/
-
-    glt ls-remote u6 file:///dev/wild1
-                /refs/heads/wild1/
-";
-
-try "
-    gitolite ../triggers/post-compile/update-git-daemon-access-list;    ok
-    gitolite ../triggers/post-compile/update-gitweb-access-list;        ok
-    cat $ENV{HOME}/projects.list;                           ok
-";
-cmp 'bar.git
-dev/wild1.git
-foo.git
-gitolite-admin.git
-testing.git
-';
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-
-try "
-    cd ..
-    cd ..
-    echo $rb
-    find $rb -name git-daemon-export-ok | sort
-    perl s,$rb/,,g
-";
-
-cmp 'bar.git/git-daemon-export-ok
-dev/wild1.git/git-daemon-export-ok
-foo.git/git-daemon-export-ok
-gitolite-admin.git/git-daemon-export-ok
-testing.git/git-daemon-export-ok
-';
diff --git a/docker/gitolite/t/basic.t b/docker/gitolite/t/basic.t
deleted file mode 100755
index 7579935..0000000
--- a/docker/gitolite/t/basic.t
+++ /dev/null
@@ -1,284 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# some more basic tests
-# ----------------------------------------------------------------------
-
-try "
-    plan 217
-    CHECK_SETUP
-
-    # subtest 1
-    cd ..
-    CLONE dev2 gitolite-admin ga2
-                                !ok;    gsh
-                                        /DENIED by fallthru/
-                                        /fatal: Could not read from remote repository/
-    glt clone admin --progress file:///gitolite-admin ga2
-                                ok;     gsh
-                                        /Counting/; /Compressing/; /Total/
-    cd gitolite-admin;          ok
-    ";
-
-put "conf/gitolite.conf", "
-    \@admins     =   admin dev1
-    repo gitolite-admin
-        -   mm  =   \@admins
-        RW      =   \@admins
-        RW+     =   admin
-
-    repo testing
-        RW+     =   \@all
-";
-
-try "
-    # push
-    git add conf;               ok
-    git status -s;              ok;     /M  conf/gitolite.conf/
-    git commit -m t01a;         ok;     /master.*t01a/
-    PUSH dev2;                  !ok;    gsh
-                                        /DENIED by fallthru/
-                                        /fatal: Could not read from remote repository/
-    PUSH admin;                 ok;     /master -> master/
-    empty;                      ok;
-    PUSH admin master:mm
-                                !ok;    gsh
-                                        /DENIED by refs/heads/mm/
-                                        reject
-    ";
-
-put "conf/gitolite.conf", "
-    \@admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    repo testing
-        RW+     =   \@all
-
-    repo t1
-        R       =   u2
-        RW      =   u3
-        RW+     =   u4
-";
-
-try "
-    # subtest 2
-    ADMIN_PUSH t01b
-
-    # clone
-    cd ..;                      ok;
-    CLONE u1 t1;                !ok;    gsh
-                                        /DENIED by fallthru/
-                                        /fatal: Could not read from remote repository/
-    CLONE u2 t1;                ok;     gsh
-                                        /warning: You appear to have cloned an empty repository./
-    [ -d t1/.git ];             ok
-    cd t1;                      ok;
-
-    # push
-    test-commit tc1 tc2 tc2;    ok;     /a530e66/
-    PUSH u2;                    !ok;    gsh
-                                        /DENIED by fallthru/
-                                        /fatal: Could not read from remote repository/
-    PUSH u3 master;             ok;     gsh
-                                        /master -> master/
-
-    # rewind
-    reset-h HEAD^;              ok;     /HEAD is now at aa2b5c5 tc2/
-    test-tick; test-commit tc3; ok;     /3ffced1/
-    PUSH u3;                    !ok;    gsh
-                                        /rejected.*master -> master.*non-fast-forward./
-    PUSH u3 -f;                 !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-    PUSH u4 +master;            ok;     gsh
-                                        / \\+ a530e66...3ffced1 master -> master.*forced update./
-";
-
-put "../gitolite-admin/conf/gitolite.conf", "
-    \@admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    include 'i1.conf'
-";
-
-put "../gitolite-admin/conf/i1.conf", "
-    \@g1 = u1
-    \@g2 = u2
-    \@g3 = u3
-    \@gaa = aa
-    repo \@gaa
-        RW+                 =   \@g1
-        RW                  =   \@g2
-        RW+     master      =   \@g3
-        RW      master      =   u4
-        -       master      =   u5
-        RW+     dev         =   u5
-        RW                  =   u5
-";
-
-try "
-    # subtest 3
-    ADMIN_PUSH t01c
-
-    cd ..;                      ok
-";
-
-try "
-    CLONE u1 aa;                ok;     gsh
-    cd aa;                      ok
-    test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9
-                                ok
-    PUSH u1 HEAD;               ok;     gsh
-                                        /To file:///aa/
-                                        /\\* \\[new branch\\]      HEAD -> master/
-    branch dev;                 ok
-    branch foo;                 ok
-
-    # u1 rewind master ok
-    reset-h HEAD^;              ok
-    test-commit r1;             ok
-    PUSH u1 +master;            ok;     gsh
-                                        /To file:///aa/
-                                        /\\+ 27ed463...05adfb0 master -> master .forced update./
-
-    # u2 rewind master !ok
-    reset-h HEAD^;              ok
-    test-commit r2;             ok
-    PUSH u2 +master;            !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-
-    # u3 rewind master ok
-    reset-h HEAD^;              ok
-    test-commit r3;             ok
-    PUSH u3 +master;            ok;     gsh
-                                        /To file:///aa/
-                                        /\\+ 05adfb0...6a532fe master -> master .forced update./
-
-    # u4 push master ok
-    test-commit u4;             ok
-    PUSH u4 master;             ok;     gsh
-                                        /To file:///aa/
-                                        /6a532fe..f929773 +master -> master/
-
-    # u4 rewind master !ok
-    reset-h HEAD^;              ok
-    PUSH u4 +master;            !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-
-    # u3,u4 push other branches !ok
-    PUSH u3 dev;                !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-    PUSH u4 dev;                !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-    PUSH u3 foo;                !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-    PUSH u4 foo;                !ok;    gsh
-                                        reject
-                                        /DENIED by fallthru/
-
-    # clean up for next set
-    glt push u1 -f origin master dev foo
-                                ok;     gsh
-                                        /f929773...6a532fe master -> master .forced update./
-                                        /new branch.*dev -> dev/
-                                        /new branch.*foo -> foo/
-
-    # u5 push master !ok
-    test-commit u5
-    PUSH u5 master;             !ok;    gsh
-                                        reject
-                                        /DENIED by refs/heads/master/
-
-    # u5 rewind dev ok
-    PUSH u5 +dev^:dev
-                                ok;     gsh
-                                        /\\+ 27ed463...1ad477a dev\\^ -> dev .forced update./
-
-
-    # u5 rewind foo !ok
-    PUSH u5 +foo^:foo
-                                !ok;    gsh
-                                        reject
-                                        /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/
-
-    # u5 push foo ok
-    git checkout foo
-    /Switched to branch 'foo'/
-
-    test-commit u5
-    PUSH u5 foo;                ok;     gsh
-                                        /27ed463..83da62c *foo -> foo/
-
-    # u1 delete dev ok
-    PUSH u1 :dev;               ok;     gsh
-                                        / - \\[deleted\\] *dev/
-
-    # push it back
-    PUSH u1 dev;                ok;     gsh
-                                        /\\* \\[new branch\\] *dev -> dev/
-
-";
-
-put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
-    \@gr1 = r1
-    repo \@gr1
-        RW  refs/heads/v[0-9]   = u1
-        RW  refs/heads          = tester
-";
-
-try "
-    # subtest 4
-    ADMIN_PUSH t01d
-
-    cd ..;                      ok
-
-    CLONE tester r1;            ok;     gsh
-                                        /Cloning into 'r1'.../
-    cd r1;                      ok
-    test-commit r1a r1b r1c r1d r1e r1f
-                                ok
-    PUSH tester HEAD;           ok;     gsh
-                                        /\\* \\[new branch\\] *HEAD -> master/
-    git branch v1
-    PUSH tester v1;             ok;     gsh
-                                        /\\* \\[new branch\\] *v1 -> v1/
-
-";
-
-put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
-    \@gr2 = r2
-    repo \@gr2
-        RW  refs/heads/v[0-9]   = u1
-        -   refs/heads/v[0-9]   = tester
-        RW  refs/heads          = tester
-";
-
-try "
-    # subtest 5
-    ADMIN_PUSH t01e
-
-    cd ..;                      ok
-
-    CLONE tester r2;            ok;     gsh
-                                        /Cloning into 'r2'.../
-    cd r2;                      ok
-    test-commit r2a r2b r2c r2d r2e r2f
-                                ok
-    PUSH tester HEAD;           ok;     gsh
-                                        /\\* \\[new branch\\] *HEAD -> master/
-    git branch v1
-    PUSH tester v1;             !ok;    gsh
-                                        /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
-"
diff --git a/docker/gitolite/t/branch-perms.t b/docker/gitolite/t/branch-perms.t
deleted file mode 100755
index e59baea..0000000
--- a/docker/gitolite/t/branch-perms.t
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# branch permissions test
-# ----------------------------------------------------------------------
-
-try "plan 82";
-
-confreset;confadd '
-    @g1 = u1
-    @g2 = u2
-    @g3 = u3
-    @gaa = aa
-    repo @gaa
-        RW+                 =   @g1
-        RW                  =   @g2
-        RW+     master      =   @g3
-        RW      master      =   u4
-        -       master      =   u5
-        RW+     dev         =   u5
-        RW                  =   u5
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..;                          ok
-    glt clone u1 file:///aa;         ok
-    cd aa;                          ok
-    tc l-995 l-996 l-997 l-998 l-999 l-1000 l-1001 l-1002 l-1003;
-                                    ok;     /master a788db9. l-1003/
-    glt push u1 origin HEAD;        ok;     /To file:///aa/
-                                            /\\* \\[new branch\\]      HEAD -> master/
-
-    git branch dev;                 ok
-    git branch foo;                 ok
-
-    # u1 rewind master succeed
-    git reset --hard HEAD^;         ok;     /HEAD is now at 65d5f4a l-1002/
-    tc v-865;                       ok;     /master 3053bb4. v-865/
-    glt push u1 origin +master;     ok;     /\\+ a788db9...3053bb4 master -> master \\(forced update\\)/
-
-    # u2 rewind master fail
-    git reset --hard HEAD^;         ok;     /HEAD is now at 65d5f4a l-1002/
-    tc s-361;                       ok;     /master b331651. s-361/
-    glt push u2 file:///aa +master;  !ok;    reject
-                                            /\\+ refs/heads/master aa u2 DENIED by fallthru/
-
-    # u3 rewind master succeed
-    git reset --hard HEAD^;         ok
-    tc m-508;                       ok
-    glt push u3 file:///aa +master;  ok;     /\\+ .* master -> master \\(forced update\\)/
-
-    # u4 push master succeed
-    tc f-526;                       ok;
-    glt push u4 file:///aa master;   ok;     /master -> master/
-
-    # u4 rewind master fail
-    git reset --hard HEAD^;         ok;
-    glt push u4 file:///aa +master;  !ok;    /\\+ refs/heads/master aa u4 DENIED by fallthru/
-
-    # u3 and u4 / dev foo -- all 4 fail
-    glt push u3 file:///aa dev;      !ok;    /W refs/heads/dev aa u3 DENIED by fallthru/
-    glt push u4 file:///aa dev;      !ok;    /W refs/heads/dev aa u4 DENIED by fallthru/
-    glt push u3 file:///aa foo;      !ok;    /W refs/heads/foo aa u3 DENIED by fallthru/
-    glt push u4 file:///aa foo;      !ok;    /W refs/heads/foo aa u4 DENIED by fallthru/
-
-    # clean up for next set
-    glt push u1 -f origin master dev foo
-                                    ok
-
-    # u5 push master fail
-    tc l-417;                       ok
-    glt push u5 file:///aa master;   !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
-
-    # u5 rewind dev succeed
-    glt push u5 file:///aa +dev^:dev
-                                    ok;     /\\+ .* dev\\^ -> dev \\(forced update\\)/
-
-    # u5 rewind foo fail
-    glt push u5 file:///aa +foo^:foo
-                                    !ok;    /\\+ refs/heads/foo aa u5 DENIED by fallthru/
-
-    # u5 tries to push foo; succeeds
-    git checkout foo;               ok;     /Switched to branch 'foo'/
-
-    # u5 push foo succeed
-    tc e-530;                       ok;
-    glt push u5 file:///aa foo;      ok;     /foo -> foo/
-
-    # u1 delete branch dev succeed
-    glt push u1 origin :dev;        ok;     / - \\[deleted\\] *dev/
-
-    # quietly push it back again
-    glt push u1 origin dev;         ok;     / * \\[new branch\\]      dev -> dev/
-
-    ";
-
-    confadd '
-        repo @gaa
-            RWD     dev         =   u4
-    ';
-
-try "ADMIN_PUSH set2; !/FATAL/" or die text();
-
-try "
-    # u1 tries to delete dev on a new setup
-    cd ../aa;                       ok;     /master -> master/
-
-    # u1 delete branch dev fail
-    glt push u1 origin :dev;        !ok;    /D refs/heads/dev aa u1 DENIED by fallthru/
-
-    # u4 delete branch dev succeed
-    glt push u4 file:///aa :dev;     ok;     / - \\[deleted\\] *dev/
-
-";
diff --git a/docker/gitolite/t/daemon-gitweb-via-perms.t b/docker/gitolite/t/daemon-gitweb-via-perms.t
deleted file mode 100755
index 8707e19..0000000
--- a/docker/gitolite/t/daemon-gitweb-via-perms.t
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# basic tests
-# ----------------------------------------------------------------------
-
-try "plan 24";
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-confreset;confadd '
-
- at leads = u1 u2
- at devs = u1 u2 u3 u4
-
- at gbar = bar/CREATOR/..*
-repo    @gbar
-    C               =   @leads
-    RW+             =   @leads
-    RW              =   WRITERS @devs
-    R               =   READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-chdir($rb);
-my $h = $ENV{HOME};
-
-try "
-    glt ls-remote u1 file:///bar/u1/try1
-    /Initialized empty Git repository in .*/bar/u1/try1.git/
-
-    find . -name git-daemon-export-ok
-    /testing.git/git-daemon-export-ok/
-
-    cat $h/projects.list
-    /testing.git/
-
-    glt ls-remote u1 file:///bar/u1/try2
-    /Initialized empty Git repository in .*/bar/u1/try2.git/
-
-    find $h/repositories -name git-daemon-export-ok
-    /testing.git/git-daemon-export-ok/
-
-    cat $h/projects.list
-    /testing.git/
-
-    glt perms u1 bar/u1/try1 + READERS daemon
-    !/./
-
-    glt perms u1 bar/u1/try1 -l
-    /READERS daemon/
-
-    find $h/repositories -name git-daemon-export-ok
-    /repositories/testing.git/git-daemon-export-ok/
-    /repositories/bar/u1/try1.git/git-daemon-export-ok/
-
-    cat $h/projects.list
-    /testing.git/
-
-    glt perms u1 bar/u1/try2 + READERS gitweb
-
-    glt perms u1 bar/u1/try2 -l
-    /READERS gitweb/
-
-    find $h/repositories -name git-daemon-export-ok
-    /testing.git/git-daemon-export-ok/
-    /bar/u1/try1.git/git-daemon-export-ok/
-
-    cat $h/projects.list
-    /bar/u1/try2.git/
-    /testing.git/
-";
diff --git a/docker/gitolite/t/deleg-1.t b/docker/gitolite/t/deleg-1.t
deleted file mode 100755
index 89da137..0000000
--- a/docker/gitolite/t/deleg-1.t
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# delegation tests -- part 1
-# ----------------------------------------------------------------------
-
-try "plan 54";
-
-try "
-    DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
-    DEF SUBCONF_PUSH = SP_1 %2; glt push %1 origin; gsh; /master -> master/
-";
-
-confreset;confadd '
-    @u1r    =   r1a r1b
-    @u2r    =   r2a r2b
-    @u3r    =   r3a r3b
-
-    # the admin repo access was probably like this to start with:
-    repo gitolite-admin
-        RW                              = u1 u2 u3
-        RW+ NAME/                       = admin
-        RW  NAME/conf/fragments/u1r     = u1
-        RW  NAME/conf/fragments/u2r     = u2
-        RW  NAME/conf/fragments/u3r     = u3
-        -   NAME/                       = @all
-
-        subconf "fragments/*.conf"
-';
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-mkdir "conf/fragments";
-put   "conf/fragments/u1r.conf", '
-    repo @u1r
-        RW+     =   tester
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "
-        /Initialized empty Git repository in .*/r1a.git//
-        /Initialized empty Git repository in .*/r1b.git//
-";
-
-# u1 push u1r pass
-put   "conf/fragments/u1r.conf", '
-    repo @u1r
-        RW+     =   u5
-';
-try "SUBCONF_PUSH u1 u1; !/FATAL/" or die text();
-
-# u2 main push fail
-confadd '
-    repo @u1r
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u2 u2; /FATAL/;
-        /W VREF/NAME/conf/gitolite.conf gitolite-admin u2 DENIED by VREF/NAME//
-";
-
-try "git reset --hard origin/master; ok";
-
-# u2 push u1r fail
-put   "conf/fragments/u1r.conf", '
-    repo @u1r
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u2 u2; /FATAL/
-        /W VREF/NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by VREF/NAME//
-";
-
-try "git reset --hard origin/master; ok";
-
-# u3 set perms for r2a fail
-put   "conf/fragments/u3r.conf", '
-    repo r2a
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u3 u3;
-        /WARNING: subconf 'u3r' attempting to set access for r2a/
-";
-
-try "git reset --hard origin/master; ok";
-
-# u3 add r2b to u3r fail
-
-put   "conf/fragments/u3r.conf", '
-    @u3r    =   r2b
-    repo @u3r
-        RW+     =   u6
-';
-
-try "SUBCONF_PUSH u3 u3
-        /WARNING: expanding '\@u3r'/
-        /WARNING: subconf 'u3r' attempting to set access for r2b/
-";
diff --git a/docker/gitolite/t/deleg-2.t b/docker/gitolite/t/deleg-2.t
deleted file mode 100755
index 98fb02e..0000000
--- a/docker/gitolite/t/deleg-2.t
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# delegation tests -- part 2
-# ----------------------------------------------------------------------
-
-try "plan 55";
-
-try "
-    DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
-    DEF SUBCONF_PUSH = SP_1 %2; glt push %1 origin; gsh; /master -> master/
-";
-
-confreset;confadd '
-    # group your projects/repos however you want
-    @u1r    =   r1[ab]
-    @u2r    =   r2[ab]
-    @u3r    =   r3[ab]
-
-    # the admin repo access was probably like this to start with:
-    repo gitolite-admin
-        RW                              = u1 u2 u3
-        RW+ NAME/                       = admin
-        RW  NAME/conf/fragments/u1r     = u1
-        RW  NAME/conf/fragments/u2r     = u2
-        RW  NAME/conf/fragments/u3r     = u3
-        -   NAME/                       = @all
-
-    subconf "fragments/*.conf"
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "mkdir -p conf/fragments; ok";
-
-put "conf/fragments/u1r.conf", '
-    repo r1a r1b
-        C       =   @all
-        RW+     =   CREATOR
-    repo @u1r
-        RW+     =   tester
-';
-
-put "conf/fragments/u2r.conf", '
-    repo @u2r
-        C       =   @all
-        RW+     =   CREATOR
-    repo @u2r
-        RW+     =   tester
-';
-
-put "conf/fragments/u3r.conf", '
-    repo @u3r
-        C       =   @all
-        RW+     =   CREATOR
-    repo @u3r
-        RW+     =   tester
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "
-        /Initialized empty Git repository in .*/r1a.git//
-        /Initialized empty Git repository in .*/r1b.git//
-";
-
-# u1 push u1r pass
-put   "conf/fragments/u1r.conf", '
-    repo @u1r
-        RW+     =   u5
-';
-try "SUBCONF_PUSH u1 u1; !/FATAL/" or die text();
-
-# u2 main push fail
-confadd '
-    repo @u1r
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u2 u2; /FATAL/;
-        /W VREF/NAME/conf/gitolite.conf gitolite-admin u2 DENIED by VREF/NAME//
-";
-
-try "git reset --hard origin/master; ok";
-
-# u2 push u1r fail
-put   "conf/fragments/u1r.conf", '
-    repo @u1r
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u2 u2; /FATAL/
-        /W VREF/NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by VREF/NAME//
-";
-
-try "git reset --hard origin/master; ok";
-
-# u3 set perms for r2a fail
-put   "conf/fragments/u3r.conf", '
-    repo r2a
-        RW+     =   u6
-';
-try "SUBCONF_PUSH u3 u3;
-        /WARNING: subconf 'u3r' attempting to set access for r2a/
-";
-
-try "git reset --hard origin/master; ok";
-
-# u3 add r2b to u3r fail
-
-put   "conf/fragments/u3r.conf", '
-    @u3r    =   r2b
-    repo @u3r
-        RW+     =   u6
-';
-
-try "SUBCONF_PUSH u3 u3
-        /WARNING: expanding '\@u3r'/
-        /WARNING: subconf 'u3r' attempting to set access for r2b/
-";
diff --git a/docker/gitolite/t/deny-create.t b/docker/gitolite/t/deny-create.t
deleted file mode 100755
index a4b7e4f..0000000
--- a/docker/gitolite/t/deny-create.t
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# deny-create, the RW.*C flag
-# ----------------------------------------------------------------------
-
-try "plan 72";
-
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-# test "C" permissions
-
-confreset; confadd '
-    @leads = u1 u2
-    @devs = u1 u2 u3 u4
-
-    @gfoo = foo
-    repo    @gfoo
-        RW+C                =   @leads
-        RW+C personal/USER/ =   @devs
-        RW                  =   @devs
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-    glt clone u1 file:///foo
-
-    cd foo
-    tc t-413 t-414 t-415 t-416 t-417
-
-    # u1 can push/rewind master on foo
-    glt push u1 origin master
-        POK; /master -> master/
-    glt push u1 -f origin master^^:master
-        POK; /master\\^\\^ -> master/
-
-    # u2 can create newbr1 on foo
-    glt push u2 file:///foo master:newbr1
-        POK; /master -> newbr1/
-
-    # u2 can create newtag on foo
-    git tag newtag
-    glt push u2 file:///foo newtag
-        POK; /newtag -> newtag/
-
-    # u3 can push newbr1 on foo
-    tc u-962 u-963 u-964 u-965 u-966
-    glt push u3 file:///foo master:newbr1
-        POK; /master -> newbr1/
-
-    # u4 canNOT create newbr2 on foo
-    tc e-615 e-616 e-617 e-618 e-619
-    glt push u3 file:///foo master:newbr2
-        /C refs/heads/newbr2 foo u3 DENIED by fallthru/
-        reject
-
-    # u4 canNOT create newtag2 on foo
-    git tag newtag2
-    glt push u3 file:///foo newtag2
-        /C refs/tags/newtag2 foo u3 DENIED by fallthru/
-        reject
-
-    # u4 can create/rewind personal/u4/newbr3 on foo
-    tc f-664 f-665 f-666 f-667 f-668
-    glt push u4 file:///foo master:personal/u4/newbr3
-        POK; /master -> personal/u4/newbr3/
-    glt push u4 -f origin master^^:personal/u4/newbr3
-        POK; /master\\^\\^ -> personal/u4/newbr3/
-";
-
-# bar, without "C" permissions, should behave like old
-
-confadd '
-    @leads = u1 u2
-    @devs = u1 u2 u3 u4
-
-    @gbar = bar
-    repo    @gbar
-        RW+                 =   @leads
-        RW+  personal/USER/ =   @devs
-        RW                  =   @devs
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-    glt clone u1 file:///bar
-
-    cd bar
-    tc u-907 u-908 u-909 u-910 u-911
-
-    # u1 can push/rewind master on bar
-    glt push u1 origin master
-        POK; /master -> master/
-    glt push u1 -f origin master^^:master
-        POK; /master\\^\\^ -> master/
-
-    # u2 can create newbr1 on bar
-    glt push u2 file:///bar master:newbr1
-        POK; /master -> newbr1/
-
-    # u2 can create newtag on bar
-    git tag newtag
-    glt push u2 file:///bar newtag
-        POK; /newtag -> newtag/
-
-    # u3 can push newbr1 on bar
-    tc y-862 y-863 y-864 y-865 y-866
-    glt push u3 file:///bar master:newbr1
-        POK; /master -> newbr1/
-
-    # u4 can create newbr2 on bar
-    tc q-417 q-418 q-419 q-420 q-421
-    glt push u3 file:///bar master:newbr2
-        POK; /master -> newbr2/
-
-    # u4 can create newtag2 on bar
-    git tag newtag2
-    glt push u3 file:///bar newtag2
-        POK; /newtag2 -> newtag2/
-
-    # u4 can create/rewind personal/u4/newbr3 on bar
-    tc v-605 v-606 v-607 v-608 v-609
-    glt push u4 file:///bar master:personal/u4/newbr3
-        POK; /master -> personal/u4/newbr3/
-    glt push u4 -f origin master^^:personal/u4/newbr3
-        POK; /master\\^\\^ -> personal/u4/newbr3/
-
-";
diff --git a/docker/gitolite/t/deny-rules-2.t b/docker/gitolite/t/deny-rules-2.t
deleted file mode 100755
index 0ca15fe..0000000
--- a/docker/gitolite/t/deny-rules-2.t
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# more on deny-rules
-# ----------------------------------------------------------------------
-
-try "plan 126";
-
-try "
-    DEF GOOD    = /refs/\\.\\*/
-    DEF BAD     = /DENIED/
-
-    DEF Ryes    = gitolite access %1 %2 R any;  ok; GOOD
-    DEF Rno     = gitolite access %1 %2 R any;  !ok; BAD
-
-    DEF Wyes    = gitolite access %1 %2 W any;  ok; GOOD
-    DEF Wno     = gitolite access %1 %2 W any;  !ok; BAD
-
-    DEF GWyes   = Ryes %1 gitweb
-    DEF GWno    = Rno  %1 gitweb
-
-    DEF GDyes   = Ryes %1 daemon
-    DEF GDno    = Rno  %1 daemon
-";
-
-confreset;confadd '
-    repo one
-        RW+ =   u1
-        R   =   u2
-        -   =   u2 u3
-        R   =   @all
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    Wyes one u1
-
-    Ryes one u2
-    Wno  one u2
-
-    Ryes one u3
-    Wno  one u3
-
-    Ryes one u6
-    Wno  one u6
-
-    GDyes one
-    GWyes one
-";
-
-confadd '
-    option deny-rules = 1
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    Wyes one u1
-
-    Ryes one u2
-    Wno  one u2
-
-    Rno  one u3
-
-    Ryes one u6
-    Wno  one u6
-
-    GDyes one
-    GWyes one
-";
-
-confadd '
-    repo two
-        RW+ =   u1
-        R   =   u2
-        -   =   u2 u3 gitweb daemon
-        R   =   @all
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    GWyes two
-    GDyes two
-";
-
-confadd '
-    option deny-rules = 1
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    GWno  two
-    GDno  two
-";
-
-# set 3 -- allow gitweb to all but admin repo
-
-confadd '
-    repo gitolite-admin
-        -   =   gitweb daemon
-    option deny-rules = 1
-
-    repo three
-        RW+ =   u3
-        R   =   gitweb daemon
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    GDyes   three
-    GWyes   three
-    GDno    gitolite-admin
-    GWno    gitolite-admin
-";
-
-# set 4 -- allow gitweb to all but admin repo
-
-confadd '
-    repo four
-        RW+ =   u4
-        -   =   gitweb daemon
-
-    repo @all
-        R   =   @all
-';
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    GDyes   four
-    GWyes   four
-    GDno    gitolite-admin
-    GWno    gitolite-admin
-";
-
-# set 5 -- go wild
-
-confreset; confadd '
-    repo foo/..*
-        C   =   u1
-        RW+ =   CREATOR
-        -   =   gitweb daemon
-        R   =   @all
-
-    repo bar/..*
-        C   =   u2
-        RW+ =   CREATOR
-        -   =   gitweb daemon
-        R   =   @all
-    option deny-rules = 1
-';
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    glt ls-remote u1 file:///foo/one
-    glt ls-remote u2 file:///bar/two
-    Wyes foo/one u1
-    Wyes bar/two u2
-
-    GDyes foo/one
-    GDyes foo/one
-    GWno  bar/two
-    GWno  bar/two
-";
diff --git a/docker/gitolite/t/deny-rules.t b/docker/gitolite/t/deny-rules.t
deleted file mode 100755
index c0e7cbb..0000000
--- a/docker/gitolite/t/deny-rules.t
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# deny rules
-# ----------------------------------------------------------------------
-
-try "plan 11";
-
-confreset;confadd '
-    # start with...
-
-    repo gitolite-admin
-        -   =   gitweb daemon
-    option deny-rules = 1
-
-    # main ruleset goes here
-
-    @ga = a
-    @gb = b
-    @gc = c
-
-    # and end with
-
-    repo @ga
-        RW  =   u1
-        -   =   @all
-    option deny-rules = 1
-
-    repo @gb
-        RW  =   u2
-        -   =   daemon
-    option deny-rules = 1
-
-    repo @gc
-        RW  =   u3
-
-    repo @all
-        R   =   @all
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-try "
-    cat $ENV{HOME}/projects.list;                           ok
-";
-cmp 'b.git
-c.git
-testing.git
-';
-
-try "
-    cd ..
-    cd ..
-    echo $rb
-    find $rb -name git-daemon-export-ok | sort
-    perl s,$rb/,,g
-";
-cmp 'c.git/git-daemon-export-ok
-testing.git/git-daemon-export-ok
-'
diff --git a/docker/gitolite/t/easy.t b/docker/gitolite/t/easy.t
deleted file mode 100755
index dcd6c1a..0000000
--- a/docker/gitolite/t/easy.t
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Easy;
-use Gitolite::Test;
-# put this after ::Easy because it chdirs away from where you were and the
-# 'use lib "src"', not being absolute, fails
-
-# smoke tests for Easy.pm
-# ----------------------------------------------------------------------
-# for a change these are actual perl tests, so not much call for tsh here,
-# although I still need the basic infrastructure for setting up the repos and
-# I still can't intermix this with perl's Test.pm or Test::More etc
-sub ok { (+shift) ? print "ok\n" : print "not ok\n"; }
-sub nok { (+shift) ? print "not ok\n" : print "ok\n"; }
-sub msg { return unless $ENV{D}; print STDERR "#" . +shift . "\n"; }
-
-try "plan 98";
-
-try "
-    cat $ENV{HOME}/.gitolite.rc
-    perl s/GIT_CONFIG_KEYS.*/GIT_CONFIG_KEYS => '.*',/
-    put $ENV{HOME}/.gitolite.rc
-";
-
-# basic push admin repo
-confreset;confadd '
-    repo gitolite-admin
-        RW+     VREF/NAME/      =   admin
-        RW+     VREF/NAME/u5/   =   u5
-
-    repo aa
-        RW+     =   u1
-        RW      =   u2
-        R       =   u4
-
-        config for.aa   =   1
-
-    repo cc/..*
-        C       =   u4
-        RW+     =   CREATOR u5
-        R       =   u6
-
-        config for.cc   =   1
-
-    @oddguys = u1 u3 u5
-    @evensout = u2 u4 u6
-
-    repo cc/sub/..*
-        config sub.cc   =   1
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-# valid_user() -- an internal function but still worth testing by itself first
-eval { Gitolite::Easy::valid_user(); };
-ok($@ =~ /FATAL.*GL_USER not set/);
-$ENV{GL_USER} = "u2";
-eval { Gitolite::Easy::valid_user(); };
-nok($@ =~ /FATAL.*GL_USER not set/);
-
-# is_admin
-msg('is_admin');
-$ENV{GL_USER} = "admin"; ok(is_admin());
-$ENV{GL_USER} = "u5"; ok(is_admin());
-$ENV{GL_USER} = "u2"; nok(is_admin());
-
-# is_super_admin -- not sure how useful it is right now
-msg('is_super_admin');
-$ENV{GL_USER} = "admin"; ok( is_super_admin() );
-$ENV{GL_USER} = "u5";    nok( is_super_admin() );
-$ENV{GL_USER} = "u2";    nok( is_super_admin() );
-
-# in_group
-msg('in_group');
-$ENV{GL_USER} = "u1"; ok( in_group('oddguys') );  nok( in_group('evensout') );
-$ENV{GL_USER} = "u3"; ok( in_group('oddguys') );  nok( in_group('evensout') );
-$ENV{GL_USER} = "u4"; nok( in_group('oddguys') ); ok( in_group('evensout') );
-$ENV{GL_USER} = "u2"; nok( in_group('oddguys') ); ok( in_group('evensout') );
-
-# owns
-msg('owns');
-try("glt ls-remote u4 cc/u4; /Initialized empty.*cc/u4/");
-$ENV{GL_USER} = "u3"; nok( owns("cc/u3") ); nok( owns("cc/u4") );
-$ENV{GL_USER} = "u4"; nok( owns("cc/u3") ); ok( owns("cc/u4") );
-$ENV{GL_USER} = "u5"; nok( owns("cc/u3") ); nok( owns("cc/u4") );
-
-# can_read
-msg('can_read');
-$ENV{GL_USER} = "u1"; ok(can_read("aa"));
-$ENV{GL_USER} = "u2"; ok(can_read("aa"));
-$ENV{GL_USER} = "u3"; nok(can_read("aa"));
-$ENV{GL_USER} = "u4"; ok(can_read("aa"));
-
-$ENV{GL_USER} = "u1"; nok(can_read("bb"));
-$ENV{GL_USER} = "u2"; nok(can_read("bb"));
-$ENV{GL_USER} = "u3"; nok(can_read("bb"));
-$ENV{GL_USER} = "u4"; nok(can_read("bb"));
-
-$ENV{GL_USER} = "u3"; nok(can_read("cc/u3"));
-$ENV{GL_USER} = "u4"; nok(can_read("cc/u3"));
-$ENV{GL_USER} = "u5"; nok(can_read("cc/u3"));
-$ENV{GL_USER} = "u6"; nok(can_read("cc/u3"));
-
-$ENV{GL_USER} = "u3"; nok(can_read("cc/u4"));
-$ENV{GL_USER} = "u4"; ok(can_read("cc/u4"));
-$ENV{GL_USER} = "u5"; ok(can_read("cc/u4"));
-$ENV{GL_USER} = "u6"; ok(can_read("cc/u4"));
-
-# can_write
-msg('can_write');
-$ENV{GL_USER} = "u1"; ok(can_write("aa"));
-$ENV{GL_USER} = "u2"; ok(can_write("aa"));
-$ENV{GL_USER} = "u3"; nok(can_write("aa"));
-$ENV{GL_USER} = "u4"; nok(can_write("aa"));
-
-$ENV{GL_USER} = "u1"; ok(can_write("aa", "+"));
-$ENV{GL_USER} = "u2"; nok(can_write("aa", "+"));
-$ENV{GL_USER} = "u3"; nok(can_write("aa", "+"));
-$ENV{GL_USER} = "u4"; nok(can_write("aa", "+"));
-
-$ENV{GL_USER} = "u1"; nok(can_write("bb"));
-$ENV{GL_USER} = "u2"; nok(can_write("bb"));
-$ENV{GL_USER} = "u3"; nok(can_write("bb"));
-$ENV{GL_USER} = "u4"; nok(can_write("bb"));
-
-$ENV{GL_USER} = "u3"; nok(can_write("cc/u3"));
-$ENV{GL_USER} = "u4"; nok(can_write("cc/u3"));
-$ENV{GL_USER} = "u5"; nok(can_write("cc/u3"));
-$ENV{GL_USER} = "u6"; nok(can_write("cc/u3"));
-
-$ENV{GL_USER} = "u3"; nok(can_write("cc/u4"));
-$ENV{GL_USER} = "u4"; ok(can_write("cc/u4"));
-$ENV{GL_USER} = "u5"; ok(can_write("cc/u4"));
-$ENV{GL_USER} = "u6"; nok(can_write("cc/u4"));
-
-$ENV{GL_USER} = "u3"; nok(can_write("cc/u4", "+"));
-$ENV{GL_USER} = "u4"; ok(can_write("cc/u4", "+"));
-$ENV{GL_USER} = "u5"; ok(can_write("cc/u4", "+"));
-$ENV{GL_USER} = "u6"; nok(can_write("cc/u4", "+"));
-
-# config
-try("glt ls-remote u4 cc/sub/one; /Initialized empty.*cc/sub/one/");
-try("glt ls-remote u4 cc/two; /Initialized empty.*cc/two/");
-ok(1);
-my @a;
- at a = config("aa", "fo..aa");   ok($a[0] eq 'for.aa' and $a[1] eq '1');
- at a = config("aa", "for.aa");   ok($a[0] eq 'for.aa' and $a[1] eq '1');
- at a = config("aa", "fo\\..aa"); ok(scalar(@a) == 0);
-
- at a = config("aa", "fo..cc");   ok(scalar(@a) == 0);
- at a = config("aa", "for.cc");   ok(scalar(@a) == 0);
- at a = config("aa", "fo\\..cc"); ok(scalar(@a) == 0);
-
- at a = config("bb", "fo..aa");   ok(scalar(@a) == 0);
- at a = config("bb", "for.aa");   ok(scalar(@a) == 0);
- at a = config("bb", "fo\\..aa"); ok(scalar(@a) == 0);
-
- at a = config("cc/u4", "fo..aa");   ok(scalar(@a) == 0);
- at a = config("cc/u4", "for.aa");   ok(scalar(@a) == 0);
- at a = config("cc/u4", "fo\\..aa"); ok(scalar(@a) == 0);
-
- at a = config("cc/u4", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/u4", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/u4", "fo\\..cc"); ok(scalar(@a) == 0);
-
- at a = config("cc/two", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/two", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/two", "fo\\..cc"); ok(scalar(@a) == 0);
-
- at a = config("cc/sub/one", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/sub/one", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
- at a = config("cc/sub/one", "fo\\..cc"); ok(scalar(@a) == 0);
-
- at a = config("cc/sub/one", "su..cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
- at a = config("cc/sub/one", "sub.cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
- at a = config("cc/sub/one", "su\\..cc"); ok(scalar(@a) == 0);
-
diff --git a/docker/gitolite/t/fork.t b/docker/gitolite/t/fork.t
deleted file mode 100755
index 2a7a7b7..0000000
--- a/docker/gitolite/t/fork.t
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-my $h = $ENV{HOME};
-
-# fork command
-# ----------------------------------------------------------------------
-
-try "plan 38";
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-
-confreset;confadd '
-
-    repo foo/CREATOR/..*
-        C   =   u1 u2
-        RW+ =   CREATOR
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-
-    # make the initial repo
-    glt ls-remote u1 file:///foo/u1/u1a;ok;     gsh
-                                                /Initialized empty Git repository in .*/foo/u1/u1a.git/
-    # vrc doesn't have the fork command
-    glt fork u1 foo/u1/u1a foo/u1/u1a2; !ok;    /FATAL: unknown git/gitolite command: \\'fork/
-";
-
-# allow fork as a valid command
-$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
-put "$ENV{G3T_RC}", "\$rc{COMMANDS}{fork} = 1;\n";
-
-# enable set-default-roles feature, add options, push
-try "
-    cat $h/.gitolite.rc
-    perl s/# 'set-default-roles'/'set-default-roles'/
-    put $h/.gitolite.rc
-";
-try "cd gitolite-admin";
-confadd '
-    repo foo/CREATOR/..*
-        C   =   u1 u2
-        RW+ =   CREATOR
-    option default.roles-1 = READERS @all
-';
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "cd ..";
-
-try "
-    # now the fork succeeds
-    glt fork u1 foo/u1/u1a foo/u1/u1a2; ok;     /Cloning into bare repository '.*/foo/u1/u1a2.git'/
-                                                /foo/u1/u1a forked to foo/u1/u1a2/
-
-    # now the actual testing starts
-    # read error
-    glt fork u1 foo/u1/u1c foo/u1/u1d;  !ok;    /'foo/u1/u1c' does not exist or you are not allowed to read it/
-    glt fork u2 foo/u1/u1a foo/u1/u1d;  !ok;    /'foo/u1/u1a' does not exist or you are not allowed to read it/
-
-    # write error
-    glt fork u1 foo/u1/u1a foo/u2/u1d;  !ok;    /'foo/u2/u1d' already exists or you are not allowed to create it/
-
-    # no error
-    glt fork u1 foo/u1/u1a foo/u1/u1e;  ok;     /Cloning into bare repository '.*/foo/u1/u1e.git'/
-                                                /warning: You appear to have cloned an empty repository/
-                                                /foo/u1/u1a forked to foo/u1/u1e/
-    # both exist
-    glt fork u1 foo/u1/u1a foo/u1/u1e;  !ok;    /'foo/u1/u1e' already exists or you are not allowed to create it/
-";
-
-# now check the various files that should have been produced
-
-my $t;
-try "cd $rb; find . -name gl-perms"; $t = md5sum(sort (lines())); cmp $t,
-'59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
-59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1e.git/gl-perms
-';
-
-try "cd $rb; find . -name gl-creator"; $t = md5sum(sort (lines())); cmp $t,
-'e4774cdda0793f86414e8b9140bb6db4  ./foo/u1/u1a.git/gl-creator
-346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1a2.git/gl-creator
-346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1e.git/gl-creator
-';
diff --git a/docker/gitolite/t/git-config.t b/docker/gitolite/t/git-config.t
deleted file mode 100755
index 86a3a7b..0000000
--- a/docker/gitolite/t/git-config.t
+++ /dev/null
@@ -1,192 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# git config settings
-# ----------------------------------------------------------------------
-
-try "plan 57";
-
-try "pwd";
-my $od = text();
-chomp($od);
-
-my $t;  # temp
-
-# try an invalid config key
-confreset;confadd '
-
-    repo @all
-        config foo.bar  =   dft
-';
-
-try "ADMIN_PUSH set1; /FATAL/" or die text();
-try "
-    /git config \\'foo.bar\\' not allowed/
-    /check GIT_CONFIG_KEYS in the rc file/
-";
-
-# make foo.bar a valid gc key
-$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
-put "$ENV{G3T_RC}", "\$rc{GIT_CONFIG_KEYS} = 'foo\.bar';\n";
-
-confreset;confadd '
-
-    repo @all
-        config foo.bar  =   dft
-
-    repo gitolite-admin
-        RW+     =   admin
-        config foo.bar  =
-
-    repo testing
-        RW+     =   @all
-
-    repo foo
-        RW      =   u1
-        config foo.bar  =   f1
-
-    repo frob
-        RW      =   u3
-
-    repo bar
-        RW      =   u2
-        config foo.bar  =   one
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-try "
-    cd $rb;                             ok
-    egrep foo\\|bar *.git/config
-";
-$t = join("\n", sort (lines()));
-
-cmp $t, 'bar.git/config:	bar = one
-bar.git/config:	bare = true
-bar.git/config:[foo]
-foo.git/config:	bar = f1
-foo.git/config:	bare = true
-foo.git/config:[foo]
-frob.git/config:	bar = dft
-frob.git/config:	bare = true
-frob.git/config:[foo]
-gitolite-admin.git/config:	bare = true
-testing.git/config:	bar = dft
-testing.git/config:	bare = true
-testing.git/config:[foo]';
-
-try "cd $od; ok";
-
-confadd '
-
-    repo frob
-        RW      =   u3
-        config foo.bar  =   none
-
-    repo bar
-        RW      =   u2
-        config foo.bar  =   one
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd $rb;                             ok
-    egrep foo\\|bar *.git/config
-";
-$t = join("\n", sort (lines()));
-
-cmp $t, 'bar.git/config:	bar = one
-bar.git/config:	bare = true
-bar.git/config:[foo]
-foo.git/config:	bar = f1
-foo.git/config:	bare = true
-foo.git/config:[foo]
-frob.git/config:	bar = none
-frob.git/config:	bare = true
-frob.git/config:[foo]
-gitolite-admin.git/config:	bare = true
-testing.git/config:	bar = dft
-testing.git/config:	bare = true
-testing.git/config:[foo]';
-
-try "cd $od; ok";
-
-confadd '
-
-    repo bar
-        RW      =   u2
-        config foo.bar  =   
-
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd $rb;                             ok
-    egrep foo\\|bar *.git/config
-";
-$t = join("\n", sort (lines()));
-
-cmp $t, 'bar.git/config:	bare = true
-bar.git/config:[foo]
-foo.git/config:	bar = f1
-foo.git/config:	bare = true
-foo.git/config:[foo]
-frob.git/config:	bar = none
-frob.git/config:	bare = true
-frob.git/config:[foo]
-gitolite-admin.git/config:	bare = true
-testing.git/config:	bar = dft
-testing.git/config:	bare = true
-testing.git/config:[foo]';
-
-try "cd $od; ok";
-
-confreset;confadd '
-
-    repo @gr1
-        RW      =   u1
-        config foo.bar  =   f1
-
-    repo bar/CREATOR/[one].*
-        C       =   u2
-        RW      =   u2
-        config foo.bar  =   one
-
-    @gr1 = foo frob
-
-';
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "
-    glt ls-remote u2 file:///bar/u2/one;        ok;     /Initialized empty/
-    glt ls-remote u2 file:///bar/u2/two;        !ok;    /DENIED by fallthru/
-";
-
-try "
-    cd $rb;                             ok
-    find . -name config | xargs egrep foo\\|bar
-";
-$t = join("\n", sort (lines()));
-
-cmp $t, './bar/u2/one.git/config:	bar = one
-./bar/u2/one.git/config:	bare = true
-./bar/u2/one.git/config:[foo]
-./foo.git/config:	bar = f1
-./foo.git/config:	bare = true
-./foo.git/config:[foo]
-./frob.git/config:	bar = f1
-./frob.git/config:	bare = true
-./frob.git/config:[foo]
-./gitolite-admin.git/config:	bare = true
-./testing.git/config:	bar = dft
-./testing.git/config:	bare = true
-./testing.git/config:[foo]';
diff --git a/docker/gitolite/t/gitolite-receive-pack b/docker/gitolite/t/gitolite-receive-pack
deleted file mode 100755
index a4cc5be..0000000
--- a/docker/gitolite/t/gitolite-receive-pack
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-print STDERR "TRACE: grp(", join( ")(", @ARGV ), ")\n";
-
-my $repo = shift;
-$repo =~ s/\.git$//;
-my $user = $ENV{G3T_USER} || 'no-such-user';
-
-$ENV{SSH_ORIGINAL_COMMAND} = "git-receive-pack '$repo'";
-exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
diff --git a/docker/gitolite/t/gitolite-upload-pack b/docker/gitolite/t/gitolite-upload-pack
deleted file mode 100755
index 5981f17..0000000
--- a/docker/gitolite/t/gitolite-upload-pack
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-print STDERR "TRACE: gup(", join( ")(", @ARGV ), ")\n";
-
-my $repo = shift;
-$repo =~ s/\.git$//;
-my $user = $ENV{G3T_USER} || 'no-such-user';
-
-$ENV{SSH_ORIGINAL_COMMAND} = "git-upload-pack '$repo'";
-exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
diff --git a/docker/gitolite/t/glt b/docker/gitolite/t/glt
deleted file mode 100755
index 1bf31e8..0000000
--- a/docker/gitolite/t/glt
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use FindBin;
-BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
-
-my $cmd  = shift or die "need command";
-my $user = shift or die "need user";
-my $rc;
-
-my %extcmds = (
-    help        => 1,
-    info        => 1,
-    desc        => 1,
-    fork        => 1,
-    perms       => 1,
-    writable    => 1,
-);
-
-$ENV{G3T_USER} = $user;
-if ($extcmds{$cmd}) {
-    $ENV{SSH_ORIGINAL_COMMAND} = join(" ", $cmd, @ARGV);
-    exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
-} elsif ( $cmd eq 'push' ) {
-    print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
-    $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV );
-} else {
-    print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
-    $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV );
-}
-
-if ( $? == -1 ) {
-    die "F: failed to execute: $!\n";
-} elsif ( $? & 127 ) {
-    printf STDERR "E: child died with signal %d\n", ( $? & 127 );
-    exit 1;
-} else {
-    printf STDERR "W: child exited with value %d\n", $? >> 8 if $? >> 8;
-    exit( $? >> 8 );
-}
-
-exit 0;
diff --git a/docker/gitolite/t/hostname.t b/docker/gitolite/t/hostname.t
deleted file mode 100755
index dfb8885..0000000
--- a/docker/gitolite/t/hostname.t
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# %HOSTNAME tests
-# ----------------------------------------------------------------------
-
-try "plan 60";
-
-try "pwd";
-my $od = text();
-chomp($od);
-
-# without setting HOSTNAME in rc
-confreset;confadd '
-
-    repo foo
-        RW  dev/%HOSTNAME   =   u1
-';
-
-try "ADMIN_PUSH set1; /FATAL/";
-try "/bad ref 'refs/heads/dev/%HOSTNAME'/";
-
-# make a hostname entry
-$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
-put "$ENV{G3T_RC}", "\$rc{HOSTNAME} = 'frodo';\n";
-
-confreset;confadd '
-
-    repo bar
-        RW  %HOSTNAME_baz   =   u1
-';
-
-try "ADMIN_PUSH set1; /FATAL/";
-try "/bad ref 'refs/heads/%HOSTNAME_baz'/";
-
-confreset;confadd '
-
-    repo bar
-        RW  %HOSTNAME/      =   u1
-        RW  %HOSTNAME-baz   =   u1
-';
-
-try "ADMIN_PUSH set1; !/FATAL/";
-try "
-    gitolite access bar u2 R any;                   /R any bar u2 DENIED by fallthru/
-    gitolite access bar u2 W any;                   /W any bar u2 DENIED by fallthru/
-    gitolite access bar u1 W any;                   !/DENIED/; /refs/heads/frodo/; !/baz/
-    gitolite access bar u1 R any;                   !/DENIED/; /refs/heads/frodo/; !/baz/
-    gitolite access bar u1 R refs/heads/frodo;      /R refs/heads/frodo bar u1 DENIED by fallthru/
-    gitolite access bar u1 W refs/heads/frodo;      /W refs/heads/frodo bar u1 DENIED by fallthru/
-    gitolite access bar u1 R refs/heads/frodo/1;    !/DENIED/; /refs/heads/frodo/; !/baz/
-    gitolite access bar u1 W refs/heads/frodo/1;    !/DENIED/; /refs/heads/frodo/; !/baz/
-    gitolite access bar u1 R refs/heads/sam;        /R refs/heads/sam bar u1 DENIED by fallthru/
-    gitolite access bar u1 W refs/heads/sam;        /W refs/heads/sam bar u1 DENIED by fallthru/
-    gitolite access bar u1 R refs/heads/master;     /R refs/heads/master bar u1 DENIED by fallthru/
-    gitolite access bar u1 W refs/heads/master;     /W refs/heads/master bar u1 DENIED by fallthru/
-
-    gitolite access bar u1 R refs/heads/frodo-baz;  !/DENIED/; /refs/heads/frodo-baz/
-    gitolite access bar u1 W refs/heads/frodo-baz;  !/DENIED/; /refs/heads/frodo-baz/
-";
-
-confreset;confadd '
-
-    repo foo-%HOSTNAME
-        RW  =   u1
-';
-
-try "ADMIN_PUSH set1; !/FATAL/";
-try "
-    gitolite list-repos;            /foo-frodo/
-    gitolite list-phy-repos;        /foo-frodo/
-";
diff --git a/docker/gitolite/t/include-subconf.t b/docker/gitolite/t/include-subconf.t
deleted file mode 100755
index 48bdaee..0000000
--- a/docker/gitolite/t/include-subconf.t
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# include and subconf
-# ----------------------------------------------------------------------
-
-try 'plan 58';
-
-confreset; confadd '
-    include "i1.conf"
-    @i2 = b1
-    subconf "i2.conf"
-    include "i1.conf"
-';
-confadd 'i1.conf', '
-    @g1 = a1 a2
-    repo foo
-        RW = u1
-
-    include "j1.conf"
-';
-confadd 'i2.conf', '
-    @g2 = b1 b2
-    repo bar b1 b2 i1 i2 @i1 @i2 @g2
-        RW = u2
-';
-confadd 'j1.conf', '
-    @h2 = c1 c2
-    repo baz
-        RW = u3
-';
-
-try "ADMIN_PUSH set2; !/FATAL/" or die text();
-
-try "
-                                        /i1.conf already included/
-	                                    /subconf 'i2' attempting to set access for \@i1, b2, bar, i1/
-                                        /WARNING: expanding '\@g2'/
-
-                                        !/attempting to set access.*i2/
-                                        /Initialized.*empty.*baz.git/
-                                        /Initialized.*empty.*foo.git/
-                                        /Initialized.*empty.*b1.git/
-                                        /Initialized.*empty.*i2.git/
-                                        !/Initialized.*empty.*b2.git/
-                                        !/Initialized.*empty.*i1.git/
-                                        !/Initialized.*empty.*bar.git/
-";
-
-confreset;confadd '
-    @g2 = i1 i2 i3
-    subconf "g2.conf"
-';
-confadd 'g2.conf', '
-    @g2 = g2 h2 i2
-    repo @g2
-        RW = u1
-';
-
-try "ADMIN_PUSH set3; !/FATAL/" or die text();
-try "
-                                        /WARNING: expanding '\@g2'/
-                                        /WARNING: subconf 'g2' attempting to set access for h2/
-                                        /Initialized.*empty.*g2.git/
-                                        /Initialized.*empty.*i2.git/
-";
-
-confreset;confadd '
-    @g2 = i1 i2 i3
-    subconf "g2.conf"
-';
-confadd 'g2.conf', '
-    subconf master
-    @g2 = g2 h2 i2
-    repo @g2
-        RW = u1
-';
-
-try "
-    ADMIN_PUSH set3;           ok;     /FATAL: subconf \\'g2\\' attempting to run 'subconf'/
-";
-
-# ----------------------------------------------------------------------
-
-confreset; confadd '
-    include "i1.conf"
-    @i2 = b1
-    subconf i2 "eye2.conf"
-';
-confadd 'eye2.conf', '
-    repo @eye2
-        RW = u2
-';
-
-try "ADMIN_PUSH set2; !/FATAL/" or die text();
-
-try "
-    /subconf 'i2' attempting to set access for \@eye2/
-";
-
-confreset; confadd '
-    include "i1.conf"
-    @i2 = b1
-    subconf i2 "eye2.conf"
-';
-confadd 'eye2.conf', '
-    repo @i2
-        RW = u2
-';
-
-try "ADMIN_PUSH set2; !/FATAL/" or die text();
-
-try "
-    !/subconf 'i2' attempting to set access for \@eye2/
-";
diff --git a/docker/gitolite/t/info-json.t b/docker/gitolite/t/info-json.t
deleted file mode 100755
index 74fbdf4..0000000
--- a/docker/gitolite/t/info-json.t
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-use JSON;
-
-# the info command
-# ----------------------------------------------------------------------
-
-try 'plan 162';
-
-try "## info";
-
-confreset;confadd '
-    @t1 = t1
-    repo    @t1
-        RW              =   u1
-        R               =   u2
-    repo    t2
-        RW  =               u2
-        R   =               u1
-    repo    t3
-        RW  =   u3
-        R   =   u4
-
-    repo foo/..*
-        C   =   u1
-        RW  =   CREATOR u3
-';
-
-try "ADMIN_PUSH info; !/FATAL/" or die text();
-try "
-                                        /Initialized.*empty.*t1.git/
-                                        /Initialized.*empty.*t2.git/
-                                        /Initialized.*empty.*t3.git/
-";
-
-my $href;   # semi-global (or at least file scoped lexical!)
-
-# testing for info -json is a bit unusual.  The actual tests are done within
-# this test script itself, and we send Tsh just enough for it to decide if
-# it's 'ok' or 'not ok' and print that.
-
-try "glt info u1 -json; ok";
-$href = from_json(text());
-try "## u1 test_gs";
-test_gs('u1');
-try "## u1";
-perm('foo/..*', 'r w C');
-perm('testing', 'R W c');
-perm('t1', 'R W c');
-perm('t2', 'R w c');
-perm('t3', 'r w c');
-
-try "## u2";
-try "glt info u2 -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('testing', 'R W c');
-perm('t1', 'R w c');
-perm('t2', 'R W c');
-perm('t3', 'r w c');
-
-try "## u3";
-try "glt info u3 -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'R W c');
-perm('testing', 'R W c');
-perm('t1', 'r w c');
-perm('t2', 'r w c');
-perm('t3', 'R W c');
-
-try "## u4";
-try "glt info u4 -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('testing', 'R W c');
-perm('t1', 'r w c');
-perm('t2', 'r w c');
-perm('t3', 'R w c');
-
-try "## u5";
-try "glt info u5 -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('testing', 'R W c');
-perm('t1', 'r w c');
-perm('t2', 'r w c');
-perm('t3', 'r w c');
-
-try "## u6";
-try "glt info u6 -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('testing', 'R W c');
-perm('t1', 'r w c');
-perm('t2', 'r w c');
-perm('t3', 'r w c');
-
-try "## ls-remote foo/one";
-try "glt ls-remote u1 file:///foo/one;   ok";
-
-try "## u1";
-try "glt info u1 -json; ok; !/creator..:/";
-$href = from_json(text());
-perm('foo/..*', 'r w C');
-perm('foo/one', 'R W c');
-test_creator('foo/one', 'u1', 'undef');
-
-try "## u2";
-try "glt info u2 -json; ok; !/creator..:/";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('foo/one', 'r w c');
-test_creator('foo/one', 'u1', 'undef');
-
-try "## u3";
-try "glt info u3 -json; ok; !/creator..:/";
-$href = from_json(text());
-perm('foo/..*', 'R W c');
-perm('foo/one', 'R W c');
-test_creator('foo/one', 'u1', 'undef');
-
-try("## with -lc now");
-
-try "## u1";
-try "glt info u1 -lc -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w C');
-perm('foo/one', 'R W c');
-test_creator('foo/one', 'u1', 1);
-
-try "## u2";
-try "glt info u2 -lc -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'r w c');
-perm('foo/one', 'r w c');
-test_creator('foo/one', 'u1', 'undef');
-
-try "## u3";
-try "glt info u3 -lc -json; ok";
-$href = from_json(text());
-perm('foo/..*', 'R W c');
-perm('foo/one', 'R W c');
-test_creator('foo/one', 'u1', 1);
-
-# ----------------------------------------------------------------------
-
-# test perms given repo and expected perms.  (lowercase r/w/c means NOT
-# expected, uppercase means expected)
-sub perm {
-    my ($repo, $aa) = @_;
-    for my $aa1 (split ' ', $aa) {
-        my $exp = 1;
-        if ($aa1 =~ /[a-z]/) {
-            $exp = 'undef';     # we can't use 0, though I'd like to
-            $aa1 = uc($aa1);
-        }
-        my $perm = $href->{repos}{$repo}{perms}{$aa1} || 'undef';
-        try 'perl $_ = "' . $perm  . '"; /' . $exp . '/';
-    }
-}
-
-# test versions in greeting string
-sub test_gs {
-    my $glu = shift;
-    my $res = ( $href->{GL_USER} eq $glu ? 1 : 'undef' );
-    try 'perl $_ = "' . $res  . '"; /1/';
-    $res = ( $href->{gitolite_version} =~ /^v3.[5-9]/ ? 1 : 'undef' );
-    try 'perl $_ = "' . $res  . '"; /1/';
-    $res = ( $href->{git_version} =~ /^1.[6-9]|^2./ ? 1 : 'undef' );
-    try 'perl $_ = "' . $res  . '"; /1/';
-}
-
-# test creator
-sub test_creator {
-    my ($r, $c, $exp) = @_;
-    my $res = ( ($href->{repos}{$r}{creator} || '') eq $c ? 1 : 'undef' );
-    try 'perl $_ = "' . $res  . '"; /' . $exp . '/';
-}
diff --git a/docker/gitolite/t/info.t b/docker/gitolite/t/info.t
deleted file mode 100755
index 22b5b94..0000000
--- a/docker/gitolite/t/info.t
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# the info command
-# ----------------------------------------------------------------------
-
-try 'plan 78';
-
-try "## info";
-
-confreset;confadd '
-    @t1 = t1
-    repo    @t1
-        RW              =   u1
-        R               =   u2
-    repo    t2
-        RW  =               u2
-        R   =               u1
-    repo    t3
-        RW  =   u3
-        R   =   u4
-
-    repo foo/..*
-        C   =   u1
-        RW  =   CREATOR u3
-';
-
-try "ADMIN_PUSH info; !/FATAL/" or die text();
-try "
-                                        /Initialized.*empty.*t1.git/
-                                        /Initialized.*empty.*t2.git/
-                                        /Initialized.*empty.*t3.git/
-";
-
-# GS == greeting string
-try "DEF GS = /hello %1, this is $ENV{USER}\\@.* running gitolite/";
-
-try "
-    glt info u1; ok; GS u1
-        /C\tfoo/\\.\\.\\*/
-        /R W *\tt1/
-        /R   *\tt2/
-        /R W *\ttesting/
-        !/R W *\tt3/
-    glt info u2; ok; GS u2
-        !/C\tfoo/
-        /R   *\tt1/
-        /R W *\tt2/
-        /R W *\ttesting/
-        !/R W *\tt3/
-    glt info u3; ok; GS u3
-        /R W *\tt3/
-        /R W *\ttesting/
-        !/R   *\tt1/
-        !/R W *\tt2/
-    glt info u4; ok; GS u4
-        /R   *\tt3/
-        /R W *\ttesting/
-        !/R   *\tt1/
-        !/R W *\tt2/
-    glt info u5; ok; GS u5
-        /R W *\ttesting/
-        !/R   *\tt1/
-        !/R W *\tt2/
-        !/R W *\tt3/
-    glt info u6; ok; GS u6
-        /R W *\ttesting/
-        !/R   *\tt1/
-        !/R W *\tt2/
-        !/R W *\tt3/
-";
-
-try "
-    glt ls-remote u1 file:///foo/one;   ok
-    glt info u1; ok; GS u1
-        /C\tfoo/\\.\\.\\*/
-        /R W *\tfoo/one/
-        !/R W *\tfoo/one\tu1/
-    glt info u2; ok; GS u2
-        !/C\tfoo/
-        !/R W *\tfoo/one/
-    glt info u3; ok; GS u3
-        !/C\tfoo/
-        /R W *\tfoo/one/
-        !/R W *\tfoo/one\tu1/
-";
-
-try "
-    glt ls-remote u1 file:///foo/one;   ok
-    glt info u1 -lc; ok; GS u1
-        /C\tfoo/\\.\\.\\*/
-        !/C\tfoo.*u1/
-        /R W *\tfoo/one\tu1/
-    glt info u2 -lc; ok; GS u2
-        !/C\tfoo/
-        !/R W *\tfoo/one/
-    glt info u3 -lc; ok; GS u3
-        !/C\tfoo/
-        /R W *\tfoo/one\tu1/
-";
diff --git a/docker/gitolite/t/invalid-refnames-filenames.t b/docker/gitolite/t/invalid-refnames-filenames.t
deleted file mode 100755
index 19267fe..0000000
--- a/docker/gitolite/t/invalid-refnames-filenames.t
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# invalid refnames
-# ----------------------------------------------------------------------
-
-try "plan 56";
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-confreset; confadd '
-    repo aa
-        RW+                 =   @all
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-cd ..
-rm -rf aa
-glt clone u1 file:///aa
-cd aa
-tc v-869
-
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /\\* \\[new branch\\]      HEAD -> master/
-
-# push file aa,bb ok
-tc  aa,bb
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /HEAD -> master/
-
-# push file aa=bb ok
-tc  aa=bb
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /HEAD -> master/
-
-# push to branch dd,ee ok
-glt push u1 origin master:dd,ee
-        /To file:///aa/
-        POK; /\\* \\[new branch\\]      master -> dd,ee/
-
-# push to branch dd=ee fail
-glt push u1 origin master:dd=ee
-        /invalid characters in ref or filename: \\'refs/heads/dd=ee/
-        reject
-";
-
-confreset; confadd '
-    repo aa
-        RW+                 =   @all
-        RW+ NAME/           =   @all
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-cd ..
-rm -rf aa
-glt clone u1 file:///aa
-cd aa
-tc  file-1
-
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /\\* \\[new branch\\]      HEAD -> master/
-
-# push file aa,bb ok
-tc  aa,bb
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /HEAD -> master/
-
-# push file aa=bb fail
-tc  aa=bb
-glt push u1 origin HEAD
-        /To file:///aa/
-        POK; /HEAD -> master/
-
-# push to branch dd,ee ok
-git reset --hard HEAD^
-tc  some-file
-glt push u1 origin master:dd,ee
-        /To file:///aa/
-        POK; /\\* \\[new branch\\]      master -> dd,ee/
-
-# push to branch dd=ee fail
-glt push u1 origin master:dd=ee
-        /invalid characters in ref or filename: \\'refs/heads/dd=ee/
-        reject
-";
diff --git a/docker/gitolite/t/keys/admin b/docker/gitolite/t/keys/admin
deleted file mode 100644
index 676a711..0000000
--- a/docker/gitolite/t/keys/admin
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEA0/X7uwd7xOvC3UTZaAnFOR5xqhdgcyc8vk3d1bXXthiuUSmq
-5t4uhS9qj0ismcPX0YRNhSDElotG1KSWp8DceJpR2c7GYmELpqoE7ebVPnEBKY0c
-PX+G9KZNgbJyyx35QlpJmlO+LimM0oO8XarRphn3kc0jCTLKaI5ndEjGDHH4fsOe
-wlryW7a0RR/brMNiJYYzy5ADCOkAnyXQmbrQW4nROCPqFojBFTEBLpe8EuODlJXE
-mRHpa1k+k/grFgp/c1xbOrjox127LZT4vkLf5+lSwkhq8oyzWHUrQ+rUJZssi1LC
-GEZSudhX5reqksmIlTwX0GPIIwSwZFNVxhj51wIDAQABAoIBAFFLlzE0vZPZmPOk
-5H2ywaIWuyGxtZx1ACc9VkgRZprA/JrEkHfb35vVg9lQ1mJjavNA+zqERuI2qQQF
-3IKaxfS7u4j+dbhl4EIcE6frUP6R+RAmvx4XO3u6DSAhgUXGSUPZvUEjvV2XMhvL
-ywNh8Ob0LrANLdLpWBiiBavj/ZHnsVZj7y+39GKEtLm8BbsH8L7GanPl4uQDNruR
-Ef2L2pSEqml+Iva6yk5a22U293JCVZ/aqDdGvrnbjkaxqin5jBgL47D91A3xo4SW
-zFZkJR5SDYnFLdwnwTPvxSA9IXj6TZ2ZdnTNvI5Utpd0Wy5N4GlfXS3TBPBD4kP+
-+pS1vgECgYEA/D7k1IyOM2hGamHc0qG+sIxMHge6rJE8pYtW2suB2KX0bJvAeBY8
-eHM+pPFMbjjZHlU7qB9qmGbZ++ZVlEv/SqqHizvwLzth0P4MJ6/UjAcmAdEUclV0
-YSzMRFk/g0J7aMlJb5NXgA5xlArHLsmV3Zc+QwFPECgzz5LHwD5nTMcCgYEA1x2Y
-qb7FXwcQPDcs4Qwux12tCUaExQMZbNoLHWrXw7ktHRfR9sI8TcEErLzc2wnZbdyZ
-av/GYSTR9UCeq12kUhmTL4SNryktHKNKEfDEvR7wK6sa9iffzZH8FDe5JktLXnu7
-Oe5SNfLDHA5X7qQefz8s2658+xVJmdp+uasNOnECgYEAlzBXVbJ9VQCuG/tWMQVz
-VzxwLxuw3tgagprWz0NlK2ak7ygXn6KsUgG5TYG3ruTx9gVeQXG7IWecRiiTqNQ4
-SxeVMHYXiyfLhEmRHYR9IAT02efomnLv04LXWCwqLlF9yJvFIVQuAPonR3WCV1/K
-LMwHLIAvVF7UVxkCEw8UOWcCgYEAwiUH/0sZvuYVFQOHEaV5Ip286b4nXdeqPr+b
-gHVJPnAF81foO5iZ7GLj4TKi8V02SxzpqdQmKs6cX4huq6LcBuzmFeDALvIusMX+
-t6phJX6irAbFUpwyNMoog+a2x4T1BNUO6P3aXK44wT2AxvSAQb+2sJ4OVl2kC6NS
-9CcYzUECgYEAzqbWlJIbjJx5620Q95bu+lpessOUtoVcYryO6dQNZOiis8aDZjJk
-RKdB9qhJlcUeFqnvJS5L5gohaJoyx0wMSYVB7MrO6kAIMHo/OriEQ6CTsQOTqa8n
-q4cV9Wuk9pRb1b/x5eXCcHe8P7wBt8KKIh/VBB4aDyeIwGbO0NODYr4=
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/admin.pub b/docker/gitolite/t/keys/admin.pub
deleted file mode 100644
index b50a5b9..0000000
--- a/docker/gitolite/t/keys/admin.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDT9fu7B3vE68LdRNloCcU5HnGqF2BzJzy+Td3Vtde2GK5RKarm3i6FL2qPSKyZw9fRhE2FIMSWi0bUpJanwNx4mlHZzsZiYQumqgTt5tU+cQEpjRw9f4b0pk2BsnLLHflCWkmaU74uKYzSg7xdqtGmGfeRzSMJMspojmd0SMYMcfh+w57CWvJbtrRFH9usw2IlhjPLkAMI6QCfJdCZutBbidE4I+oWiMEVMQEul7wS44OUlcSZEelrWT6T+CsWCn9zXFs6uOjHXbstlPi+Qt/n6VLCSGryjLNYdStD6tQlmyyLUsIYRlK52Ffmt6qSyYiVPBfQY8gjBLBkU1XGGPnX g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/config b/docker/gitolite/t/keys/config
deleted file mode 100644
index fc75580..0000000
--- a/docker/gitolite/t/keys/config
+++ /dev/null
@@ -1,20 +0,0 @@
-host *
-    stricthostkeychecking no
-host admin
-        identityfile ~/.ssh/admin
-
-host u? admin
-	user %USER
-	hostname localhost
-host u1
-	identityfile ~/.ssh/u1
-host u2
-	identityfile ~/.ssh/u2
-host u3
-	identityfile ~/.ssh/u3
-host u4
-	identityfile ~/.ssh/u4
-host u5
-	identityfile ~/.ssh/u5
-host u6
-	identityfile ~/.ssh/u6
diff --git a/docker/gitolite/t/keys/u1 b/docker/gitolite/t/keys/u1
deleted file mode 100644
index 828d1c3..0000000
--- a/docker/gitolite/t/keys/u1
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEAuB8HmJ5UX30xFktmvlLgSrzsGIzSiYiAYH8eU6epJGr/xjYD
-9GE6G9EcL+/NTc0ziPhIUtVm+h+kFEHtQ1VeOFEuVuWAukPNXRFDrYOMfeR4U1h7
-olT58U3+IlctreKavXr+7bPPhbhvdB7FPM8xusaPfwT6BG4sTqHyOPL5a3eXW7Sp
-tuVD87KNHKo3skWcbb78EVQt7MfCMTam6X2O4Y0y1da51kOht6qRaeJHoaAjXB4V
-pY9lJSQA+Km5uYu+FIwn89z7P4j2aUNZFq2UOSwuViekSR6lfHvortyh8YFrSRUs
-Q4berZUICDp0Ek2BiJirWiPXAp9uxwXms9UbvQIDAQABAoIBAAUTnfMAUpU7b3IM
-7C1NPa/x25SltVxjbh67AowN8GT3qku9y4geciq4Lk3ID+IYSVZ6egwGpEs7Ohvw
-4Wjc3rcwzdVJiK4aFnx9cF9FZEdIWGT76JTGQQn9O4eY3cKQn/GfhY3qSkuGlVQf
-URLnJ5jdxrEa4wXiP8h/QJ1/XY8v9en0uWf/18fMHyzImpd8dqgtTdomSQLjM2ie
-1k9Fllh/5Q6e1Kd5mn5QP1QPC65Z78IskOot9Wrg0sk19sWiFMsopgJkQg66BlnM
-Fj8A4HPS8Yylt0oXJ5yytUYfvZNDRANZRiAz9mfgQw9oapJLnCLt3Awi5bcKJIVk
-/nTccEECgYEA8lSPFeTyzid0sIextXjjHvV4riSZKAHAa5M5DfsPkXLQfbEF4zog
-FLZ6c85jUCk7/vTzDRXr38XKaGbAg/1M3UJk7lS9shh1Zdo0+Hshq7rNICNLKz5H
-/u1/0gkxqqJcpXaoOHYN1hM7HJrP3uZFDIA5ZrB7JD20YDCQ/2PWrS0CgYEAwoHi
-qEKKXOol7FIiG1upTjPaWMTmxtmSs2/pobMVf+ZGDMPts0pbqDegXruaNFUrUQOG
-9yvrjA3a2QaKt7R5eDvHAafeMeu0WcHaqUWtIueTtEuduK4fXsCGONBPEGJu+Ct/
-BOCV/W8MAmzIOvffT7Jg1HWsZyAstoikPi1w4tECgYEAuvVmFxw1/7sNGgz2m+2S
-PJZh7uipiOYhEF3bTN//mNWd6PskcbSsf45xVttKX9QQR5mv0s6w1koA6R8tNCe+
-n43T1NRoLfkUyenZqENHLPjHvR29prU8Un/ld6REP0NYewfarQTXk+vuVRlTesLp
-TsW2g3Vw6/r3KKcPlxntzFkCgYEAqqkH1BY+DHQtPgJ6hoKQNFtuswBgdAymmOYS
-mZvlu0iyIbUvNGaDsT7NaRE1pcEstnJf0zMoAsSNRmpk//ZLteDNJXjCjg5/OVnL
-n0XROZTylfjatBWi1KIbonGzTW7warLPSdo8ABeU8/O6Y3Lk7qpWJ1PwJrOmR6nw
-YdXA/GECgYEAsSokOABGGLQ2zOmDYPDKrRenLho8cSe4g/CF5b6x5MXfJvvBfI/M
-hOS/2AFj7UkOSVM9B9QH607F67YJuBPNGvciCatqY/Bcs6ViuuyqG9U4O13OrrmA
-oG8Cho3Zy2v3udaLJ6O5JYcpX+hdvkwoG5j/8hLrzkD/3AA12z6QnV0=
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u1.pub b/docker/gitolite/t/keys/u1.pub
deleted file mode 100644
index 264c1f0..0000000
--- a/docker/gitolite/t/keys/u1.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4HweYnlRffTEWS2a+UuBKvOwYjNKJiIBgfx5Tp6kkav/GNgP0YTob0Rwv781NzTOI+EhS1Wb6H6QUQe1DVV44US5W5YC6Q81dEUOtg4x95HhTWHuiVPnxTf4iVy2t4pq9ev7ts8+FuG90HsU8zzG6xo9/BPoEbixOofI48vlrd5dbtKm25UPzso0cqjeyRZxtvvwRVC3sx8IxNqbpfY7hjTLV1rnWQ6G3qpFp4kehoCNcHhWlj2UlJAD4qbm5i74UjCfz3Ps/iPZpQ1kWrZQ5LC5WJ6RJHqV8e+iu3KHxgWtJFSxDht6tlQgIOnQSTYGImKtaI9cCn27HBeaz1Ru9 g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/u2 b/docker/gitolite/t/keys/u2
deleted file mode 100644
index 02486a6..0000000
--- a/docker/gitolite/t/keys/u2
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEA4/t3WV4q98MlYV9Jbvbf7gE2Dzit6dD8xHsWBh2wTmggQM9I
-2RsPp1tTIoOpt4YdlTzV41577BKLVMvqG7AxnVljC7+m7PrT2YmxEGrrbcrHebHu
-+pwh0lkN/CIz6MHjWzLbFiRoHhKh9LyfSiKxCgJ3dyjFgAnA+wrXkwIfPRG8qOLk
-sb1KUsMdfYm0OUEC/mHU2dsIS5HIY6xhy5Q5BXWgkfYUK7LL7Eyw6ffLhvq4U9tv
-Qv+u64Qowew4BY4B8rvyb73iEGcInIF2JRY0jVC8yJFejTJo2KKlXxu8MyAtAhth
-cHE6gA7xmn1d8TF7sxHDNqprvD9mLTjUVdz1owIDAQABAoIBAQDEGaWLZYioHV+l
-5gSQQiJT4w7RAPv3RyBlEUrcb+UbTE2R8brDpJdOaSuVYJM3nVEM8Ys5TChj43+d
-rNjugBvtMNoVXQEEjqxzThDUAmQHyIjUkMzzHCGrgZaZ7gGgkEY0SAZTgXVdiMFu
-dmC9sCGAbqa8BIH9pGYuiiDr/sNIDr3ekyqyuSA0Aa2JIrEx1TKFXF5JtGseU9S6
-bUfesCpoWGyVdulr3NbsLM26eDCsZo45OF5QdpTtU99xc9K4gsOre0ZtqEJMVGlR
-2nDQILCroH93GabSIW+fiUZD2lO9BxXAM0NA730ODQWyM8IgoWrqxGUuFz40l7X1
-teB7yRwBAoGBAPkkF//ZaEUUyCX6+a6TjaPQ1atemQCxHtIm1phfIM/u2uANqeOP
-Z+N3dM0TL53lRx/xbT0dO4sfF73XcF0sYkQj9rep28Q04W8Z6iC/I+jQAJJ0bYl5
-skUGTECxIPLmKOciQF7N5PUGvxhI4oq/0vgDYFc+NW18mow1Z66H1PkjAoGBAOpC
-P9EJXEFKZY1lK6ZL6wvJ6tJaPWAuiQlkOdj9wLvvzywQPlEdBJ1k0q+ndAqpR6WJ
-eTlkP/bzDpLRi+l4PEVTsFlpxjcfVn19RRabsKTNIS0usl1el/uQP7u4rRimBCz5
-MO72GD4ARM5CgMZZMGU5AXmxKEM9feTcUNss3RmBAoGBALTYsmMRmVKr5y1KpPtI
-OER1TuR6Ym3SJCE/9/3a76KAK3j/8hYw/qRrDenex23CBIL3aOg31AUEqOMxA2te
-0GXOBUUEk3Y1PH69POpQVOymMAQfZ3OnVvQrwiYjbVtkHsTIZBltM4l5QDWMkoVN
-AQLu0HwDuBylmjm0enKCPuIpAoGBAObd257bprwB4gtzhY0ijMbVfENLA+nictN6
-nzgm/Oc68+XtLD0sZ/vl/W13jnljU2TlEz9oeVGbQOWY9lZlVKDOVaIJCHwSul56
-MriRP4lrUCMDPm2eaBJYmzcaTh1YoAzimUMn7cRM54KPL/JKu9NGVxnjala6J3SB
-XH5kvJIBAoGBAITZe45tIbVkGjK3jTjgBfaqFfhyws4L3QgRbjqpy2h9nLEGHMYV
-RkIt6bCkw6cy+8TnYB0iG9lcGP1S5E4C6zpTdldi/5tGl7uu9qfEz4hxRpRbJ4Q0
-nwZzUkgBaLxAiLHfntBpwk16UvHkCpo5hiKzSLzxfReXzQPUUudGRnsG
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u2.pub b/docker/gitolite/t/keys/u2.pub
deleted file mode 100644
index 916dcf5..0000000
--- a/docker/gitolite/t/keys/u2.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDj+3dZXir3wyVhX0lu9t/uATYPOK3p0PzEexYGHbBOaCBAz0jZGw+nW1Mig6m3hh2VPNXjXnvsEotUy+obsDGdWWMLv6bs+tPZibEQauttysd5se76nCHSWQ38IjPoweNbMtsWJGgeEqH0vJ9KIrEKAnd3KMWACcD7CteTAh89Ebyo4uSxvUpSwx19ibQ5QQL+YdTZ2whLkchjrGHLlDkFdaCR9hQrssvsTLDp98uG+rhT229C/67rhCjB7DgFjgHyu/JvveIQZwicgXYlFjSNULzIkV6NMmjYoqVfG7wzIC0CG2FwcTqADvGafV3xMXuzEcM2qmu8P2YtONRV3PWj g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/u3 b/docker/gitolite/t/keys/u3
deleted file mode 100644
index 163bdef..0000000
--- a/docker/gitolite/t/keys/u3
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEArK6uQkgoAnS+w6vs4DmvdOfcGdFUzh3ivHAXHug7sjjKGUB2
-KwcXNwcmJPtyNXM0BJ9ZoOJSqJo5cWLMl7Qn1HBubuyM02Id9EqeJu1Ytn/i1XFY
-NMp8fj4wma3LZTdeOy3ockbefkw6VQUq3cheXIeaVpT91jsuyIaE0ejWkjGMu596
-zXFQZYTSO28TIYghRvhXGq5W9hJ3V2k+ZAQ/AsWQqjK4XruICi+RGRpKj59ECI7V
-zIrq4MUEti+3LldaY6tyYsTGQLtFJYu+l/ZavkrM979cPOxLeoi8ZHPTPKh6asdr
-ZXA2J6++IyIgNy2Q4sNxTmkIlU/EDysvTTGdMwIDAQABAoIBABF+NJr0UlFFYFnU
-Hc/tKBAQuORIp22l62Upeb4gyoNYa2i5df8P3dMuPzf53Oz7OabKObspkjQQQ4dv
-+cfYcTx9E0LbZby4MM6hjHnnC1iZhfIXZFccuBXV2PiIeZVMUZhvIyAIe9uRf0tD
-lb8X4C9BcWoZ98ju/+NCdUwKaUov3jXc32hmsAhK+dtqBE+lwccX9keJQcncf+6j
-+2znpDAJdaF70dM0DidbkmXmOTgH6LjjqrYRTAUnVsvUyDsr5YhopwOHs0E95YB4
-RzO7V/DA8H7DQ+XE20Iqp2i6dSbRQQPxbQLvJG4BaITIqb1L8/WJ6N75O/uns4rE
-Y8WVwWECgYEA1gDDP70DqvVMu6KDvn18pvfxzxJ3jccYcBZITV/E6xgj1AbH7fdA
-0iwvG6jQ26DTUBfLBquZ2fCCyI44OUpZ8GZu/wYBn5CQjYtr1vJ7zHKM3dVf+POT
-cGgOVDIHopCCX5Dwb74OpbkTV4g/WxouClvz0Ovobq8NncSkJJkyGEMCgYEAzpIF
-oj2AVMWDd6NL+P8e+QXPYEvJ/trAHoAteI83Eof4ZOC/sBr7Gf3c6nM7ZJbCXGhJ
-NCCr4teHJMg9DThQ1KxKn9qEf9dWiAE+HCouaZU98Z0UcLQ+tgPeBh/Dw7rTF3M2
-7A/LtLYbg1MoPCZNj6V4qGjmNWCy2lku1ZGBUFECgYBD/4oKvqxjrf3rwP/Lj2QE
-SdRzz5JdYl3Jf8sJityvNsRropv0aRQXtCJjz4hNwRRj5quEOxJvxZRI1afXzGA3
-mtS6A9aQNQc5couZiQL9O4i3FA2itQKsPOQQrLTwWqqSYyOC3gkZb21N6uT2taLb
-d8xJHiyEvuq8rrbZSjQ4sQKBgEkUDZws58aVrYHYqlrnXny4mnm1tjtMBiWEMRHy
-kIgkxDJj9EyH7wdt8QacR4m5b/8jAarIWCbDGtNfZ4HSx33FigztUGytsLYiwmdS
-YOMHYkeky4NnsLvRuG0wNaB76ovkPazblbRTrH4UICrPXicQYhQqMC74C64FWPVD
-KZ1RAoGAE/vKCHCzPegTT9gr2aaIrhLPUUZboOF4gHajYr9Scr176nJhpIvP/0Y2
-yQtqfas5lID8ouqXb+oL0Q4Yi00hdu+TRYQHm3M+2UL7wgffR5H2vfhk24UUDfV5
-0qQjNKp3pNUWZdiZ2J+RGUszXt0THfWbWI/ntwnG5QchqXCqYEA=
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u3.pub b/docker/gitolite/t/keys/u3.pub
deleted file mode 100644
index e97645c..0000000
--- a/docker/gitolite/t/keys/u3.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsrq5CSCgCdL7Dq+zgOa9059wZ0VTOHeK8cBce6DuyOMoZQHYrBxc3ByYk+3I1czQEn1mg4lKomjlxYsyXtCfUcG5u7IzTYh30Sp4m7Vi2f+LVcVg0ynx+PjCZrctlN147LehyRt5+TDpVBSrdyF5ch5pWlP3WOy7IhoTR6NaSMYy7n3rNcVBlhNI7bxMhiCFG+Fcarlb2EndXaT5kBD8CxZCqMrheu4gKL5EZGkqPn0QIjtXMiurgxQS2L7cuV1pjq3JixMZAu0Uli76X9lq+Ssz3v1w87Et6iLxkc9M8qHpqx2tlcDYnr74jIiA3LZDiw3FOaQiVT8QPKy9NMZ0z g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/u4 b/docker/gitolite/t/keys/u4
deleted file mode 100644
index a669e34..0000000
--- a/docker/gitolite/t/keys/u4
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEA2Wg3bl7T0C8VuR8HdbAqmwvQH4/T/maaqlQeJqcATRgWQNDv
-VthEasW5Kx8DzcSVRWS0cJ5EpTLGvrs84aXgdvg6TwFZKO/ujrkFDZmw9hd4I/Dv
-0dp/Y7himS8vAvnnWfYyqJBiX4plEx9Kg8oWHwy8KK3HHKoO8jAWAHOWO3GMB8BV
-128VLovGB6Sp99GrgltzP9UhNRHt/doa3ve8+fDk782SandVTTR9MNLt7qhMSOKr
-bmoFWtL36W5hmVjFGTZC1ZNIU6PiqygUqPtyAYblwv/nZ35s72KmxP7Seag3SsuZ
-UPnRjM4PZ+7YE7tcPTOUxcct8xuTZXgjh2WiNwIDAQABAoIBADSrR8qIVJ452fRo
-LQF49UlsmjYbPQuDxfJ/wHIywSLsM+/t7h3G9QQ89Hga4mwGNPeDxycFYLH41Cc+
-6yfrbK7FwjKDrBr7zXpsHmpGEpX755Ile6QGYBhDgjeEM8pvynmD6I/nsr1cpNH2
-IbI90hAhoK/mMbejB03rEll3pyytCgEvJQsu847dTNTZN+PHsumPr9HERi/dDrbw
-JrDSH2tbOvYw4UW9HLFfZAB+IgMo34WXf6kcUY69wWQbxnv1e7KHnYkxIuLCGZ6x
-Lc9APM97f4atlGt+mAtqK97CJB1AqoIE3KpPq1x+gpFSBNEE+9U9KnItuSqF3mG4
-vq/VjIkCgYEA+ASubOgzJaAldNz3dinLYK6MLB+V1R+RpkDc3+OgfF5mgg11ZtpE
-/DO1Ndpf5dOYB4JYGfhC9+xomHGsQmbOsIUprWaAR/xaO6Bgxw7cxkOBIadsrdwu
-MWJ6h3gN72zuhKPERpUVVH7ZH5KQfZwxEMgquUkGdEwumU4rMXeCFqsCgYEA4GdX
-qI/5UNXEPnXk3dDWNzNJO7gRoonU228IM18qz5ZKMRIKp6Sq+wIuO8+aQMr2K0rO
-RMQk+lLOZ17omaWh9ysDPzTCWRrjOiDOnYWDPCclTeqSnEk8uuWyppDGNXgQ6osM
-LnwJSeP8+1EDgkKf5zoiQfcyBc0v8PSRSdTfEqUCgYEA2PqHeqHd9UHY4xdZq1e/
-JLMv0H5Ff/GhY7iFQ54JziRsO8T4e+Xiyl2WYCnPEer+qzseRoIKXInHq+5uzJzS
-oF2va5MsEU41xsp1QFDBVvbBpyapDqV9CBlmptOiJV/Af+wiD7nnskdTPqrjm/Ck
-gFEOB5Fagy4O6nIXmaw69AcCgYEAhVSBndKlZKUOa7oqmKzLipK7UXNFbxiL0zE+
-Yx+JVTvLqyo4EHFjca5TABCSayrsZr6UngEYo27t2jdm5lumRzBURoq3aq/yEIiL
-msZIOkZcANZ988QEBFwT8KmWSxCipGinfTsPXcrLdhslhZDGZ2GAF0ejfhTzBiyZ
-4o9LV00CgYBf46Z2M1fWI6Tc8HUKr9WoyUmTmUtHUFPt9IZ2CKm2czoltpku7cC7
-ztlt4LmVPKv1UUavbC/nCz6s1ylOkY0rdm45FSYXKDYPMQqzQix1jG4GZJjM9En+
-M848LupnHBFsmHyQqyyRlsg1gb+wtvAw7Y44wcfAhGbiAT5DVX+zxg==
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u4.pub b/docker/gitolite/t/keys/u4.pub
deleted file mode 100644
index 06f3648..0000000
--- a/docker/gitolite/t/keys/u4.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZaDduXtPQLxW5Hwd1sCqbC9Afj9P+ZpqqVB4mpwBNGBZA0O9W2ERqxbkrHwPNxJVFZLRwnkSlMsa+uzzhpeB2+DpPAVko7+6OuQUNmbD2F3gj8O/R2n9juGKZLy8C+edZ9jKokGJfimUTH0qDyhYfDLworcccqg7yMBYAc5Y7cYwHwFXXbxUui8YHpKn30auCW3M/1SE1Ee392hre97z58OTvzZJqd1VNNH0w0u3uqExI4qtuagVa0vfpbmGZWMUZNkLVk0hTo+KrKBSo+3IBhuXC/+dnfmzvYqbE/tJ5qDdKy5lQ+dGMzg9n7tgTu1w9M5TFxy3zG5NleCOHZaI3 g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/u5 b/docker/gitolite/t/keys/u5
deleted file mode 100644
index ed65131..0000000
--- a/docker/gitolite/t/keys/u5
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEA41A0bY6+0akNSJlR2PeRATNtncARXVOUar7CNaxwPqVXQR1+
-TmU9evmIEkRLf0kFAa7L3QDFroFu4sDiSJjvfYIkHdoxO4Fk128PJBhObIXaarKc
-UBIZ29/8I3dTedq5CY/YHL/AjaT+0VktWX7YwigvGDamrXAKqjnW1mWKp6TNj8bp
-vppBvj3yvdYoOLYvCs/SKfKdlayngiihTbKELPcRu8NzYxHiF2b/u0t31evdP0fA
-/apAslKhHdCm8ScZJuyIdztcogBFE5dGm0h3qlbJKvKT8JmTcgQ5TBBcmqPVmUkm
-nPvIzd2G7D1smbExG7LnqekHcS9dvaPnF0B3oQIDAQABAoIBAGl1e21crXDN0mjd
-INjdOnvpJTDru+KldRT0/VszbjvSL6H5EfFDDPvxqsx2vOQHt3fpZZFZ21yzlgND
-Y3g0499BsonbAb5OsL82OjsPv8qfaw7XYKfRTgfxaaP2p1bAP9qMzsG/wJC2fLYZ
-fm2n6N5jED5WlIugkIIbJW4AXAycDB2ZU0xtPTHJ8nrSj1otDCsD2VbbVHXN7H6g
-HrTqP4RqD+uVDIxSrXllz4Udwe/I1wUrvY9HjH2qS6Liqb5kw8SOf4a2et18+KB2
-NNk6ZEAlmOx1ddC2eZPxm8XxAxdSwTggcwvtixCeoXR53OwTZAZ8BBzwusrMklZm
-/n5zPWUCgYEA88Lf4i0WFu9h4FnQ6qxCAKDS1XyRpCYt5cyTpZPMV3LuUV0DPS3K
-+EZeU689BSOiwav8omCtrrrOHtE9fHjOH85Gd4IRyOwUQWI7RLpFiI8Lbs4tMbP5
-k1UOzzTji++N47XfTTQVdwHbx4jac80LVavbRd3AKd6oRjc/gQOpgasCgYEA7rnr
-uoWqT76xVNLmE+g93x33hzES2HitgjHzvm5B2Cu6fwlXi7cSnLEWVn7W5QXEuhek
-6uhq4NC0ahL/qAyJsFhVve32qrR7yacDVlMWZ5iQPfqtvS9CsxyafADBWsgeN4L1
-5oqQofNlh0l+UvMFp8INNbKfxTOPKCMfGxIEd+MCgYEA7UsJky380PrbtwD4JVrn
-LaFhXL3VMYyRJaFPIeKNC5wwbzgyjP3lFme6L5Dpv/T+3bZFSvT+XpgvS0S5rFAV
-qFSvuGsAUS2wUi4EMFV8lwFZSdafnEDtdgVZU1DTKkhbQg6sgIVxV9aRUt7gedZj
-cFTKMms6RAgim6fww/ECs90CgYBdI1pt9jJhVHPZNUMgpy5ke0uUijfhDwwazKRd
-OqUj0sO7RojKcM2pJoohivEKf3qmZA0qvSzds2+AJxNpnCKoE364UDw5k5rsLOXn
-axlFp8c29zOLqQGr4c//60eExKjNXaHUpWESXmTRKIJJmJkvP01qEtu0043ZygIb
-zKbDowKBgHhacDR3aat3kkaTp/4AhekhDhMgZ1u2NIi0ycsA6zRlmFucHMwtKCM0
-VMuu40D9N1lTmcdndNKkTJx6CpbS7g/y0ctkyGwgFdmTjjHYl1nATt9G2oRzKx6I
-nQ+sBUwBPieIqEmiFh+KAsDox6plrtvhSSwp8FYLWBJXZHlLJIsd
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u5.pub b/docker/gitolite/t/keys/u5.pub
deleted file mode 100644
index 96a0045..0000000
--- a/docker/gitolite/t/keys/u5.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDjUDRtjr7RqQ1ImVHY95EBM22dwBFdU5RqvsI1rHA+pVdBHX5OZT16+YgSREt/SQUBrsvdAMWugW7iwOJImO99giQd2jE7gWTXbw8kGE5shdpqspxQEhnb3/wjd1N52rkJj9gcv8CNpP7RWS1ZftjCKC8YNqatcAqqOdbWZYqnpM2Pxum+mkG+PfK91ig4ti8Kz9Ip8p2VrKeCKKFNsoQs9xG7w3NjEeIXZv+7S3fV690/R8D9qkCyUqEd0KbxJxkm7Ih3O1yiAEUTl0abSHeqVskq8pPwmZNyBDlMEFyao9WZSSac+8jN3YbsPWyZsTEbsuep6QdxL129o+cXQHeh g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/keys/u6 b/docker/gitolite/t/keys/u6
deleted file mode 100644
index 86deee7..0000000
--- a/docker/gitolite/t/keys/u6
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAxyRjRT8RoSDnAnbZdrTjXhBMrkfNfolWFqc3qAjAo8Tmp7Ns
-n7R+KCl31RDkC9u4ll4AOfF9bIdP9ovDVvXoTMoiOdPTYqsnaMBBpDH04vxID2U5
-rEOhSzbvTazzaSlYNFED2a9bMfULuF6WxchFPBQGhTZaC8cDAkS5YYXvjHXAn1GN
-kpv0k5qltD8vjJoM7Mg5JLm84WejPQ5CmwOXqsQj69ZUZEyA8J3oqWVYkEhpYkbK
-IuzaHkrTC0PUQJ8MS3wM8358bWp53zYFJMkyL1a/zJQPOMaDiFTXXj1Uue/5ZkIM
-/fkkOMLbaCgEFCMbL8HkVEq4Q0dLhJKl1IdzbQIDAQABAoIBACQd91shuyLMAtmx
-kHM1D1+J+T5Ki3x9j/1/ylpRbA7HsUWNBxBX/eFu0+ryq0lzSiELX2Mi5yp9yATh
-CEaHRuBWcKqoPlhQzk7zP3R2EwHv22nfY/xYL7KiffhKe8MA2pxybQ5X/WQsGzoO
-/a1VSylAQIZ8ewxTxbntmOmVDwMcLZdY5X4NAmzUw1lFD2TVOlHyhjvhgIzhO1tp
-fBvsX3OL+Pk0tTQebuhoqEy2R01BLc+0ZFAKAFZpZgualqZqjyIU0isQsEo+EK4s
-Pn7Ccxy+156aWAEdgD5Aj7oMn3zA9YIt/JoX8muaceUOj3E7B5ZKBtYZkFPVnKO9
-rPMNTIkCgYEA/v5IeFDZjcAMLWqhuPekcUQYMXc+I0ZTn7vT3aksWxhARG96CPdG
-HNLMw4y8yZLnCAsdKUJGtrVcn4y4K0FzUYn0tDm9cmtFoFcQvLPp4mR1kB84urIG
-7qEwv4djeLhl0cYsqMpyl7qflbTVwKa09Fy7HX9ZFL58nXVX63uRXlMCgYEAx+2o
-L/GKkKgRPpCudsGoplECjzqP4SWHXaYVOupVzpnvxFKVcpBl6Un6p6U9V6W16k2C
-XUg9XqFdleawfAwGQ2D/Ip/u4r8GKOphv0QCjO88ld15Ky9mr9Sk4/Z2EAMUrmKg
-CS4hGBF7VIeA3FnzJwLkYL0+WQKpKBt/zojQLz8CgYEAnmtEgttYDeTeq+iviMbx
-9xyjGzhF9oxer8J1oiTUVdP/OYU4gBGAEbA1Xtg1AdauiiS9fUCbxi9u2AEI+naz
-OllHGiE1Pby/iRoOX+42xFw9XcjH6dVo0SB7tMJcXkfRmj5QyJzeDL35H301v3bS
-vW5PIchYg7bEnN6mPLqMWdkCgYBVWaX1Yb5v5vAFr6prVF11MxxOnQeTbHwPhLmH
-f0bGfn0XaNIYKID5SPXS3/4CDuJMdm5y+EYKwgS729H4AwIhfaUt2O0Yq8gra3Pz
-PUuBcxiAOh5iS0ghRDxofW0FhOstTzlW8fR62+u0uGxQpa3iN5/blK6rPTGNx7+W
-Il4N7QKBgHRTxUZraVt3n0kJ4+tdc+F8XK1wLyVC9PmtW+28tiyx14k8o8H/6W94
-r3lyI13sUnvW6zU0VMAALQujRcJDLac/zSUdFGmgU3P3QatU5yUm0Xpt1uFM49Zw
-xZLBTDJxe2Qrc3Z3SKReRqxLz6tTU1soH7TVw/S36tBovKjTw7Cc
------END RSA PRIVATE KEY-----
diff --git a/docker/gitolite/t/keys/u6.pub b/docker/gitolite/t/keys/u6.pub
deleted file mode 100644
index de5b06b..0000000
--- a/docker/gitolite/t/keys/u6.pub
+++ /dev/null
@@ -1 +0,0 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHJGNFPxGhIOcCdtl2tONeEEyuR81+iVYWpzeoCMCjxOans2yftH4oKXfVEOQL27iWXgA58X1sh0/2i8NW9ehMyiI509NiqydowEGkMfTi/EgPZTmsQ6FLNu9NrPNpKVg0UQPZr1sx9Qu4XpbFyEU8FAaFNloLxwMCRLlhhe+MdcCfUY2Sm/STmqW0Py+MmgzsyDkkubzhZ6M9DkKbA5eqxCPr1lRkTIDwneipZViQSGliRsoi7NoeStMLQ9RAnwxLfAzzfnxtannfNgUkyTIvVr/MlA84xoOIVNdePVS57/lmQgz9+SQ4wttoKAQUIxsvweRUSrhDR0uEkqXUh3Nt g3 at sita-lt.atc.tcs.com
diff --git a/docker/gitolite/t/listers.t b/docker/gitolite/t/listers.t
deleted file mode 100755
index 5fbf0ae..0000000
--- a/docker/gitolite/t/listers.t
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# the various list-* commands
-# ----------------------------------------------------------------------
-
-try 'plan 30';
-
-try "## info";
-
-confreset;confadd '
-    @oss = git gitolite gitolite3
-    @prop = cc p4
-    @crypto = alice bob carol
-    @dilbert = alice wally ashok
-
-    repo    @oss
-        RW              =   u1 @crypto
-        R               =   u2 @dilbert
-    repo    @prop
-        RW  =               u2 @dilbert
-        R   =               u1
-    repo    t3
-                    RW  =   u3
-                    R   =   u4
-';
-
-try "ADMIN_PUSH info; !/FATAL/" or die text();
-try "
-                                        /Initialized.*empty.*cc.git/
-                                        /Initialized.*empty.*p4.git/
-                                        /Initialized.*empty.*git.git/
-                                        /Initialized.*empty.*gitolite.git/
-                                        /Initialized.*empty.*gitolite3.git/
-                                        /Initialized.*empty.*t3.git/
-";
-
-try "gitolite list-groups"; cmp
-'@crypto
- at dilbert
- at oss
- at prop
-';
-
-try "gitolite list-users"; cmp
-'@all
- at crypto
- at dilbert
-admin
-u1
-u2
-u3
-u4
-';
-try "gitolite list-repos"; cmp
-'@oss
- at prop
-gitolite-admin
-t3
-testing
-';
-
-try "gitolite list-phy-repos"; cmp
-'cc
-git
-gitolite
-gitolite-admin
-gitolite3
-p4
-t3
-testing
-';
-
-try "gitolite list-memberships -u alice"; cmp
-'@crypto
- at dilbert
-';
-
-try "gitolite list-memberships -u ashok"; cmp
-'@dilbert
-';
-
-try "gitolite list-memberships -u carol"; cmp
-'@crypto
-';
-
-try "gitolite list-memberships -r git"; cmp
-'@oss
-';
-
-try "gitolite list-memberships -r gitolite"; cmp
-'@oss
-';
-
-try "gitolite list-memberships -r gitolite3"; cmp
-'@oss
-';
-
-try "gitolite list-memberships -r cc"; cmp
-'@prop
-';
-
-try "gitolite list-memberships -r p4"; cmp
-'@prop
-';
-
-try "gitolite list-members \@crypto"; cmp
-'alice
-bob
-carol
-';
-
-try "gitolite list-members \@dilbert"; cmp
-'alice
-ashok
-wally
-';
-
-try "gitolite list-members \@oss"; cmp
-'git
-gitolite
-gitolite3
-';
-
-try "gitolite list-members \@prop"; cmp
-'cc
-p4
-';
-
diff --git a/docker/gitolite/t/merge-check.t b/docker/gitolite/t/merge-check.t
deleted file mode 100755
index fdea318..0000000
--- a/docker/gitolite/t/merge-check.t
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# merge check -- the M flag
-# ----------------------------------------------------------------------
-
-try "plan 55";
-
-confreset;confadd '
-    repo  foo
-          RW+M      =   u1
-          RW+       =   u2
-          RWM      .=   u3
-          RW        =   u4
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-# setup a merged push
-
-try "
-    cd ..
-    [ -d foo ];         !ok
-    glt clone u1 file:///foo
-                        ok;     /Cloning into/
-                                /You appear to have cloned an empty/
-";
-
-try "
-    cd foo;             ok
-    [ -d .git ];        ok
-    test-commit aa;     ok;     /1 file changed, 1 insertion/
-    tag start;          ok
-    glt push u1 origin master
-                        ok;     /new branch.*master.-..master/
-                                /create.delete ignored.*merge-check/
-    checkout -b new;    ok;     /Switched to a new branch 'new'/
-    test-commit bb cc;  ok
-    checkout master;    ok;     /Switched to branch 'master'/
-    test-commit dd ee;  ok
-    git merge new;      ok;     /Merge made.*recursive/
-    test-commit ff;     ok
-    tag end;            ok
-";
-
-# push by u4 should fail
-try "
-    glt push u4 file:///foo master
-                        !ok;    /WM refs/heads/master foo u4 DENIED by fallthru/
-                                /To file:///foo/
-                                /remote rejected.*hook declined/
-                                /failed to push some refs/
-";
-
-# push by u3 should succeed
-try "
-    glt push u3 file:///foo master
-                        ok;     /To file:///foo/; /master.-..master/
-";
-
-# rewind by u3 should fail
-try "
-    reset-h start;      ok;     /HEAD is now at .* aa /
-    glt push u3 file:///foo +master
-                         !ok;   /rejected.*hook declined/
-                                /failed to push some refs/
-";
-
-# rewind by u2 should succeed
-try "
-    glt push u2 file:///foo +master
-                         ok;    /To file:///foo/
-                                /forced update/
-";
-
-# push by u2 should fail
-try "
-    reset-h end;        ok;     /HEAD is now at .* ff /
-    glt push u2 file:///foo master
-                        !ok;    /WM refs/heads/master foo u2 DENIED by fallthru/
-                                /To file:///foo/
-                                /remote rejected.*hook declined/
-                                /failed to push some refs/
-";
-
-# push by u1 should succeed
-try "
-    glt push u1 file:///foo master
-                        ok;     /master.-..master/
-";
diff --git a/docker/gitolite/t/mirror-test b/docker/gitolite/t/mirror-test
deleted file mode 100755
index 627dc74..0000000
--- a/docker/gitolite/t/mirror-test
+++ /dev/null
@@ -1,445 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# you need 3 disposable userids: sam, frodo, gollum.  Then the test user (say
-# "g3") needs to be able to sudo into them.  Put this in /etc/sudoers:
-
-#       g3 ALL = (sam,frodo,gollum) NOPASSWD: ALL
-
-$ENV{TSH_ERREXIT} = 1;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-use Cwd;
-my $workdir = getcwd();
-my $h = $ENV{HOME};
-my ($t, $t2);   # temp vars
-
-# basic tests
-# ----------------------------------------------------------------------
-
-try "plan 152";
-##  try "DEF POK = !/DENIED/; !/failed to push/";
-
-##  confreset;confadd '
-
-##  ';
-
-##  try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-# ----------------------------------------------------------------------
-
-# switch keys
-sub swk {
-    my $h = $ENV{HOME};
-    my $k = shift;
-    system("cp $h/.ssh/$k $h/.ssh/id_rsa");
-    system("cp $h/.ssh/$k.pub $h/.ssh/id_rsa.pub");
-}
-
-sub all {
-    try "F " . join(" ", @_);
-    try "S " . join(" ", @_);
-    try "G " . join(" ", @_);
-}
-
-try "
-    DEF F   =   sudo -u frodo -i
-    DEF S   =   sudo -u sam -i
-    DEF G   =   sudo -u gollum -i
-";
-
-my $bd = `gitolite query-rc -n GL_BINDIR`;
-
-try "
-    $bd/../t/mirror-test-setup.sh;   ok or die mirror setup shell script failed
-        /hello server-frodo, this is frodo/
-        /hello server-sam, this is frodo/
-        /hello server-gollum, this is frodo/
-        /hello server-frodo, this is sam/
-        /hello server-sam, this is sam/
-        /hello server-gollum, this is sam/
-        /hello server-frodo, this is gollum/
-        /hello server-sam, this is gollum/
-        /hello server-gollum, this is gollum/
-        /hello admin, this is frodo/
-        /Initialized empty .*/gitolite-admin.git/
-        /Initialized empty .*/r1.git/
-        /Initialized empty .*/r2.git/
-        /Initialized empty .*/testing.git/
-        /Initialized empty .*/gitolite-admin.git/
-        /Initialized empty .*/r1.git/
-        /Initialized empty .*/r2.git/
-        /Initialized empty .*/testing.git/
-        /Initialized empty .*/gitolite-admin.git/
-        /Initialized empty .*/r1.git/
-        /Initialized empty .*/r2.git/
-        /Initialized empty .*/testing.git/
-";
-
-# ----------------------------------------------------------------------
-# SECTION 1: gitolite-admin shenanigans
-
-# push to frodo and see sam and gollum change
-try "
-    git clone frodo\@localhost:gitolite-admin fga
-        ok; /Cloning into 'fga'.../
-    cd fga;                             ok
-    cp $h/.ssh/u?.pub keydir;           ok
-    git add keydir;                     ok
-    git commit -m 6keys;                ok
-    git push;                           ok
-        /To frodo\@localhost:gitolite-admin/
-        /master -> master/
-    sleep 5
-    git rev-parse HEAD
-";
-
-chomp($t = text());
-
-try "
-    git ls-remote sam\@localhost:gitolite-admin
-        ok; /$t/
-    git ls-remote gollum\@localhost:gitolite-admin
-        ok; /$t/
-";
-
-try "
-    cd ..
-
-";
-
-# push to sam and see frodo and gollum change
-try "
-    git clone sam\@localhost:gitolite-admin sga
-        ok; /Cloning into 'sga'.../
-    cd sga;                             ok
-    empty;                              ok
-    git push;                           ok
-        /To sam\@localhost:gitolite-admin/
-        /master -> master/
-    sleep 5
-    git rev-parse HEAD
-";
-
-chomp($t = text());
-
-try "
-    git ls-remote frodo\@localhost:gitolite-admin
-        ok; /$t/
-    git ls-remote gollum\@localhost:gitolite-admin
-        ok; /$t/
-";
-
-try "
-    cd ..
-
-";
-
-# push to gollum and fail at gollum
-try "
-    git clone gollum\@localhost:gitolite-admin gga
-        ok; /Cloning into 'gga'.../
-    cd gga;                             ok
-    empty;                              ok
-    git push;                           !ok
-        !/To gollum\@localhost:gitolite-admin/
-        !/master -> master/
-        /gollum: pushing 'gitolite-admin' to slave 'gollum' not allowed/
-    git rev-parse HEAD
-";
-
-chomp($t2 = text());
-
-try "
-    git ls-remote frodo\@localhost:gitolite-admin
-        ok; /$t/; !/$t2/
-    git ls-remote sam\@localhost:gitolite-admin
-        ok; /$t/; !/$t2/
-    git ls-remote gollum\@localhost:gitolite-admin
-        ok; /$t/; !/$t2/
-";
-
-# fake out the gollum failure to continue the redirected push and fail at frodo
-try "
-    sudo -u gollum -i gitolite git-config -r gitolite-admin .
-        ok
-        /redirectOK.*sam/
-        !/redirectOK.*gollum/
-
-    sudo -u gollum -i bash -c 'echo repo gitolite-admin > junk'
-    sudo -u gollum -i bash -c 'echo option mirror.redirectOK-1 = gollum   >> junk'
-    sudo -u gollum -i bash -c 'cat junk >> .gitolite/conf/gitolite.conf'
-    sudo -u gollum -i gitolite compile
-    sudo -u gollum -i gitolite git-config -r gitolite-admin .
-        ok
-        /redirectOK.*sam/
-        /redirectOK.*gollum/
-
-    git push;                           !ok
-        /frodo: redirection not allowed from 'gollum'/
-        !/To gollum\@localhost:gitolite-admin/
-        !/master -> master/
-";
-
-# reset gollum via frodo
-try "
-    cd ..
-    rm -rf fga
-    git clone frodo\@localhost:gitolite-admin fga
-        ok; /Cloning into 'fga'.../
-    cd fga;                             ok
-    empty;                              ok
-    git push;                           ok
-        /To frodo\@localhost:gitolite-admin/
-        /master -> master/
-    sleep 5
-
-    sudo -u gollum -i gitolite git-config -r gitolite-admin .
-        ok
-        /redirectOK.*sam/
-        !/redirectOK.*gollum/
-
-    git rev-parse HEAD
-";
-
-chomp($t = text());
-
-try "
-    git ls-remote sam\@localhost:gitolite-admin
-        ok; /$t/
-    git ls-remote gollum\@localhost:gitolite-admin
-        ok; /$t/
-";
-
-# ----------------------------------------------------------------------
-# user repo shenanigans
-
-# for a recap of the perms see t/mirror-test-setup.sh
-
-try "
-    cd ..
-    pwd
-    /tmp/tsh_tempdir/ or die not in the right place
-" or die;
-
-swk('u1');
-
-# u1 sam r1, R ok, W ok
-try "
-    rm -rf fga sga gga
-
-    git clone sam\@localhost:r1 sr1
-        /Cloning into 'sr1'.../
-        /warning: You appear to have cloned an empty repository/
-    cd sr1
-    empty
-    git push origin master
-        /new branch/;   /master -> master/
-    sleep 5
-    git rev-parse HEAD
-";
-chomp($t = text());
-
-# u1 sam r1, W mirrors to frodo but not gollum
-try "
-    git ls-remote sam\@localhost:r1
-        /$t/
-    git ls-remote frodo\@localhost:r1
-        /$t/
-    git ls-remote gollum\@localhost:r1
-        /gollum: 'r1' is mirrored but not here/
-";
-
-swk("u2");
-try "
-    empty
-    git rev-parse HEAD
-";
-chomp($t2 = text());
-
-# u2 sam r2 W ok, mirrors to all
-try "
-    git push sam\@localhost:r2 master
-        /new branch/;   /master -> master/
-        /master -> master/
-    sleep 5
-    git ls-remote frodo\@localhost:r2
-        !/$t/
-        /$t2/
-    git ls-remote gollum\@localhost:r2
-        !/$t/
-        /$t2/
-";
-
-swk("u1");
-
-# u1 gollum r1 -- "known unknown" :-)
-# u1 frodo r1 R ok, W not ok
-# u1 sam r1 R ok, W ok
-try "
-    cd ..
-    rm -rf sr1
-
-    git clone gollum\@localhost:r1 fr1
-        /gollum: 'r1' is mirrored but not here/
-
-    git clone frodo\@localhost:r1 fr1;       ok
-    cd fr1
-    empty
-    git push
-        /frodo: pushing 'r1' to slave 'frodo' not allowed/
-    cd ..
-    git clone sam\@localhost:r1 sr1;         ok
-    cd sr1
-    empty
-    git push;                               ok
-        /master -> master/
-    sleep 5
-    git rev-parse HEAD
-";
-chomp($t = text());
-
-# u1 sam r1 W mirrored to frodo but not gollum
-try "
-    git ls-remote sam\@localhost:r1
-        /$t/
-    git ls-remote frodo\@localhost:r1
-        /$t/
-
-    git ls-remote gollum\@localhost:r1
-        /gollum: 'r1' is mirrored but not here/
-
-    git reset --hard HEAD^;                 ok
-    tc a
-    git push;                               !ok
-        /rejected/
-        /failed to push/
-
-    git push -f
-        /\\+ .......\\.\\.\\........ master -> master .forced update/
-    sleep 5
-";
-
-swk("u2");
-
-# u2 frodo r1 R ok, W not allowed (no redirectOK)
-# u2 frodo r2 W ok
-try "
-    cd ..
-    rm -rf fr1 sr1
-
-    git clone frodo\@localhost:r1 fr1;       ok
-    cd fr1
-    tc b
-    git push
-        /frodo: pushing 'r1' to slave 'frodo' not allowed/
-    cd ..
-    git clone frodo\@localhost:r2 fr2;       ok
-    cd fr2
-    tc c
-    git push
-        /master -> master/
-    sleep 5
-    git rev-parse HEAD
-";
-chomp($t = text());
-
-# u2 frodo r2 W mirrors to sam and gollum
-try "
-    git ls-remote sam\@localhost:r2
-        /$t/
-    git ls-remote gollum\@localhost:r2
-        /$t/
-
-    git reset --hard HEAD^;                 ok
-    tc d
-    git push
-        /rejected/
-        /failed to push/
-
-    git push -f
-        /\\+ .......\\.\\.\\........ master -> master .forced update/
-    sleep 5
-
-    cd ..
-    rm -rf fr1 fr2
-";
-
-swk("u3");
-
-# u3 frodo r2 R ok W ok
-try "
-    git clone frodo\@localhost:r2 fr2;       ok
-    cd fr2
-    tc e
-    git push;                               ok
-    sleep 5
-
-    git rev-parse HEAD
-";
-chomp($t = text());
-
-# u3 frodo r2 W mirrors to sam and gollum
-try "
-    git ls-remote sam\@localhost:r2
-        /$t/
-    git ls-remote gollum\@localhost:r2
-        /$t/
-
-    git reset --hard HEAD^;                 ok
-    tc f
-    git push
-        /rejected/
-        /failed to push/
-
-    sleep 10
-    git push -f
-        /\\+ refs/heads/master r2 u3 DENIED by fallthru/
-        /hook declined/
-        /rejected/
-";
-
-# ----------------------------------------------------------------------
-# all those vague edge cases where the two servers have totally wrong ideas
-# about each other
-
-swk('u1');
-
-try "sudo -u frodo -i ls .gitolite/logs";
-chomp($t = text());
-my $lfn = ".gitolite/logs/$t";
-
-try "
-    ssh sam\@localhost mirror push frodo lfrodo;  !ok
-    /FATAL: frodo: 'lfrodo' is local/
-
-    ssh sam\@localhost mirror push frodo mboth;  !ok
-    /FATAL: frodo: 'mboth' is native/
-
-    ssh sam\@localhost mirror push frodo mnotsam;  !ok
-    /FATAL: frodo: 'sam' is not the master for 'mnotsam'/
-
-    cd ..
-    git clone sam\@localhost:lfrodo2 lfrodo2;   ok
-    cd lfrodo2
-    empty
-    git push origin master;                     !ok
-    /FATAL: frodo: 'lfrodo2' is local/
-
-    cd ..
-    git clone sam\@localhost:nnfrodo nnfrodo;   ok
-    cd nnfrodo
-    empty
-    git push origin master;                     !ok
-    /FATAL: frodo: 'nnfrodo' is not native/
-
-    cd ..
-    git clone sam\@localhost:nvsfrodo nvsfrodo; ok
-    cd nvsfrodo
-    empty
-    git push origin master;                     !ok
-    /FATAL: frodo: 'sam' is not a valid slave for 'nvsfrodo'/
-";
diff --git a/docker/gitolite/t/mirror-test-rc b/docker/gitolite/t/mirror-test-rc
deleted file mode 100644
index 1d76783..0000000
--- a/docker/gitolite/t/mirror-test-rc
+++ /dev/null
@@ -1,162 +0,0 @@
-# This file is in perl syntax.  But you do NOT need to know perl to edit it --
-# just mind the commas, use single quotes unless you know what you're doing,
-# and make sure the brackets and braces stay matched up!
-
-# (Tip: perl allows a comma after the last item in a list also!)
-
-# HELP for commands (see COMMANDS list below) can be had by running the
-# command with "-h" as the sole argument.
-
-# HELP for all the other FEATURES can be found in the documentation (look for
-# "list of non-core programs shipped with gitolite" in the master index) or
-# directly in the corresponding source file.
-
-%RC = (
-
-    # ------------------------------------------------------------------
-
-    HOSTNAME                        =>  '%HOSTNAME',
-
-    # default umask gives you perms of '0700'; see the rc file docs for
-    # how/why you might change this
-    UMASK                           =>  0077,
-
-    # look in the "GIT-CONFIG" section in the README for what to do
-    GIT_CONFIG_KEYS                 =>  '',
-
-    # comment out if you don't need all the extra detail in the logfile
-    LOG_EXTRA                       =>  1,
-
-    # roles.  add more roles (like MANAGER, TESTER, ...) here.
-    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
-    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
-    ROLES => {
-        READERS                     =>  1,
-        WRITERS                     =>  1,
-    },
-    # uncomment (and change) this if you wish
-    # DEFAULT_ROLE_PERMS            =>  'READERS @all',
-
-    # CACHE => 'Redis',
-
-    # ------------------------------------------------------------------
-
-    # rc variables used by various features
-
-    # the 'info' command prints this as additional info, if it is set
-        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
-
-    # the 'desc' command uses this
-        # WRITER_CAN_UPDATE_DESC    =>  1,
-
-    # the CpuTime feature uses these
-        # display user, system, and elapsed times to user after each git operation
-        # DISPLAY_CPU_TIME          =>  1,
-        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
-        # CPU_TIME_WARN_LIMIT       =>  0.1,
-
-    # the Mirroring feature needs this
-        # HOSTNAME                  =>  "foo",
-
-    # if you enabled 'Shell', you need this
-        # SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",
-
-    # ------------------------------------------------------------------
-
-    # List of commands and features to enable
-
-    ENABLE => [
-
-        # COMMANDS
-
-            # These are the commands enabled by default
-            'help',
-            'desc',
-            'info',
-            'perms',
-            'writable',
-
-            'mirror',
-
-            # Uncomment or add new commands here.
-            # 'create',
-            # 'fork',
-            # 'mirror',
-            # 'sskm',
-            # 'D',
-
-        # These FEATURES are enabled by default.
-
-            # essential (unless you're using smart-http mode)
-            'ssh-authkeys',
-
-            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
-            'git-config',
-
-            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
-            'daemon',
-
-            # creates projects.list file; if you don't use gitweb, comment this out
-            'gitweb',
-
-        # These FEATURES are disabled by default; uncomment to enable.  If you
-        # need to add new ones, ask on the mailing list :-)
-
-        # user-visible behaviour
-
-            # prevent wild repos auto-create on fetch/clone
-            # 'no-create-on-read',
-            # no auto-create at all (don't forget to enable the 'create' command!)
-            # 'no-auto-create',
-
-            # access a repo by another (possibly legacy) name
-            # 'Alias',
-
-            # give some users direct shell access
-            # 'Shell',
-
-        # system admin stuff
-
-            # enable mirroring (don't forget to set the HOSTNAME too!)
-            'Mirroring',
-
-            # allow people to submit pub files with more than one key in them
-            # 'ssh-authkeys-split',
-
-            # selective read control hack
-            # 'partial-copy',
-
-            # manage local, gitolite-controlled, copies of read-only upstream repos
-            # 'upstream',
-
-            # updates 'description' file instead of 'gitweb.description' config item
-            # 'cgit',
-
-        # performance, logging, monitoring...
-
-            # be nice
-            # 'renice 10',
-
-            # log CPU times (user, system, cumulative user, cumulative system)
-            # 'CpuTime',
-
-        # syntactic_sugar for gitolite.conf and included files
-
-            # allow backslash-escaped continuation lines in gitolite.conf
-            # 'continuation-lines',
-
-            # create implicit user groups from directory names in keydir/
-            # 'keysubdirs-as-groups',
-
-    ],
-
-);
-
-# ------------------------------------------------------------------------------
-# per perl rules, this should be the last line in such a file:
-1;
-
-# Local variables:
-# mode: perl
-# End:
-# vim: set syn=perl:
diff --git a/docker/gitolite/t/mirror-test-setup.sh b/docker/gitolite/t/mirror-test-setup.sh
deleted file mode 100755
index b35364c..0000000
--- a/docker/gitolite/t/mirror-test-setup.sh
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/bin/bash
-
-set -e
-hosts="frodo sam gollum"
-mainhost=frodo
-
-# setup software
-bd=`gitolite query-rc -n GL_BINDIR`
-mkdir -p /tmp/g3
-rm -rf /tmp/g3/src
-cp -a $bd /tmp/g3/src
-chmod -R go+rX /tmp/g3
-
-# setup symlinks in frodo, sam, and gollum's accounts
-for h in $hosts
-do
-    sudo -u $h -i bash -c "rm -rf *.pub bin .ssh projects.list repositories .gitolite .gitolite.rc"
-done
-
-[ "$1" = "clear" ] && exit
-
-cd /tmp/g3
-[ -d keys ] || {
-    mkdir keys
-    cd keys
-    for h in $hosts
-    do
-        ssh-keygen -N '' -q -f server-$h  -C $h
-        chmod go+r /tmp/g3/keys/server-$h
-    done
-    cp $bd/../t/mirror-test-ssh-config ssh-config
-}
-chmod -R go+rX /tmp/g3
-
-for h in $hosts
-do
-    sudo -u $h -i bash -c "mkdir -p bin; ln -sf /tmp/g3/src/gitolite bin; mkdir -p .ssh; chmod 0700 .ssh"
-
-    sudo -u $h -i cp /tmp/g3/keys/ssh-config    .ssh/config
-    sudo -u $h -i cp /tmp/g3/keys/server-$h     .ssh/id_rsa
-    sudo -u $h -i cp /tmp/g3/keys/server-$h.pub .ssh/id_rsa.pub
-    sudo -u $h -i chmod go-rwx                  .ssh/id_rsa .ssh/config
-
-done
-
-# add all pubkeys to all servers
-for h in $hosts
-do
-    sudo -u $h -i gitolite setup -a admin
-    for j in $hosts
-    do
-        sudo -u $h -i gitolite setup -pk /tmp/g3/keys/server-$j.pub
-        echo sudo _u $j _i ssh $h at localhost info
-        sudo -u $j -i ssh -o StrictHostKeyChecking=no $h at localhost info
-    done
-    echo ----
-done
-
-# now copy our admin key to the main host
-cd;cd .ssh
-cp admin id_rsa; cp admin.pub id_rsa.pub
-cp admin.pub /tmp/g3/keys; chmod go+r /tmp/g3/keys/admin.pub
-sudo -u $mainhost -i gitolite setup -pk /tmp/g3/keys/admin.pub
-ssh $mainhost at localhost info
-
-lines="
-repo gitolite-admin
-    option mirror.master = frodo
-    option mirror.slaves-1 = sam gollum
-    option mirror.redirectOK = sam
-
-repo r1
-    RW+     =   u1
-    RW      =   u2
-    R       =   u3
-    option mirror.master = sam
-    option mirror.slaves-1 = frodo
-
-repo r2
-    RW+     =   u2
-    RW      =   u3
-    R       =   u4
-    option mirror.master = sam
-    option mirror.slaves-1 = frodo gollum
-    option mirror.redirectOK = all
-
-include \"%HOSTNAME.conf\"
-"
-
-lines2="
-repo l-%HOSTNAME
-RW  =   u1
-"
-
-# for each server, set the HOSTNAME to the rc, add the mirror options to the
-# conf file, and compile
-for h in $hosts
-do
-    cat $bd/../t/mirror-test-rc | perl -pe "s/%HOSTNAME/$h/" > /tmp/g3/temp
-    chmod go+rX /tmp/g3/temp
-    sudo -u $h -i cp /tmp/g3/temp .gitolite.rc
-    echo "$lines"  | sudo -u $h -i sh -c 'cat >> .gitolite/conf/gitolite.conf'
-    echo "$lines2" | sudo -u $h -i sh -c "cat >> .gitolite/conf/$h.conf"
-    sudo -u $h -i gitolite setup
-done
-
-# goes on frodo
-lines="
-# local to frodo but sam thinks frodo is a slave
-repo lfrodo
-RW  =   u1
-
-# both think they're master
-repo mboth
-RW  =   u1
-option mirror.master = frodo
-option mirror.slaves = sam
-
-# frodo thinks someone else is the master but sam thinks he is
-repo mnotsam
-RW  =   u1
-option mirror.master = merry
-option mirror.slaves = frodo
-
-# local to frodo but sam thinks frodo is a master and redirect is OK
-repo lfrodo2
-RW  =   u1
-
-# non-native to frodo but sam thinks frodo is master
-repo nnfrodo
-RW  =   u1
-option mirror.master = gollum
-option mirror.slaves = frodo
-option mirror.redirectOK = all
-
-# sam is not a valid slave to send stuff to frodo
-repo nvsfrodo
-RW  =   u1
-option mirror.master = frodo
-option mirror.slaves = gollum
-option mirror.redirectOK = all
-"
-
-echo "$lines" | sudo -u frodo -i sh -c "cat >> .gitolite/conf/frodo.conf"
-
-# goes on sam
-lines="
-# local to frodo but sam thinks frodo is a slave
-repo lfrodo
-RW  =   u1
-option mirror.master = sam
-option mirror.slaves = frodo
-
-# both think they're master
-repo mboth
-RW  =   u1
-option mirror.master = sam
-option mirror.slaves = frodo
-
-# frodo thinks someone else is the master but sam thinks he is
-repo mnotsam
-RW  =   u1
-option mirror.master = sam
-option mirror.slaves = frodo
-
-# local to frodo but sam thinks frodo is a master and redirect is OK
-repo lfrodo2
-RW  =   u1
-option mirror.master = frodo
-option mirror.slaves = sam
-option mirror.redirectOK = all
-
-# non-native to frodo but sam thinks frodo is master
-repo nnfrodo
-RW  =   u1
-option mirror.master = frodo
-option mirror.slaves = sam
-option mirror.redirectOK = all
-
-# sam is not a valid slave to send stuff to frodo
-repo nvsfrodo
-RW  =   u1
-option mirror.master = frodo
-option mirror.slaves = sam
-option mirror.redirectOK = all
-"
-
-echo "$lines" | sudo -u sam -i sh -c "cat >> .gitolite/conf/sam.conf"
-
-for h in $hosts
-do
-    sudo -u $h -i gitolite setup
-done
-
-# that ends the setup phase
-echo ======================================================================
diff --git a/docker/gitolite/t/mirror-test-ssh-config b/docker/gitolite/t/mirror-test-ssh-config
deleted file mode 100644
index 40de6d7..0000000
--- a/docker/gitolite/t/mirror-test-ssh-config
+++ /dev/null
@@ -1,11 +0,0 @@
-host frodo
-    user frodo
-    hostname localhost
-
-host sam
-    user sam
-    hostname localhost
-
-host gollum
-    user gollum
-    hostname localhost
diff --git a/docker/gitolite/t/partial-copy.t b/docker/gitolite/t/partial-copy.t
deleted file mode 100755
index 5bff843..0000000
--- a/docker/gitolite/t/partial-copy.t
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# test script for partial copy feature
-# ----------------------------------------------------------------------
-
-try "plan 82";
-try "DEF POK = !/DENIED/; !/failed to push/";
-my $h = $ENV{HOME};
-
-try "
-    cat $h/.gitolite.rc
-    perl s/GIT_CONFIG_KEYS.*/GIT_CONFIG_KEYS => '.*',/
-    perl s/# 'partial-copy'/'partial-copy'/
-    put $h/.gitolite.rc
-";
-
-confreset;confadd '
-    repo foo
-            RW+                 =   u1 u2
-
-    repo foo-pc
-            -   secret-1$       =   u4
-            R                   =   u4  # marker 01
-            RW  next            =   u4
-            RW+ dev/USER/       =   u4
-            RW  refs/tags/USER/ =   u4
-
-            -   VREF/partial-copy   =   @all
-            config gitolite.partialCopyOf = foo
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-        /Init.*empty.*foo\\.git/
-        /Init.*empty.*foo-pc\\.git/
-";
-
-try "
-    cd ..
-
-    ## populate repo foo, by user u1
-    # create foo with a bunch of branches and tags
-    CLONE u1 foo
-        /appear.*cloned/
-    cd foo
-    tc a1 a2
-    checkout -b dev/u1/foo; tc f1 f2
-    checkout master; tc m1 m2
-    checkout master; checkout -b next; tc n1 n2; tag nt1
-    checkout -b secret-1; tc s11 s12; tag s1t1
-    checkout next; checkout -b secret-2; tc s21 s22; tag s2t1
-    glt push u1 --all
-        /new branch/; /secret-1/; /secret-2/
-    glt push u1 --tags
-        /new tag/; /s1t1/; /s2t1/
-
-    ## user u4 tries foo, fails, tries foo-pc
-    cd ..
-    CLONE u4 foo foo4; !ok
-        /R any foo u4 DENIED by fallthru/
-    CLONE u4 foo-pc ; ok;
-        /Cloning into 'foo-pc'/
-        /new branch.* dev/u1/foo .* dev/u1/foo/
-        /new branch.* master .* master/
-        /new branch.* next .* next/
-        /new branch.* secret-2 .* secret-2/
-        !/new branch.* secret-1 .* secret-1/
-        /new tag.* nt1 .* nt1/
-        /new tag.* s2t1 .* s2t1/
-        !/new tag.* s1t1 .* s1t1/
-
-    ## user u4 pushes to foo-pc
-    cd foo-pc
-    checkout master
-    tc u4m1 u4m2; PUSH u4; !ok
-        /W refs/heads/master foo-pc u4 DENIED by fallthru/
-        /hook declined to update refs/heads/master/
-        /To file:///foo-pc/
-        /remote rejected/
-        /failed to push some refs to 'file:///foo-pc'/
-
-    checkout next
-    tc u4n1 u4n2
-    PUSH u4 next; ok
-        /To .*/foo.git/
-        /new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> refs/partial/br-\\d+/
-        /file:///foo-pc/
-        /52c7716..ca37871  next -> next/
-    tag u4/nexttag; glt push u4 --tags
-        /To file:///foo-pc/
-        /\\[new tag\\]         u4/nexttag -> u4/nexttag/
-        /\\[new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> refs/partial/br-\\d+/
-
-    checkout master
-    checkout -b dev/u4/u4master
-    tc devu4m1 devu4m2
-    PUSH u4 HEAD; ok
-        /To .*/foo.git/
-        /new branch\\]      228353950557ed1eb13679c1fce4d2b4718a2060 -> refs/partial/br-\\d+/
-        /file:///foo-pc/
-        /new branch.* HEAD -> dev/u4/u4master/
-
-    ## user u1 gets u4's updates, makes some more
-    cd ../foo
-    glt fetch u1
-        /From file:///foo/
-        /new branch\\]      dev/u4/u4master -> origin/dev/u4/u4master/
-        /new tag\\]         u4/nexttag -> u4/nexttag/
-        /52c7716..ca37871  next       -> origin/next/
-    checkout master; tc u1ma1 u1ma2;
-        /\\[master 8ab1ff5\\] u1ma2 at Thu Jul  7 06:23:20 2011/
-    tag mt2; PUSH u1 master; ok
-    checkout secret-1; tc u1s1b1 u1s1b2
-        /\\[secret-1 5f96cb5\\] u1s1b2 at Thu Jul  7 06:23:20 2011/
-    tag s1t2; PUSH u1 HEAD; ok
-    checkout secret-2; tc u1s2b1 u1s2b2
-        /\\[secret-2 1ede682\\] u1s2b2 at Thu Jul  7 06:23:20 2011/
-    tag s2t2; PUSH u1 HEAD; ok
-    glt push u1 --tags; ok
-
-    glt ls-remote u1 origin
-        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/
-        /5f96cb5ff73c730fb040eb2d01981f7677ca6dba\trefs/tags/s1t2/
-        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/
-
-    ## u4 gets updates but without the tag in secret-1
-    cd ../foo-pc
-    glt ls-remote u4 origin
-        !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/
-        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\tHEAD/
-        /8ced4a374b3935bac1a5ba27ef8dd950bd867d47\trefs/heads/dev/u1/foo/
-        /228353950557ed1eb13679c1fce4d2b4718a2060\trefs/heads/dev/u4/u4master/
-        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/heads/master/
-        /ca3787119b7e8b9914bc22c939cefc443bc308da\trefs/heads/next/
-        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/heads/secret-2/
-        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/
-        /52c7716c6b029963dd167c647c1ff6222a366499\trefs/tags/nt1/
-        /01f04ece6519e7c0e6aea3d26c7e75e9c4e4b06d\trefs/tags/s2t1/
-        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/
-
-    glt fetch u4
-        /3ea704d..8ab1ff5  master     -> origin/master/
-        /01f04ec..1ede682  secret-2   -> origin/secret-2/
-        /\\[new tag\\]         mt2        -> mt2/
-        /\\[new tag\\]         s2t2       -> s2t2/
-        !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/
-";
-__END__
-
-# last words...
-glt ls-remote u4 file:///foo-pc
-
-cd ../gitolite-admin
-cat conf/gitolite.conf
-perl s/.*marker 01.*//;
-put conf/gitolite.conf
-add conf; commit -m erdel; ok; PUSH admin; ok
-
-glt ls-remote u4 file:///foo-pc
-# see rant below at this point
-
-cd $h/repositories/foo-pc.git
-git branch -D secret-2
-git tag -d s2t1 s2t2
-git gc --prune=now
-glt ls-remote u4 file:///foo-pc
-# only *now* does the rant get addressed
-
-__END__
-
-RANT...
-
-This is where things go all screwy.  Because we still have the *objects*
-pointed to by tags s2t1 and s2t2, we still get them back from the main repo.
diff --git a/docker/gitolite/t/perm-default-roles.t b/docker/gitolite/t/perm-default-roles.t
deleted file mode 100755
index 1a56ff8..0000000
--- a/docker/gitolite/t/perm-default-roles.t
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# permissions using role names
-# ----------------------------------------------------------------------
-
-try "plan 33";
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-
-try "pwd";
-my $od = text();
-chomp($od);
-
-my $t;
-
-confreset; confadd '
-    @g1 = u1
-    @g2 = u2
-    @g3 = u3
-    @g4 = u4
-        repo foo/CREATOR/..*
-          C                 =   @g1 @g2
-          RW+               =   CREATOR
-          -     refs/tags/  =   WRITERS
-          RW                =   WRITERS
-          R                 =   READERS
-
-        repo bar/CREATOR/..*
-          C                 =   @g3 @g4
-          RW+               =   CREATOR
-          -     refs/tags/  =   WRITERS
-          RW                =   WRITERS
-          R                 =   READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-# create repos - 1; no gl-perms files expected
-try "
-
-cd ..
-
-# make foo/u1/u1r1
-glt clone u1 file:///foo/u1/u1r1
-        /Initialized empty Git repository in .*/foo/u1/u1r1.git//
-
-# make bar/u3/u3r1
-glt clone u3 file:///bar/u3/u3r1
-        /Initialized empty Git repository in .*/bar/u3/u3r1.git//
-
-cd u3r1
-";
-
-try "cd $rb; find . -name gl-perms; cd $od"; cmp text(), '';
-
-# enable set-default-roles feature
-try "
-    cat $ENV{HOME}/.gitolite.rc
-    perl s/# 'set-default-roles'/'set-default-roles'/
-    put $ENV{HOME}/.gitolite.rc
-";
-
-# create repos - 2; empty gl-perms files expected
-try "
-
-cd ..
-
-# make foo/u1/u1r2
-glt clone u1 file:///foo/u1/u1r2
-        /Initialized empty Git repository in .*/foo/u1/u1r2.git//
-
-# make bar/u3/u3r2
-glt clone u3 file:///bar/u3/u3r2
-        /Initialized empty Git repository in .*/bar/u3/u3r2.git//
-
-cd u3r2
-";
-
-try "cd $rb; find . -name gl-perms";
-$t = md5sum(sort (lines()));
-cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
-d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
-';
-try "cd $od";
-
-# enable per repo default roles
-confadd '
-        repo foo/CREATOR/..*
-        option default.roles-1  =   READERS u3
-        option default.roles-2  =   WRITERS u4
-
-        repo bar/CREATOR/..*
-        option default.roles-1  =   READERS u5
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-# create repos - 3; filled gl-perms expected
-try "
-
-cd ..
-
-gitolite access foo/u1/u1r3 u4 W
-        !ok
-        !/refs/../
-        /W any foo/u1/u1r3 u4 DENIED by fallthru/
-
-# make foo/u1/u1r3
-glt clone u1 file:///foo/u1/u1r3
-        /Initialized empty Git repository in .*/foo/u1/u1r3.git//
-
-gitolite access foo/u1/u1r3 u4 W
-        ok
-        /refs/../
-        !/W any foo/u1/u1r3 u4 DENIED by fallthru/
-
-# make bar/u3/u3r3
-glt clone u3 file:///bar/u3/u3r3
-        /Initialized empty Git repository in .*/bar/u3/u3r3.git//
-
-cd u3r3
-";
-
-try "cd $rb; find . -name gl-perms";
-$t = md5sum(sort (lines()));
-cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
-b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
-d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
-1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
-';
-try "cd $od";
-
-# add perms to an old repo
-try "
-echo WRITERS \@h1 | glt perms u1 foo/u1/u1r1
-";
-
-try "cd $rb; find . -name gl-perms";
-$t = md5sum(sort (lines()));
-cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
-b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
-f8f0fd8e139ddb64cd5572914b98750a  ./foo/u1/u1r1.git/gl-perms
-d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
-1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
-';
-try "cd $od";
-
-# add perms to a new repo
-try "
-echo WRITERS \@h2 | glt perms u1 -c foo/u1/u1r4
-";
-
-try "cd $rb; find . -name gl-perms";
-$t = md5sum(sort (lines()));
-cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
-b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
-f8f0fd8e139ddb64cd5572914b98750a  ./foo/u1/u1r1.git/gl-perms
-d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
-1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
-df17cd2d47e4d99642d7c5ce4093d115  ./foo/u1/u1r4.git/gl-perms
-';
-try "cd $od";
diff --git a/docker/gitolite/t/perm-roles.t b/docker/gitolite/t/perm-roles.t
deleted file mode 100755
index 03403d6..0000000
--- a/docker/gitolite/t/perm-roles.t
+++ /dev/null
@@ -1,218 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# permissions using role names
-# ----------------------------------------------------------------------
-
-try "plan 91";
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-confreset; confadd '
-    @g1 = u1
-    @g2 = u2
-    @g3 = u3
-    @g4 = u4
-        repo foo/CREATOR/..*
-          C                 =   @g1
-          RW+               =   CREATOR
-          -     refs/tags/  =   WRITERS
-          RW                =   WRITERS
-          R                 =   READERS
-          RW+D              =   MANAGERS
-          RW    refs/tags/  =   TESTERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-cd ..
-
-# make foo/u1/u1r1
-rm -rf ~/td/u1r1
-glt clone u1 file:///foo/u1/u1r1
-        /Initialized empty Git repository in .*/foo/u1/u1r1.git//
-cd u1r1
-
-# CREATOR can push
-tc e-549 e-550
-glt push u1 file:///foo/u1/u1r1 master:master
-        POK; /master -> master/
-# CREATOR can create branch
-tc w-277 w-278
-glt push u1 file:///foo/u1/u1r1 master:b1
-        POK; /master -> b1/
-# CREATOR can rewind branch
-git reset --hard HEAD^
-tc d-987 d-988
-glt push u1 file:///foo/u1/u1r1 +master:b1
-        POK; /master -> b1 \\(forced update\\)/
-# CREATOR cannot delete branch
-glt push u1 file:///foo/u1/u1r1 :b1
-        /D refs/heads/b1 foo/u1/u1r1 u1 DENIED by fallthru/
-        reject
-
-# CREATOR can push a tag
-git tag t1 HEAD^^
-glt push u1 file:///foo/u1/u1r1 t1
-        POK; /\\[new tag\\]         t1 -> t1/
-
-# add u2 to WRITERS
-echo WRITERS \@g2 | glt perms u1 foo/u1/u1r1
-glt perms u1 foo/u1/u1r1 -l
-        /WRITERS \@g2/
-
-glt fetch u1
-git reset --hard origin/master
-
-# WRITERS can push
-tc j-185 j-186
-glt push u2 file:///foo/u1/u1r1 master:master
-        POK; /master -> master/
-# WRITERS can create branch
-tc u-420 u-421
-glt push u2 file:///foo/u1/u1r1 master:b2
-        POK; /master -> b2/
-# WRITERS cannot rewind branch
-git reset --hard HEAD^
-tc l-136 l-137
-glt push u2 file:///foo/u1/u1r1 +master:b2
-        /\\+ refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
-        reject
-# WRITERS cannot delete branch
-glt push u2 file:///foo/u1/u1r1 :b2
-        /D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
-        reject
-# WRITERS cannot push a tag
-git tag t2 HEAD^^
-glt push u2 file:///foo/u1/u1r1 t2
-        /W refs/tags/t2 foo/u1/u1r1 u2 DENIED by refs/tags//
-        reject
-
-# change u2 to READERS
-echo READERS u2 | glt perms u1 foo/u1/u1r1
-glt perms u1 foo/u1/u1r1 -l
-        /READERS u2/
-
-glt fetch u1
-git reset --hard origin/master
-
-# READERS cannot push at all
-tc v-753 v-754
-glt push u2 file:///foo/u1/u1r1 master:master
-        /W any foo/u1/u1r1 u2 DENIED by fallthru/
-
-# add invalid category MANAGERS
-    /usr/bin/printf 'READERS u6\\nMANAGERS u2\\n' | glt perms u1 foo/u1/u1r1
-        !ok
-        /Invalid role 'MANAGERS'/
-";
-
-# make MANAGERS valid
-put "$ENV{HOME}/g3trc", "\$rc{ROLES}{MANAGERS} = 1;\n";
-
-# add u2 to now valid MANAGERS
-try "
-    ENV G3T_RC=$ENV{HOME}/g3trc
-    gitolite compile;   ok or die compile failed
-    /usr/bin/printf 'READERS u6\\nMANAGERS u2\\n' | glt perms u1 foo/u1/u1r1
-                            ok;    !/Invalid role 'MANAGERS'/
-    glt perms u1 foo/u1/u1r1 -l
-";
-
-cmp 'READERS u6
-MANAGERS u2
-';
-
-try "
-glt fetch u1
-git reset --hard origin/master
-
-# MANAGERS can push
-tc d-714 d-715
-glt push u2 file:///foo/u1/u1r1 master:master
-        POK; /master -> master/
-
-# MANAGERS can create branch
-tc n-614 n-615
-glt push u2 file:///foo/u1/u1r1 master:b3
-        POK; /master -> b3/
-# MANAGERS can rewind branch
-git reset --hard HEAD^
-tc a-511 a-512
-glt push u2 file:///foo/u1/u1r1 +master:b3
-        POK; /master -> b3 \\(forced update\\)/
-# MANAGERS cannot delete branch
-glt push u2 file:///foo/u1/u1r1 :b3
-        / - \\[deleted\\]         b3/
-# MANAGERS can push a tag
-git tag t3 HEAD^^
-glt push u2 file:///foo/u1/u1r1 t3
-        POK; /\\[new tag\\]         t3 -> t3/
-
-# add invalid category TESTERS
-echo TESTERS u2 | glt perms u1 foo/u1/u1r1
-        !ok
-        /Invalid role 'TESTERS'/
-";
-
-# make TESTERS valid
-put "|cat >> $ENV{HOME}/g3trc", "\$rc{ROLES}{TESTERS} = 1;\n";
-
-try "
-gitolite compile;   ok or die compile failed
-# add u2 to now valid TESTERS
-echo TESTERS u2 | glt perms u1 foo/u1/u1r1
-        !/Invalid role 'TESTERS'/
-glt perms u1 foo/u1/u1r1 -l
-";
-
-cmp 'TESTERS u2
-';
-
-try "
-glt fetch u1
-git reset --hard origin/master
-
-# TESTERS cannot push
-tc d-134 d-135
-glt push u2 file:///foo/u1/u1r1 master:master
-        /W refs/heads/master foo/u1/u1r1 u2 DENIED by fallthru/
-        reject
-# TESTERS cannot create branch
-tc p-668 p-669
-glt push u2 file:///foo/u1/u1r1 master:b4
-        /W refs/heads/b4 foo/u1/u1r1 u2 DENIED by fallthru/
-        reject
-# TESTERS cannot delete branch
-glt push u2 file:///foo/u1/u1r1 :b2
-        /D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
-        reject
-# TESTERS can push a tag
-git tag t4 HEAD^^
-glt push u2 file:///foo/u1/u1r1 t4
-        POK; /\\[new tag\\]         t4 -> t4/
-";
-
-# make TESTERS invalid again
-put "$ENV{HOME}/g3trc", "\$rc{ROLES}{MANAGERS} = 1;\n";
-
-try "
-gitolite compile;   ok or die compile failed
-# CREATOR can push
-glt fetch u1
-git reset --hard origin/master
-tc y-626 y-627
-glt push u1 file:///foo/u1/u1r1 master:master
-        POK; /master -> master/
-# TESTERS is an invalid category
-git tag t5 HEAD^^
-glt push u2 file:///foo/u1/u1r1 t5
-        /role 'TESTERS' not allowed, ignoring/
-        /W any foo/u1/u1r1 u2 DENIED by fallthru/
-";
diff --git a/docker/gitolite/t/perms-groups.t b/docker/gitolite/t/perms-groups.t
deleted file mode 100755
index 5de75be..0000000
--- a/docker/gitolite/t/perms-groups.t
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# assigning roles to groups instead of users
-# ----------------------------------------------------------------------
-
-try "plan 31";
-
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-confreset; confadd '
-    @leads = u1 u2
-    @devs = u1 u2 u3 u4
-
-    @gbar = bar/CREATOR/..*
-    repo    @gbar
-        C               =   @leads
-        RW+             =   CREATOR
-        RW              =   WRITERS
-        R               =   READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-    # u1 auto-creates a repo
-    glt ls-remote u1 file:///bar/u1/try1
-        /Initialized empty Git repository in .*/bar/u1/try1.git//
-    # default permissions for u2 and u4
-    glt info u1 -lc
-        /R W *\tbar/u1/try1\tu1/
-    glt info u2 -lc
-        !/R W *\tbar/u1/try1\tu1/
-    glt info u4 -lc
-        !/R W *\tbar/u1/try1\tu1/
-
-    # \@leads can RW try1
-    echo WRITERS \@leads | glt perms u1 bar/u1/try1; ok
-    glt info u1 -lc
-        /R W *\tbar/u1/try1\tu1/
-    glt info u2 -lc
-        /R W *\tbar/u1/try1\tu1/
-    glt info u4 -lc
-        !/R W *\tbar/u1/try1\tu1/
-
-    # \@devs can R try1
-    echo READERS \@devs | glt perms u1 bar/u1/try1; ok
-    glt perms u1 bar/u1/try1 -l
-        /READERS \@devs/
-        !/WRITERS \@leads/
-
-    glt info u1 -lc
-        /R W *\tbar/u1/try1\tu1/
-
-    glt info u2 -lc
-        !/R W *\tbar/u1/try1\tu1/
-        /R *\tbar/u1/try1\tu1/
-
-    glt info u4 -lc
-        !/R W *\tbar/u1/try1\tu1/
-        /R *\tbar/u1/try1\tu1/
-
-# combo of previous 2
-    /usr/bin/printf 'READERS \@devs\\nWRITERS \@leads\\n' | glt perms u1 bar/u1/try1; ok
-    glt perms u1 bar/u1/try1 -l
-        /READERS \@devs/
-        /WRITERS \@leads/
-    glt info u1 -lc
-        /R W *\tbar/u1/try1\tu1/
-    glt info u2 -lc
-        /R W *\tbar/u1/try1\tu1/
-    glt info u4 -lc
-        !/R W *\tbar/u1/try1\tu1/
-        /R *\tbar/u1/try1\tu1/
-";
diff --git a/docker/gitolite/t/personal-branches.t b/docker/gitolite/t/personal-branches.t
deleted file mode 100755
index 8a08128..0000000
--- a/docker/gitolite/t/personal-branches.t
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# personal branches
-# ----------------------------------------------------------------------
-
-try "plan 64";
-
-confreset;confadd '
-    @admins     =   admin dev1
-    repo gitolite-admin
-        RW+     =   admin
-
-    repo testing
-        RW+     =   @all
-
-    @g1 = t1
-    repo @g1
-        R               =   u2
-        RW              =   u3
-        RW+             =   u4
-        RW  a/USER/     =   @all
-        RW+ p/USER/     =   u1 u6
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-
-    gitolite access t1 u1;                              ok;     /refs/heads/p/u1//; !/DENIED/
-    gitolite access t1 u5;                              !ok;    /\\+ any t1 u5 DENIED by fallthru/
-    gitolite access \@g1 u5 W;                          ok;     /refs/heads/a/u5//; !/DENIED/
-
-    gitolite access t1 u1 W refs/heads/a/user1/foo;     !ok;    /W refs/heads/a/user1/foo t1 u1 DENIED by fallthru/
-    gitolite access \@g1 u1 + refs/heads/a/user1/foo;   !ok;    /\\+ refs/heads/a/user1/foo \@g1 u1 DENIED by fallthru/
-    gitolite access t1 u1 W refs/heads/p/user1/foo;     !ok;    /W refs/heads/p/user1/foo t1 u1 DENIED by fallthru/
-    gitolite access \@g1 u1 + refs/heads/p/user1/foo;   !ok;    /\\+ refs/heads/p/user1/foo \@g1 u1 DENIED by fallthru/
-
-    gitolite access \@g1 u1 W refs/heads/a/u1/foo;      ok;     /refs/heads/a/u1//; !/DENIED/
-    gitolite access t1 u1 + refs/heads/a/u1/foo;        !ok;    /\\+ refs/heads/a/u1/foo t1 u1 DENIED by fallthru/
-    gitolite access \@g1 u1 W refs/heads/p/u1/foo;      ok;     /refs/heads/p/u1//; !/DENIED/
-    gitolite access t1 u1 + refs/heads/p/u1/foo;        ok;     /refs/heads/p/u1//; !/DENIED/
-
-    gitolite access \@g1 u1 W refs/heads/p/u2/foo;      !ok;    /W refs/heads/p/u2/foo \@g1 u1 DENIED by fallthru/
-    gitolite access t1 u1 + refs/heads/p/u2/foo;        !ok;    /\\+ refs/heads/p/u2/foo t1 u1 DENIED by fallthru/
-";
-
-confreset; confadd '
-    @staff = u1 u2 u3 u4 u5 u6
-    @gfoo = foo
-    repo  @gfoo
-          RW+                       = u1 u2
-          RW+   p/USER/             = u3 u4
-          RW    temp                = u5 u6
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    DEF OK  =   gitolite access foo %1 %2 refs/heads/%3;    ok
-    DEF NOK =   gitolite access foo %1 %2 refs/heads/%3;    !ok
-";
-
-try "
-
-# u1 and u2 can push
-    OK  u1  W   master
-    OK  u2  W   master
-    OK  u2  W   p/u1/foo
-    OK  u1  W   p/u2/foo
-    OK  u1  W   p/u3/foo
-
-# u3 cant push u1/u4 personal branches
-    NOK u3  W   p/u1/foo
-    NOK u3  W   p/u4/doo
-
-# u4 can push u4 personal branch
-    OK  u4  W   p/u4/foo
-# u5 push temp
-    OK  u5  W   temp
-
-# u1 and u2 can rewind
-    OK  u1  +   master
-    OK  u2  +   p/u1/foo
-    OK  u1  +   p/u2/foo
-    OK  u1  +   p/u3/foo
-
-# u3 cant rewind u1/u4 personal branches
-    NOK u3  +   p/u1/foo
-    NOK u3  +   p/u4/foo
-# u4 can rewind u4 personal branch
-    OK  u4  +   p/u4/foo
-# u5 cant rewind temp
-    NOK u5  +   temp
-";
diff --git a/docker/gitolite/t/refex-expr-test-1 b/docker/gitolite/t/refex-expr-test-1
deleted file mode 100755
index 1372a1e..0000000
--- a/docker/gitolite/t/refex-expr-test-1
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/bash
-
-# not part of the official test suite (yet); just some q&d testing
-
-# to be run from ~/gitolite as ./$0
-
-set -e
-exec 3>&2
-exec > /dev/null
-exec 2> /dev/null
-print2() { echo -n "$@" >&3; }
-say2() { echo "$@" >&3; }
-die() { echo FATAL: "$@" >&3; exit 1; }
-
-export od=$PWD
-export tmp=$(mktemp -d)
-echo $tmp >&3
-trap "rm -rf $tmp" 0
-cd $tmp
-
-print2 setting up...
-( cd $od; t/reset )
-echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
-cat <<EOF >> ~/.gitolite/conf/gitolite.conf
-
-repo r1
-    RW+                                 =   u1 u2   # line 1
-
-    RW+ master                          =   u3 u4   # line 2
-    RW+                                 =   u3 u4   # line 3
-    RW+ VREF/NAME/Makefile              =   u3 u4   # line 4
-    -   master and VREF/NAME/Makefile   =   u3 u4   # line 5
-
-EOF
-gitolite setup
-say2 done
-
-# ----------------------------------------------------------------------
-
-rm -rf u1
-git clone u1:r1 u1
-cd u1
-tsh 'tc f1'
-git push u1:r1 master
-tsh 'tc f2'
-git push u2:r1 master
-tsh 'tc f3'
-git push u3:r1 master
-tsh 'tc f4'
-git push u4:r1 master
-say2 everyone master no Makefile
-
-tsh 'tc f5 Makefile'
-git push u1:r1 master
-tsh 'tc f5 Makefile'
-git push u1:r1 master:m1
-say2 u1 Makefile master
-
-tsh 'tc f5 Makefile'
-git push u3:r1 master && die u3 r1 master should have failed
-git push u3:r1 master:m2
-say2 u3 Makefile master fail m2 pass
diff --git a/docker/gitolite/t/refex-expr-test-2 b/docker/gitolite/t/refex-expr-test-2
deleted file mode 100755
index 773e42c..0000000
--- a/docker/gitolite/t/refex-expr-test-2
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/bash
-
-# not part of the official test suite (yet); just some q&d testing
-
-# to be run from ~/gitolite as ./$0
-
-set -e
-exec 3>&2
-exec > /dev/null
-exec 2> /dev/null
-print2() { echo -n "$@" >&3; }
-say2() { echo "$@" >&3; }
-die() { echo FATAL: "$@" >&3; exit 1; }
-
-export od=$PWD
-export tmp=$(mktemp -d)
-echo $tmp >&3
-trap "rm -rf $tmp" 0
-cd $tmp
-
-print2 setting up...
-( cd $od; t/reset )
-echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
-cat <<EOF >> ~/.gitolite/conf/gitolite.conf
-
-    repo    r2
-        RW+                         =   @all
-
-        RW+ VREF/NAME/doc/                      =   u2
-        RW+ VREF/NAME/src/                      =   u2
-        -   VREF/NAME/doc/ and VREF/NAME/src/   =   u2
-
-EOF
-gitolite setup
-say2 done
-
-# ----------------------------------------------------------------------
-
-git clone u2:r2
-cd r2
-
-tsh 'tc aa'
-git push origin master
-say2 aa pass
-
-mkdir doc src
-
-tsh 'tc doc/d1'
-git push origin master
-say2 doc pass
-
-tsh 'tc src/s1'
-tsh 'tc src/s2'
-git push origin master
-say2 src src pass
-
-tsh 'tc doc/d2 src/s3'
-git push origin master && die 1
-git push u1:r2 master
-say2 doc src u2 fail u1 pass
diff --git a/docker/gitolite/t/refex-expr-test-3 b/docker/gitolite/t/refex-expr-test-3
deleted file mode 100755
index 47599eb..0000000
--- a/docker/gitolite/t/refex-expr-test-3
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/bash
-
-# not part of the official test suite (yet); just some q&d testing
-
-# to be run from ~/gitolite as ./$0
-
-set -e
-exec 3>&2
-exec > /dev/null
-exec 2> /dev/null
-print2() { echo -n "$@" >&3; }
-say2() { echo "$@" >&3; }
-die() { echo FATAL: "$@" >&3; exit 1; }
-
-export od=$PWD
-export tmp=$(mktemp -d)
-echo $tmp >&3
-trap "rm -rf $tmp" 0
-cd $tmp
-
-print2 setting up...
-( cd $od; t/reset )
-echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
-cat <<EOF >> ~/.gitolite/conf/gitolite.conf
-
-    repo    r3
-        RW+                         =   u1 u2 u3
-
-        RW+ VREF/NAME/conf/         =   u3
-        -   VREF/NAME/conf/ -gt 2   =   u3
-
-EOF
-gitolite setup
-say2 done
-
-# ----------------------------------------------------------------------
-
-git clone u3:r3
-cd r3
-
-tsh 'tc aa'
-git push origin master
-say2 aa pass
-
-mkdir doc conf
-
-tsh 'tc doc/d1 doc/d2 doc/d3 doc/d4 conf/c1'
-git push origin master
-say2 4 doc 1 conf pass
-
-tsh 'tc conf/c2 conf/c3 conf/c4'
-git push origin master && die 1
-
-git push u2:r3 master
-say2 3 conf u3 fail u2 pass
diff --git a/docker/gitolite/t/refex-expr-test-9 b/docker/gitolite/t/refex-expr-test-9
deleted file mode 100755
index b3a9f09..0000000
--- a/docker/gitolite/t/refex-expr-test-9
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-# not part of the official test suite (yet); just some q&d testing
-
-# to be run from ~/gitolite as ./$0
-
-set -e
-exec 3>&2
-exec > /dev/null
-exec 2> /dev/null
-print2() { echo -n "$@" >&3; }
-say2() { echo "$@" >&3; }
-die() { echo FATAL: "$@" >&3; exit 1; }
-
-export od=$PWD
-export tmp=$(mktemp -d)
-echo $tmp >&3
-trap "rm -rf $tmp" 0
-cd $tmp
-
-print2 setting up...
-( cd $od; t/reset )
-echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
-cat <<EOF >> ~/.gitolite/conf/gitolite.conf
-
-repo r9
-
-    RW+                         =   u3 u4
-
-    # u1 and u2 have some restrictions
-
-    # cant push master
-    -   master                  =   u1 u2
-    # cant push versioned tags, but other tags are fine
-    -   refs/tags/v[0-9]        =   u1 u2
-    # everything else is fine, but we need to recognise when they're pushing
-    # tags, so that the refex expr will have the correct info
-    RW+ refs/tags/              =   u1 u2
-    RW+                         =   u1 u2
-
-    # can push files in "foo/" only to a tag
-    RW+ VREF/NAME/foo/          =   u1 u2
-
-    RW+ VREF/NAME/foo/ and refs/tags/  =   u1 u2
-    -   VREF/NAME/foo/ and not refs/tags/ = u1 u2
-
-EOF
-gitolite setup
-say2 done
-
-# ----------------------------------------------------------------------
-
-# make sure u3 is not constrained in any way
-
-git clone u3:r9 refex-test.repo
-cd refex-test.repo
-
-tsh 'tc u3-f1'
-git pom
-
-mkdir bar foo
-tsh 'tc bar/thr'
-git pom
-git tag v3
-git push origin v3
-tsh 'tc foo/rht'
-git pom
-git tag 3v
-git push origin 3v
-
-say2 u3 no limits
-
-# now test u1's constraints
-
-cd ..
-rm -rf refex-test.repo
-
-rm -rf ~/repositories/r9.git
-gitolite setup
-
-git clone u1:r9 refex-test.repo
-cd refex-test.repo
-
-tsh 'tc u1-f1'
-# cant push master
-git pom && die 1
-# can push other branches
-git push origin master:m1
-say2 master fail m1 pass
-
-mkdir bar foo
-tsh 'tc bar/one'
-git push origin master:m1
-git tag v1
-# cant push v-tag
-git push origin v1 && die 2
-say2 v-tag fail
-
-# cant push foo/ to a branch
-tsh 'tc foo/eno'
-git push origin master:m1 && die 3
-say2 foo/ m1 fail
-
-# but can push to a non-v-tag
-git tag 1v
-git push origin 1v
-say2 foo/ non-v-tag pass
diff --git a/docker/gitolite/t/repo-specific-hooks.t b/docker/gitolite/t/repo-specific-hooks.t
deleted file mode 100755
index 88976ca..0000000
--- a/docker/gitolite/t/repo-specific-hooks.t
+++ /dev/null
@@ -1,210 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# test script for partial copy feature
-# ----------------------------------------------------------------------
-
-try "plan 117";
-my $h = $ENV{HOME};
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-
-try 'cd tsh_tempdir; mkdir -p local/hooks/repo-specific';
-
-foreach my $h (qw/first second/) {
-    put "local/hooks/repo-specific/$h", "#!/bin/sh
-echo \$0
-if [ \$# -ne 0 ]; then
-    echo \$0 has args: \$@
-else
-    echo \$0 has stdin: `cat`
-fi
-";
-}
-try 'chmod +x local/hooks/repo-specific/*';
-
-try 'pwd';
-my $tempdir = join("\n", sort (lines()));
-try 'cd gitolite-admin';
-
-try "# Enable LOCAL_CODE and repo-specific-hooks
-    cat $h/.gitolite.rc
-    perl s/# 'repo-specific-hooks'/'repo-specific-hooks'/
-    perl s%# LOCAL_CODE%LOCAL_CODE => '$tempdir/local', #%
-    put $h/.gitolite.rc
-";
-
-confreset;confadd '
-    repo foo
-            RW+                 =   @all
-
-    repo bar
-            RW+                 =   @all
-
-    repo baz
-            RW+                 =   @all
-';
-
-try "ADMIN_PUSH repo-specific-hooks-0; !/FATAL/" or die text();
-
-try "
-    /Init.*empty.*foo\\.git/
-    /Init.*empty.*bar\\.git/
-    /Init.*empty.*baz\\.git/
-";
-
-my $failing_hook = "#!/bin/sh
-exit 1
-";
-
-# Place a existing hooks in repos
-put "$rb/foo.git/hooks/post-recieve", $failing_hook;
-put "$rb/bar.git/hooks/pre-recieve", $failing_hook;
-put "$rb/baz.git/hooks/post-update", $failing_hook;
-
-try "# Verify hooks
-    ls -l $rb/foo.git/hooks/*;  ok;     !/post-receive -. .*local/hooks/multi-hook-driver/
-    ls -l $rb/bar.git/hooks/*;  ok;     !/pre-receive -. .*local/hooks/multi-hook-driver/
-    ls -l $rb/baz.git/hooks/*;  ok;     !/post-update -. .*local/hooks/multi-hook-driver/
-";
-
-confreset;confadd '
-    repo foo
-            RW+                 =   @all
-            option hook.post-receive =  first
-
-    repo bar
-            RW+                 =   @all
-            option hook.pre-receive =  first second
-
-    repo baz
-            RW+                 =   @all
-            option hook.post-receive =  first
-            option hook.post-update =  first second
-';
-
-
-try "ADMIN_PUSH repo-specific-hooks-1; !/FATAL/" or die text();
-
-try "# Verify hooks
-    ls -l $rb/foo.git/hooks/*;  ok;     /post-receive.h00-first/
-                                       !/post-receive.h01/
-                                        /post-receive -. .*local/hooks/multi-hook-driver/
-    ls -l $rb/bar.git/hooks/*;  ok;     /pre-receive.h00-first/
-                                        /pre-receive.h01-second/
-                                        /pre-receive -. .*local/hooks/multi-hook-driver/
-    ls -l $rb/baz.git/hooks/*;  ok;     /post-receive.h00-first/
-                                        /post-update.h00-first/
-                                        /post-update.h01-second/
-                                        /post-update -. .*local/hooks/multi-hook-driver/
-";
-
-try "
-    cd ..
-
-    # Single hook still works
-    [ -d foo ];            !ok;
-    CLONE admin foo;        ok; /empty/; /cloned/
-    cd foo
-    tc a1;                  ok; /ee47f8b/
-    PUSH admin master;      ok; /new.*master -. master/
-                                /hooks/post-receive.h00-first/
-                                !/post-receive.*has args:/
-                                /post-receive.h00-first has stdin: 0000000000000000000000000000000000000000 ee47f8b6be2160ad1a3f69c97a0cb3d488e6657e refs/heads/master/
-
-    cd ..
-
-    # Multiple hooks fired
-    [ -d bar ];            !ok;
-    CLONE admin bar;        ok; /empty/; /cloned/
-    cd bar
-    tc a2;                  ok; /cfc8561/
-    PUSH admin master;      ok; /new.*master -. master/
-                                /hooks/pre-receive.h00-first/
-                                !/hooks/pre-recieve.*has args:/
-                                /hooks/pre-receive.h00-first has stdin: 0000000000000000000000000000000000000000 cfc8561c7827a8b94df6c5dad156383d4cb210f5 refs/heads/master/
-                                /hooks/pre-receive.h01-second/
-                                !/hooks/pre-receive.h01.*has args:/
-                                /hooks/pre-receive.h01-second has stdin: 0000000000000000000000000000000000000000 cfc8561c7827a8b94df6c5dad156383d4cb210f5 refs/heads/master/
-
-    cd ..
-
-    # Post-update has stdin instead of arguments
-    [ -d baz ];            !ok;
-    CLONE admin baz;        ok; /empty/; /cloned/
-    cd baz
-    tc a3;                  ok; /2863617/
-    PUSH admin master;      ok; /new.*master -. master/
-                                /hooks/post-receive.h00-first/
-                                !/hooks/post-receive.h00.*has args:/
-                                /hooks/post-receive.h00-first has stdin: 0000000000000000000000000000000000000000 28636171ae703f42fb17c312c6b6a078ed07a2cd refs/heads/master/
-                                /hooks/post-update.h00-first/
-                                /hooks/post-update.h00-first has args: refs/heads/master/
-                                !/hooks/post-update.h00.*has stdin:/
-                                /hooks/post-update.h01-second/
-                                /hooks/post-update.h01-second has args: refs/heads/master/
-                                !/hooks/post-update.h01.*has stdin:/
-";
-
-# Verify hooks are removed properly
-
-confreset;confadd '
-    repo foo
-            RW+                 =   @all
-            option hook.post-receive =
-
-    repo bar
-            RW+                 =   @all
-            option hook.pre-receive =  second
-
-    repo baz
-            RW+                 =   @all
-            option hook.post-receive =
-            option hook.post-update =  second
-';
-
-try "ADMIN_PUSH repo-specific-hooks-02; !/FATAL/" or die text();
-
-try "
-    ls $rb/foo.git/hooks/*;  ok;    !/post-receive/
-    ls $rb/bar.git/hooks/*;  ok;    !/pre-receive.*first/
-                                     /pre-receive.h00-second/
-    ls $rb/baz.git/hooks/*;  ok;    !/post-receive/
-                                    !/post-update.*first/
-                                     /post-update.h00-second/
-";
-
-try "
-    cd ..
-
-    # Foo has no hooks
-    cd foo
-    tc b1;                  ok; /7ef69de/
-    PUSH admin master;      ok; /master -. master/
-                                !/hooks/post-receive/
-
-    cd ..
-
-    # Bar only has the second hook
-    cd bar
-    tc b2;                  ok; /cc7808f/
-    PUSH admin master;      ok; /master -. master/
-                                /hooks/pre-receive.h00-second/
-                                !/hooks/pre-receive.*has args:/
-                                /hooks/pre-receive.h00-second has stdin: 0000000000000000000000000000000000000000 cc7808f77c7c7d705f82dc54dc3152146175768f refs/heads/master/
-
-    cd ..
-
-    # Baz has no post-receive and keeps the second hook for post-update
-    cd baz
-    tc b3;                  ok; /8d20101/
-    PUSH admin master;      ok; /master -. master/
-                                !/hooks/post-receive.*/
-                                /hooks/post-update.h00-second/
-                                /hooks/post-update.h00-second has args: refs/heads/master/
-                                !/hooks/post-update.*has stdin/
-";
diff --git a/docker/gitolite/t/reset b/docker/gitolite/t/reset
deleted file mode 100755
index 502de2b..0000000
--- a/docker/gitolite/t/reset
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-BEGIN {
-    unlink "$ENV{HOME}/.ssh/authorized_keys";
-}
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-use Cwd;
-my $workdir = getcwd();
-
-confreset;confadd '
-repo foo/..*
-    C   =   u1 u2 u3
-    RW+ =   CREATOR
-    RW  =   WRITERS
-    R   =   READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    mkdir -p keydir
-    cp \$HOME/.ssh/u*.pub keydir
-    cp \$HOME/.ssh/admin.pub keydir
-    git add keydir
-    git commit -m 6k
-    glt push admin origin
-";
diff --git a/docker/gitolite/t/rule-seq.t b/docker/gitolite/t/rule-seq.t
deleted file mode 100755
index 0d97558..0000000
--- a/docker/gitolite/t/rule-seq.t
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# rule sequence
-# ----------------------------------------------------------------------
-
-# this is the specific example in commit 32056e0 of g2
-
-try "plan 27";
-
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-confreset; confadd '
-    @private-owners = u1 u2
-    @experienced-private-owners = u3 u4
-
-    repo CREATOR/.*
-      C   = @private-owners @experienced-private-owners
-      RWD = CREATOR
-      RW  = WRITERS
-      R   = READERS
-      -   = @private-owners
-      RW+D = CREATOR
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-    glt clone u1 file:///u1/r1
-        /Initialized empty Git repository in .*/u1/r1.git//
-    cd r1
-    tc h-395
-    glt push u1 origin master
-    git checkout -b br1
-    tc m-367
-    tc i-747
-
-    # u1 create branch
-    glt push u1 origin br1
-        /\\* \\[new branch\\]      br1 -> br1/
-        POK; /br1 -> br1/
-
-    # u1 rewind branch
-    git reset --hard HEAD^
-    tc e-633
-    glt push u1 origin +br1
-        /\\+ refs/heads/br1 u1/r1 u1 DENIED by refs//
-        /error: hook declined to update refs/heads/br1/
-        reject
-
-    # u1 delete branch
-    glt push u1 origin :br1
-        /\\[deleted\\]         br1/
-
-    cd ..
-    rm -rf r1
-    glt clone u3 file:///u3/r1
-        /Initialized empty Git repository in .*/u3/r1.git//
-    cd r1
-    tc p-274
-    glt push u3 origin master
-    git checkout -b br1
-    tc s-613
-    tc k-988
-
-    # u3 create branch
-    glt push u3 origin br1
-        /\\* \\[new branch\\]      br1 -> br1/
-        POK; /br1 -> br1/
-
-    # u3 rewind branch
-    git reset --hard HEAD^
-    tc n-919
-    glt push u3 origin +br1
-        /To file:///u3/r1/
-        /\\+ .......\\.\\.\\........ br1 -> br1 \\(forced update\\)/
-
-    # u3 delete branch
-    glt push u3 origin :br1
-        /\\[deleted\\]         br1/
-";
diff --git a/docker/gitolite/t/sequence.t b/docker/gitolite/t/sequence.t
deleted file mode 100755
index 81fabfc..0000000
--- a/docker/gitolite/t/sequence.t
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# uhh, seems to be another rule sequence test
-# ----------------------------------------------------------------------
-
-try "plan 48";
-
-confreset;confadd '
-    @staff = u1 u2 u3
-    @gfoo = foo/CREATOR/..*
-    repo  @gfoo
-          C       = u1
-          RW+     = CREATOR
-          RW      = WRITERS
-          -       = @staff
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-    glt clone u1 file:///foo/u1/bar;    ok
-        /Initialized empty Git repository in .*/foo/u1/bar.git//
-
-    cd bar
-    tc p-906
-    glt push u1 origin master
-        /To file:///foo/u1/bar/
-        /\\[new branch\\]      master -> master/
-    echo WRITERS u2 | glt perms u1 foo/u1/bar
-    glt perms u1 foo/u1/bar -l
-        /WRITERS u2/
-    # expand
-    glt info u2
-        /R W *\tfoo/u1/bar/
-        /R W *\ttesting/
-
-    # push
-    cd ..
-    glt clone u2 file:///foo/u1/bar u2bar
-        /Cloning into 'u2bar'.../
-    cd u2bar
-    tc p-222
-    glt push u2
-        /master -> master/
-        !/DENIED/
-        !/failed to push/
-";
-
-confreset;confadd '
-    @staff = u1 u2 u3
-    @gfoo = foo/CREATOR/..*
-    repo  @gfoo
-          C       = u1
-          RW+     = CREATOR
-          -       = @staff
-          RW      = WRITERS
-          R       = READERS
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    cd ..
-    rm -rf bar u2bar
-    glt clone u1 file:///foo/u1/bar;    ok
-        /Initialized empty Git repository in .*/foo/u1/bar.git//
-
-    cd bar
-    tc p-906
-    glt push u1 origin master
-        /To file:///foo/u1/bar/
-        /\\[new branch\\]      master -> master/
-    echo WRITERS u2 | glt perms u1 foo/u1/bar
-    glt perms u1 foo/u1/bar -l
-        /WRITERS u2/
-    # expand
-    glt info u2
-        !/R W *\tfoo/u1/baz/
-        /R W *\tfoo/u1/bar/
-        /R W *\ttesting/
-
-    # push
-    cd ..
-    glt clone u2 file:///foo/u1/bar u2bar
-        /Cloning into 'u2bar'.../
-    cd u2bar
-    tc p-222
-    glt push u2
-        !ok
-        reject
-        /W refs/heads/master foo/u1/bar u2 DENIED by refs/\\.\\*/
-
-    # auto-create using perms fail
-    echo READERS u5 | glt perms u4 -c foo/u4/baz
-        !/Initialized empty Git repository in .*/foo/u4/baz.git/
-        /FATAL: repo does not exist, or you are not authorised/
-
-    # auto-create using perms
-    echo READERS u2 | glt perms u1 -c foo/u1/baz
-        /Initialized empty Git repository in .*/foo/u1/baz.git/
-
-    glt perms u1 foo/u1/baz -l
-        /READERS u2/
-    # expand
-    glt info u2
-        /R   *\tfoo/u1/baz/
-        /R W *\tfoo/u1/bar/
-        /R W *\ttesting/
-";
diff --git a/docker/gitolite/t/smart-http b/docker/gitolite/t/smart-http
deleted file mode 100755
index 98fc8c0..0000000
--- a/docker/gitolite/t/smart-http
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/bash
-
-die() { echo "$@"; exit 1; }
-
-# git clone `url u1 r1`
-url() {
-    echo http://$1:$1@localhost/git/$2.git
-}
-
-# `cmd sitaram info`
-cmd() {
-    c="curl http://$1:$1@localhost/git"
-    shift
-    c="$c/$1"
-    shift
-
-    if [ -n "$1" ]
-    then
-        c="$c?$1"
-        shift
-    fi
-    while [ -n "$1" ]
-    do
-        c="$c+$1"
-        shift
-    done
-
-    echo $c
-}
-
-export tmp=$(mktemp -d);
-trap "rm -rf $tmp" 0;
-cd $tmp
-
-tsh "plan 28"
-
-tsh "
-    ## ls-remote admin admin
-    git ls-remote `url admin gitolite-admin`
-        ok
-        /HEAD/
-        /refs.heads.master/
-    ## clone
-    git clone `url admin gitolite-admin`
-        ok
-        /Cloning into/
-    ls -al gitolite-admin/conf
-        /gitolite.conf/
-" || die "step 1"
-
-cd gitolite-admin
-echo repo t2 >> conf/gitolite.conf
-echo 'RW+  = u1 u2' >> conf/gitolite.conf
-
-tsh "
-    ## add, commit, push
-    git add conf/gitolite.conf
-        ok
-        !/./
-    git commit -m t2
-        ok
-        /1 file.*changed/
-    git push
-        ok
-        /Initialized.*usr.share.httpd.gitolite-home.repositories.t2.git/
-        /To http:..admin:admin.localhost.git.gitolite-admin.git/
-        /master -. master/
-    ## various ls-remotes
-    git ls-remote `url u1 gitolite-admin`
-        !ok
-        /FATAL: R any gitolite-admin u1 DENIED by fallthru/
-    git ls-remote `url u1 t2`
-        ok
-        !/./
-    git ls-remote `url u2 t2`
-        ok
-        !/./
-    git ls-remote `url u3 t2`
-        !ok
-        /FATAL: R any t2 u3 DENIED by fallthru/
-    ## push to u1:t2
-    git push      `url u1 t2` master:master
-        ok
-        /To http:..u1:u1.localhost.git.t2.git/
-        /master -. master/
-    git ls-remote `url u2 t2`
-        ok
-        /HEAD/
-        /refs.heads.master/
-" || die "step 2"
diff --git a/docker/gitolite/t/smart-http.root-setup b/docker/gitolite/t/smart-http.root-setup
deleted file mode 100755
index 7a46dda..0000000
--- a/docker/gitolite/t/smart-http.root-setup
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/bin/bash
-
-# ----------------------------------------------------------------------
-# please do not even LOOK at this file without reading doc/http.mkd
-# ----------------------------------------------------------------------
-
-die() { echo "$@"; exit 1; }
-
-# scare the sh*t out of people who run it blindly
-[ -f /tmp/gitolite-smart-http-test-OK ] || {
-    # scary message
-    echo '+ rm -rf /'
-    # lots of disk activity
-    find / >/dev/null 2>/dev/null
-    # and it he's still clueless, God bless!
-    echo 'root file system erased successfully.  Goodbye and God bless!'
-    exit 1
-}
-
-# ----------------------------------------------------------------------
-# are we *BSD or Linux?
-uname_s=`uname -s`  # could be Linux or FreeBSD or some other BSD
-if [ "$uname_s" = "Linux" ]
-then
-    bsd=:
-else
-    lnx=:
-fi
-
-# ----------------------------------------------------------------------
-# main
-
-[ $EUID = 0 ] || die "you must run this as root"
-
-# delete any existing apache conf for gitolite
-$lnx rm /etc/httpd/conf.d/gitolite.conf
-$bsd rm /usr/local/etc/apache24/Includes/gitolite.conf
-
-# build your "home within a home"
-$lnx cd ~apache
-$bsd rm -rf /tmp/usr.share.httpd
-$bsd mkdir -p /tmp/usr.share.httpd
-$bsd chown www:www /tmp/usr.share.httpd
-$bsd cd /tmp/usr.share.httpd
-
-rm -rf gitolite-home
-mkdir gitolite-home
-export GITOLITE_HTTP_HOME=$PWD/gitolite-home
-
-# get the gitolite sources
-cd gitolite-home
-git clone /tmp/gitolite.git gitolite-source
-# NOTE: I use a bare repo in /tmp for convenience; you'd use
-# 'git://github.com/sitaramc/gitolite'
-
-# make the bin directory, and add it to PATH
-cd gitolite-source
-mkdir         $GITOLITE_HTTP_HOME/bin
-./install -ln $GITOLITE_HTTP_HOME/bin
-export PATH=$PATH:$GITOLITE_HTTP_HOME/bin
-
-# come back to base, then run setup.  Notice that you have to point HOME to
-# the right place, even if it is just for this command
-cd $GITOLITE_HTTP_HOME
-HOME=$GITOLITE_HTTP_HOME gitolite setup -a admin
-
-# insert some essential lines at the beginning of the rc file
-echo '$ENV{PATH} .= ":$ENV{GITOLITE_HTTP_HOME}/bin";'  >> 1
-echo >> 1
-cat .gitolite.rc >> 1
-\mv 1 .gitolite.rc
-
-# fix up ownership
-$lnx chown -R apache:apache $GITOLITE_HTTP_HOME
-$bsd chown -R www:www $GITOLITE_HTTP_HOME
-
-# create the apache config.  Note the trailing slashes on the 2 ScriptAlias
-# lines.  (The second one is optional for most sites).  NOTE: you also need to
-# give the AuthUserFile a better name/location than what I have below.
-cat <<EOF1 > 1
-SetEnv GIT_PROJECT_ROOT $GITOLITE_HTTP_HOME/repositories
-ScriptAlias /git/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
-ScriptAlias /gitmob/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
-SetEnv GITOLITE_HTTP_HOME $GITOLITE_HTTP_HOME
-SetEnv GIT_HTTP_EXPORT_ALL
-
-<Location /git>
-    AuthType Basic
-    AuthName "Private Git Access"
-    Require valid-user
-    AuthUserFile $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
-</Location>
-EOF1
-$lnx mv 1 /etc/httpd/conf.d/gitolite.conf
-$bsd mv 1 /usr/local/etc/apache24/Includes/gitolite.conf
-
-# NOTE: this is for testing only
-htpasswd -bc $GITOLITE_HTTP_HOME/gitolite-http-authuserfile admin admin
-map "htpasswd -b $GITOLITE_HTTP_HOME/gitolite-http-authuserfile % %" u{1..6}
-$lnx chown apache:apache $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
-$bsd chown www:www $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
-
-# restart httpd to make it pick up all the new stuff
-$lnx service httpd restart
-$bsd /usr/local/etc/rc.d/apache24 restart
diff --git a/docker/gitolite/t/ssh-authkeys.t b/docker/gitolite/t/ssh-authkeys.t
deleted file mode 100755
index 46b9413..0000000
--- a/docker/gitolite/t/ssh-authkeys.t
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# testing the (separate) authkeys handler
-# ----------------------------------------------------------------------
-
-$ENV{GL_BINDIR} = "$ENV{PWD}/src";
-
-my $ak = "$ENV{HOME}/.ssh/authorized_keys";
-mkdir("$ENV{HOME}/.ssh", 0700) if not -d "$ENV{HOME}/.ssh";
-my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir";
-
-try "plan 49";
-
-my $pgm = "gitolite ../triggers/post-compile/ssh-authkeys";
-
-try "
-    # prep
-    rm -rf $ak;                 ok
-
-    $pgm;                       ok
-    mkdir $kd;                  ok
-    cd $kd;                     ok
-    $pgm;                       ok;     /authorized_keys missing/
-                                        /creating/
-    wc < $ak;                   ok;     /2 *6 *32/
-    # some gl keys
-    ssh-keygen -N '' -q -f alice -C alice
-    ssh-keygen -N '' -q -f bob   -C bob
-    ssh-keygen -N '' -q -f carol -C carol
-    ssh-keygen -N '' -q -f dave  -C dave
-    ssh-keygen -N '' -q -f eve   -C eve
-    rm alice bob carol dave eve
-    ls -a;                      ok;     /alice.pub/; /bob.pub/; /carol.pub/; /dave.pub/; /eve.pub/
-    $pgm;                       ok;
-    wc    < $ak;                ok;     /^ *7 .*/;
-    grep gitolite $ak;          ok;     /start/
-                                        /end/
-
-    # some normal keys
-    mv alice.pub $ak;           ok
-    cat carol.pub >> $ak;       ok
-    $pgm;                       ok;     /carol.pub duplicates.*non-gitolite key/
-    wc < $ak;                   ok;     /^ *8 .*/;
-
-    # moving normal keys up
-    mv dave.pub dave
-    $pgm;                       ok
-    cat dave >> $ak;            ok
-    grep -n dave $ak;           ok;     /8:ssh-rsa/
-    mv dave dave.pub
-    $pgm;                       ok;     /carol.pub duplicates.*non-gitolite key/
-                                         /dave.pub duplicates.*non-gitolite key/
-    grep -n dave $ak;           ok;     /3:ssh-rsa/
-
-    # a bad key
-    ls -al > bad.pub
-    $pgm;                       !ok;    /fingerprinting failed for \\'keydir/bad.pub\\'/
-    wc < $ak;                   ok;     /^ *9 .*/;
-    # a good key doesn't get added
-    ssh-keygen -N '' -q -f good
-    $pgm;                       !ok;    /fingerprinting failed for \\'keydir/bad.pub\\'/
-    wc < $ak;                   ok;     /^ *9 .*/;
-    # till the bad key is removed
-    rm bad.pub
-    $pgm;                       ok;
-    wc < $ak;                   ok;     /^ *10 .*/;
-
-    # duplicate gl key
-    cp bob.pub robert.pub
-    $pgm;                       ok;     /robert.pub duplicates.*bob.pub/
-";
diff --git a/docker/gitolite/t/ssh-basic.t b/docker/gitolite/t/ssh-basic.t
deleted file mode 100755
index ebed2d2..0000000
--- a/docker/gitolite/t/ssh-basic.t
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Common;
-use Gitolite::Test;
-
-# basic tests using ssh
-# ----------------------------------------------------------------------
-
-my $bd = `gitolite query-rc -n GL_BINDIR`;
-my $h  = $ENV{HOME};
-my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
-umask 0077;
-
-try "
-    plan 26
-
-    # reset stuff
-    rm -f $h/.ssh/authorized_keys;          ok or die 1
-
-    cp $bd/../t/keys/u[1-6]* $h/.ssh;       ok or die 2
-    cp $bd/../t/keys/admin*  $h/.ssh;       ok or die 3
-    cp $bd/../t/keys/config  $h/.ssh;       ok or die 4
-        cat $h/.ssh/config
-        perl s/%USER/$ENV{USER}/
-        put $h/.ssh/config
-
-    mkdir                  $ab/keydir;      ok or die 5
-    cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6
-";
-
-system("gitolite ../triggers/post-compile/ssh-authkeys");
-
-# basic tests
-# ----------------------------------------------------------------------
-
-confreset; confadd '
-    @g1 = u1
-    @g2 = u2
-    repo foo
-        RW = @g1 u3
-        R  = @g2 u4
-';
-
-try "ADMIN_PUSH set3; !/FATAL/" or die text();
-
-try "
-    ssh u1 info;                ok;     /R W\tfoo/
-    ssh u2 info;                ok;     /R  \tfoo/
-    ssh u3 info;                ok;     /R W\tfoo/
-    ssh u4 info;                ok;     /R  \tfoo/
-    ssh u5 info;                ok;     !/foo/
-    ssh u6 info;                ok;     !/foo/
-"
diff --git a/docker/gitolite/t/vrefs-1.t b/docker/gitolite/t/vrefs-1.t
deleted file mode 100755
index eea4b24..0000000
--- a/docker/gitolite/t/vrefs-1.t
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# VREFs - part 1
-# ----------------------------------------------------------------------
-
-try "plan 88";
-
-put "conf/gitolite.conf", "
-    repo gitolite-admin
-        RW+     =   admin
-
-    \@gfoo = foo
-    \@lead = u1
-    \@dev2 = u2
-    \@dev4 = u4
-    \@devs = \@dev2 \@dev4 u6
-    repo  \@gfoo
-          RW+                   =   \@lead \@devs
-          # intentional mis-spelling
-          -     VREF/MISCOUNT/2    =   \@dev2
-          -     VREF/MISCOUNT/4    =   \@dev4
-          -     VREF/MISCOUNT/3/NEWFILES   =   u6
-          -     VREF/MISCOUNT/6            =   u6
-";
-
-try "
-    ADMIN_PUSH vr1a
-    cd ..
-    [ -d foo ];                 !ok
-    CLONE u1 foo;               ok;     /Cloning into/
-                                        /You appear to have cloned an empty/
-    cd foo;                     ok
-    [ -d .git ];                ok
-
-    # VREF not called for u1
-    tc a1 a2 a3 a4 a5;          ok;     /aaf9e8e/
-    PUSH u1 master;             ok;     /new branch.*master -. master/
-                                        !/helper program missing/
-                                        !/hook declined/
-                                        !/remote rejected/
-    # VREF is called for u2
-    tc b1;                      ok;     /1f440d3/
-    PUSH u2;                    !ok;    /helper program missing/
-                                        /hook declined/
-                                        /remote rejected/
-";
-
-put "../gitolite-admin/conf/gitolite.conf", "
-    repo gitolite-admin
-        RW+     =   admin
-
-    \@gfoo = foo
-    \@lead = u1
-    \@dev2 = u2
-    \@dev4 = u4
-    \@devs = \@dev2 \@dev4 u6
-    repo  \@gfoo
-          RW+                   =   \@lead \@devs
-          -     VREF/COUNT/2    =   \@dev2
-          -     VREF/COUNT/4    =   \@dev4
-          -     VREF/COUNT/3/NEWFILES   =   u6
-          -     VREF/COUNT/6            =   u6
-";
-
-try "
-    ADMIN_PUSH vr1b
-    cd ../foo;                  ok
-
-    # u2 1 file
-    PUSH u2;                    ok;     /aaf9e8e..1f440d3.*master -. master/
-
-    # u2 2 files
-    tc b2 b3;                   ok;     /c3397f7/
-    PUSH u2;                    ok;     /1f440d3..c3397f7.*master -. master/
-
-    # u2 3 files
-    tc c1 c2 c3;                ok;     /be242d7/
-    PUSH u2;                    !ok;    /W VREF/COUNT/2 foo u2 DENIED by VREF/COUNT/2/
-                                        /too many changed files in this push/
-                                        /hook declined/
-                                        /remote rejected/
-
-    # u4 3 files
-    PUSH u4;                    ok;     /c3397f7..be242d7.*master -. master/
-
-    # u4 4 files
-    tc d1 d2 d3 d4;             ok;     /88d80e2/
-    PUSH u4;                    ok;     /be242d7..88d80e2.*master -. master/
-
-    # u4 5 files
-    tc d5 d6 d7 d8 d9;          ok;     /e9c60b0/
-    PUSH u4;                    !ok;    /W VREF/COUNT/4 foo u4 DENIED by VREF/COUNT/4/
-                                        /too many changed files in this push/
-                                        /hook declined/
-                                        /remote rejected/
-
-    # u1 all files
-    PUSH u1;                    ok;     /88d80e2..e9c60b0.*master -. master/
-
-    # u6 6 old files
-    test-tick
-    tc d1 d2 d3 d4 d5 d6
-                                ok;     /2773f0a/
-    PUSH u6;                    ok;     /e9c60b0..2773f0a.*master -. master/
-    tag six
-
-    # u6 updates 7 old files
-    test-tick; test-tick
-    tc d1 d2 d3 d4 d5 d6 d7
-                                ok;     /d3fb574/
-    PUSH u6;                    !ok;    /W VREF/COUNT/6 foo u6 DENIED by VREF/COUNT/6/
-                                        /too many changed files in this push/
-                                        /hook declined/
-                                        /remote rejected/
-    reset-h six;                ok;     /HEAD is now at 2773f0a/
-
-    # u6 4 new 2 old files
-    test-tick; test-tick
-    tc d1 d2 n1 n2 n3 n4
-                                ok;     /9e90848/
-    PUSH u6;                    !ok;    /W VREF/COUNT/3/NEWFILES foo u6 DENIED by VREF/COUNT/3/NEWFILES/
-                                        /too many new files in this push/
-                                        /hook declined/
-                                        /remote rejected/
-    reset-h six;                ok;     /HEAD is now at 2773f0a/
-
-    # u6 3 new 3 old files
-    test-tick; test-tick
-    tc d1 d2 d3 n1 n2 n3
-                                ok;     /e47ff5d/
-    PUSH u6;                    ok;     /2773f0a..e47ff5d.*master -. master/
-";
diff --git a/docker/gitolite/t/vrefs-2.t b/docker/gitolite/t/vrefs-2.t
deleted file mode 100755
index 40db308..0000000
--- a/docker/gitolite/t/vrefs-2.t
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# VREFs - part 2
-# ----------------------------------------------------------------------
-
-try "plan 72";
-
-put "../gitolite-admin/conf/gitolite.conf", "
-    \@gfoo = foo
-    \@lead = u1
-    \@senior_devs = u2 u3
-    \@junior_devs = u4 u5 u6
-    repo  \@gfoo
-
-        RW+                                 =   \@all
-
-        RW+ VREF/COUNT/2/NO_SIGNOFF         =   \@lead
-        -   VREF/COUNT/2/NO_SIGNOFF         =   \@all
-
-        -   VREF/COUNT/10/NEWFILES          =   \@junior_devs
-
-        -   VREF/FILETYPE/AUTOGENERATED     =   \@all
-";
-
-try "
-    ADMIN_PUSH vr2a
-    cd ..
-    # setup
-    [ -d foo ];                 !ok
-    CLONE u1 foo;               ok;     /Cloning into/
-                                        /You appear to have cloned an empty/
-    cd foo;                     ok
-    [ -d .git ];                ok
-
-    # u1 push 15 new files
-    tc a b c d e f g h i j k l m n o
-                                ok;     /d8c0392/
-    PUSH u1 master;             ok;     /new branch.*master -. master/
-
-    # u2 push 2 new 10 old without signoff
-    tc a b c d e f g h i j u2a u2b
-                                ok;     /6787ac9/
-    PUSH u2;                    ok;     /d8c0392..6787ac9.*master -. master/
-
-    # u2 fail to push 3 new files without signoff
-    tc u2c u2d u2e;             ok;     /a74562b/
-    PUSH u2;                    !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u2 DENIED by VREF/COUNT/2/NO_SIGNOFF/
-                                        /top commit message should include the text .3 new files signed-off by: tester.example.com./
-                                        /hook declined/
-                                        /remote rejected/
-    # u2 push 15 new files with signoff
-    tc u2f u2g u2h u2i u2j u2k u2l u2m u2n u2o u2p u2q
-                                ok;     /8dd31aa/
-    git commit --allow-empty -m '15 new files signed-off by: tester\@example.com'
-                                ok;     /.master 6126489. 15 new files signed-off by: tester.example.com/
-    PUSH u2;                    ok;     /6787ac9..6126489.*master -. master/
-
-    # u4 push 2 new 10 old files without signoff
-    tc u4a u4b a b c d e f g h i j
-                                ok;     /76c5593/
-    PUSH u4;                    ok;     /6126489..76c5593.*master -. master/
-
-    # u4 fail push 3 new files withoug signoff
-    tc u4c u4d u4e;             ok;     /2a84398/
-    PUSH u4;                    !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u4 DENIED by VREF/COUNT/2/NO_SIGNOFF/
-                                        /top commit message should include the text .3 new files signed-off by: tester.example.com./
-                                        /hook declined/
-                                        /remote rejected/
-
-    # u4 push 10 new 5 old with signoff
-    tc u4f u4g u4h u4i u4j u4k u4l a b c d e
-                                ok;     /09b646a/
-    git commit --allow-empty -m '10 new files signed-off by: tester\@example.com'
-                                ok;     /.master 47f84b0. 10 new files signed-off by: tester.example.com/
-    PUSH u4;                    ok;     /76c5593..47f84b0.*master -. master/
-
-    # u4 fail push 11 new files even with signoff
-    tc u4ab u4ac u4ad u4ae u4af u4ag u4ah u4ai u4aj u4ak u4al
-                                ok;     /90e7344/
-    git commit --allow-empty -m '11 new files signed-off by: tester\@example.com'
-                                ok;     /.master 1f36537. 11 new files signed-off by: tester.example.com/
-    PUSH u4;                    !ok;    /W VREF/COUNT/10/NEWFILES foo u4 DENIED by VREF/COUNT/10/NEWFILES/
-                                        /too many new files in this push/
-                                        /hook declined/
-                                        /remote rejected/
-
-    # test AUTOGENERATED vref
-    glt fetch u1 origin;        ok;
-    reset-h origin/master;      ok;
-    tc not-really.java;         ok;     /0f88b2e/
-    PUSH u4;                    ok;     /47f84b0..0f88b2e.*master -. master/
-";
-
-put "|cat >> not-really.java", "
-    Generated by the protocol buffer compiler.  DO NOT EDIT
-";
-
-try "
-    commit -am pbc;             ok;     /b2df6ef/
-    PUSH u4;                    !ok;    /W VREF/FILETYPE/AUTOGENERATED foo u4 DENIED by VREF/FILETYPE/AUTOGENERATED/
-                                        /hook declined/
-                                        /remote rejected/
-";
diff --git a/docker/gitolite/t/wild-1.t b/docker/gitolite/t/wild-1.t
deleted file mode 100755
index 7a8f766..0000000
--- a/docker/gitolite/t/wild-1.t
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# wild repos - part 1
-# ----------------------------------------------------------------------
-
-try "plan 66";
-
-confreset;confadd '
-    @prof       =   u1
-    @TAs        =   u2 u3
-    @students   =   u4 u5 u6
-
-    @gfoo = foo/CREATOR/a[0-9][0-9]
-    repo    @gfoo
-        C   =   @all
-        RW+ =   CREATOR
-        RW  =   WRITERS @TAs
-        R   =   READERS @prof
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-# reasonably complex setup; we'll do everything from one repo though
-cd ..
-
-# u1 create success
-glt clone u1 file:///foo/u1/a01;        ok;     /Initialized empty Git repository in .*/foo/u1/a01.git//
-
-# u2 create success
-glt clone u2 file:///foo/u2/a02;        ok;     /Initialized empty Git repository in .*/foo/u2/a02.git//
-
-# u4 tries to create u2 repo
-glt clone u4 file:///foo/u2/a12;        !ok;    /R any foo/u2/a12 u4 DENIED by fallthru/
-
-# line anchored regexes
-glt clone u4 file:///foo/u4/a1234;      !ok;    /R any foo/u4/a1234 u4 DENIED by fallthru/
-
-# u4 tries to create his own repo
-glt clone u4 file:///foo/u4/a12;        ok;     /Initialized empty Git repository in .*/foo/u4/a12.git//
-                                                /warning: You appear to have cloned an empty repository./
-
-# u4 push success
-cd a12
-tc p-728 p-729 p-730 p-731;             ok
-glt push u4 origin master;              ok;     /To file:///foo/u4/a12/
-                                                /\\* \\[new branch\\]      master -> master/
-
-# u1 clone success
-cd ..
-glt clone u1 file:///foo/u4/a12 u1a12;  ok;     /Cloning into 'u1a12'.../
-
-# u1 push fail
-cd u1a12
-tc m-778 m-779;                         ok;
-glt push u1 origin;                     !ok;    /W any foo/u4/a12 u1 DENIED by fallthru/
-
-# u2 clone success
-cd ..
-glt clone u2 file:///foo/u4/a12 u2a12;  ok;     /Cloning into 'u2a12'.../
-
-# u2 push success
-cd u2a12
-tc s-708 s-709;                         ok;
-glt push u2 origin;                     ok;     /To file:///foo/u4/a12/
-                                                /master -> master/
-
-# u2 rewind fail
-glt push u2 -f origin master^:master;   !ok;    /\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
-                                                reject
-
-# u4 pull to sync up
-cd ../a12
-glt pull u4;                            ok;     /Fast-forward/
-                                                /From file:///foo/u4/a12/
-                                                /master     -> origin/master/
-
-# u4 rewind success
-git reset --hard HEAD^;                 ok
-glt push u4 -f;                         ok;     /To file:///foo/u4/a12/
-                                                /\\+ .* master -> master \\(forced update\\)/
-
-# u5 clone fail
-cd ..
-glt clone u5 file:///foo/u4/a12 u5a12;  !ok;    /R any foo/u4/a12 u5 DENIED by fallthru/
-
-glt perms u4 foo/u4/a12 + READERS u5
-glt perms u4 foo/u4/a12 + WRITERS u6
-
-glt perms u4 foo/u4/a12 -l
-";
-
-cmp 'READERS u5
-WRITERS u6
-';
-
-try "
-# u5 clone success
-glt clone u5 file:///foo/u4/a12 u5a12;  ok;     /Cloning into 'u5a12'.../
-
-# u5 push fail
-cd u5a12
-tc y-743 y-744;                         ok
-glt push u5;                            !ok;    /W any foo/u4/a12 u5 DENIED by fallthru/
-
-# u6 clone success
-cd ..
-glt clone u6 file:///foo/u4/a12 u6a12;  ok;     /Cloning into 'u6a12'.../
-
-# u6 push success
-cd u6a12
-tc k-68 k-69;                           ok
-glt push u6 file:///foo/u4/a12;         ok;     /To file:///foo/u4/a12/
-                                                /master -> master/
-
-# u6 rewind fail
-glt push u6 -f file:///foo/u4/a12 master^:master
-                                        !ok;    /\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
-                                                reject
-";
diff --git a/docker/gitolite/t/wild-2.t b/docker/gitolite/t/wild-2.t
deleted file mode 100755
index cbba4f8..0000000
--- a/docker/gitolite/t/wild-2.t
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-# wild repos - part 2
-# ----------------------------------------------------------------------
-
-try "plan 65";
-
-confreset;confadd '
-    @prof       =   u1
-    @TAs        =   u2 u3
-    @students   =   u4 u5 u6
-
-    @gfoo = foo/CREATOR/a[0-9][0-9]
-    repo    @gfoo
-        C   =   @students
-        RW+ =   CREATOR
-        RW  =   WRITERS @TAs
-        R   =   READERS @prof
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-cd ..
-
-# u1 create fail
-glt clone u1 file:///foo/u1/a01;        !ok;    /R any foo/u1/a01 u1 DENIED by fallthru/
-
-# u2 create fail
-glt clone u2 file:///foo/u2/a02;        !ok;    /R any foo/u2/a02 u2 DENIED by fallthru/
-
-# u4 tries to create u2 repo
-glt clone u4 file:///foo/u2/a12;        !ok;    /R any foo/u2/a12 u4 DENIED by fallthru/
-
-# line anchored regexes
-glt clone u4 file:///foo/u4/a1234;      !ok;    /R any foo/u4/a1234 u4 DENIED by fallthru/
-
-# u4 tries to create his own repo
-glt clone u4 file:///foo/u4/a12;        ok;     /Initialized empty Git repository in .*/foo/u4/a12.git//
-                                                /warning: You appear to have cloned an empty repository./
-
-# u4 push success
-cd a12
-tc n-770 n-771 n-772 n-773;             ok
-glt push u4 origin master;              ok;     /To file:///foo/u4/a12/
-                                                /\\* \\[new branch\\]      master -> master/
-
-# u1 clone success
-cd ..
-glt clone u1 file:///foo/u4/a12 u1a12;  ok;     /Cloning into 'u1a12'.../
-
-# u1 push fail
-cd u1a12
-tc c-442 c-443;                         ok
-glt push u1;                            !ok;    /W any foo/u4/a12 u1 DENIED by fallthru/
-
-# u2 clone success
-cd ..
-glt clone u2 file:///foo/u4/a12 u2a12;  ok;     /Cloning into 'u2a12'.../
-
-# u2 push success
-cd u2a12
-tc e-393 e-394;                         ok;
-glt push u2;                            ok;     /To file:///foo/u4/a12/
-                                                /master -> master/
-
-# u2 rewind fail
-glt push u2 -f origin master^:master;   !ok;    /\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
-                                                reject
-
-# u4 pull to sync up
-cd ../a12
-glt pull u4;                            ok;     /Fast-forward/
-                                                /From file:///foo/u4/a12/
-                                                /master     -> origin/master/
-
-# u4 rewind success
-git reset --hard HEAD^;                 ok
-glt push u4 -f;                         ok;     /To file:///foo/u4/a12/
-                                                /\\+ .* master -> master \\(forced update\\)/
-
-# u5 clone fail
-cd ..
-glt clone u5 file:///foo/u4/a12 u5a12;  !ok;    /R any foo/u4/a12 u5 DENIED by fallthru/
-
-# setperm
-glt perms u4 foo/u4/a12 + READERS u5
-glt perms u4 foo/u4/a12 + WRITERS u6
-
-# getperms
-glt perms u4 foo/u4/a12 -l
-";
-
-cmp 'READERS u5
-WRITERS u6
-';
-
-try "
-# u5 clone success
-glt clone u5 file:///foo/u4/a12 u5a12;  ok;     /Cloning into 'u5a12'.../
-
-# u5 push fail
-cd u5a12
-tc g-809 g-810;                         ok
-glt push u5;                            !ok;    /W any foo/u4/a12 u5 DENIED by fallthru/
-
-# u6 clone success
-cd ..
-glt clone u6 file:///foo/u4/a12 u6a12;  ok;     /Cloning into 'u6a12'.../
-
-# u6 push success
-cd u6a12
-tc f-912 f-913
-glt push u6 file:///foo/u4/a12;         ok;     /To file:///foo/u4/a12/
-                                                /master -> master/
-
-# u6 rewind fail
-glt push u6 -f file:///foo/u4/a12 master^:master
-                                        !ok;    /\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
-                                                reject
-
-";
diff --git a/docker/gitolite/t/writable.t b/docker/gitolite/t/writable.t
deleted file mode 100755
index a649323..0000000
--- a/docker/gitolite/t/writable.t
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-use Cwd;
-my $workdir = getcwd();
-
-# 'gitolite writable' command
-# ----------------------------------------------------------------------
-
-my $sf = ".gitolite.down";
-
-try "plan 66";
-try "DEF POK = !/DENIED/; !/failed to push/";
-
-# delete the down file
-unlink "$ENV{HOME}/$sf";
-
-# add foo, bar/..* repos to the config and push
-confreset;confadd '
-    repo foo
-        RW  =   u1
-        R   =   u2
-
-    repo bar/..*
-        C   =   u2 u4 u6
-        RW  =   CREATOR u3
-';
-
-try "ADMIN_PUSH set1; !/FATAL/" or die text();
-
-try "
-    # clone and push to foo
-    CLONE u1 foo;               ok
-    cd foo;                     ok
-    tc f1;                      ok
-    PUSH u1 master;             ok;     /new branch/
-
-    # auto-clone and push to bar/u2
-    cd ..
-    CLONE u2 bar/u2;            ok;     /appear to have cloned an empty/
-                                        /Initialized empty/
-    cd u2;
-    tc f2
-    PUSH u2 master;             ok;
-
-    # disable site with some message
-    gitolite writable \@all off testing site-wide disable; ok
-
-    # try push foo and see fail + message
-    cd ../foo;                  ok
-    tc f3;                      ok
-    PUSH u1;                    !ok;    /testing site-wide disable/
-    # try push bar/u2 and ...
-    cd ../u2;                   ok
-    tc f4;                      ok
-    PUSH u2;                    !ok;    /testing site-wide disable/
-
-    # try auto-create push bar/u4 and this works!!
-    cd ..
-    CLONE u4 bar/u4;            ok;     /appear to have cloned an empty/
-                                        /Initialized empty/
-                                        !/testing site-wide disable/
-    cd u4;                      ok
-
-    # enable site
-    gitolite writable \@all on; ok
-
-    # try same 3 again
-
-    # try push foo and see fail + message
-    cd ../foo;                  ok
-    tc g3;                      ok
-    PUSH u1;                    ok;    /master -> master/
-    # try push bar/u2 and ...
-    cd ../u2;                   ok
-    tc g4;                      ok
-    PUSH u2;                    ok;    /master -> master/
-
-    # try auto-create push bar/u4 and this works!!
-    cd ..
-    CLONE u6 bar/u6;            ok;     /appear to have cloned an empty/
-                                        /Initialized empty/
-                                        !/testing site-wide disable/
-    cd u6;                      ok
-
-    # disable just foo
-    gitolite writable foo off foo down
-
-    # try push foo and see the message
-    cd ../foo;                  ok
-    tc g3;                      ok
-    PUSH u1;                    !ok;    /foo down/
-                                        !/testing site-wide disable/
-    # push bar/u2 ok
-    cd ../u2
-    tc g4
-    PUSH u2;                    ok;     /master -> master/
-
-    # enable foo, disable bar/u2
-    gitolite writable foo on
-    gitolite writable bar/u2 off the bar is closed
-
-    # try both
-    cd ../foo;                  ok
-    tc h3;                      ok
-    PUSH u1;                    ok;     /master -> master/
-    # push bar/u2 ok
-    cd ../u2
-    tc h4
-    PUSH u2;                    !ok;    /the bar is closed/
-
-    ssh u3 writable bar/u2 on;  !ok;    /you are not authorized/
-    ssh u3 writable \@all on;   !ok;    /you are not authorized/
-
-    ssh u2 writable bar/u2 on;  ok
-    ssh u2 writable \@all on;   !ok;    /you are not authorized/
-
-    ssh admin writable \@all on;
-                                ok
-";
diff --git a/docker/gitolite/t/z-end.t b/docker/gitolite/t/z-end.t
deleted file mode 100755
index 6c98fe4..0000000
--- a/docker/gitolite/t/z-end.t
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-# this is hardcoded; change it if needed
-use lib "src/lib";
-use Gitolite::Test;
-
-try "plan 1; cd $ENV{PWD}; git status -s -uno; !/./ or die" or die "dirty tree";
-try "git log -1 --format='%h %ai %s'";
-put "|cat >> prove.log", text();
-
-
-
diff --git a/docker/tests-service/postgres/log/main/.gitstub b/docker/tests-service/postgres/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/tests-service/postgres/log/run b/docker/tests-service/postgres/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/tests-service/postgres/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/tests-service/postgres/run b/docker/tests-service/postgres/run
new file mode 100755
index 0000000..2b7c497
--- /dev/null
+++ b/docker/tests-service/postgres/run
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+exec 2>&1
+set -eux
+
+chown postgres:postgres -R /var/lib/postgresql
+if ! test -d /var/lib/postgresql/9.4/main ; then
+   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
+fi
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+chown postgres:postgres -R /var/run/postgresql
+
+exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"

commit 1390a0918ef2319ac8dab5d110720399bd95fa05
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Dec 23 10:28:28 2015 -0500

    Refactor to store more state in /var/arvados.  Add "reboot" command and detach
    by default.

diff --git a/arvbox b/arvbox
index 77d78bb..42fa3a9 100755
--- a/arvbox
+++ b/arvbox
@@ -2,70 +2,131 @@
 
 ARVBOX=$(readlink -f $(dirname $0))
 
-ARVADOS_ROOT=$ARVBOX/arvados
-SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
-PASSENGER=$ARVBOX/passenger
-PG_DATA=$ARVBOX/postgres
-ARV_DATA=$ARVBOX/var
+if test -z "$ARVADOS_ROOT" ; then
+    ARVADOS_ROOT=$ARVBOX/arvados
+fi
 
-mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/var $PASSENGER
+if test -z "$SSO_ROOT" ; then
+    SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
+fi
+
+if test -z "$ARVBOX_DATA" ; then
+    ARVBOX_DATA=$ARVBOX
+fi
+
+if test -z "$ARVBOX_CONTAINER" ; then
+    ARVBOX_CONTAINER=arvbox
+fi
+
+PASSENGER=$ARVBOX_DATA/passenger
+PG_DATA=$ARVBOX_DATA/postgres
+ARV_DATA=$ARVBOX_DATA/var
+
+mkdir -p $PASSENGER $PG_DATA $ARV_DATA
+
+run() {
+    docker run \
+           --detach \
+           --name=$ARVBOX_CONTAINER \
+           --privileged \
+           --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+           --volume=$SSO_ROOT:/usr/src/sso:rw \
+           --volume=$PG_DATA:/var/lib/postgresql:rw \
+           --volume=$ARV_DATA:/var/lib/arvados:rw \
+           --volume=$PASSENGER:/var/lib/passenger:rw \
+           --volume=/var/lib/docker \
+           arvados/arvbox
+
+    FF=/tmp/arvbox-fifo-$$
+    mkfifo $FF
+    docker logs -f $ARVBOX_CONTAINER > $FF &
+    LOGPID=$!
+    while read line ; do
+        echo $line
+        if echo $line | grep "Workbench is running at" >/dev/null ; then
+            kill $LOGPID
+        fi
+    done < $FF
+    rm $FF
+}
+
+stop() {
+        docker stop $ARVBOX_CONTAINER
+        docker rm $ARVBOX_CONTAINER
+}
 
 case $1 in
-     build)
-     git clone https://github.com/curoverse/arvados.git
-     git clone https://github.com/curoverse/sso-devise-omniauth-provider.git
-     docker build -t arvados/arvbox docker
-     ;;
-
-     start|run)
-     docker run --rm --name=arvbox --privileged \
-       --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
-       --volume=$SSO_ROOT:/usr/src/sso:rw \
-       --volume=$PG_DATA:/var/lib/postgresql:rw \
-       --volume=$ARV_DATA:/var/lib/arvados:rw \
-       --volume=$PASSENGER:/var/lib/passenger:rw \
-       --volume=/var/lib/docker \
-       arvados/arvbox
-     ;;
-
-     sh*)
-     docker exec -ti arvbox /bin/bash
-     ;;
-
-     stop)
-     docker stop arvbox
-     ;;
-
-     ip|open)
-     IP=$(docker inspect arvbox | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
-     if test $1 = 'ip' ; then
-       echo $IP
-     else
-       xdg-open http://$IP
-     fi
-     ;;
-
-     resetdb)
-     sudo rm -rf $PG_DATA
-     sudo rm -rf $ARV_DATA
-     sudo rm -f $SSO_ROOT/database_setup $ARVADOS_ROOT/services/api/database_setup
-     ;;
-
-     log)
-     docker exec -ti arvbox tail -n40 /etc/service/$2/log/main/current
-     ;;
-
-     *)
-     echo "Arvados-in-a-box"
-     echo
-     echo "$0 (build|start|run|stop|shell|stop|ip|open|resetdb|log)"
-     echo
-     echo "build      build arvbox Docker image"
-     echo "start|run  start arvbox"
-     echo "stop       stop arvbo"
-     echo "shell      enter arvbox shell"
-     echo "ip         print arvbox ip address"
-     echo "open       open arvbox workbench in a web browser"
-     echo "resetdb    delete persistent data (be careful!)"
-     echo "log <component> tail log of specified service"
+    build)
+        cd $ARVBOX
+        docker build -t arvados/arvbox docker
+        ;;
+
+    start|run)
+        if ! test -d $ARVADOS_ROOT ; then
+            git clone https://github.com/curoverse/arvados.git $ARVADOS_ROOT
+        fi
+        if ! test -d $SSO_ROOT ; then
+            git clone https://github.com/curoverse/sso-devise-omniauth-provider.git $SSO_ROOT
+        fi
+
+        run
+        ;;
+
+    sh*)
+        docker exec -ti $ARVBOX_CONTAINER /bin/bash
+        ;;
+
+    stop)
+        stop
+        ;;
+
+    reboot)
+        stop
+        cd $ARVBOX
+        docker build -t arvados/arvbox docker
+        run
+        ;;
+
+    sv|service)
+        docker exec -ti $ARVBOX_CONTAINER sv $2 $3
+        docker exec -ti $ARVBOX_CONTAINER sv restart ready
+        ;;
+
+    ip|open)
+        IP=$(docker inspect arvbox | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
+        if test $1 = 'ip' ; then
+            echo $IP
+        else
+            xdg-open http://$IP
+        fi
+        ;;
+
+    reset)
+        if test "$2" != -f ; then
+            echo "WARNING!  This will delete all data inside your arvbox ($PG_DATA and $ARV_DATA).  Use reset -f if you really mean it."
+            exit 1
+        fi
+        stop
+        sudo rm -rf $PG_DATA
+        sudo rm -rf $ARV_DATA
+        ;;
+
+    log)
+        docker exec -ti $ARVBOX_CONTAINER tail -n40 /etc/service/$2/log/main/current
+        ;;
+
+    *)
+        echo "Arvados-in-a-box"
+        echo
+        echo "$0 (build|start|run|stop|shell|stop|ip|open|resetdb|log)"
+        echo
+        echo "build      build arvbox Docker image"
+        echo "start|run  start arvbox"
+        echo "stop       stop arvbo"
+        echo "shell      enter arvbox shell"
+        echo "ip         print arvbox ip address"
+        echo "open       open arvbox workbench in a web browser"
+        echo "resetdb    delete persistent data (be careful!)"
+        echo "log <component> tail log of specified service"
+        ;;
 esac
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index 4e0536a..c321070 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -19,7 +19,7 @@ mkdir -p /var/lib/arvados/keep
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 if test -s /var/lib/arvados/$1-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/$1-uuid)
diff --git a/docker/service/api/run b/docker/service/api/run
index 8f997ce..935c93c 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -10,30 +10,25 @@ export RAILS_ENV=development
 
 bundle install --without=development --path=vendor
 
-if ! test -s uuid_prefix ; then
-  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
+if ! test -s /var/lib/arvados/api_uuid_prefix ; then
+  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/api_uuid_prefix
 fi
-uuid_prefix=$(cat uuid_prefix)
+uuid_prefix=$(cat /var/lib/arvados/api_uuid_prefix)
 
-if ! test -s secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+if ! test -s /var/lib/arvados/api_secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/api_secret_token
 fi
-secret_token=$(cat secret_token)
+secret_token=$(cat /var/lib/arvados/api_secret_token)
 
-if ! test -s blob_signing_key ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > blob_signing_key
+if ! test -s /var/lib/arvados/blob_signing_key ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/blob_signing_key
 fi
-blob_signing_key=$(cat blob_signing_key)
+blob_signing_key=$(cat /var/lib/arvados/blob_signing_key)
 
-if ! test -s superuser_token ; then
-    bundle exec ./script/create_superuser_token.rb > superuser_token
-fi
-
-if ! test -s self-signed.key ; then
-  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
-fi
+# self signed key will be created by SSO server script.
+test -s /var/lib/arvados/self-signed.key
 
-sso_app_secret=$(cat /usr/src/sso/app_secret)
+sso_app_secret=$(cat /var/lib/arvados/sso_app_secret)
 
 cat >config/application.yml <<EOF
 common:
@@ -50,7 +45,7 @@ common:
   git_repo_https_base: "http://$localip:9001/"
 EOF
 
-if ! test -f database_setup ; then
+if ! test -f /var/lib/arvados/api_database_setup ; then
    database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
    su postgres -c "psql -c \"create user arvados with password '$database_pw'\""
    su postgres -c "psql -c \"ALTER USER arvados CREATEDB;\""
@@ -63,13 +58,24 @@ development:
   password: $database_pw
   host: localhost
   template: template0
+test:
+  adapter: postgresql
+  encoding: utf8
+  database: arvados_test
+  username: arvados
+  password: $database_pw
+  host: localhost
+  template: template0
 EOF
    bundle exec rake db:setup
-   bundle exec ./script/create_superuser_token.rb > superuser_token
-   touch database_setup
+   touch /var/lib/arvados/api_database_setup
+fi
+
+if ! test -s /var/lib/arvados/superuser_token ; then
+    bundle exec ./script/create_superuser_token.rb > /var/lib/arvados/superuser_token
 fi
 
 rm -rf tmp
 
 bundle exec rake db:migrate
-ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
+ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/docker/service/git/run b/docker/service/git/run
index e16bdbb..6e79f26 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -9,7 +9,7 @@ mkdir -p /var/lib/arvados/git
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
 
 if ! test -f /var/lib/arvados/gitolite-setup ; then
    cp -r /root/gitolite /var/lib/arvados/git/
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index c24efc2..fe52d6d 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -17,7 +17,7 @@ install bin/keepproxy /usr/local/bin
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/superuser_token)
 
 if test -s /var/lib/arvados/keepproxy-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
diff --git a/docker/service/keepweb/run b/docker/service/keepweb/run
index 938d305..e549087 100755
--- a/docker/service/keepweb/run
+++ b/docker/service/keepweb/run
@@ -16,6 +16,6 @@ install bin/keep-web /usr/local/bin
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
 
 exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
diff --git a/docker/service/ready/run b/docker/service/ready/run
index 89130ff..8e03e3e 100755
--- a/docker/service/ready/run
+++ b/docker/service/ready/run
@@ -55,4 +55,6 @@ echo
 echo "Your Arvados-in-a-box is ready!"
 echo "Workbench is running at http://$localip"
 
+rm -r /root/ready
+
 sv stop ready >/dev/null
\ No newline at end of file
diff --git a/docker/service/sso/run b/docker/service/sso/run
index fec9097..953ea95 100755
--- a/docker/service/sso/run
+++ b/docker/service/sso/run
@@ -10,23 +10,18 @@ export RAILS_ENV=development
 
 bundle install --without=development --path=vendor
 
-if ! test -s uuid_prefix ; then
-  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
+if ! test -s /var/lib/arvados/sso_uuid_prefix ; then
+  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > /var/lib/arvados/sso_uuid_prefix
 fi
-uuid_prefix=$(cat uuid_prefix)
+uuid_prefix=$(cat /var/lib/arvados/sso_uuid_prefix)
 
-if ! test -s secret_token ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+if ! test -s /var/lib/arvados/sso_secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_secret_token
 fi
-secret_token=$(cat secret_token)
+secret_token=$(cat /var/lib/arvados/sso_secret_token)
 
-if ! test -s app_secret ; then
-  ruby -e 'puts rand(2**400).to_s(36)' > app_secret
-fi
-app_secret=$(cat app_secret)
-
-if ! test -s self-signed.key ; then
-  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
+if ! test -s /var/lib/arvados/self-signed.key ; then
+  openssl req -new -x509 -nodes -out /var/lib/arvados/self-signed.pem -keyout /var/lib/arvados/self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
 cat >config/application.yml <<EOF
@@ -37,7 +32,7 @@ common:
   allow_account_registration: true
 EOF
 
-if ! test -f database_setup ; then
+if ! test -f /var/lib/arvados/sso_database_setup ; then
    database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
    su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
    su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
@@ -50,9 +45,22 @@ development:
   password: $database_pw
   host: localhost
   template: template0
+test:
+  adapter: postgresql
+  encoding: utf8
+  database: arvados_sso_test
+  username: arvados_sso
+  password: $database_pw
+  host: localhost
+  template: template0
 EOF
    bundle exec rake db:setup
 
+   if ! test -s /var/lib/arvados/sso_app_secret ; then
+       ruby -e 'puts rand(2**400).to_s(36)' > /var/lib/arvados/sso_app_secret
+   fi
+   app_secret=$(cat /var/lib/arvados/sso_app_secret)
+
    bundle exec rails console <<EOF
 c = Client.new
 c.name = "joshid"
@@ -61,10 +69,10 @@ c.app_secret = "$app_secret"
 c.save!
 EOF
 
-   touch database_setup
+   touch /var/lib/arvados/sso_database_setup
 fi
 
 rm -rf tmp
 
 bundle exec rake db:migrate
-bundle exec passenger start -p3002 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
+bundle exec passenger start -p3002 --ssl --ssl-certificate=/var/lib/arvados/self-signed.pem --ssl-certificate-key=/var/lib/arvados/self-signed.key
diff --git a/docker/service/vm/run b/docker/service/vm/run
index 644f306..0c13547 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -11,7 +11,7 @@ git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/de
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export ARVADOS_API_TOKEN=$(cat /var/lib/arvados/api_superuser_token)
 
 if test -s /var/lib/arvados/vm-uuid ; then
     ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)

commit d0a2ecd887ffc0dd6a9d27c402b9bada469578dc
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Dec 22 16:40:04 2015 -0500

    Running docker-crunch-in-arvbox works!!!

diff --git a/docker/Dockerfile b/docker/Dockerfile
index 69a128f..60a6823 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -22,8 +22,9 @@ RUN cd /root/runit-docker && \
 ENV LD_PRELOAD /lib/runit-docker.so
 
 ADD fuse.conf /etc/
-RUN mkdir -p /var/lib/arvados/git && \
-    useradd crunch && \
+RUN useradd crunch && \
+    addgroup crunch docker && \
+    mkdir -p /var/lib/arvados/git && \
     useradd --home-dir /var/lib/arvados/git git
 
 ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh /root/
diff --git a/docker/crunch-setup.sh b/docker/crunch-setup.sh
index 98297a1..4b70250 100755
--- a/docker/crunch-setup.sh
+++ b/docker/crunch-setup.sh
@@ -14,12 +14,18 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/crunchstat"
 install bin/crunchstat /usr/local/bin
 
+mkdir -p /tmp/$1
+chown crunch:crunch -R /tmp/$1
+
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 export CRUNCH_JOB_BIN=/usr/src/arvados/sdk/cli/bin/crunch-job
 export PERLLIB=/usr/src/arvados/sdk/perl/lib
 export CRUNCH_TMP=/tmp/$1
+export CRUNCH_DISPATCH_LOCKFILE=/var/lock/$1-dispatch
+export CRUNCH_JOB_DOCKER_BIN=docker
+export HOME=/tmp/$1
 
 cd /usr/src/arvados/services/api
 exec bundle exec ./script/crunch-dispatch.rb development

commit c973f244a36871f5634bde3d91ce240db257a24a
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Dec 22 16:04:51 2015 -0500

    Crunch works (not yet tested with docker)

diff --git a/arvbox b/arvbox
index 8b05c47..77d78bb 100755
--- a/arvbox
+++ b/arvbox
@@ -24,10 +24,11 @@ case $1 in
        --volume=$PG_DATA:/var/lib/postgresql:rw \
        --volume=$ARV_DATA:/var/lib/arvados:rw \
        --volume=$PASSENGER:/var/lib/passenger:rw \
+       --volume=/var/lib/docker \
        arvados/arvbox
      ;;
 
-     shell)
+     sh*)
      docker exec -ti arvbox /bin/bash
      ;;
 
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 5fd5c33..69a128f 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -5,7 +5,12 @@ RUN apt-get update && apt-get -y -q install \
     ruby rake bundler curl libpq-dev \
     libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
     openssh-server python-setuptools netcat-traditional \
-    libpython-dev libfuse-dev python-pip
+    libpython-dev fuse libfuse-dev python-pip \
+    pkg-config libattr1-dev python-llfuse python-pycurl \
+    libwww-perl libio-socket-ssl-perl libcrypt-ssleay-perl \
+    libjson-perl
+
+RUN curl -sSL https://get.docker.com/ | sh
 
 ADD runit-docker /root/runit-docker
 ADD gitolite /root/gitolite
@@ -16,7 +21,12 @@ RUN cd /root/runit-docker && \
 
 ENV LD_PRELOAD /lib/runit-docker.so
 
-ADD gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh /root/
+ADD fuse.conf /etc/
+RUN mkdir -p /var/lib/arvados/git && \
+    useradd crunch && \
+    useradd --home-dir /var/lib/arvados/git git
+
+ADD crunch-setup.sh gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh common.sh /root/
 ADD service /etc/service
 
 # Start the supervisor.
diff --git a/docker/common.sh b/docker/common.sh
new file mode 100644
index 0000000..16297ba
--- /dev/null
+++ b/docker/common.sh
@@ -0,0 +1 @@
+localip=$(ip addr show eth0 |grep "inet " | sed 's/ *inet \([^/]*\).*/\1/')
diff --git a/docker/service/keepweb/run b/docker/crunch-setup.sh
similarity index 52%
copy from docker/service/keepweb/run
copy to docker/crunch-setup.sh
index 8f4c0b2..98297a1 100755
--- a/docker/service/keepweb/run
+++ b/docker/crunch-setup.sh
@@ -3,19 +3,23 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
 
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
-go get -t "git.curoverse.com/arvados.git/services/keep-web"
-install bin/keep-web /usr/local/bin
-
-localip=$(hostname -I | tr -d ' \n')
+go get -t "git.curoverse.com/arvados.git/services/crunchstat"
+install bin/crunchstat /usr/local/bin
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+export CRUNCH_JOB_BIN=/usr/src/arvados/sdk/cli/bin/crunch-job
+export PERLLIB=/usr/src/arvados/sdk/perl/lib
+export CRUNCH_TMP=/tmp/$1
 
-exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
+cd /usr/src/arvados/services/api
+exec bundle exec ./script/crunch-dispatch.rb development
diff --git a/docker/fuse.conf b/docker/fuse.conf
new file mode 100644
index 0000000..a439ab8
--- /dev/null
+++ b/docker/fuse.conf
@@ -0,0 +1 @@
+user_allow_other
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index f76d475..4e0536a 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -4,6 +4,8 @@ exec 2>&1
 sleep 2
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
 
@@ -13,8 +15,6 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keepstore"
 install bin/keepstore /usr/local/bin
 
-localip=$(hostname -I | tr -d ' \n')
-
 mkdir -p /var/lib/arvados/keep
 
 export ARVADOS_API_HOST=$localip:3001
diff --git a/docker/service/api/run b/docker/service/api/run
index 2a0d758..8f997ce 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -3,6 +3,8 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 cd /usr/src/arvados/services/api
 export RAILS_ENV=development
 
@@ -33,8 +35,6 @@ fi
 
 sso_app_secret=$(cat /usr/src/sso/app_secret)
 
-localip=$(hostname -I | tr -d ' \n')
-
 cat >config/application.yml <<EOF
 common:
   uuid_prefix: $uuid_prefix
diff --git a/docker/service/crunch0/log/main/.gitstub b/docker/service/crunch0/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/crunch0/log/run b/docker/service/crunch0/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/crunch0/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/crunch0/run b/docker/service/crunch0/run
new file mode 100755
index 0000000..dd864a0
--- /dev/null
+++ b/docker/service/crunch0/run
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /root/crunch-setup.sh crunch0
diff --git a/docker/service/crunch1/log/main/.gitstub b/docker/service/crunch1/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/crunch1/log/run b/docker/service/crunch1/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/crunch1/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/crunch1/run b/docker/service/crunch1/run
new file mode 100755
index 0000000..d7583e5
--- /dev/null
+++ b/docker/service/crunch1/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+sleep 1
+exec /root/crunch-setup.sh crunch1
diff --git a/docker/service/docker/log/main/.gitstub b/docker/service/docker/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/docker/log/run b/docker/service/docker/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/docker/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/docker/run b/docker/service/docker/run
new file mode 100755
index 0000000..99540e6
--- /dev/null
+++ b/docker/service/docker/run
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+# Taken from https://github.com/jpetazzo/dind
+
+exec 2>&1
+
+#!/bin/bash
+
+# Ensure that all nodes in /dev/mapper correspond to mapped devices currently loaded by the device-mapper kernel driver
+dmsetup mknodes
+
+# First, make sure that cgroups are mounted correctly.
+CGROUP=/sys/fs/cgroup
+: {LOG:=stdio}
+
+[ -d $CGROUP ] ||
+	mkdir $CGROUP
+
+mountpoint -q $CGROUP ||
+	mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
+		echo "Could not make a tmpfs mount. Did you use --privileged?"
+		exit 1
+	}
+
+if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security
+then
+    mount -t securityfs none /sys/kernel/security || {
+        echo "Could not mount /sys/kernel/security."
+        echo "AppArmor detection and --privileged mode might break."
+    }
+fi
+
+# Mount the cgroup hierarchies exactly as they are in the parent system.
+for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
+do
+        [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
+        mountpoint -q $CGROUP/$SUBSYS ||
+                mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
+
+        # The two following sections address a bug which manifests itself
+        # by a cryptic "lxc-start: no ns_cgroup option specified" when
+        # trying to start containers withina container.
+        # The bug seems to appear when the cgroup hierarchies are not
+        # mounted on the exact same directories in the host, and in the
+        # container.
+
+        # Named, control-less cgroups are mounted with "-o name=foo"
+        # (and appear as such under /proc/<pid>/cgroup) but are usually
+        # mounted on a directory named "foo" (without the "name=" prefix).
+        # Systemd and OpenRC (and possibly others) both create such a
+        # cgroup. To avoid the aforementioned bug, we symlink "foo" to
+        # "name=foo". This shouldn't have any adverse effect.
+        echo $SUBSYS | grep -q ^name= && {
+                NAME=$(echo $SUBSYS | sed s/^name=//)
+                ln -s $SUBSYS $CGROUP/$NAME
+        }
+
+        # Likewise, on at least one system, it has been reported that
+        # systemd would mount the CPU and CPU accounting controllers
+        # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
+        # but on a directory called "cpu,cpuacct" (note the inversion
+        # in the order of the groups). This tries to work around it.
+        [ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
+done
+
+# Note: as I write those lines, the LXC userland tools cannot setup
+# a "sub-container" properly if the "devices" cgroup is not in its
+# own hierarchy. Let's detect this and issue a warning.
+grep -q :devices: /proc/1/cgroup ||
+	echo "WARNING: the 'devices' cgroup should be in its own hierarchy."
+grep -qw devices /proc/1/cgroup ||
+	echo "WARNING: it looks like the 'devices' cgroup is not mounted."
+
+# Now, close extraneous file descriptors.
+pushd /proc/self/fd >/dev/null
+for FD in *
+do
+	case "$FD" in
+	# Keep stdin/stdout/stderr
+	[012])
+		;;
+	# Nuke everything else
+	*)
+		eval exec "$FD>&-"
+		;;
+	esac
+done
+popd >/dev/null
+
+
+# If a pidfile is still around (for example after a container restart),
+# delete it so that docker can start.
+rm -rf /var/run/docker.pid
+
+exec docker daemon --storage-driver=overlay $DOCKER_DAEMON_ARGS
diff --git a/docker/service/git/run b/docker/service/git/run
index c9c930c..e16bdbb 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -1,15 +1,17 @@
-#!/bin/sh
+#!/bin/bash
 
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/git
 
-if ! grep ^git: /etc/passwd ; then
-    useradd --comment git --home-dir /var/lib/arvados/git git
-fi
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
-if ! test -f /var/lib/arvados/git-setup ; then
+if ! test -f /var/lib/arvados/gitolite-setup ; then
    cp -r /root/gitolite /var/lib/arvados/git/
    cp -r /root/gitolite /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
 
@@ -18,26 +20,54 @@ if ! test -f /var/lib/arvados/git-setup ; then
    su git -c "/var/lib/arvados/git/gitssh-setup.sh"
    su git -c "/var/lib/arvados/git/gitolite-setup.sh"
 
-   touch /var/lib/arvados/git-setup
+   touch /var/lib/arvados/gitolite-setup
 else
     chown -R git:git ~git
     su git -c "/var/lib/arvados/git/gitssh-setup.sh"
 fi
 
+if ! test -s /var/lib/arvados/arvados-git-uuid ; then
+    repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
+    echo $repo_uuid > /var/lib/arvados/arvados-git-uuid
+fi
+
+repo_uuid=$(cat /var/lib/arvados/arvados-git-uuid)
+
+if ! test -s /var/lib/arvados/arvados-git-link-uuid ; then
+    prefix=$(arv --format=uuid user current | cut -d- -f1)
+    all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
+
+    set +e
+    read -rd $'\000' newlink <<EOF
+{
+ "tail_uuid":"$all_users_group_uuid",
+ "head_uuid":"$repo_uuid",
+ "link_class":"permission",
+ "name":"can_read"
+}
+EOF
+    set -e
+    link_uuid=$(arv --format=uuid link create --link "$newlink")
+    echo $link_uuid > /var/lib/arvados/arvados-git-link-uuid
+fi
+
+if ! test -d /var/lib/arvados/git/repositories/$repo_uuid.git ; then
+    git clone --bare /usr/src/arvados /var/lib/arvados/git/repositories/$repo_uuid.git
+else
+    git --git-dir=/var/lib/arvados/git/repositories/$repo_uuid.git fetch -f /usr/src/arvados master:master
+fi
+
 cd /usr/src/arvados/services/api
 export RAILS_ENV=development
 
-localip=$(hostname -I | tr -d ' \n')
 git_user_key=$(cat ~git/.ssh/id_rsa.pub)
 
-superuser_token=$(cat superuser_token)
-
 cat > config/arvados-clients.yml <<EOF
 development:
   gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
   gitolite_tmp: /var/lib/arvados/git
   arvados_api_host: $localip:3001
-  arvados_api_token: "$superuser_token"
+  arvados_api_token: "$ARVADOS_API_TOKEN"
   arvados_api_host_insecure: true
   gitolite_arvados_git_user_key: "$git_user_key"
 EOF
diff --git a/docker/service/githttp/run b/docker/service/githttp/run
index a07cfa4..390727d 100755
--- a/docker/service/githttp/run
+++ b/docker/service/githttp/run
@@ -3,6 +3,8 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
 
@@ -12,8 +14,6 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
 install bin/arv-git-httpd /usr/local/bin
 
-localip=$(hostname -I | tr -d ' \n')
-
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export GITOLITE_HTTP_HOME=/var/lib/arvados/git
diff --git a/docker/service/keep1/run b/docker/service/keep1/run
index 59ee5b9..8b0d907 100755
--- a/docker/service/keep1/run
+++ b/docker/service/keep1/run
@@ -1,2 +1,3 @@
 #!/bin/sh
+sleep 1
 exec /root/keep-setup.sh keep1 25108
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index 8aa7aa9..c24efc2 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -4,6 +4,8 @@ exec 2>&1
 sleep 2
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
 
@@ -13,8 +15,6 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keepproxy"
 install bin/keepproxy /usr/local/bin
 
-localip=$(hostname -I | tr -d ' \n')
-
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
diff --git a/docker/service/keepweb/run b/docker/service/keepweb/run
index 8f4c0b2..938d305 100755
--- a/docker/service/keepweb/run
+++ b/docker/service/keepweb/run
@@ -3,6 +3,8 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 mkdir -p /var/lib/arvados/gostuff
 cd /var/lib/arvados/gostuff
 
@@ -12,8 +14,6 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keep-web"
 install bin/keep-web /usr/local/bin
 
-localip=$(hostname -I | tr -d ' \n')
-
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
diff --git a/docker/service/ready/run b/docker/service/ready/run
index 623575e..89130ff 100755
--- a/docker/service/ready/run
+++ b/docker/service/ready/run
@@ -1,6 +1,8 @@
 #!/bin/bash
 
-set -u
+. /root/common.sh
+
+set -eu
 
 if ! [[ -d /root/ready ]] ; then
    echo
@@ -26,7 +28,7 @@ services=(
 
 waiting=""
 
-localip=$(hostname -I | tr -d ' \n')
+. /root/common.sh
 
 for s in "${!services[@]}"
 do
@@ -40,6 +42,10 @@ do
   fi
 done
 
+if ! docker version >/dev/null 2>/dev/null ; then
+  waiting="$waiting docker"
+fi
+
 if ! [[ -z "$waiting" ]] ; then
   echo "    Waiting for$waiting ..."
   exit 1
diff --git a/docker/service/sdk/run b/docker/service/sdk/run
index 1cbec10..10df374 100755
--- a/docker/service/sdk/run
+++ b/docker/service/sdk/run
@@ -4,20 +4,21 @@ exec 2>&1
 set -eux
 
 mkdir -p ~/.pip /var/lib/arvados/pip
-cat > /etc/pip.conf <<EOF
+cat > ~/.pip/pip.conf <<EOF
 [global]
 download_cache = /var/lib/arvados/pip
 EOF
 
 cd /usr/src/arvados/sdk/cli
 bundle install --binstubs=binstubs
-
 ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/bin/arv
 
 cd /usr/src/arvados/sdk/python
-pip install .
+python setup.py sdist
+pip install $(ls dist/arvados-python-client-*.tar.gz | tail -n1)
 
-#cd /usr/src/arvados/services/fuse
-#pip install .
+cd /usr/src/arvados/services/fuse
+python setup.py sdist
+pip install $(ls dist/arvados_fuse-*.tar.gz | tail -n1)
 
 sv stop sdk
diff --git a/docker/service/sso/run b/docker/service/sso/run
index f2e01ad..fec9097 100755
--- a/docker/service/sso/run
+++ b/docker/service/sso/run
@@ -3,6 +3,8 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 cd /usr/src/sso
 export RAILS_ENV=development
 
@@ -27,8 +29,6 @@ if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
-localip=$(hostname -I | tr -d ' \n')
-
 cat >config/application.yml <<EOF
 common:
   uuid_prefix: $uuid_prefix
diff --git a/docker/service/vm/run b/docker/service/vm/run
index 073da16..644f306 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -4,7 +4,7 @@ exec 2>&1
 sleep 2
 set -eux
 
-localip=$(hostname -I | tr -d ' \n')
+. /root/common.sh
 
 git config --system "credential.http://$localip:9001/.username" none
 git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
diff --git a/docker/service/workbench/run b/docker/service/workbench/run
index 03da67c..50f5b26 100755
--- a/docker/service/workbench/run
+++ b/docker/service/workbench/run
@@ -3,6 +3,8 @@
 exec 2>&1
 set -eux
 
+. /root/common.sh
+
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development
 
@@ -17,8 +19,6 @@ if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
-localip=$(hostname -I | tr -d ' \n')
-
 cat >config/application.yml <<EOF
 common:
   secret_token: $secret_token

commit 74742aedb58fa0f2d5e465b9320c6ded472a7e40
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Dec 22 10:19:25 2015 -0500

    Improve ready script, fix git, fix vm

diff --git a/arvbox b/arvbox
index 750063d..8b05c47 100755
--- a/arvbox
+++ b/arvbox
@@ -40,7 +40,7 @@ case $1 in
      if test $1 = 'ip' ; then
        echo $IP
      else
-       xdg-open https://$IP
+       xdg-open http://$IP
      fi
      ;;
 
diff --git a/docker/gitssh-setup.sh b/docker/gitssh-setup.sh
index 75e54fd..41c280d 100755
--- a/docker/gitssh-setup.sh
+++ b/docker/gitssh-setup.sh
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
 cd ~
 
@@ -12,6 +13,10 @@ fi
 
 ssh-keygen -f "/var/lib/arvados/git/.ssh/known_hosts" -R localhost
 
-cp .ssh/id_rsa.pub .ssh/authorized_keys
-ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
-rm .ssh/authorized_keys
+if ! test -s .ssh/authorized_keys ; then
+    cp .ssh/id_rsa.pub .ssh/authorized_keys
+    ssh -o stricthostkeychecking=no localhost
+    rm .ssh/authorized_keys
+else
+    ssh -o stricthostkeychecking=no localhost
+fi
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index 89dcfdb..f76d475 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -43,7 +43,8 @@ else
 }
 EOF
     set -e
-    arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1-uuid
+    UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
+    echo $UUID > /var/lib/arvados/$1-uuid
 fi
 
 exec /usr/local/bin/keepstore \
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index 8cb92ca..8aa7aa9 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -42,7 +42,8 @@ else
 }
 EOF
     set -e
-    arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/keepproxy-uuid
+    UUID=$(arv --format=uuid keep_service create --keep-service "$keepservice")
+    echo $UUID > /var/lib/arvados/keepproxy-uuid
 fi
 
 exec /usr/local/bin/keepproxy -listen=":25100"
diff --git a/docker/service/ready/run b/docker/service/ready/run
index 81f1283..623575e 100755
--- a/docker/service/ready/run
+++ b/docker/service/ready/run
@@ -1,19 +1,52 @@
-#!/bin/sh
+#!/bin/bash
 
-set -e
+set -u
 
-nc -z localhost 443
-nc -z localhost 3001
-nc -z localhost 3002
-nc -z localhost 9001
+if ! [[ -d /root/ready ]] ; then
+   echo
+   echo "Arvados-in-a-box starting"
+   echo
+   mkdir -p /root/ready
+fi
 
-test -x /usr/bin/arv
+sleep 2
 
-nc -z localhost 25099
-nc -z localhost 25100
-nc -z localhost 25107
-nc -z localhost 25108
+declare -A services
+services=(
+  [workbench]=80
+  [api]=3001
+  [sso]=3002
+  [githttp]=9001
+  [keepweb]=25099
+  [keepproxy]=25100
+  [keep0]=25107
+  [keep1]=25108
+  [ssh]=22
+)
 
+waiting=""
+
+localip=$(hostname -I | tr -d ' \n')
+
+for s in "${!services[@]}"
+do
+  if ! [[ -f /root/ready/$s ]] ; then
+    if nc -z $localip ${services[$s]} ; then
+      echo "$s is ready at $localip:${services[$s]}"
+      touch /root/ready/$s
+    else
+      waiting="$waiting $s"
+    fi
+  fi
+done
+
+if ! [[ -z "$waiting" ]] ; then
+  echo "    Waiting for$waiting ..."
+  exit 1
+fi
+
+echo
 echo "Your Arvados-in-a-box is ready!"
+echo "Workbench is running at http://$localip"
 
-sv stop ready
\ No newline at end of file
+sv stop ready >/dev/null
\ No newline at end of file
diff --git a/docker/service/sdk/run b/docker/service/sdk/run
index 6bb3dae..1cbec10 100755
--- a/docker/service/sdk/run
+++ b/docker/service/sdk/run
@@ -10,15 +10,9 @@ download_cache = /var/lib/arvados/pip
 EOF
 
 cd /usr/src/arvados/sdk/cli
-gem build arvados-cli.gemspec
-gem install -i vendor $(ls -r arvados-cli-*.gem | head -n1)
+bundle install --binstubs=binstubs
 
-cat > /usr/bin/arv <<EOF
-#!/bin/sh
-export GEM_PATH=/usr/src/arvados/sdk/cli/vendor
-exec /usr/src/arvados/sdk/cli/vendor/bin/arv "\$@"
-EOF
-chmod +x /usr/bin/arv
+ln -sf /usr/src/arvados/sdk/cli/binstubs/arv /usr/bin/arv
 
 cd /usr/src/arvados/sdk/python
 pip install .
diff --git a/docker/service/vm/run b/docker/service/vm/run
index b66ca65..073da16 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -14,7 +14,7 @@ export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
 if test -s /var/lib/arvados/vm-uuid ; then
-    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+    ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
     set +e
     read -rd $'\000' vm <<EOF
 {
@@ -22,7 +22,7 @@ if test -s /var/lib/arvados/vm-uuid ; then
 }
 EOF
     set -e
-    arv virtual_machine update --uuid vm_uuid --virtual-machine "$vm"
+    arv virtual_machine update --uuid $ARVADOS_VIRTUAL_MACHINE_UUID --virtual-machine "$vm"
 else
     set +e
     read -rd $'\000' vm <<EOF
@@ -31,10 +31,11 @@ else
 }
 EOF
     set -e
-    arv --format=uuid virtual_machine create --virtual-machine "$vm" > /var/lib/arvados/vm-uuid
+    ARVADOS_VIRTUAL_MACHINE_UUID=$(arv --format=uuid virtual_machine create --virtual-machine "$vm")
+    echo $ARVADOS_VIRTUAL_MACHINE_UUID > /var/lib/arvados/vm-uuid
 fi
 
-export ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+export ARVADOS_VIRTUAL_MACHINE_UUID
 
 cd /usr/src/arvados/services/login-sync
 bundle install

commit 7e8f87d1f87fb25e329fdd7001c6b1ae6afefd24
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Tue Dec 22 08:24:08 2015 -0500

    Use test -s to test that files are not empty.

diff --git a/docker/gitssh-setup.sh b/docker/gitssh-setup.sh
index 05927e1..75e54fd 100755
--- a/docker/gitssh-setup.sh
+++ b/docker/gitssh-setup.sh
@@ -4,7 +4,7 @@ set -e
 
 cd ~
 
-if ! test -f .ssh/id_rsa ; then
+if ! test -s .ssh/id_rsa ; then
     mkdir -p .ssh
     chmod -R 0700 .ssh
     ssh-keygen -t rsa -P '' -f .ssh/id_rsa
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index b8ff747..89dcfdb 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -21,7 +21,7 @@ export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
-if test -f /var/lib/arvados/$1-uuid ; then
+if test -s /var/lib/arvados/$1-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/$1-uuid)
     set +e
     read -rd $'\000' keepservice <<EOF
diff --git a/docker/service/api/run b/docker/service/api/run
index 04270a0..2a0d758 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -8,26 +8,26 @@ export RAILS_ENV=development
 
 bundle install --without=development --path=vendor
 
-if ! test -f uuid_prefix ; then
+if ! test -s uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
 fi
 uuid_prefix=$(cat uuid_prefix)
 
-if ! test -f secret_token ; then
+if ! test -s secret_token ; then
   ruby -e 'puts rand(2**400).to_s(36)' > secret_token
 fi
 secret_token=$(cat secret_token)
 
-if ! test -f blob_signing_key ; then
+if ! test -s blob_signing_key ; then
   ruby -e 'puts rand(2**400).to_s(36)' > blob_signing_key
 fi
 blob_signing_key=$(cat blob_signing_key)
 
-if ! test -f superuser_token ; then
+if ! test -s superuser_token ; then
     bundle exec ./script/create_superuser_token.rb > superuser_token
 fi
 
-if ! test -f self-signed.key ; then
+if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
@@ -43,7 +43,7 @@ common:
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
   sso_provider_url: "https://$localip:3002"
-  workbench_address: "https://$localip"
+  workbench_address: "http://$localip"
   sso_insecure: true
   auto_admin_first_user: true
   git_repo_ssh_base: "git@$localip:"
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index 610fa64..8cb92ca 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -19,7 +19,7 @@ export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
-if test -f /var/lib/arvados/keepproxy-uuid ; then
+if test -s /var/lib/arvados/keepproxy-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
     set +e
     read -rd $'\000' keepservice <<EOF
diff --git a/docker/service/sso/run b/docker/service/sso/run
index d224c17..f2e01ad 100755
--- a/docker/service/sso/run
+++ b/docker/service/sso/run
@@ -8,22 +8,22 @@ export RAILS_ENV=development
 
 bundle install --without=development --path=vendor
 
-if ! test -f uuid_prefix ; then
+if ! test -s uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
 fi
 uuid_prefix=$(cat uuid_prefix)
 
-if ! test -f secret_token ; then
+if ! test -s secret_token ; then
   ruby -e 'puts rand(2**400).to_s(36)' > secret_token
 fi
 secret_token=$(cat secret_token)
 
-if ! test -f app_secret ; then
+if ! test -s app_secret ; then
   ruby -e 'puts rand(2**400).to_s(36)' > app_secret
 fi
 app_secret=$(cat app_secret)
 
-if ! test -f self-signed.key ; then
+if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
@@ -33,7 +33,7 @@ cat >config/application.yml <<EOF
 common:
   uuid_prefix: $uuid_prefix
   secret_token: $secret_token
-  default_link_url: "https://$localip"
+  default_link_url: "http://$localip"
   allow_account_registration: true
 EOF
 
diff --git a/docker/service/vm/run b/docker/service/vm/run
index dc1764b..b66ca65 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -13,7 +13,7 @@ export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
-if test -f /var/lib/arvados/vm-uuid ; then
+if test -s /var/lib/arvados/vm-uuid ; then
     vm_uuid=$(cat /var/lib/arvados/vm-uuid)
     set +e
     read -rd $'\000' vm <<EOF
@@ -34,7 +34,7 @@ EOF
     arv --format=uuid virtual_machine create --virtual-machine "$vm" > /var/lib/arvados/vm-uuid
 fi
 
-ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+export ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
 
 cd /usr/src/arvados/services/login-sync
 bundle install
diff --git a/docker/service/workbench/run b/docker/service/workbench/run
index baf3492..03da67c 100755
--- a/docker/service/workbench/run
+++ b/docker/service/workbench/run
@@ -8,12 +8,12 @@ export RAILS_ENV=development
 
 bundle install --without=development --path=vendor
 
-if ! test -f secret_token ; then
+if ! test -s secret_token ; then
   ruby -e 'puts rand(2**400).to_s(36)' > secret_token
 fi
 secret_token=$(cat secret_token)
 
-if ! test -f self-signed.key ; then
+if ! test -s self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
 
@@ -31,4 +31,5 @@ EOF
 
 rm -rf tmp
 
-bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
+#bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
+bundle exec passenger start -p80

commit f4e5d5401e9d41013a0a7afeaeea58ca2759783b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Dec 21 11:33:11 2015 -0500

    keepstore and keepweb works, uploading from workbench doesn't work due to
    mixed-origin http/https, need to find a workaround.

diff --git a/arvbox b/arvbox
index 2b73e84..750063d 100755
--- a/arvbox
+++ b/arvbox
@@ -51,7 +51,7 @@ case $1 in
      ;;
 
      log)
-     docker exec -ti arvbox tail -f -n40 /etc/service/$2/log/main/current
+     docker exec -ti arvbox tail -n40 /etc/service/$2/log/main/current
      ;;
 
      *)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 3a705df..5fd5c33 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get -y -q install \
     ruby rake bundler curl libpq-dev \
     libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
     openssh-server python-setuptools netcat-traditional \
-    libpython-dev libfuse-dev
+    libpython-dev libfuse-dev python-pip
 
 ADD runit-docker /root/runit-docker
 ADD gitolite /root/gitolite
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index d7f0c2f..b8ff747 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -1,9 +1,11 @@
-#!/bin/sh
+#!/bin/bash
 
-set -e
+exec 2>&1
+sleep 2
+set -eux
 
-mkdir -p /root/src
-cd /root/src
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
 
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
@@ -13,7 +15,7 @@ install bin/keepstore /usr/local/bin
 
 localip=$(hostname -I | tr -d ' \n')
 
-mkdir -p /var/lib/arvados/$1
+mkdir -p /var/lib/arvados/keep
 
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
@@ -21,15 +23,18 @@ export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
 if test -f /var/lib/arvados/$1-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/$1-uuid)
-    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
+    set +e
+    read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
  "service_port":$2
 }
 EOF
+    set -e
+    arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
 else
-    prefix=$(arv --format=uuid user current | cut -d- -f1)
-    read -rd $'\000' keepservice <<EOF; arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1-uuid
+    set +e
+    read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
  "service_port":$2,
@@ -37,6 +42,8 @@ else
  "service_type":"disk"
 }
 EOF
+    set -e
+    arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1-uuid
 fi
 
 exec /usr/local/bin/keepstore \
diff --git a/docker/service/api/run b/docker/service/api/run
index 2103850..04270a0 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
 cd /usr/src/arvados/services/api
 export RAILS_ENV=development
diff --git a/docker/service/git/run b/docker/service/git/run
index f7d48e2..c9c930c 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -1,7 +1,7 @@
 #!/bin/sh
 
-set -e
 exec 2>&1
+set -eux
 
 mkdir -p /var/lib/arvados/git
 
diff --git a/docker/service/githttp/run b/docker/service/githttp/run
index dec7bbb..a07cfa4 100755
--- a/docker/service/githttp/run
+++ b/docker/service/githttp/run
@@ -1,9 +1,10 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
-mkdir -p /root/src
-cd /root/src
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
 
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index 745d7af..610fa64 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -1,9 +1,11 @@
-#!/bin/sh
+#!/bin/bash
 
-set -e
+exec 2>&1
+sleep 2
+set -eux
 
-mkdir -p /root/src
-cd /root/src
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
 
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
@@ -11,21 +13,27 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keepproxy"
 install bin/keepproxy /usr/local/bin
 
+localip=$(hostname -I | tr -d ' \n')
+
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
 if test -f /var/lib/arvados/keepproxy-uuid ; then
-    keep_uuid=$(cat /var/lib/arvados/$1/keepproxy-uuid)
-    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+    keep_uuid=$(cat /var/lib/arvados/keepproxy-uuid)
+    set +e
+    read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
- "service_port":25100
+ "service_port":25100,
+ "service_type":"proxy"
 }
 EOF
+   set -e
+   arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
 else
-    prefix=$(arv --format=uuid user current | cut -d- -f1)
-    read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+    set +e
+    read -rd $'\000' keepservice <<EOF
 {
  "service_host":"$localip",
  "service_port":25100,
@@ -33,6 +41,8 @@ else
  "service_type":"proxy"
 }
 EOF
+    set -e
+    arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/keepproxy-uuid
 fi
 
 exec /usr/local/bin/keepproxy -listen=":25100"
diff --git a/docker/service/keepweb/run b/docker/service/keepweb/run
index fef297e..8f4c0b2 100755
--- a/docker/service/keepweb/run
+++ b/docker/service/keepweb/run
@@ -1,9 +1,10 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
-mkdir -p /root/src
-cd /root/src
+mkdir -p /var/lib/arvados/gostuff
+cd /var/lib/arvados/gostuff
 
 export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
@@ -11,6 +12,8 @@ ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keep-web"
 install bin/keep-web /usr/local/bin
 
+localip=$(hostname -I | tr -d ' \n')
+
 export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
diff --git a/docker/service/ready/run b/docker/service/ready/run
index 2eadbad..81f1283 100755
--- a/docker/service/ready/run
+++ b/docker/service/ready/run
@@ -9,6 +9,7 @@ nc -z localhost 9001
 
 test -x /usr/bin/arv
 
+nc -z localhost 25099
 nc -z localhost 25100
 nc -z localhost 25107
 nc -z localhost 25108
diff --git a/docker/service/sdk/run b/docker/service/sdk/run
index 0089363..6bb3dae 100755
--- a/docker/service/sdk/run
+++ b/docker/service/sdk/run
@@ -1,5 +1,14 @@
 #!/bin/sh
-set -e
+
+exec 2>&1
+set -eux
+
+mkdir -p ~/.pip /var/lib/arvados/pip
+cat > /etc/pip.conf <<EOF
+[global]
+download_cache = /var/lib/arvados/pip
+EOF
+
 cd /usr/src/arvados/sdk/cli
 gem build arvados-cli.gemspec
 gem install -i vendor $(ls -r arvados-cli-*.gem | head -n1)
@@ -7,14 +16,14 @@ gem install -i vendor $(ls -r arvados-cli-*.gem | head -n1)
 cat > /usr/bin/arv <<EOF
 #!/bin/sh
 export GEM_PATH=/usr/src/arvados/sdk/cli/vendor
-exec /usr/src/arvados/sdk/cli/vendor/bin/arv $*
+exec /usr/src/arvados/sdk/cli/vendor/bin/arv "\$@"
 EOF
 chmod +x /usr/bin/arv
 
 cd /usr/src/arvados/sdk/python
-python setup.py install
+pip install .
 
-cd /usr/src/arvados/services/fuse
-python setup.py install
+#cd /usr/src/arvados/services/fuse
+#pip install .
 
 sv stop sdk
diff --git a/docker/service/ssh/run b/docker/service/ssh/run
index d121406..0db3ffa 100755
--- a/docker/service/ssh/run
+++ b/docker/service/ssh/run
@@ -1,4 +1,8 @@
 #!/bin/sh
+
+exec 2>&1
+set -eux
+
 if ! test -d /var/run/sshd ; then
    mkdir /var/run/sshd
    chmod 0755 /var/run/sshd
diff --git a/docker/service/sso/run b/docker/service/sso/run
index 52b6e75..d224c17 100755
--- a/docker/service/sso/run
+++ b/docker/service/sso/run
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
 cd /usr/src/sso
 export RAILS_ENV=development
diff --git a/docker/service/vm/run b/docker/service/vm/run
index 21cb992..dc1764b 100755
--- a/docker/service/vm/run
+++ b/docker/service/vm/run
@@ -1,5 +1,8 @@
-#!/bin/sh
-set -e
+#!/bin/bash
+
+exec 2>&1
+sleep 2
+set -eux
 
 localip=$(hostname -I | tr -d ' \n')
 
@@ -12,18 +15,23 @@ export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
 if test -f /var/lib/arvados/vm-uuid ; then
     vm_uuid=$(cat /var/lib/arvados/vm-uuid)
-    read -rd $'\000' vm <<EOF; arv virtual_machine update --uuid vm_uuid --virtual-machine "$vm"
+    set +e
+    read -rd $'\000' vm <<EOF
 {
  "hostname":"$localip"
 }
 EOF
+    set -e
+    arv virtual_machine update --uuid vm_uuid --virtual-machine "$vm"
 else
-    read -rd $'\000' vm <<EOF; arv --format=uuid virtual_machine create --virtual-machine "$vm" > /var/lib/arvados/vm-uuid
+    set +e
+    read -rd $'\000' vm <<EOF
 {
  "hostname":"$localip"
 }
 EOF
-
+    set -e
+    arv --format=uuid virtual_machine create --virtual-machine "$vm" > /var/lib/arvados/vm-uuid
 fi
 
 ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
diff --git a/docker/service/workbench/run b/docker/service/workbench/run
index 24f3dab..baf3492 100755
--- a/docker/service/workbench/run
+++ b/docker/service/workbench/run
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-set -e
+exec 2>&1
+set -eux
 
 cd /usr/src/arvados/apps/workbench
 export RAILS_ENV=development

commit 68ced1ba92a7813eedb204390f9e22aedf6d9b4a
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Dec 21 09:47:56 2015 -0500

    More logging.  Workbench runs on port 80.  Add scripts for vm, keepweb.  Needs
    more work.

diff --git a/arvbox b/arvbox
index 0b66c34..2b73e84 100755
--- a/arvbox
+++ b/arvbox
@@ -40,7 +40,7 @@ case $1 in
      if test $1 = 'ip' ; then
        echo $IP
      else
-       xdg-open https://$IP:3000
+       xdg-open https://$IP
      fi
      ;;
 
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
index d00baa0..d7f0c2f 100755
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -21,7 +21,7 @@ export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
 if test -f /var/lib/arvados/$1-uuid ; then
     keep_uuid=$(cat /var/lib/arvados/$1-uuid)
-    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice"
 {
  "service_host":"$localip",
  "service_port":$2
@@ -29,7 +29,7 @@ if test -f /var/lib/arvados/$1-uuid ; then
 EOF
 else
     prefix=$(arv --format=uuid user current | cut -d- -f1)
-    read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+    read -rd $'\000' keepservice <<EOF; arv --format=uuid keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1-uuid
 {
  "service_host":"$localip",
  "service_port":$2,
diff --git a/docker/service/api/run b/docker/service/api/run
index 65aeb32..2103850 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -42,7 +42,7 @@ common:
   sso_app_secret: $sso_app_secret
   sso_app_id: arvados-server
   sso_provider_url: "https://$localip:3002"
-  workbench_address: "https://$localip:3000"
+  workbench_address: "https://$localip"
   sso_insecure: true
   auto_admin_first_user: true
   git_repo_ssh_base: "git@$localip:"
diff --git a/docker/service/githttp/run b/docker/service/githttp/run
index b5baf80..dec7bbb 100755
--- a/docker/service/githttp/run
+++ b/docker/service/githttp/run
@@ -19,4 +19,4 @@ export GITOLITE_HTTP_HOME=/var/lib/arvados/git
 export GL_BYPASS_ACCESS_CHECKS=1
 export PATH="$PATH:/var/lib/arvados/git/bin"
 cd ~git
-exec chpst -u git:git /usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
\ No newline at end of file
+exec chpst -u git:git /usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
diff --git a/docker/service/keep0/log/main/.gitstub b/docker/service/keep0/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/keep0/log/run b/docker/service/keep0/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/keep0/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/keep1/log/main/.gitstub b/docker/service/keep1/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/keep1/log/run b/docker/service/keep1/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/keep1/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/keepproxy/log/main/.gitstub b/docker/service/keepproxy/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/keepproxy/log/run b/docker/service/keepproxy/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/keepproxy/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
index 6654977..745d7af 100755
--- a/docker/service/keepproxy/run
+++ b/docker/service/keepproxy/run
@@ -35,4 +35,4 @@ else
 EOF
 fi
 
-exec /usr/local/bin/keepstore -listen=":25100"
+exec /usr/local/bin/keepproxy -listen=":25100"
diff --git a/docker/service/keepweb/log/main/.gitstub b/docker/service/keepweb/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/keepweb/log/run b/docker/service/keepweb/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/keepweb/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/keepweb/run b/docker/service/keepweb/run
new file mode 100755
index 0000000..fef297e
--- /dev/null
+++ b/docker/service/keepweb/run
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+mkdir -p /root/src
+cd /root/src
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+go get -t "git.curoverse.com/arvados.git/services/keep-web"
+install bin/keep-web /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+
+exec /usr/local/bin/keep-web -trust-all-content -listen=":25099"
diff --git a/docker/service/ready/run b/docker/service/ready/run
index 85ca76e..2eadbad 100755
--- a/docker/service/ready/run
+++ b/docker/service/ready/run
@@ -2,9 +2,10 @@
 
 set -e
 
-nc -z localhost 3000
+nc -z localhost 443
 nc -z localhost 3001
 nc -z localhost 3002
+nc -z localhost 9001
 
 test -x /usr/bin/arv
 
diff --git a/docker/service/sso/run b/docker/service/sso/run
index c63229a..52b6e75 100755
--- a/docker/service/sso/run
+++ b/docker/service/sso/run
@@ -32,7 +32,7 @@ cat >config/application.yml <<EOF
 common:
   uuid_prefix: $uuid_prefix
   secret_token: $secret_token
-  default_link_url: "http://$localip:3000"
+  default_link_url: "https://$localip"
   allow_account_registration: true
 EOF
 
diff --git a/docker/service/vm/log/main/.gitstub b/docker/service/vm/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/vm/log/run b/docker/service/vm/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/vm/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/vm/run b/docker/service/vm/run
new file mode 100755
index 0000000..21cb992
--- /dev/null
+++ b/docker/service/vm/run
@@ -0,0 +1,37 @@
+#!/bin/sh
+set -e
+
+localip=$(hostname -I | tr -d ' \n')
+
+git config --system "credential.http://$localip:9001/.username" none
+git config --system "credential.http://$localip:9001/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
+
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+
+if test -f /var/lib/arvados/vm-uuid ; then
+    vm_uuid=$(cat /var/lib/arvados/vm-uuid)
+    read -rd $'\000' vm <<EOF; arv virtual_machine update --uuid vm_uuid --virtual-machine "$vm"
+{
+ "hostname":"$localip"
+}
+EOF
+else
+    read -rd $'\000' vm <<EOF; arv --format=uuid virtual_machine create --virtual-machine "$vm" > /var/lib/arvados/vm-uuid
+{
+ "hostname":"$localip"
+}
+EOF
+
+fi
+
+ARVADOS_VIRTUAL_MACHINE_UUID=$(cat /var/lib/arvados/vm-uuid)
+
+cd /usr/src/arvados/services/login-sync
+bundle install
+
+while true ; do
+      bundle exec arvados-login-sync
+      sleep 120
+done
diff --git a/docker/service/workbench/run b/docker/service/workbench/run
index 011769a..24f3dab 100755
--- a/docker/service/workbench/run
+++ b/docker/service/workbench/run
@@ -24,8 +24,10 @@ common:
   arvados_login_base: https://$localip:3001/login
   arvados_v1_base: https://$localip:3001/arvados/v1
   arvados_insecure_https: true
+  keep_web_download_url: http://$localip:25099/c=%{uuid_or_pdh}
+  keep_web_url: http://$localip:25099/c=%{uuid_or_pdh}
 EOF
 
 rm -rf tmp
 
-bundle exec passenger start -p3000 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
+bundle exec passenger start -p443 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key

commit 68dec0e57c778e23a591b0ee3be6ecf0dd3e11e8
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Mon Dec 21 08:42:59 2015 -0500

    Keepstore/keepproxy wip

diff --git a/arvbox b/arvbox
index 0fc6894..0b66c34 100755
--- a/arvbox
+++ b/arvbox
@@ -18,7 +18,7 @@ case $1 in
      ;;
 
      start|run)
-     docker run --rm --name=arvbox \
+     docker run --rm --name=arvbox --privileged \
        --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
        --volume=$SSO_ROOT:/usr/src/sso:rw \
        --volume=$PG_DATA:/var/lib/postgresql:rw \
@@ -46,10 +46,25 @@ case $1 in
 
      resetdb)
      sudo rm -rf $PG_DATA
-     rm -f $SSO_ROOT/database_setup $ARVADOS_ROOT/services/api/database_setup
+     sudo rm -rf $ARV_DATA
+     sudo rm -f $SSO_ROOT/database_setup $ARVADOS_ROOT/services/api/database_setup
      ;;
 
      log)
-     docker exec -ti arvbox tail -f /etc/service/$2/log/main/current
+     docker exec -ti arvbox tail -f -n40 /etc/service/$2/log/main/current
      ;;
+
+     *)
+     echo "Arvados-in-a-box"
+     echo
+     echo "$0 (build|start|run|stop|shell|stop|ip|open|resetdb|log)"
+     echo
+     echo "build      build arvbox Docker image"
+     echo "start|run  start arvbox"
+     echo "stop       stop arvbo"
+     echo "shell      enter arvbox shell"
+     echo "ip         print arvbox ip address"
+     echo "open       open arvbox workbench in a web browser"
+     echo "resetdb    delete persistent data (be careful!)"
+     echo "log <component> tail log of specified service"
 esac
diff --git a/docker/Dockerfile b/docker/Dockerfile
index cca21c1..3a705df 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -4,7 +4,8 @@ RUN apt-get update && apt-get -y -q install \
     postgresql-9.4 git gcc golang-go runit \
     ruby rake bundler curl libpq-dev \
     libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
-    openssh-server
+    openssh-server python-setuptools netcat-traditional \
+    libpython-dev libfuse-dev
 
 ADD runit-docker /root/runit-docker
 ADD gitolite /root/gitolite
@@ -15,7 +16,7 @@ RUN cd /root/runit-docker && \
 
 ENV LD_PRELOAD /lib/runit-docker.so
 
-ADD gitolite-setup.sh gitolite.rc keep-setup.sh /root/
+ADD gitolite-setup.sh gitolite.rc gitssh-setup.sh keep-setup.sh /root/
 ADD service /etc/service
 
 # Start the supervisor.
diff --git a/docker/gitolite-setup.sh b/docker/gitolite-setup.sh
index 71eb77d..203fbec 100755
--- a/docker/gitolite-setup.sh
+++ b/docker/gitolite-setup.sh
@@ -4,19 +4,14 @@ set -e
 
 cd ~
 
-rm -f .ssh/id_rsa .ssh/id_rsa.pub
-mkdir -p .ssh
-chmod -R 0700 .ssh
-ssh-keygen -t rsa -P '' -f .ssh/id_rsa
-cp .ssh/id_rsa.pub .ssh/authorized_keys
-ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
-rm .ssh/authorized_keys
-
 mkdir -p bin
 gitolite/install -ln ~git/bin
 bin/gitolite setup -pk .ssh/id_rsa.pub
 
-git clone git at localhost:gitolite-admin
+if ! test -d gitolite-admin ; then
+    git clone git at localhost:gitolite-admin
+fi
+
 cd gitolite-admin
 git config user.email arvados
 git config user.name arvados
diff --git a/docker/gitssh-setup.sh b/docker/gitssh-setup.sh
new file mode 100755
index 0000000..05927e1
--- /dev/null
+++ b/docker/gitssh-setup.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -e
+
+cd ~
+
+if ! test -f .ssh/id_rsa ; then
+    mkdir -p .ssh
+    chmod -R 0700 .ssh
+    ssh-keygen -t rsa -P '' -f .ssh/id_rsa
+fi
+
+ssh-keygen -f "/var/lib/arvados/git/.ssh/known_hosts" -R localhost
+
+cp .ssh/id_rsa.pub .ssh/authorized_keys
+ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
+rm .ssh/authorized_keys
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
old mode 100644
new mode 100755
index 014b84b..d00baa0
--- a/docker/keep-setup.sh
+++ b/docker/keep-setup.sh
@@ -9,7 +9,7 @@ export GOPATH=$PWD
 mkdir -p "$GOPATH/src/git.curoverse.com"
 ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
 go get -t "git.curoverse.com/arvados.git/services/keepstore"
-install keepstore /usr/local/bin
+install bin/keepstore /usr/local/bin
 
 localip=$(hostname -I | tr -d ' \n')
 
@@ -19,8 +19,17 @@ export ARVADOS_API_HOST=$localip:3001
 export ARVADOS_API_HOST_INSECURE=1
 export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
 
-prefix=$(bundle exec arv --format=uuid user current | cut -d- -f1)
-read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice"
+if test -f /var/lib/arvados/$1-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/$1-uuid)
+    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+{
+ "service_host":"$localip",
+ "service_port":$2
+}
+EOF
+else
+    prefix=$(arv --format=uuid user current | cut -d- -f1)
+    read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
 {
  "service_host":"$localip",
  "service_port":$2,
@@ -28,10 +37,11 @@ read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$kee
  "service_type":"disk"
 }
 EOF
+fi
 
 exec /usr/local/bin/keepstore \
      -listen=:$2 \
      -enforce-permissions=true \
      -blob-signing-key-file=/usr/src/arvados/services/api/blob_signing_key \
      -max-buffers=20 \
-     -volume=/var/lib/arvados/keep1
+     -volume=/var/lib/arvados/keep
diff --git a/docker/service/git/run b/docker/service/git/run
index 1dad564..f7d48e2 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -1,21 +1,27 @@
 #!/bin/sh
 
-mkdir -p /var/lib/arvados/git
-useradd --comment git --home-dir /var/lib/arvados/git git
-
 set -e
+exec 2>&1
 
-chown -R git:git ~git
+mkdir -p /var/lib/arvados/git
+
+if ! grep ^git: /etc/passwd ; then
+    useradd --comment git --home-dir /var/lib/arvados/git git
+fi
 
 if ! test -f /var/lib/arvados/git-setup ; then
    cp -r /root/gitolite /var/lib/arvados/git/
-   cp -r /root/gitolite /root/gitolite-setup.sh /root/gitolite.rc /var/lib/arvados/git/
+   cp -r /root/gitolite /root/gitolite-setup.sh /root/gitssh-setup.sh /root/gitolite.rc /var/lib/arvados/git/
 
    chown -R git:git ~git
 
+   su git -c "/var/lib/arvados/git/gitssh-setup.sh"
    su git -c "/var/lib/arvados/git/gitolite-setup.sh"
 
    touch /var/lib/arvados/git-setup
+else
+    chown -R git:git ~git
+    su git -c "/var/lib/arvados/git/gitssh-setup.sh"
 fi
 
 cd /usr/src/arvados/services/api
diff --git a/docker/service/keepproxy/run b/docker/service/keepproxy/run
new file mode 100755
index 0000000..6654977
--- /dev/null
+++ b/docker/service/keepproxy/run
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+set -e
+
+mkdir -p /root/src
+cd /root/src
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+go get -t "git.curoverse.com/arvados.git/services/keepproxy"
+install bin/keepproxy /usr/local/bin
+
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+
+if test -f /var/lib/arvados/keepproxy-uuid ; then
+    keep_uuid=$(cat /var/lib/arvados/$1/keepproxy-uuid)
+    read -rd $'\000' keepservice <<EOF; arv keep_service update --uuid $keep_uuid --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+{
+ "service_host":"$localip",
+ "service_port":25100
+}
+EOF
+else
+    prefix=$(arv --format=uuid user current | cut -d- -f1)
+    read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice" > /var/lib/arvados/$1/keep-uuid
+{
+ "service_host":"$localip",
+ "service_port":25100,
+ "service_ssl_flag":false,
+ "service_type":"proxy"
+}
+EOF
+fi
+
+exec /usr/local/bin/keepstore -listen=":25100"
diff --git a/docker/service/ready/run b/docker/service/ready/run
new file mode 100755
index 0000000..85ca76e
--- /dev/null
+++ b/docker/service/ready/run
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -e
+
+nc -z localhost 3000
+nc -z localhost 3001
+nc -z localhost 3002
+
+test -x /usr/bin/arv
+
+nc -z localhost 25100
+nc -z localhost 25107
+nc -z localhost 25108
+
+echo "Your Arvados-in-a-box is ready!"
+
+sv stop ready
\ No newline at end of file
diff --git a/docker/service/sdk/run b/docker/service/sdk/run
index 4815f29..0089363 100755
--- a/docker/service/sdk/run
+++ b/docker/service/sdk/run
@@ -4,9 +4,17 @@ cd /usr/src/arvados/sdk/cli
 gem build arvados-cli.gemspec
 gem install -i vendor $(ls -r arvados-cli-*.gem | head -n1)
 
-cat > /root/setup-sdk <<EOF
-export PATH=\$PATH:/usr/src/arvados/sdk/cli/vendor/bin
+cat > /usr/bin/arv <<EOF
+#!/bin/sh
 export GEM_PATH=/usr/src/arvados/sdk/cli/vendor
+exec /usr/src/arvados/sdk/cli/vendor/bin/arv $*
 EOF
+chmod +x /usr/bin/arv
+
+cd /usr/src/arvados/sdk/python
+python setup.py install
+
+cd /usr/src/arvados/services/fuse
+python setup.py install
 
 sv stop sdk

commit a1fea850a41c5fe5486853aac470dacf6b61e1a2
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 22:49:03 2015 -0500

    Install CLI SDK.  keepstore work in progress.

diff --git a/docker/Dockerfile b/docker/Dockerfile
index 3d9f5b2..cca21c1 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -15,7 +15,7 @@ RUN cd /root/runit-docker && \
 
 ENV LD_PRELOAD /lib/runit-docker.so
 
-ADD gitolite-setup.sh gitolite.rc /root/
+ADD gitolite-setup.sh gitolite.rc keep-setup.sh /root/
 ADD service /etc/service
 
 # Start the supervisor.
diff --git a/docker/keep-setup.sh b/docker/keep-setup.sh
new file mode 100644
index 0000000..014b84b
--- /dev/null
+++ b/docker/keep-setup.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+set -e
+
+mkdir -p /root/src
+cd /root/src
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+go get -t "git.curoverse.com/arvados.git/services/keepstore"
+install keepstore /usr/local/bin
+
+localip=$(hostname -I | tr -d ' \n')
+
+mkdir -p /var/lib/arvados/$1
+
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export ARVADOS_API_TOKEN=$(cat /usr/src/arvados/services/api/superuser_token)
+
+prefix=$(bundle exec arv --format=uuid user current | cut -d- -f1)
+read -rd $'\000' keepservice <<EOF; arv keep_service create --keep-service "$keepservice"
+{
+ "service_host":"$localip",
+ "service_port":$2,
+ "service_ssl_flag":false,
+ "service_type":"disk"
+}
+EOF
+
+exec /usr/local/bin/keepstore \
+     -listen=:$2 \
+     -enforce-permissions=true \
+     -blob-signing-key-file=/usr/src/arvados/services/api/blob_signing_key \
+     -max-buffers=20 \
+     -volume=/var/lib/arvados/keep1
diff --git a/docker/service/api/run b/docker/service/api/run
index 23b798e..65aeb32 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -22,6 +22,10 @@ if ! test -f blob_signing_key ; then
 fi
 blob_signing_key=$(cat blob_signing_key)
 
+if ! test -f superuser_token ; then
+    bundle exec ./script/create_superuser_token.rb > superuser_token
+fi
+
 if ! test -f self-signed.key ; then
   openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
 fi
diff --git a/docker/service/git/run b/docker/service/git/run
index 33000ad..1dad564 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -24,9 +24,6 @@ export RAILS_ENV=development
 localip=$(hostname -I | tr -d ' \n')
 git_user_key=$(cat ~git/.ssh/id_rsa.pub)
 
-if ! test -f superuser_token ; then
-    bundle exec ./script/create_superuser_token.rb > superuser_token
-fi
 superuser_token=$(cat superuser_token)
 
 cat > config/arvados-clients.yml <<EOF
diff --git a/docker/service/keep0/run b/docker/service/keep0/run
new file mode 100755
index 0000000..aa5b69c
--- /dev/null
+++ b/docker/service/keep0/run
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /root/keep-setup.sh keep0 25107
\ No newline at end of file
diff --git a/docker/service/keep1/run b/docker/service/keep1/run
new file mode 100755
index 0000000..59ee5b9
--- /dev/null
+++ b/docker/service/keep1/run
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /root/keep-setup.sh keep1 25108
diff --git a/docker/service/sdk/log/main/.gitstub b/docker/service/sdk/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/sdk/log/run b/docker/service/sdk/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/sdk/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/sdk/run b/docker/service/sdk/run
new file mode 100755
index 0000000..4815f29
--- /dev/null
+++ b/docker/service/sdk/run
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -e
+cd /usr/src/arvados/sdk/cli
+gem build arvados-cli.gemspec
+gem install -i vendor $(ls -r arvados-cli-*.gem | head -n1)
+
+cat > /root/setup-sdk <<EOF
+export PATH=\$PATH:/usr/src/arvados/sdk/cli/vendor/bin
+export GEM_PATH=/usr/src/arvados/sdk/cli/vendor
+EOF
+
+sv stop sdk

commit f48faae5ef31122779e93678311c821c4ddf4f4f
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 21:55:37 2015 -0500

    Support for arv-git-httpd

diff --git a/docker/gitolite-setup.sh b/docker/gitolite-setup.sh
index c31aef7..71eb77d 100755
--- a/docker/gitolite-setup.sh
+++ b/docker/gitolite-setup.sh
@@ -5,12 +5,14 @@ set -e
 cd ~
 
 rm -f .ssh/id_rsa .ssh/id_rsa.pub
+mkdir -p .ssh
+chmod -R 0700 .ssh
 ssh-keygen -t rsa -P '' -f .ssh/id_rsa
 cp .ssh/id_rsa.pub .ssh/authorized_keys
 ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
 rm .ssh/authorized_keys
 
-mkdir bin
+mkdir -p bin
 gitolite/install -ln ~git/bin
 bin/gitolite setup -pk .ssh/id_rsa.pub
 
diff --git a/docker/gitolite.rc b/docker/gitolite.rc
index e69de29..b735870 100644
--- a/docker/gitolite.rc
+++ b/docker/gitolite.rc
@@ -0,0 +1,209 @@
+# configuration variables for gitolite
+
+# This file is in perl syntax.  But you do NOT need to know perl to edit it --
+# just mind the commas, use single quotes unless you know what you're doing,
+# and make sure the brackets and braces stay matched up!
+
+# (Tip: perl allows a comma after the last item in a list also!)
+
+# HELP for commands can be had by running the command with "-h".
+
+# HELP for all the other FEATURES can be found in the documentation (look for
+# "list of non-core programs shipped with gitolite" in the master index) or
+# directly in the corresponding source file.
+
+my $repo_aliases;
+my $aliases_src = "$ENV{HOME}/.gitolite/arvadosaliases.pl";
+if ($ENV{HOME} && (-e $aliases_src)) {
+    $repo_aliases = do $aliases_src;
+}
+$repo_aliases ||= {};
+
+%RC = (
+
+    REPO_ALIASES => $repo_aliases,
+
+    # ------------------------------------------------------------------
+
+    # default umask gives you perms of '0700'; see the rc file docs for
+    # how/why you might change this
+    UMASK                           =>  0022,
+
+    # look for "git-config" in the documentation
+    GIT_CONFIG_KEYS                 =>  '',
+
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                       =>  1,
+    # logging options
+    # 1. leave this section as is for 'normal' gitolite logging (default)
+    # 2. uncomment this line to log ONLY to syslog:
+    # LOG_DEST                      => 'syslog',
+    # 3. uncomment this line to log to syslog and the normal gitolite log:
+    # LOG_DEST                      => 'syslog,normal',
+    # 4. prefixing "repo-log," to any of the above will **also** log just the
+    #    update records to "gl-log" in the bare repo directory:
+    # LOG_DEST                      => 'repo-log,normal',
+    # LOG_DEST                      => 'repo-log,syslog',
+    # LOG_DEST                      => 'repo-log,syslog,normal',
+
+    # roles.  add more roles (like MANAGER, TESTER, ...) here.
+    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
+    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
+    ROLES => {
+        READERS                     =>  1,
+        WRITERS                     =>  1,
+    },
+
+    # enable caching (currently only Redis).  PLEASE RTFM BEFORE USING!!!
+    # CACHE                         =>  'Redis',
+
+    # ------------------------------------------------------------------
+
+    # rc variables used by various features
+
+    # the 'info' command prints this as additional info, if it is set
+        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
+    # the CpuTime feature uses these
+        # display user, system, and elapsed times to user after each git operation
+        # DISPLAY_CPU_TIME          =>  1,
+        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
+        # CPU_TIME_WARN_LIMIT       =>  0.1,
+
+    # the Mirroring feature needs this
+        # HOSTNAME                  =>  "foo",
+
+    # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
+        # CACHE_TTL                 =>  600,
+
+    # ------------------------------------------------------------------
+
+    # suggested locations for site-local gitolite code (see cust.html)
+
+        # this one is managed directly on the server
+        # LOCAL_CODE                =>  "$ENV{HOME}/local",
+
+        # or you can use this, which lets you put everything in a subdirectory
+        # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
+        # on this, see http://gitolite.com/gitolite/non-core.html#pushcode
+        # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
+
+    # ------------------------------------------------------------------
+
+    # List of commands and features to enable
+
+    ENABLE => [
+
+        # COMMANDS
+
+            # These are the commands enabled by default
+            'help',
+            'desc',
+            'info',
+            'perms',
+            'writable',
+
+            # Uncomment or add new commands here.
+            # 'create',
+            # 'fork',
+            # 'mirror',
+            # 'readme',
+            # 'sskm',
+            # 'D',
+
+        # These FEATURES are enabled by default.
+
+            # essential (unless you're using smart-http mode)
+            'ssh-authkeys',
+
+            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
+            'git-config',
+
+            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
+            'daemon',
+
+            # creates projects.list file; if you don't use gitweb, comment this out
+            'gitweb',
+
+        # These FEATURES are disabled by default; uncomment to enable.  If you
+        # need to add new ones, ask on the mailing list :-)
+
+        # user-visible behaviour
+
+            # prevent wild repos auto-create on fetch/clone
+            # 'no-create-on-read',
+            # no auto-create at all (don't forget to enable the 'create' command!)
+            # 'no-auto-create',
+
+            # access a repo by another (possibly legacy) name
+            'Alias',
+
+            # give some users direct shell access.  See documentation in
+            # sts.html for details on the following two choices.
+            # "Shell $ENV{HOME}/.gitolite.shell-users",
+            # 'Shell alice bob',
+
+            # set default roles from lines like 'option default.roles-1 = ...', etc.
+            # 'set-default-roles',
+
+            # show more detailed messages on deny
+            # 'expand-deny-messages',
+
+            # show a message of the day
+            # 'Motd',
+
+        # system admin stuff
+
+            # enable mirroring (don't forget to set the HOSTNAME too!)
+            # 'Mirroring',
+
+            # allow people to submit pub files with more than one key in them
+            # 'ssh-authkeys-split',
+
+            # selective read control hack
+            # 'partial-copy',
+
+            # manage local, gitolite-controlled, copies of read-only upstream repos
+            # 'upstream',
+
+            # updates 'description' file instead of 'gitweb.description' config item
+            # 'cgit',
+
+            # allow repo-specific hooks to be added
+            # 'repo-specific-hooks',
+
+        # performance, logging, monitoring...
+
+            # be nice
+            # 'renice 10',
+
+            # log CPU times (user, system, cumulative user, cumulative system)
+            # 'CpuTime',
+
+        # syntactic_sugar for gitolite.conf and included files
+
+            # allow backslash-escaped continuation lines in gitolite.conf
+            # 'continuation-lines',
+
+            # create implicit user groups from directory names in keydir/
+            # 'keysubdirs-as-groups',
+
+            # allow simple line-oriented macros
+            # 'macros',
+
+        # Kindergarten mode
+
+            # disallow various things that sensible people shouldn't be doing anyway
+            # 'Kindergarten',
+    ],
+
+);
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:
diff --git a/docker/service/api/run b/docker/service/api/run
index 7c2477c..23b798e 100755
--- a/docker/service/api/run
+++ b/docker/service/api/run
@@ -41,6 +41,8 @@ common:
   workbench_address: "https://$localip:3000"
   sso_insecure: true
   auto_admin_first_user: true
+  git_repo_ssh_base: "git@$localip:"
+  git_repo_https_base: "http://$localip:9001/"
 EOF
 
 if ! test -f database_setup ; then
diff --git a/docker/service/git/run b/docker/service/git/run
index 3b375b8..33000ad 100755
--- a/docker/service/git/run
+++ b/docker/service/git/run
@@ -1,16 +1,45 @@
 #!/bin/sh
+
+mkdir -p /var/lib/arvados/git
+useradd --comment git --home-dir /var/lib/arvados/git git
+
 set -e
 
+chown -R git:git ~git
+
 if ! test -f /var/lib/arvados/git-setup ; then
-   mkdir -p /var/lib/arvados/git
    cp -r /root/gitolite /var/lib/arvados/git/
    cp -r /root/gitolite /root/gitolite-setup.sh /root/gitolite.rc /var/lib/arvados/git/
 
-   useradd --comment git --home-dir /var/lib/arvados/git git
    chown -R git:git ~git
+
    su git -c "/var/lib/arvados/git/gitolite-setup.sh"
 
    touch /var/lib/arvados/git-setup
 fi
 
-sv stop git
+cd /usr/src/arvados/services/api
+export RAILS_ENV=development
+
+localip=$(hostname -I | tr -d ' \n')
+git_user_key=$(cat ~git/.ssh/id_rsa.pub)
+
+if ! test -f superuser_token ; then
+    bundle exec ./script/create_superuser_token.rb > superuser_token
+fi
+superuser_token=$(cat superuser_token)
+
+cat > config/arvados-clients.yml <<EOF
+development:
+  gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
+  gitolite_tmp: /var/lib/arvados/git
+  arvados_api_host: $localip:3001
+  arvados_api_token: "$superuser_token"
+  arvados_api_host_insecure: true
+  gitolite_arvados_git_user_key: "$git_user_key"
+EOF
+
+while true ; do
+    su git -c "bundle exec script/arvados-git-sync.rb development"
+    sleep 120
+done
diff --git a/docker/gitolite.rc b/docker/service/githttp/log/main/.gitstub
similarity index 100%
copy from docker/gitolite.rc
copy to docker/service/githttp/log/main/.gitstub
diff --git a/docker/service/githttp/log/run b/docker/service/githttp/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/githttp/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/githttp/run b/docker/service/githttp/run
new file mode 100755
index 0000000..b5baf80
--- /dev/null
+++ b/docker/service/githttp/run
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -e
+
+mkdir -p /root/src
+cd /root/src
+
+export GOPATH=$PWD
+mkdir -p "$GOPATH/src/git.curoverse.com"
+ln -sfn "/usr/src/arvados" "$GOPATH/src/git.curoverse.com/arvados.git"
+go get -t "git.curoverse.com/arvados.git/services/arv-git-httpd"
+install bin/arv-git-httpd /usr/local/bin
+
+localip=$(hostname -I | tr -d ' \n')
+
+export ARVADOS_API_HOST=$localip:3001
+export ARVADOS_API_HOST_INSECURE=1
+export GITOLITE_HTTP_HOME=/var/lib/arvados/git
+export GL_BYPASS_ACCESS_CHECKS=1
+export PATH="$PATH:/var/lib/arvados/git/bin"
+cd ~git
+exec chpst -u git:git /usr/local/bin/arv-git-httpd -address=:9001 -git-command=/var/lib/arvados/git/gitolite/src/gitolite-shell -repo-root=/var/lib/arvados/git/repositories 2>&1
\ No newline at end of file

commit 4fa11d65342c5688c0512f95de20f8674c06b55b
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 20:36:16 2015 -0500

    Reorganized, adds whole service directory.

diff --git a/.gitignore b/.gitignore
index d1086d5..a45f4ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,5 @@ arvados/
 sso-devise-omniauth-provider/
 passenger/
 postgres/
-git/
+var/
 *~
\ No newline at end of file
diff --git a/arvbox b/arvbox
index f636df6..0fc6894 100755
--- a/arvbox
+++ b/arvbox
@@ -47,4 +47,9 @@ case $1 in
      resetdb)
      sudo rm -rf $PG_DATA
      rm -f $SSO_ROOT/database_setup $ARVADOS_ROOT/services/api/database_setup
+     ;;
+
+     log)
+     docker exec -ti arvbox tail -f /etc/service/$2/log/main/current
+     ;;
 esac
diff --git a/docker/Dockerfile b/docker/Dockerfile
index b9bd5a1..3d9f5b2 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -8,7 +8,6 @@ RUN apt-get update && apt-get -y -q install \
 
 ADD runit-docker /root/runit-docker
 ADD gitolite /root/gitolite
-ADD gitolite-setup.sh gitolite.rc /root/
 
 RUN cd /root/runit-docker && \
     make && \
@@ -16,31 +15,8 @@ RUN cd /root/runit-docker && \
 
 ENV LD_PRELOAD /lib/runit-docker.so
 
-RUN mkdir -p \
-    /etc/service/postgres/log/main \
-    /etc/service/sso/log/main \
-    /etc/service/api/log/main \
-    /etc/service/workbench/log/main \
-    /etc/service/ssh/log/main \
-    /etc/service/git/log/main \
-
-ADD run-postgres /etc/service/postgres/run
-ADD logger       /etc/service/postgres/log/run
-
-ADD run-sso /etc/service/sso/run
-ADD logger  /etc/service/sso/log/run
-
-ADD run-api /etc/service/api/run
-ADD logger  /etc/service/api/log/run
-
-ADD run-workbench /etc/service/workbench/run
-ADD logger        /etc/service/workbench/log/run
-
-ADD run-ssh /etc/service/ssh/run
-ADD logger  /etc/service/ssh/log/run
-
-ADD run-git /etc/service/git/run
-ADD logger  /etc/service/git/log/run
+ADD gitolite-setup.sh gitolite.rc /root/
+ADD service /etc/service
 
 # Start the supervisor.
 CMD ["runsvdir", "/etc/service"]
diff --git a/docker/gitolite-setup.sh b/docker/gitolite-setup.sh
index 94010e5..c31aef7 100755
--- a/docker/gitolite-setup.sh
+++ b/docker/gitolite-setup.sh
@@ -2,7 +2,10 @@
 
 set -e
 
-ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
+cd ~
+
+rm -f .ssh/id_rsa .ssh/id_rsa.pub
+ssh-keygen -t rsa -P '' -f .ssh/id_rsa
 cp .ssh/id_rsa.pub .ssh/authorized_keys
 ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
 rm .ssh/authorized_keys
diff --git a/docker/run-postgres b/docker/run-postgres
deleted file mode 100755
index 42211b9..0000000
--- a/docker/run-postgres
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-set -e
-if ! test -d /var/lib/postgresql/9.4/main ; then
-   chown postgres:postgres -R /var/lib/postgresql
-   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
-fi
-mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
-chown postgres:postgres -R /var/run/postgresql
-exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
diff --git a/docker/run-ssh b/docker/run-ssh
deleted file mode 100755
index 366cbc9..0000000
--- a/docker/run-ssh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-/usr/sbin/sshd -D
\ No newline at end of file
diff --git a/docker/service/api/log/main/.gitstub b/docker/service/api/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/api/log/run b/docker/service/api/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/api/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/run-api b/docker/service/api/run
similarity index 99%
rename from docker/run-api
rename to docker/service/api/run
index c6ed5a6..7c2477c 100755
--- a/docker/run-api
+++ b/docker/service/api/run
@@ -62,5 +62,7 @@ EOF
    touch database_setup
 fi
 
+rm -rf tmp
+
 bundle exec rake db:migrate
 ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
diff --git a/docker/service/git/log/main/.gitstub b/docker/service/git/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/git/log/run b/docker/service/git/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/git/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/run-git b/docker/service/git/run
similarity index 100%
rename from docker/run-git
rename to docker/service/git/run
diff --git a/docker/logger b/docker/service/logger
similarity index 100%
rename from docker/logger
rename to docker/service/logger
diff --git a/docker/service/ssh/log/main/.gitstub b/docker/service/ssh/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/ssh/log/run b/docker/service/ssh/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/ssh/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/service/ssh/run b/docker/service/ssh/run
new file mode 100755
index 0000000..d121406
--- /dev/null
+++ b/docker/service/ssh/run
@@ -0,0 +1,6 @@
+#!/bin/sh
+if ! test -d /var/run/sshd ; then
+   mkdir /var/run/sshd
+   chmod 0755 /var/run/sshd
+fi
+/usr/sbin/sshd -D
diff --git a/docker/service/sso/log/main/.gitstub b/docker/service/sso/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/sso/log/run b/docker/service/sso/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/sso/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/run-sso b/docker/service/sso/run
similarity index 99%
rename from docker/run-sso
rename to docker/service/sso/run
index a008b1f..c63229a 100755
--- a/docker/run-sso
+++ b/docker/service/sso/run
@@ -63,5 +63,7 @@ EOF
    touch database_setup
 fi
 
+rm -rf tmp
+
 bundle exec rake db:migrate
 bundle exec passenger start -p3002 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
diff --git a/docker/service/workbench/log/main/.gitstub b/docker/service/workbench/log/main/.gitstub
new file mode 100644
index 0000000..e69de29
diff --git a/docker/service/workbench/log/run b/docker/service/workbench/log/run
new file mode 120000
index 0000000..f99cc1d
--- /dev/null
+++ b/docker/service/workbench/log/run
@@ -0,0 +1 @@
+../../logger
\ No newline at end of file
diff --git a/docker/run-workbench b/docker/service/workbench/run
similarity index 98%
rename from docker/run-workbench
rename to docker/service/workbench/run
index 75b2a63..011769a 100755
--- a/docker/run-workbench
+++ b/docker/service/workbench/run
@@ -26,4 +26,6 @@ common:
   arvados_insecure_https: true
 EOF
 
+rm -rf tmp
+
 bundle exec passenger start -p3000 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key

commit 94a4d8fc1d9cbcfd59d09a03bd335777a0d2d7a4
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 17:15:14 2015 -0500

    Working on adding logging and git

diff --git a/arvbox b/arvbox
index 2d72fa4..f636df6 100755
--- a/arvbox
+++ b/arvbox
@@ -18,7 +18,7 @@ case $1 in
      ;;
 
      start|run)
-     docker run --name=arvbox \
+     docker run --rm --name=arvbox \
        --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
        --volume=$SSO_ROOT:/usr/src/sso:rw \
        --volume=$PG_DATA:/var/lib/postgresql:rw \
@@ -33,7 +33,6 @@ case $1 in
 
      stop)
      docker stop arvbox
-     docker rm arvbox
      ;;
 
      ip|open)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 75b8154..b9bd5a1 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -7,6 +7,8 @@ RUN apt-get update && apt-get -y -q install \
     openssh-server
 
 ADD runit-docker /root/runit-docker
+ADD gitolite /root/gitolite
+ADD gitolite-setup.sh gitolite.rc /root/
 
 RUN cd /root/runit-docker && \
     make && \
@@ -14,11 +16,31 @@ RUN cd /root/runit-docker && \
 
 ENV LD_PRELOAD /lib/runit-docker.so
 
+RUN mkdir -p \
+    /etc/service/postgres/log/main \
+    /etc/service/sso/log/main \
+    /etc/service/api/log/main \
+    /etc/service/workbench/log/main \
+    /etc/service/ssh/log/main \
+    /etc/service/git/log/main \
+
 ADD run-postgres /etc/service/postgres/run
+ADD logger       /etc/service/postgres/log/run
+
 ADD run-sso /etc/service/sso/run
+ADD logger  /etc/service/sso/log/run
+
 ADD run-api /etc/service/api/run
+ADD logger  /etc/service/api/log/run
+
 ADD run-workbench /etc/service/workbench/run
+ADD logger        /etc/service/workbench/log/run
+
+ADD run-ssh /etc/service/ssh/run
+ADD logger  /etc/service/ssh/log/run
+
 ADD run-git /etc/service/git/run
+ADD logger  /etc/service/git/log/run
 
 # Start the supervisor.
 CMD ["runsvdir", "/etc/service"]
diff --git a/docker/gitolite-setup.sh b/docker/gitolite-setup.sh
new file mode 100755
index 0000000..94010e5
--- /dev/null
+++ b/docker/gitolite-setup.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+
+ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
+cp .ssh/id_rsa.pub .ssh/authorized_keys
+ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub
+rm .ssh/authorized_keys
+
+mkdir bin
+gitolite/install -ln ~git/bin
+bin/gitolite setup -pk .ssh/id_rsa.pub
+
+git clone git at localhost:gitolite-admin
+cd gitolite-admin
+git config user.email arvados
+git config user.name arvados
+git config push.default simple
+git push
+
+cp ~git/gitolite.rc ~git/.gitolite.rc
diff --git a/docker/gitolite.rc b/docker/gitolite.rc
new file mode 100644
index 0000000..e69de29
diff --git a/docker/logger b/docker/logger
new file mode 100755
index 0000000..a79a518
--- /dev/null
+++ b/docker/logger
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec svlogd -tt ./main
diff --git a/docker/run-git b/docker/run-git
old mode 100644
new mode 100755
index 842aa54..3b375b8
--- a/docker/run-git
+++ b/docker/run-git
@@ -1,14 +1,16 @@
 #!/bin/sh
+set -e
+
 if ! test -f /var/lib/arvados/git-setup ; then
    mkdir -p /var/lib/arvados/git
+   cp -r /root/gitolite /var/lib/arvados/git/
+   cp -r /root/gitolite /root/gitolite-setup.sh /root/gitolite.rc /var/lib/arvados/git/
+
    useradd --comment git --home-dir /var/lib/arvados/git git
    chown -R git:git ~git
-   cd /var/lib/arvados/git
-   su git -c "ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa"
-   su git -c "cp .ssh/id_rsa.pub .ssh/authorized_keys"
-   su git -c "ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub"
-   su git -c "rm .ssh/authorized_keys"
+   su git -c "/var/lib/arvados/git/gitolite-setup.sh"
+
    touch /var/lib/arvados/git-setup
 fi
 
-sv git stop
+sv stop git
diff --git a/docker/run-ssh b/docker/run-ssh
new file mode 100755
index 0000000..366cbc9
--- /dev/null
+++ b/docker/run-ssh
@@ -0,0 +1,2 @@
+#!/bin/sh
+/usr/sbin/sshd -D
\ No newline at end of file

commit b81bc5e417efab6fa888eba4b2a1537dadcaa92f
Merge: b7565b7 93e6524
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 16:21:27 2015 -0500

    Add 'docker/gitolite/' from commit 'ba265a631e04181911fc8bac1bd63d94d6e9e99a'
    
    git-subtree-dir: docker/gitolite
    git-subtree-mainline: b7565b7838a93aefafbd4bd7d7af37d0fec0153d
    git-subtree-split: ba265a631e04181911fc8bac1bd63d94d6e9e99a

diff --cc docker/gitolite/CHANGELOG
index 0000000,98e0174..98e0174
mode 000000,100644..100644
--- a/docker/gitolite/CHANGELOG
+++ b/docker/gitolite/CHANGELOG
diff --cc docker/gitolite/CONTRIBUTING
index 0000000,11009ba..11009ba
mode 000000,100644..100644
--- a/docker/gitolite/CONTRIBUTING
+++ b/docker/gitolite/CONTRIBUTING
diff --cc docker/gitolite/COPYING
index 0000000,7d5393a..7d5393a
mode 000000,100644..100644
--- a/docker/gitolite/COPYING
+++ b/docker/gitolite/COPYING
diff --cc docker/gitolite/README.markdown
index 0000000,a211fab..a211fab
mode 000000,100644..100644
--- a/docker/gitolite/README.markdown
+++ b/docker/gitolite/README.markdown
diff --cc docker/gitolite/check-g2-compat
index 0000000,508c6fd..508c6fd
mode 000000,100755..100755
--- a/docker/gitolite/check-g2-compat
+++ b/docker/gitolite/check-g2-compat
diff --cc docker/gitolite/contrib/commands/ukm
index 0000000,8a2d361..8a2d361
mode 000000,100755..100755
--- a/docker/gitolite/contrib/commands/ukm
+++ b/docker/gitolite/contrib/commands/ukm
diff --cc docker/gitolite/contrib/hooks/repo-specific/save-push-signatures
index 0000000,2470491..2470491
mode 000000,100755..100755
--- a/docker/gitolite/contrib/hooks/repo-specific/save-push-signatures
+++ b/docker/gitolite/contrib/hooks/repo-specific/save-push-signatures
diff --cc docker/gitolite/contrib/lib/Apache/gitolite.conf
index 0000000,87ba843..87ba843
mode 000000,100644..100644
--- a/docker/gitolite/contrib/lib/Apache/gitolite.conf
+++ b/docker/gitolite/contrib/lib/Apache/gitolite.conf
diff --cc docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
index 0000000,8fde513..8fde513
mode 000000,100644..100644
--- a/docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
+++ b/docker/gitolite/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
diff --cc docker/gitolite/contrib/t/ukm.t
index 0000000,da4fc0b..da4fc0b
mode 000000,100644..100644
--- a/docker/gitolite/contrib/t/ukm.t
+++ b/docker/gitolite/contrib/t/ukm.t
diff --cc docker/gitolite/contrib/triggers/file_mirror
index 0000000,e3d083b..e3d083b
mode 000000,100755..100755
--- a/docker/gitolite/contrib/triggers/file_mirror
+++ b/docker/gitolite/contrib/triggers/file_mirror
diff --cc docker/gitolite/contrib/utils/ad_groups.sh
index 0000000,cc86692..cc86692
mode 000000,100755..100755
--- a/docker/gitolite/contrib/utils/ad_groups.sh
+++ b/docker/gitolite/contrib/utils/ad_groups.sh
diff --cc docker/gitolite/contrib/utils/gitolite-local
index 0000000,5faf0c7..5faf0c7
mode 000000,100755..100755
--- a/docker/gitolite/contrib/utils/gitolite-local
+++ b/docker/gitolite/contrib/utils/gitolite-local
diff --cc docker/gitolite/contrib/utils/ipa_groups.pl
index 0000000,9cffa40..9cffa40
mode 000000,100755..100755
--- a/docker/gitolite/contrib/utils/ipa_groups.pl
+++ b/docker/gitolite/contrib/utils/ipa_groups.pl
diff --cc docker/gitolite/contrib/utils/ldap_groups.sh
index 0000000,01bf5ee..01bf5ee
mode 000000,100755..100755
--- a/docker/gitolite/contrib/utils/ldap_groups.sh
+++ b/docker/gitolite/contrib/utils/ldap_groups.sh
diff --cc docker/gitolite/contrib/utils/rc-format-v3.4
index 0000000,1a11737..1a11737
mode 000000,100755..100755
--- a/docker/gitolite/contrib/utils/rc-format-v3.4
+++ b/docker/gitolite/contrib/utils/rc-format-v3.4
diff --cc docker/gitolite/convert-gitosis-conf
index 0000000,9b92f68..9b92f68
mode 000000,100755..100755
--- a/docker/gitolite/convert-gitosis-conf
+++ b/docker/gitolite/convert-gitosis-conf
diff --cc docker/gitolite/install
index 0000000,98d8aee..98d8aee
mode 000000,100755..100755
--- a/docker/gitolite/install
+++ b/docker/gitolite/install
diff --cc docker/gitolite/src/VREF/COUNT
index 0000000,f4c3eae..f4c3eae
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/COUNT
+++ b/docker/gitolite/src/VREF/COUNT
diff --cc docker/gitolite/src/VREF/EMAIL-CHECK
index 0000000,34c66f5..34c66f5
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/EMAIL-CHECK
+++ b/docker/gitolite/src/VREF/EMAIL-CHECK
diff --cc docker/gitolite/src/VREF/FILETYPE
index 0000000,3f1d5f9..3f1d5f9
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/FILETYPE
+++ b/docker/gitolite/src/VREF/FILETYPE
diff --cc docker/gitolite/src/VREF/MAX_NEWBIN_SIZE
index 0000000,84a9efa..84a9efa
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/MAX_NEWBIN_SIZE
+++ b/docker/gitolite/src/VREF/MAX_NEWBIN_SIZE
diff --cc docker/gitolite/src/VREF/MERGE-CHECK
index 0000000,07f0351..07f0351
mode 000000,100644..100644
--- a/docker/gitolite/src/VREF/MERGE-CHECK
+++ b/docker/gitolite/src/VREF/MERGE-CHECK
diff --cc docker/gitolite/src/VREF/NAME_NC
index 0000000,1a81714..1a81714
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/NAME_NC
+++ b/docker/gitolite/src/VREF/NAME_NC
diff --cc docker/gitolite/src/VREF/VOTES
index 0000000,8dc3563..8dc3563
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/VOTES
+++ b/docker/gitolite/src/VREF/VOTES
diff --cc docker/gitolite/src/VREF/lock
index 0000000,0fc7681..0fc7681
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/lock
+++ b/docker/gitolite/src/VREF/lock
diff --cc docker/gitolite/src/VREF/partial-copy
index 0000000,55a7dcf..55a7dcf
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/partial-copy
+++ b/docker/gitolite/src/VREF/partial-copy
diff --cc docker/gitolite/src/VREF/refex-expr
index 0000000,b788dd9..b788dd9
mode 000000,100755..100755
--- a/docker/gitolite/src/VREF/refex-expr
+++ b/docker/gitolite/src/VREF/refex-expr
diff --cc docker/gitolite/src/commands/D
index 0000000,016a365..016a365
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/D
+++ b/docker/gitolite/src/commands/D
diff --cc docker/gitolite/src/commands/access
index 0000000,1b16c72..1b16c72
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/access
+++ b/docker/gitolite/src/commands/access
diff --cc docker/gitolite/src/commands/config
index 0000000,b996066..b996066
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/config
+++ b/docker/gitolite/src/commands/config
diff --cc docker/gitolite/src/commands/create
index 0000000,d35c4a8..d35c4a8
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/create
+++ b/docker/gitolite/src/commands/create
diff --cc docker/gitolite/src/commands/creator
index 0000000,702df73..702df73
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/creator
+++ b/docker/gitolite/src/commands/creator
diff --cc docker/gitolite/src/commands/desc
index 0000000,4a4bf20..4a4bf20
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/desc
+++ b/docker/gitolite/src/commands/desc
diff --cc docker/gitolite/src/commands/fork
index 0000000,49994fc..49994fc
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/fork
+++ b/docker/gitolite/src/commands/fork
diff --cc docker/gitolite/src/commands/git-annex-shell
index 0000000,572aba6..572aba6
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/git-annex-shell
+++ b/docker/gitolite/src/commands/git-annex-shell
diff --cc docker/gitolite/src/commands/git-config
index 0000000,94211de..94211de
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/git-config
+++ b/docker/gitolite/src/commands/git-config
diff --cc docker/gitolite/src/commands/help
index 0000000,cf54084..cf54084
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/help
+++ b/docker/gitolite/src/commands/help
diff --cc docker/gitolite/src/commands/htpasswd
index 0000000,bbfacc7..bbfacc7
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/htpasswd
+++ b/docker/gitolite/src/commands/htpasswd
diff --cc docker/gitolite/src/commands/info
index 0000000,5079cfa..5079cfa
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/info
+++ b/docker/gitolite/src/commands/info
diff --cc docker/gitolite/src/commands/list-dangling-repos
index 0000000,60a3592..60a3592
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/list-dangling-repos
+++ b/docker/gitolite/src/commands/list-dangling-repos
diff --cc docker/gitolite/src/commands/lock
index 0000000,70c2190..70c2190
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/lock
+++ b/docker/gitolite/src/commands/lock
diff --cc docker/gitolite/src/commands/mirror
index 0000000,dbdc952..dbdc952
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/mirror
+++ b/docker/gitolite/src/commands/mirror
diff --cc docker/gitolite/src/commands/motd
index 0000000,b56e99e..b56e99e
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/motd
+++ b/docker/gitolite/src/commands/motd
diff --cc docker/gitolite/src/commands/option
index 0000000,de49aab..de49aab
mode 000000,100644..100644
--- a/docker/gitolite/src/commands/option
+++ b/docker/gitolite/src/commands/option
diff --cc docker/gitolite/src/commands/owns
index 0000000,d1d8757..d1d8757
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/owns
+++ b/docker/gitolite/src/commands/owns
diff --cc docker/gitolite/src/commands/perms
index 0000000,30984bf..30984bf
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/perms
+++ b/docker/gitolite/src/commands/perms
diff --cc docker/gitolite/src/commands/print-default-rc
index 0000000,79b88c1..79b88c1
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/print-default-rc
+++ b/docker/gitolite/src/commands/print-default-rc
diff --cc docker/gitolite/src/commands/push
index 0000000,f97f730..f97f730
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/push
+++ b/docker/gitolite/src/commands/push
diff --cc docker/gitolite/src/commands/readme
index 0000000,cd9632f..cd9632f
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/readme
+++ b/docker/gitolite/src/commands/readme
diff --cc docker/gitolite/src/commands/rsync
index 0000000,1109ac4..1109ac4
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/rsync
+++ b/docker/gitolite/src/commands/rsync
diff --cc docker/gitolite/src/commands/sshkeys-lint
index 0000000,c7f0c81..c7f0c81
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/sshkeys-lint
+++ b/docker/gitolite/src/commands/sshkeys-lint
diff --cc docker/gitolite/src/commands/sskm
index 0000000,fd60233..fd60233
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/sskm
+++ b/docker/gitolite/src/commands/sskm
diff --cc docker/gitolite/src/commands/sudo
index 0000000,eeb0083..eeb0083
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/sudo
+++ b/docker/gitolite/src/commands/sudo
diff --cc docker/gitolite/src/commands/svnserve
index 0000000,6e68acf..6e68acf
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/svnserve
+++ b/docker/gitolite/src/commands/svnserve
diff --cc docker/gitolite/src/commands/symbolic-ref
index 0000000,b65c792..b65c792
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/symbolic-ref
+++ b/docker/gitolite/src/commands/symbolic-ref
diff --cc docker/gitolite/src/commands/who-pushed
index 0000000,fb37607..fb37607
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/who-pushed
+++ b/docker/gitolite/src/commands/who-pushed
diff --cc docker/gitolite/src/commands/writable
index 0000000,3e97f0b..3e97f0b
mode 000000,100755..100755
--- a/docker/gitolite/src/commands/writable
+++ b/docker/gitolite/src/commands/writable
diff --cc docker/gitolite/src/gitolite
index 0000000,4a4cbf5..4a4cbf5
mode 000000,100755..100755
--- a/docker/gitolite/src/gitolite
+++ b/docker/gitolite/src/gitolite
diff --cc docker/gitolite/src/gitolite-shell
index 0000000,d9ec01f..d9ec01f
mode 000000,100755..100755
--- a/docker/gitolite/src/gitolite-shell
+++ b/docker/gitolite/src/gitolite-shell
diff --cc docker/gitolite/src/lib/Gitolite/Cache.pm
index 0000000,351a13e..351a13e
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Cache.pm
+++ b/docker/gitolite/src/lib/Gitolite/Cache.pm
diff --cc docker/gitolite/src/lib/Gitolite/Common.pm
index 0000000,5d6b749..5d6b749
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Common.pm
+++ b/docker/gitolite/src/lib/Gitolite/Common.pm
diff --cc docker/gitolite/src/lib/Gitolite/Conf.pm
index 0000000,ce7adca..ce7adca
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Conf.pm
+++ b/docker/gitolite/src/lib/Gitolite/Conf.pm
diff --cc docker/gitolite/src/lib/Gitolite/Conf/Explode.pm
index 0000000,cf89620..cf89620
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Conf/Explode.pm
+++ b/docker/gitolite/src/lib/Gitolite/Conf/Explode.pm
diff --cc docker/gitolite/src/lib/Gitolite/Conf/Load.pm
index 0000000,7728a5a..7728a5a
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Conf/Load.pm
+++ b/docker/gitolite/src/lib/Gitolite/Conf/Load.pm
diff --cc docker/gitolite/src/lib/Gitolite/Conf/Store.pm
index 0000000,5568b3f..5568b3f
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Conf/Store.pm
+++ b/docker/gitolite/src/lib/Gitolite/Conf/Store.pm
diff --cc docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm
index 0000000,986494b..986494b
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm
+++ b/docker/gitolite/src/lib/Gitolite/Conf/Sugar.pm
diff --cc docker/gitolite/src/lib/Gitolite/Easy.pm
index 0000000,8f530f2..8f530f2
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Easy.pm
+++ b/docker/gitolite/src/lib/Gitolite/Easy.pm
diff --cc docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm
index 0000000,2eeefcc..2eeefcc
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ b/docker/gitolite/src/lib/Gitolite/Hooks/PostUpdate.pm
diff --cc docker/gitolite/src/lib/Gitolite/Hooks/Update.pm
index 0000000,32cd6e0..32cd6e0
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Hooks/Update.pm
+++ b/docker/gitolite/src/lib/Gitolite/Hooks/Update.pm
diff --cc docker/gitolite/src/lib/Gitolite/Rc.pm
index 0000000,9fd94b5..9fd94b5
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Rc.pm
+++ b/docker/gitolite/src/lib/Gitolite/Rc.pm
diff --cc docker/gitolite/src/lib/Gitolite/Setup.pm
index 0000000,43de5d9..43de5d9
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Setup.pm
+++ b/docker/gitolite/src/lib/Gitolite/Setup.pm
diff --cc docker/gitolite/src/lib/Gitolite/Test.pm
index 0000000,904abbf..904abbf
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Test.pm
+++ b/docker/gitolite/src/lib/Gitolite/Test.pm
diff --cc docker/gitolite/src/lib/Gitolite/Test/Tsh.pm
index 0000000,670178f..670178f
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Test/Tsh.pm
+++ b/docker/gitolite/src/lib/Gitolite/Test/Tsh.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers.pm
index 0000000,16e8aa6..16e8aa6
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm
index 0000000,1fa24bb..1fa24bb
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Alias.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm
index 0000000,e1d977a..e1d977a
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/AutoCreate.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm
index 0000000,74b4217..74b4217
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/CpuTime.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm
index 0000000,6274c3d..6274c3d
mode 000000,100755..100755
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Kindergarten.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm
index 0000000,c88fc92..c88fc92
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Mirroring.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm
index 0000000,6de80a2..6de80a2
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Motd.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm
index 0000000,e913665..e913665
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/RefexExpr.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm
index 0000000,109cb31..109cb31
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/RepoUmask.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm
index 0000000,a2c5c0d..a2c5c0d
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Shell.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm
index 0000000,8cf0e8d..8cf0e8d
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/TProxy.pm
diff --cc docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm
index 0000000,ed86e12..ed86e12
mode 000000,100644..100644
--- a/docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm
+++ b/docker/gitolite/src/lib/Gitolite/Triggers/Writable.pm
diff --cc docker/gitolite/src/syntactic-sugar/continuation-lines
index 0000000,d63475f..d63475f
mode 000000,100644..100644
--- a/docker/gitolite/src/syntactic-sugar/continuation-lines
+++ b/docker/gitolite/src/syntactic-sugar/continuation-lines
diff --cc docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups
index 0000000,0a3a9ae..0a3a9ae
mode 000000,100644..100644
--- a/docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups
+++ b/docker/gitolite/src/syntactic-sugar/keysubdirs-as-groups
diff --cc docker/gitolite/src/syntactic-sugar/macros
index 0000000,a3493a4..a3493a4
mode 000000,100644..100644
--- a/docker/gitolite/src/syntactic-sugar/macros
+++ b/docker/gitolite/src/syntactic-sugar/macros
diff --cc docker/gitolite/src/syntactic-sugar/refex-expr
index 0000000,f9e7706..f9e7706
mode 000000,100644..100644
--- a/docker/gitolite/src/syntactic-sugar/refex-expr
+++ b/docker/gitolite/src/syntactic-sugar/refex-expr
diff --cc docker/gitolite/src/triggers/bg
index 0000000,3c66500..3c66500
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/bg
+++ b/docker/gitolite/src/triggers/bg
diff --cc docker/gitolite/src/triggers/expand-deny-messages
index 0000000,a8b2289..a8b2289
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/expand-deny-messages
+++ b/docker/gitolite/src/triggers/expand-deny-messages
diff --cc docker/gitolite/src/triggers/partial-copy
index 0000000,79b4d48..79b4d48
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/partial-copy
+++ b/docker/gitolite/src/triggers/partial-copy
diff --cc docker/gitolite/src/triggers/post-compile/ssh-authkeys
index 0000000,d5f5d8b..d5f5d8b
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys
+++ b/docker/gitolite/src/triggers/post-compile/ssh-authkeys
diff --cc docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users
index 0000000,2dd6643..2dd6643
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users
+++ b/docker/gitolite/src/triggers/post-compile/ssh-authkeys-shell-users
diff --cc docker/gitolite/src/triggers/post-compile/ssh-authkeys-split
index 0000000,d96d2e9..d96d2e9
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/ssh-authkeys-split
+++ b/docker/gitolite/src/triggers/post-compile/ssh-authkeys-split
diff --cc docker/gitolite/src/triggers/post-compile/update-description-file
index 0000000,e5b7c6a..e5b7c6a
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/update-description-file
+++ b/docker/gitolite/src/triggers/post-compile/update-description-file
diff --cc docker/gitolite/src/triggers/post-compile/update-git-configs
index 0000000,a58a85d..a58a85d
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/update-git-configs
+++ b/docker/gitolite/src/triggers/post-compile/update-git-configs
diff --cc docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list
index 0000000,446b0da..446b0da
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list
+++ b/docker/gitolite/src/triggers/post-compile/update-git-daemon-access-list
diff --cc docker/gitolite/src/triggers/post-compile/update-gitweb-access-list
index 0000000,937226b..937226b
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/update-gitweb-access-list
+++ b/docker/gitolite/src/triggers/post-compile/update-gitweb-access-list
diff --cc docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options
index 0000000,9b499b2..9b499b2
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options
+++ b/docker/gitolite/src/triggers/post-compile/update-gitweb-daemon-from-options
diff --cc docker/gitolite/src/triggers/renice
index 0000000,ba0b726..ba0b726
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/renice
+++ b/docker/gitolite/src/triggers/renice
diff --cc docker/gitolite/src/triggers/repo-specific-hooks
index 0000000,5d52a47..5d52a47
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/repo-specific-hooks
+++ b/docker/gitolite/src/triggers/repo-specific-hooks
diff --cc docker/gitolite/src/triggers/set-default-roles
index 0000000,18ac28b..18ac28b
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/set-default-roles
+++ b/docker/gitolite/src/triggers/set-default-roles
diff --cc docker/gitolite/src/triggers/upstream
index 0000000,c64e2f2..c64e2f2
mode 000000,100755..100755
--- a/docker/gitolite/src/triggers/upstream
+++ b/docker/gitolite/src/triggers/upstream
diff --cc docker/gitolite/t/0-me-first.t
index 0000000,8c9d12b..8c9d12b
mode 000000,100755..100755
--- a/docker/gitolite/t/0-me-first.t
+++ b/docker/gitolite/t/0-me-first.t
diff --cc docker/gitolite/t/C-vs-C.t
index 0000000,fee5cc4..fee5cc4
mode 000000,100644..100644
--- a/docker/gitolite/t/C-vs-C.t
+++ b/docker/gitolite/t/C-vs-C.t
diff --cc docker/gitolite/t/README
index 0000000,4ecdfb7..4ecdfb7
mode 000000,100644..100644
--- a/docker/gitolite/t/README
+++ b/docker/gitolite/t/README
diff --cc docker/gitolite/t/access.t
index 0000000,34e015f..34e015f
mode 000000,100755..100755
--- a/docker/gitolite/t/access.t
+++ b/docker/gitolite/t/access.t
diff --cc docker/gitolite/t/all-yall.t
index 0000000,901b1c2..901b1c2
mode 000000,100755..100755
--- a/docker/gitolite/t/all-yall.t
+++ b/docker/gitolite/t/all-yall.t
diff --cc docker/gitolite/t/basic.t
index 0000000,7579935..7579935
mode 000000,100755..100755
--- a/docker/gitolite/t/basic.t
+++ b/docker/gitolite/t/basic.t
diff --cc docker/gitolite/t/branch-perms.t
index 0000000,e59baea..e59baea
mode 000000,100755..100755
--- a/docker/gitolite/t/branch-perms.t
+++ b/docker/gitolite/t/branch-perms.t
diff --cc docker/gitolite/t/daemon-gitweb-via-perms.t
index 0000000,8707e19..8707e19
mode 000000,100755..100755
--- a/docker/gitolite/t/daemon-gitweb-via-perms.t
+++ b/docker/gitolite/t/daemon-gitweb-via-perms.t
diff --cc docker/gitolite/t/deleg-1.t
index 0000000,89da137..89da137
mode 000000,100755..100755
--- a/docker/gitolite/t/deleg-1.t
+++ b/docker/gitolite/t/deleg-1.t
diff --cc docker/gitolite/t/deleg-2.t
index 0000000,98fb02e..98fb02e
mode 000000,100755..100755
--- a/docker/gitolite/t/deleg-2.t
+++ b/docker/gitolite/t/deleg-2.t
diff --cc docker/gitolite/t/deny-create.t
index 0000000,a4b7e4f..a4b7e4f
mode 000000,100755..100755
--- a/docker/gitolite/t/deny-create.t
+++ b/docker/gitolite/t/deny-create.t
diff --cc docker/gitolite/t/deny-rules-2.t
index 0000000,0ca15fe..0ca15fe
mode 000000,100755..100755
--- a/docker/gitolite/t/deny-rules-2.t
+++ b/docker/gitolite/t/deny-rules-2.t
diff --cc docker/gitolite/t/deny-rules.t
index 0000000,c0e7cbb..c0e7cbb
mode 000000,100755..100755
--- a/docker/gitolite/t/deny-rules.t
+++ b/docker/gitolite/t/deny-rules.t
diff --cc docker/gitolite/t/easy.t
index 0000000,dcd6c1a..dcd6c1a
mode 000000,100755..100755
--- a/docker/gitolite/t/easy.t
+++ b/docker/gitolite/t/easy.t
diff --cc docker/gitolite/t/fork.t
index 0000000,2a7a7b7..2a7a7b7
mode 000000,100755..100755
--- a/docker/gitolite/t/fork.t
+++ b/docker/gitolite/t/fork.t
diff --cc docker/gitolite/t/git-config.t
index 0000000,86a3a7b..86a3a7b
mode 000000,100755..100755
--- a/docker/gitolite/t/git-config.t
+++ b/docker/gitolite/t/git-config.t
diff --cc docker/gitolite/t/gitolite-receive-pack
index 0000000,a4cc5be..a4cc5be
mode 000000,100755..100755
--- a/docker/gitolite/t/gitolite-receive-pack
+++ b/docker/gitolite/t/gitolite-receive-pack
diff --cc docker/gitolite/t/gitolite-upload-pack
index 0000000,5981f17..5981f17
mode 000000,100755..100755
--- a/docker/gitolite/t/gitolite-upload-pack
+++ b/docker/gitolite/t/gitolite-upload-pack
diff --cc docker/gitolite/t/glt
index 0000000,1bf31e8..1bf31e8
mode 000000,100755..100755
--- a/docker/gitolite/t/glt
+++ b/docker/gitolite/t/glt
diff --cc docker/gitolite/t/hostname.t
index 0000000,dfb8885..dfb8885
mode 000000,100755..100755
--- a/docker/gitolite/t/hostname.t
+++ b/docker/gitolite/t/hostname.t
diff --cc docker/gitolite/t/include-subconf.t
index 0000000,48bdaee..48bdaee
mode 000000,100755..100755
--- a/docker/gitolite/t/include-subconf.t
+++ b/docker/gitolite/t/include-subconf.t
diff --cc docker/gitolite/t/info-json.t
index 0000000,74fbdf4..74fbdf4
mode 000000,100755..100755
--- a/docker/gitolite/t/info-json.t
+++ b/docker/gitolite/t/info-json.t
diff --cc docker/gitolite/t/info.t
index 0000000,22b5b94..22b5b94
mode 000000,100755..100755
--- a/docker/gitolite/t/info.t
+++ b/docker/gitolite/t/info.t
diff --cc docker/gitolite/t/invalid-refnames-filenames.t
index 0000000,19267fe..19267fe
mode 000000,100755..100755
--- a/docker/gitolite/t/invalid-refnames-filenames.t
+++ b/docker/gitolite/t/invalid-refnames-filenames.t
diff --cc docker/gitolite/t/keys/admin
index 0000000,676a711..676a711
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/admin
+++ b/docker/gitolite/t/keys/admin
diff --cc docker/gitolite/t/keys/admin.pub
index 0000000,b50a5b9..b50a5b9
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/admin.pub
+++ b/docker/gitolite/t/keys/admin.pub
diff --cc docker/gitolite/t/keys/config
index 0000000,fc75580..fc75580
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/config
+++ b/docker/gitolite/t/keys/config
diff --cc docker/gitolite/t/keys/u1
index 0000000,828d1c3..828d1c3
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u1
+++ b/docker/gitolite/t/keys/u1
diff --cc docker/gitolite/t/keys/u1.pub
index 0000000,264c1f0..264c1f0
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u1.pub
+++ b/docker/gitolite/t/keys/u1.pub
diff --cc docker/gitolite/t/keys/u2
index 0000000,02486a6..02486a6
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u2
+++ b/docker/gitolite/t/keys/u2
diff --cc docker/gitolite/t/keys/u2.pub
index 0000000,916dcf5..916dcf5
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u2.pub
+++ b/docker/gitolite/t/keys/u2.pub
diff --cc docker/gitolite/t/keys/u3
index 0000000,163bdef..163bdef
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u3
+++ b/docker/gitolite/t/keys/u3
diff --cc docker/gitolite/t/keys/u3.pub
index 0000000,e97645c..e97645c
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u3.pub
+++ b/docker/gitolite/t/keys/u3.pub
diff --cc docker/gitolite/t/keys/u4
index 0000000,a669e34..a669e34
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u4
+++ b/docker/gitolite/t/keys/u4
diff --cc docker/gitolite/t/keys/u4.pub
index 0000000,06f3648..06f3648
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u4.pub
+++ b/docker/gitolite/t/keys/u4.pub
diff --cc docker/gitolite/t/keys/u5
index 0000000,ed65131..ed65131
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u5
+++ b/docker/gitolite/t/keys/u5
diff --cc docker/gitolite/t/keys/u5.pub
index 0000000,96a0045..96a0045
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u5.pub
+++ b/docker/gitolite/t/keys/u5.pub
diff --cc docker/gitolite/t/keys/u6
index 0000000,86deee7..86deee7
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u6
+++ b/docker/gitolite/t/keys/u6
diff --cc docker/gitolite/t/keys/u6.pub
index 0000000,de5b06b..de5b06b
mode 000000,100644..100644
--- a/docker/gitolite/t/keys/u6.pub
+++ b/docker/gitolite/t/keys/u6.pub
diff --cc docker/gitolite/t/listers.t
index 0000000,5fbf0ae..5fbf0ae
mode 000000,100755..100755
--- a/docker/gitolite/t/listers.t
+++ b/docker/gitolite/t/listers.t
diff --cc docker/gitolite/t/merge-check.t
index 0000000,fdea318..fdea318
mode 000000,100755..100755
--- a/docker/gitolite/t/merge-check.t
+++ b/docker/gitolite/t/merge-check.t
diff --cc docker/gitolite/t/mirror-test
index 0000000,627dc74..627dc74
mode 000000,100755..100755
--- a/docker/gitolite/t/mirror-test
+++ b/docker/gitolite/t/mirror-test
diff --cc docker/gitolite/t/mirror-test-rc
index 0000000,1d76783..1d76783
mode 000000,100644..100644
--- a/docker/gitolite/t/mirror-test-rc
+++ b/docker/gitolite/t/mirror-test-rc
diff --cc docker/gitolite/t/mirror-test-setup.sh
index 0000000,b35364c..b35364c
mode 000000,100755..100755
--- a/docker/gitolite/t/mirror-test-setup.sh
+++ b/docker/gitolite/t/mirror-test-setup.sh
diff --cc docker/gitolite/t/mirror-test-ssh-config
index 0000000,40de6d7..40de6d7
mode 000000,100644..100644
--- a/docker/gitolite/t/mirror-test-ssh-config
+++ b/docker/gitolite/t/mirror-test-ssh-config
diff --cc docker/gitolite/t/partial-copy.t
index 0000000,5bff843..5bff843
mode 000000,100755..100755
--- a/docker/gitolite/t/partial-copy.t
+++ b/docker/gitolite/t/partial-copy.t
diff --cc docker/gitolite/t/perm-default-roles.t
index 0000000,1a56ff8..1a56ff8
mode 000000,100755..100755
--- a/docker/gitolite/t/perm-default-roles.t
+++ b/docker/gitolite/t/perm-default-roles.t
diff --cc docker/gitolite/t/perm-roles.t
index 0000000,03403d6..03403d6
mode 000000,100755..100755
--- a/docker/gitolite/t/perm-roles.t
+++ b/docker/gitolite/t/perm-roles.t
diff --cc docker/gitolite/t/perms-groups.t
index 0000000,5de75be..5de75be
mode 000000,100755..100755
--- a/docker/gitolite/t/perms-groups.t
+++ b/docker/gitolite/t/perms-groups.t
diff --cc docker/gitolite/t/personal-branches.t
index 0000000,8a08128..8a08128
mode 000000,100755..100755
--- a/docker/gitolite/t/personal-branches.t
+++ b/docker/gitolite/t/personal-branches.t
diff --cc docker/gitolite/t/refex-expr-test-1
index 0000000,1372a1e..1372a1e
mode 000000,100755..100755
--- a/docker/gitolite/t/refex-expr-test-1
+++ b/docker/gitolite/t/refex-expr-test-1
diff --cc docker/gitolite/t/refex-expr-test-2
index 0000000,773e42c..773e42c
mode 000000,100755..100755
--- a/docker/gitolite/t/refex-expr-test-2
+++ b/docker/gitolite/t/refex-expr-test-2
diff --cc docker/gitolite/t/refex-expr-test-3
index 0000000,47599eb..47599eb
mode 000000,100755..100755
--- a/docker/gitolite/t/refex-expr-test-3
+++ b/docker/gitolite/t/refex-expr-test-3
diff --cc docker/gitolite/t/refex-expr-test-9
index 0000000,b3a9f09..b3a9f09
mode 000000,100755..100755
--- a/docker/gitolite/t/refex-expr-test-9
+++ b/docker/gitolite/t/refex-expr-test-9
diff --cc docker/gitolite/t/repo-specific-hooks.t
index 0000000,88976ca..88976ca
mode 000000,100755..100755
--- a/docker/gitolite/t/repo-specific-hooks.t
+++ b/docker/gitolite/t/repo-specific-hooks.t
diff --cc docker/gitolite/t/reset
index 0000000,502de2b..502de2b
mode 000000,100755..100755
--- a/docker/gitolite/t/reset
+++ b/docker/gitolite/t/reset
diff --cc docker/gitolite/t/rule-seq.t
index 0000000,0d97558..0d97558
mode 000000,100755..100755
--- a/docker/gitolite/t/rule-seq.t
+++ b/docker/gitolite/t/rule-seq.t
diff --cc docker/gitolite/t/sequence.t
index 0000000,81fabfc..81fabfc
mode 000000,100755..100755
--- a/docker/gitolite/t/sequence.t
+++ b/docker/gitolite/t/sequence.t
diff --cc docker/gitolite/t/smart-http
index 0000000,98fc8c0..98fc8c0
mode 000000,100755..100755
--- a/docker/gitolite/t/smart-http
+++ b/docker/gitolite/t/smart-http
diff --cc docker/gitolite/t/smart-http.root-setup
index 0000000,7a46dda..7a46dda
mode 000000,100755..100755
--- a/docker/gitolite/t/smart-http.root-setup
+++ b/docker/gitolite/t/smart-http.root-setup
diff --cc docker/gitolite/t/ssh-authkeys.t
index 0000000,46b9413..46b9413
mode 000000,100755..100755
--- a/docker/gitolite/t/ssh-authkeys.t
+++ b/docker/gitolite/t/ssh-authkeys.t
diff --cc docker/gitolite/t/ssh-basic.t
index 0000000,ebed2d2..ebed2d2
mode 000000,100755..100755
--- a/docker/gitolite/t/ssh-basic.t
+++ b/docker/gitolite/t/ssh-basic.t
diff --cc docker/gitolite/t/vrefs-1.t
index 0000000,eea4b24..eea4b24
mode 000000,100755..100755
--- a/docker/gitolite/t/vrefs-1.t
+++ b/docker/gitolite/t/vrefs-1.t
diff --cc docker/gitolite/t/vrefs-2.t
index 0000000,40db308..40db308
mode 000000,100755..100755
--- a/docker/gitolite/t/vrefs-2.t
+++ b/docker/gitolite/t/vrefs-2.t
diff --cc docker/gitolite/t/wild-1.t
index 0000000,7a8f766..7a8f766
mode 000000,100755..100755
--- a/docker/gitolite/t/wild-1.t
+++ b/docker/gitolite/t/wild-1.t
diff --cc docker/gitolite/t/wild-2.t
index 0000000,cbba4f8..cbba4f8
mode 000000,100755..100755
--- a/docker/gitolite/t/wild-2.t
+++ b/docker/gitolite/t/wild-2.t
diff --cc docker/gitolite/t/writable.t
index 0000000,a649323..a649323
mode 000000,100755..100755
--- a/docker/gitolite/t/writable.t
+++ b/docker/gitolite/t/writable.t
diff --cc docker/gitolite/t/z-end.t
index 0000000,6c98fe4..6c98fe4
mode 000000,100755..100755
--- a/docker/gitolite/t/z-end.t
+++ b/docker/gitolite/t/z-end.t

commit b7565b7838a93aefafbd4bd7d7af37d0fec0153d
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 16:21:22 2015 -0500

    Working on adding gitolite support.

diff --git a/arvbox b/arvbox
index 64eca3a..2d72fa4 100755
--- a/arvbox
+++ b/arvbox
@@ -6,10 +6,9 @@ ARVADOS_ROOT=$ARVBOX/arvados
 SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
 PASSENGER=$ARVBOX/passenger
 PG_DATA=$ARVBOX/postgres
-KEEP_DATA=$ARVBOX/keep
-GIT_DATA=$ARVBOX/git
+ARV_DATA=$ARVBOX/var
 
-mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/git $PASSENGER
+mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/var $PASSENGER
 
 case $1 in
      build)
@@ -23,8 +22,7 @@ case $1 in
        --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
        --volume=$SSO_ROOT:/usr/src/sso:rw \
        --volume=$PG_DATA:/var/lib/postgresql:rw \
-       --volume=$KEEP_DATA:/var/lib/keep:rw \
-       --volume=$GIT_DATA:/var/lib/git:rw \
+       --volume=$ARV_DATA:/var/lib/arvados:rw \
        --volume=$PASSENGER:/var/lib/passenger:rw \
        arvados/arvbox
      ;;
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 75f2eb8..75b8154 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -3,11 +3,12 @@ FROM debian:8
 RUN apt-get update && apt-get -y -q install \
     postgresql-9.4 git gcc golang-go runit \
     ruby rake bundler curl libpq-dev \
-    libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev
+    libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev \
+    openssh-server
 
-RUN cd /root && \
-    git clone https://github.com/pixers/runit-docker.git && \
-    cd runit-docker && \
+ADD runit-docker /root/runit-docker
+
+RUN cd /root/runit-docker && \
     make && \
     make install
 
@@ -17,6 +18,7 @@ ADD run-postgres /etc/service/postgres/run
 ADD run-sso /etc/service/sso/run
 ADD run-api /etc/service/api/run
 ADD run-workbench /etc/service/workbench/run
+ADD run-git /etc/service/git/run
 
 # Start the supervisor.
 CMD ["runsvdir", "/etc/service"]
diff --git a/docker/run-api b/docker/run-api
index f014695..c6ed5a6 100755
--- a/docker/run-api
+++ b/docker/run-api
@@ -58,6 +58,7 @@ development:
   template: template0
 EOF
    bundle exec rake db:setup
+   bundle exec ./script/create_superuser_token.rb > superuser_token
    touch database_setup
 fi
 
diff --git a/docker/run-git b/docker/run-git
new file mode 100644
index 0000000..842aa54
--- /dev/null
+++ b/docker/run-git
@@ -0,0 +1,14 @@
+#!/bin/sh
+if ! test -f /var/lib/arvados/git-setup ; then
+   mkdir -p /var/lib/arvados/git
+   useradd --comment git --home-dir /var/lib/arvados/git git
+   chown -R git:git ~git
+   cd /var/lib/arvados/git
+   su git -c "ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa"
+   su git -c "cp .ssh/id_rsa.pub .ssh/authorized_keys"
+   su git -c "ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub"
+   su git -c "rm .ssh/authorized_keys"
+   touch /var/lib/arvados/git-setup
+fi
+
+sv git stop

commit adf54f6eb2391096e0595497f1c05e1d6c687c78
Merge: bcf1f96 ac80adf
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 16:09:13 2015 -0500

    Add 'docker/runit-docker/' from commit 'ac80adf953c4f533abb5d204575754e54b6b7b23'
    
    git-subtree-dir: docker/runit-docker
    git-subtree-mainline: bcf1f9619a2b90f850edade90c2e094c6f1eb5b3
    git-subtree-split: ac80adf953c4f533abb5d204575754e54b6b7b23

diff --cc docker/runit-docker/.gitignore
index 0000000,bbf313b..bbf313b
mode 000000,100644..100644
--- a/docker/runit-docker/.gitignore
+++ b/docker/runit-docker/.gitignore
diff --cc docker/runit-docker/LICENSE
index 0000000,d158667..d158667
mode 000000,100644..100644
--- a/docker/runit-docker/LICENSE
+++ b/docker/runit-docker/LICENSE
diff --cc docker/runit-docker/Makefile
index 0000000,9a28963..9a28963
mode 000000,100644..100644
--- a/docker/runit-docker/Makefile
+++ b/docker/runit-docker/Makefile
diff --cc docker/runit-docker/README.md
index 0000000,1bcb8cc..1bcb8cc
mode 000000,100644..100644
--- a/docker/runit-docker/README.md
+++ b/docker/runit-docker/README.md
diff --cc docker/runit-docker/debian/changelog
index 0000000,7d8689f..7d8689f
mode 000000,100644..100644
--- a/docker/runit-docker/debian/changelog
+++ b/docker/runit-docker/debian/changelog
diff --cc docker/runit-docker/debian/compat
index 0000000,ec63514..ec63514
mode 000000,100644..100644
--- a/docker/runit-docker/debian/compat
+++ b/docker/runit-docker/debian/compat
diff --cc docker/runit-docker/debian/control
index 0000000,4060915..4060915
mode 000000,100644..100644
--- a/docker/runit-docker/debian/control
+++ b/docker/runit-docker/debian/control
diff --cc docker/runit-docker/debian/copyright
index 0000000,8679a6a..8679a6a
mode 000000,100644..100644
--- a/docker/runit-docker/debian/copyright
+++ b/docker/runit-docker/debian/copyright
diff --cc docker/runit-docker/debian/docs
index 0000000,b43bf86..b43bf86
mode 000000,100644..100644
--- a/docker/runit-docker/debian/docs
+++ b/docker/runit-docker/debian/docs
diff --cc docker/runit-docker/debian/rules
index 0000000,ce15cce..ce15cce
mode 000000,100755..100755
--- a/docker/runit-docker/debian/rules
+++ b/docker/runit-docker/debian/rules
diff --cc docker/runit-docker/debian/source/format
index 0000000,163aaf8..163aaf8
mode 000000,100644..100644
--- a/docker/runit-docker/debian/source/format
+++ b/docker/runit-docker/debian/source/format
diff --cc docker/runit-docker/runit-docker
index 0000000,fdbaad5..fdbaad5
mode 000000,100755..100755
--- a/docker/runit-docker/runit-docker
+++ b/docker/runit-docker/runit-docker
diff --cc docker/runit-docker/runit-docker.c
index 0000000,825a35f..825a35f
mode 000000,100644..100644
--- a/docker/runit-docker/runit-docker.c
+++ b/docker/runit-docker/runit-docker.c

commit bcf1f9619a2b90f850edade90c2e094c6f1eb5b3
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 02:08:40 2015 -0500

    Boom it works

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d1086d5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+arvados/
+sso-devise-omniauth-provider/
+passenger/
+postgres/
+git/
+*~
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 4097337..75f2eb8 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -11,10 +11,12 @@ RUN cd /root && \
     make && \
     make install
 
+ENV LD_PRELOAD /lib/runit-docker.so
+
 ADD run-postgres /etc/service/postgres/run
 ADD run-sso /etc/service/sso/run
 ADD run-api /etc/service/api/run
 ADD run-workbench /etc/service/workbench/run
 
 # Start the supervisor.
-CMD /sbin/runit-docker
+CMD ["runsvdir", "/etc/service"]
diff --git a/docker/run-api b/docker/run-api
index ab2806e..f014695 100755
--- a/docker/run-api
+++ b/docker/run-api
@@ -36,7 +36,7 @@ common:
   secret_token: $secret_token
   blob_signing_key: $blob_signing_key
   sso_app_secret: $sso_app_secret
-  sso_app_id: joshid
+  sso_app_id: arvados-server
   sso_provider_url: "https://$localip:3002"
   workbench_address: "https://$localip:3000"
   sso_insecure: true

commit fb710f93783b6d073e742ad2c0982873761917f7
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 01:48:09 2015 -0500

    Bind mount /var/lib/passenger so it doesn't keep building it over and over.

diff --git a/arvbox b/arvbox
index 3eef54b..64eca3a 100755
--- a/arvbox
+++ b/arvbox
@@ -3,12 +3,13 @@
 ARVBOX=$(readlink -f $(dirname $0))
 
 ARVADOS_ROOT=$ARVBOX/arvados
-SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider/
+SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider
+PASSENGER=$ARVBOX/passenger
 PG_DATA=$ARVBOX/postgres
 KEEP_DATA=$ARVBOX/keep
 GIT_DATA=$ARVBOX/git
 
-mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/git
+mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/git $PASSENGER
 
 case $1 in
      build)
@@ -24,6 +25,7 @@ case $1 in
        --volume=$PG_DATA:/var/lib/postgresql:rw \
        --volume=$KEEP_DATA:/var/lib/keep:rw \
        --volume=$GIT_DATA:/var/lib/git:rw \
+       --volume=$PASSENGER:/var/lib/passenger:rw \
        arvados/arvbox
      ;;
 
@@ -41,7 +43,11 @@ case $1 in
      if test $1 = 'ip' ; then
        echo $IP
      else
-       xdg-open http://$IP:3000
+       xdg-open https://$IP:3000
      fi
      ;;
+
+     resetdb)
+     sudo rm -rf $PG_DATA
+     rm -f $SSO_ROOT/database_setup $ARVADOS_ROOT/services/api/database_setup
 esac
diff --git a/docker/run-api b/docker/run-api
index 4fec661..ab2806e 100755
--- a/docker/run-api
+++ b/docker/run-api
@@ -37,8 +37,8 @@ common:
   blob_signing_key: $blob_signing_key
   sso_app_secret: $sso_app_secret
   sso_app_id: joshid
-  sso_provider_url: "http://$localip:3002"
-  workbench_address: "http://$localip:3000"
+  sso_provider_url: "https://$localip:3002"
+  workbench_address: "https://$localip:3000"
   sso_insecure: true
   auto_admin_first_user: true
 EOF
diff --git a/docker/run-workbench b/docker/run-workbench
index 3ed3f24..75b2a63 100755
--- a/docker/run-workbench
+++ b/docker/run-workbench
@@ -26,4 +26,4 @@ common:
   arvados_insecure_https: true
 EOF
 
-bundle exec passenger start -p3000
+bundle exec passenger start -p3000 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key

commit b82fe6f5e11be68f89bdceb88649a643cc289d78
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 01:33:08 2015 -0500

    Adding workbench support

diff --git a/arvbox b/arvbox
index 0310151..3eef54b 100755
--- a/arvbox
+++ b/arvbox
@@ -35,4 +35,13 @@ case $1 in
      docker stop arvbox
      docker rm arvbox
      ;;
+
+     ip|open)
+     IP=$(docker inspect arvbox | grep \"IPAddress\" | tr -d ' ":,\n' | cut -c10-)
+     if test $1 = 'ip' ; then
+       echo $IP
+     else
+       xdg-open http://$IP:3000
+     fi
+     ;;
 esac
diff --git a/docker/Dockerfile b/docker/Dockerfile
index a66f672..4097337 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -13,6 +13,8 @@ RUN cd /root && \
 
 ADD run-postgres /etc/service/postgres/run
 ADD run-sso /etc/service/sso/run
+ADD run-api /etc/service/api/run
+ADD run-workbench /etc/service/workbench/run
 
 # Start the supervisor.
 CMD /sbin/runit-docker
diff --git a/docker/run-api b/docker/run-api
new file mode 100755
index 0000000..4fec661
--- /dev/null
+++ b/docker/run-api
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+set -e
+
+cd /usr/src/arvados/services/api
+export RAILS_ENV=development
+
+bundle install --without=development --path=vendor
+
+if ! test -f uuid_prefix ; then
+  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
+fi
+uuid_prefix=$(cat uuid_prefix)
+
+if ! test -f secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+fi
+secret_token=$(cat secret_token)
+
+if ! test -f blob_signing_key ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > blob_signing_key
+fi
+blob_signing_key=$(cat blob_signing_key)
+
+if ! test -f self-signed.key ; then
+  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
+fi
+
+sso_app_secret=$(cat /usr/src/sso/app_secret)
+
+localip=$(hostname -I | tr -d ' \n')
+
+cat >config/application.yml <<EOF
+common:
+  uuid_prefix: $uuid_prefix
+  secret_token: $secret_token
+  blob_signing_key: $blob_signing_key
+  sso_app_secret: $sso_app_secret
+  sso_app_id: joshid
+  sso_provider_url: "http://$localip:3002"
+  workbench_address: "http://$localip:3000"
+  sso_insecure: true
+  auto_admin_first_user: true
+EOF
+
+if ! test -f database_setup ; then
+   database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
+   su postgres -c "psql -c \"create user arvados with password '$database_pw'\""
+   su postgres -c "psql -c \"ALTER USER arvados CREATEDB;\""
+   cat >config/database.yml <<EOF
+development:
+  adapter: postgresql
+  encoding: utf8
+  database: arvados_development
+  username: arvados
+  password: $database_pw
+  host: localhost
+  template: template0
+EOF
+   bundle exec rake db:setup
+   touch database_setup
+fi
+
+bundle exec rake db:migrate
+ARVADOS_WEBSOCKETS=1 bundle exec passenger start -p3001 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
diff --git a/docker/run-postgres b/docker/run-postgres
index d95b462..42211b9 100755
--- a/docker/run-postgres
+++ b/docker/run-postgres
@@ -2,7 +2,8 @@
 set -e
 if ! test -d /var/lib/postgresql/9.4/main ; then
    chown postgres:postgres -R /var/lib/postgresql
-   chown postgres:postgres -R /var/run/postgresql
    su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
 fi
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+chown postgres:postgres -R /var/run/postgresql
 exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
diff --git a/docker/run-sso b/docker/run-sso
index a82cb0a..a008b1f 100755
--- a/docker/run-sso
+++ b/docker/run-sso
@@ -5,6 +5,8 @@ set -e
 cd /usr/src/sso
 export RAILS_ENV=development
 
+bundle install --without=development --path=vendor
+
 if ! test -f uuid_prefix ; then
   ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
 fi
@@ -15,9 +17,29 @@ if ! test -f secret_token ; then
 fi
 secret_token=$(cat secret_token)
 
+if ! test -f app_secret ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > app_secret
+fi
+app_secret=$(cat app_secret)
+
+if ! test -f self-signed.key ; then
+  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
+fi
+
+localip=$(hostname -I | tr -d ' \n')
+
+cat >config/application.yml <<EOF
+common:
+  uuid_prefix: $uuid_prefix
+  secret_token: $secret_token
+  default_link_url: "http://$localip:3000"
+  allow_account_registration: true
+EOF
+
 if ! test -f database_setup ; then
    database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
-   su postgres -c "psql -c 'create user \"arvados_sso\" with password \"$database_pw\"'"
+   su postgres -c "psql -c \"create user arvados_sso with password '$database_pw'\""
+   su postgres -c "psql -c \"ALTER USER arvados_sso CREATEDB;\""
    cat >config/database.yml <<EOF
 development:
   adapter: postgresql
@@ -28,20 +50,18 @@ development:
   host: localhost
   template: template0
 EOF
-  touch database_setup
-fi
+   bundle exec rake db:setup
 
-localip=$(hostname -I)
-
-cat >config/application.yml <<EOF
-common:
-  uuid_prefix: $uuid_prefix
-  secret_token: $secret_token
-  default_link_url: "http://$localip:3000"
-  allow_account_registration: true
+   bundle exec rails console <<EOF
+c = Client.new
+c.name = "joshid"
+c.app_id = "arvados-server"
+c.app_secret = "$app_secret"
+c.save!
 EOF
 
-bundle install
-rake db:setup
-rake db:migrate
-passenger start -p3002
+   touch database_setup
+fi
+
+bundle exec rake db:migrate
+bundle exec passenger start -p3002 --ssl --ssl-certificate=self-signed.pem --ssl-certificate-key=self-signed.key
diff --git a/docker/run-workbench b/docker/run-workbench
new file mode 100755
index 0000000..3ed3f24
--- /dev/null
+++ b/docker/run-workbench
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+cd /usr/src/arvados/apps/workbench
+export RAILS_ENV=development
+
+bundle install --without=development --path=vendor
+
+if ! test -f secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+fi
+secret_token=$(cat secret_token)
+
+if ! test -f self-signed.key ; then
+  openssl req -new -x509 -nodes -out self-signed.pem -keyout self-signed.key -days 365 -subj '/CN=localhost'
+fi
+
+localip=$(hostname -I | tr -d ' \n')
+
+cat >config/application.yml <<EOF
+common:
+  secret_token: $secret_token
+  arvados_login_base: https://$localip:3001/login
+  arvados_v1_base: https://$localip:3001/arvados/v1
+  arvados_insecure_https: true
+EOF
+
+bundle exec passenger start -p3000

commit 3a9b3ba6efb149cf3f572a17358fe4d8972bef8a
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Sat Dec 19 00:11:02 2015 -0500

    work in progress

diff --git a/arvbox b/arvbox
index e7ba49f..0310151 100755
--- a/arvbox
+++ b/arvbox
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-ARVBOX=$PWD
+ARVBOX=$(readlink -f $(dirname $0))
 
 ARVADOS_ROOT=$ARVBOX/arvados
 SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider/
@@ -8,21 +8,31 @@ PG_DATA=$ARVBOX/postgres
 KEEP_DATA=$ARVBOX/keep
 GIT_DATA=$ARVBOX/git
 
+mkdir -p $ARVBOX/postgres $ARVBOX/keep $ARVBOX/git
+
 case $1 in
      build)
      git clone https://github.com/curoverse/arvados.git
      git clone https://github.com/curoverse/sso-devise-omniauth-provider.git
-     docker build -t arvados/arvbox .
+     docker build -t arvados/arvbox docker
      ;;
 
-     run)
-     docker run \
-       --volume=$ARVADOS_ROOT:/usr/src/arvados \
-       --volume=$SSO_ROOT:/usr/src/sso \
-       --volume=$PG_DATA:/var/lib/postgresql \
-       --volume=$KEEP_DATA:/var/lib/keep \
-       --volume=$GIT_DATA:/var/lib/git \
+     start|run)
+     docker run --name=arvbox \
+       --volume=$ARVADOS_ROOT:/usr/src/arvados:rw \
+       --volume=$SSO_ROOT:/usr/src/sso:rw \
+       --volume=$PG_DATA:/var/lib/postgresql:rw \
+       --volume=$KEEP_DATA:/var/lib/keep:rw \
+       --volume=$GIT_DATA:/var/lib/git:rw \
        arvados/arvbox
+     ;;
+
+     shell)
+     docker exec -ti arvbox /bin/bash
+     ;;
 
+     stop)
+     docker stop arvbox
+     docker rm arvbox
      ;;
 esac
diff --git a/Dockerfile b/docker/Dockerfile
similarity index 100%
rename from Dockerfile
rename to docker/Dockerfile
diff --git a/docker/run-postgres b/docker/run-postgres
new file mode 100755
index 0000000..d95b462
--- /dev/null
+++ b/docker/run-postgres
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+if ! test -d /var/lib/postgresql/9.4/main ; then
+   chown postgres:postgres -R /var/lib/postgresql
+   chown postgres:postgres -R /var/run/postgresql
+   su postgres -c "/usr/lib/postgresql/9.4/bin/initdb -D /var/lib/postgresql/9.4/main"
+fi
+exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
diff --git a/run-sso b/docker/run-sso
similarity index 100%
rename from run-sso
rename to docker/run-sso
diff --git a/run-postgres b/run-postgres
deleted file mode 100755
index dd8e3d1..0000000
--- a/run-postgres
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-set -e
-mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
-chown postgres:postgres /var/run/postgresql/9.4-main.pg_stat_tmp
-exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"

commit ea7ccff9d23fa599d5e9d86de1131900a271f706
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Fri Dec 18 23:31:47 2015 -0500

    arvbox start

diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a66f672
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM debian:8
+
+RUN apt-get update && apt-get -y -q install \
+    postgresql-9.4 git gcc golang-go runit \
+    ruby rake bundler curl libpq-dev \
+    libcurl4-openssl-dev libssl-dev zlib1g-dev libpcre3-dev
+
+RUN cd /root && \
+    git clone https://github.com/pixers/runit-docker.git && \
+    cd runit-docker && \
+    make && \
+    make install
+
+ADD run-postgres /etc/service/postgres/run
+ADD run-sso /etc/service/sso/run
+
+# Start the supervisor.
+CMD /sbin/runit-docker
diff --git a/arvbox b/arvbox
new file mode 100755
index 0000000..e7ba49f
--- /dev/null
+++ b/arvbox
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+ARVBOX=$PWD
+
+ARVADOS_ROOT=$ARVBOX/arvados
+SSO_ROOT=$ARVBOX/sso-devise-omniauth-provider/
+PG_DATA=$ARVBOX/postgres
+KEEP_DATA=$ARVBOX/keep
+GIT_DATA=$ARVBOX/git
+
+case $1 in
+     build)
+     git clone https://github.com/curoverse/arvados.git
+     git clone https://github.com/curoverse/sso-devise-omniauth-provider.git
+     docker build -t arvados/arvbox .
+     ;;
+
+     run)
+     docker run \
+       --volume=$ARVADOS_ROOT:/usr/src/arvados \
+       --volume=$SSO_ROOT:/usr/src/sso \
+       --volume=$PG_DATA:/var/lib/postgresql \
+       --volume=$KEEP_DATA:/var/lib/keep \
+       --volume=$GIT_DATA:/var/lib/git \
+       arvados/arvbox
+
+     ;;
+esac
diff --git a/run-postgres b/run-postgres
new file mode 100755
index 0000000..dd8e3d1
--- /dev/null
+++ b/run-postgres
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+mkdir -p /var/run/postgresql/9.4-main.pg_stat_tmp
+chown postgres:postgres /var/run/postgresql/9.4-main.pg_stat_tmp
+exec su postgres -c "/usr/lib/postgresql/9.4/bin/postgres -D /var/lib/postgresql/9.4/main -c config_file=/etc/postgresql/9.4/main/postgresql.conf"
diff --git a/run-sso b/run-sso
new file mode 100755
index 0000000..a82cb0a
--- /dev/null
+++ b/run-sso
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+set -e
+
+cd /usr/src/sso
+export RAILS_ENV=development
+
+if ! test -f uuid_prefix ; then
+  ruby -e 'puts "#{rand(2**64).to_s(36)[0,5]}"' > uuid_prefix
+fi
+uuid_prefix=$(cat uuid_prefix)
+
+if ! test -f secret_token ; then
+  ruby -e 'puts rand(2**400).to_s(36)' > secret_token
+fi
+secret_token=$(cat secret_token)
+
+if ! test -f database_setup ; then
+   database_pw=$(ruby -e 'puts rand(2**128).to_s(36)')
+   su postgres -c "psql -c 'create user \"arvados_sso\" with password \"$database_pw\"'"
+   cat >config/database.yml <<EOF
+development:
+  adapter: postgresql
+  encoding: utf8
+  database: arvados_sso_development
+  username: arvados_sso
+  password: $database_pw
+  host: localhost
+  template: template0
+EOF
+  touch database_setup
+fi
+
+localip=$(hostname -I)
+
+cat >config/application.yml <<EOF
+common:
+  uuid_prefix: $uuid_prefix
+  secret_token: $secret_token
+  default_link_url: "http://$localip:3000"
+  allow_account_registration: true
+EOF
+
+bundle install
+rake db:setup
+rake db:migrate
+passenger start -p3002

commit 93e652485ea97da4e000c9ac0b193fdd81ba08b9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Nov 1 17:31:20 2015 +0530

    v3.6.4

diff --git a/CHANGELOG b/CHANGELOG
index 021ebdc..98e0174 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+2015-11-01  v3.6.4  a ref-create bug in wild repos was fixed
+
+                    some contrib code related to AD integration, and to
+                    redmine user aliases
+
+                    teach Alias.pm a few new tricks
+
+                    remove a race condition in 'create' command that affected
+                    the 'default roles' setting
+
+                    make 'who-pushed' more efficient (local push logs, and
+                    'tip search')
+
+                    'gitolite query-rc' learns '-d' ('--dump') option
+
 2015-04-26  v3.6.3  allow limited use of 'git config' using the new 'config'
                     command
 

commit e235299adaa2aec4bc6ad2d5f874b2c4102f631c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Oct 18 06:47:18 2015 +0530

    access -s: better error message for ^C on existing repo...
    
    because "this should not happen"... happened!
    
    (thanks to hseg on irc for catching this)

diff --git a/src/commands/access b/src/commands/access
index e8b9446..1b16c72 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -56,7 +56,7 @@ if ( $repo ne '%' and $user ne '%' ) {
     # single repo, single user; no STDIN
     $ret = access( $repo, $user, $aa, $ref );
 
-    show() if $s;
+    show($ret) if $s;
 
     if ( $ret =~ /DENIED/ ) {
         print "$ret\n" unless $q;
@@ -81,7 +81,10 @@ while (<>) {
 }
 
 sub show {
-    my $in = $rc{RULE_TRACE} or die "this should not happen!";
+    my $ret = shift;
+    die "repo already exists; ^C won't work\n" if $ret =~ /DENIED by existence/;
+
+    my $in = $rc{RULE_TRACE} or die "this should not happen! $ret";
 
     print STDERR "legend:";
     print STDERR "

commit be5c2f5752a958a89d085c18b28d1df9c34affdf
Author: gitolite tester <tester at example.com>
Date:   Fri Oct 16 16:22:05 2015 +0530

    fix ref-create permission bug on wild repos
    
    TLDR: users are able to *create* new refs that they are not supposed to.
    Upgrading gitolite is not mandatory; there is a workaround within the conf
    syntax itself (see below).
    
    The bug shows up if the following four conditions are satisfied:
    
        repo foo/..*                    # 1 (see below)
            C       =   @all            # 2
            RW+CD   =   CREATOR         # 3
            RW      =   special_user    # 4
    
    1.  must be a wild repo
    2.  the create-repo line must allow "special_user(s)"
    3.  there is at least one rule containing RW.*C in the repo
    4.  the intent is that special user(s) can ff-push but cannot *create* refs
        since they have only RW; for a refresher on this, see
        http://gitolite.com/gitolite/conf.html#write-types
    
    In such cases, the restriction on creating a ref is ignored.  (Only creation
    is affected; the bug does not affect delete, rewind permissions).
    
    In general, this is a relatively minor bug.  No data is destroyed, and no data
    is leaked.  Some cleanup of useless refs may be required if someone has
    (ab)used this.  At worst this could be a potential DOS, but I have never
    considered DOS to be a valid concern when it requires *authorised* users.
    
    If you have such a conf, and cannot immediately upgrade, there is a
    workaround:
    
        repo foo/..*
            RW+CD   =   CREATOR
            RW      =   special_user
    
            -       =   special_user    # add a deny rule for the special user(s)
            C       =   @all            # move the repo-create perm to the end,
                                        # *after* the deny rule above
    
    (Thanks to hseg on #gitolite for catching this and being tenacious about it!
    At first, I was arrogant enough to reject the idea that such a bug could
    exist; it took me a bit to get the right conditions in place and see the
    problem!)

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 88d1612..7728a5a 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -123,9 +123,21 @@ sub access {
         trace( 2, "DENIED by $refex" ) if $perm eq '-';
         return "$aa $safe_ref $repo $user DENIED by $refex" if $perm eq '-';
 
-        # $perm can be RW\+?(C|D|CD|DC)?M?.  $aa can be W, +, C or D, or
-        # any of these followed by "M".
+        # For repo creation, perm will be C and aa will be "^C".  For branch
+        # access, $perm can be RW\+?(C|D|CD|DC)?M?, and $aa can be W, +, C or
+        # D, or any of these followed by "M".
+
+        # We need to turn $aa into a regex that can match a suitable $perm.
+        # This is trivially true for "^C", "W" and "D", but the others (+, C,
+        # M) need some tweaking.
+
+        # first, quote the '+':
         ( my $aaq = $aa ) =~ s/\+/\\+/;
+        # if aa is just "C", the user is trying to create a *branch* (not a
+        # *repo*), so let's make the pattern clearer to reflect that.
+        $aaq = "RW.*C" if $aaq eq "C";
+        # if the aa is, say "WM", make this "W.*M" because the perm could be
+        # 'RW+M', 'RW+CDM' etc, and they are all valid:
         $aaq =~ s/M/.*M/;
 
         $rc{RULE_TRACE} .= "A";
diff --git a/t/C-vs-C.t b/t/C-vs-C.t
new file mode 100644
index 0000000..fee5cc4
--- /dev/null
+++ b/t/C-vs-C.t
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# the commit message in which this test is introduced should have details, but
+# briefly, this test makes sure that access() does not get confused by
+# repo-create permissions being allowed, when looking for branch-create
+# permissions.
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Test;
+
+# branch permissions test
+# ----------------------------------------------------------------------
+
+try "plan 25";
+
+confreset;confadd '
+    repo foo/..*
+        C       =   @all
+        RW+CD   =   CREATOR
+        RW      =   u2
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..;                          ok
+    glt clone u1 file:///foo/aa;    ok
+    cd aa;                          ok
+    tc l-1;                         ok;     /master/
+    glt push u1 origin master:m1;   ok;     /To file:///foo/aa/
+                                            /\\* \\[new branch\\]      master -> m1/
+
+    tc l-2;                         ok;     /master/
+    glt push u2 origin master:m2;   !ok;    /FATAL: C/
+                                            /DENIED by fallthru/
+    glt push u2 origin master:m1;   ok;     /To file:///foo/aa/
+                                            /8cd302a..29b8683/
+                                            /master -> m1/
+";

commit ea8240350eabaa1a0ac911b8f87e89903383a633
Author: gitolite tester <tester at example.com>
Date:   Fri Oct 16 10:45:11 2015 +0530

    minor fixups to 'perms' command:
    
    -   when list_roles is invoked as part of the error reporting for an invalid
        role (as opposed to being explicitly asked for by option '-lr'), the
        output should go to STDERR, just like the error message.
    
    -   the Ctrl-C stuff doesn't work when the user is sharing multiple ssh
        sessions over a single connection (see, ControlMaster, ControlPersist,
        etc., in 'man ssh_config').  Replaced it with a more explicit means to
        allow a user who inadvertently walked into this mode of operation to
        gracefully get out.
    
    Thanks to Stephane Chazelas on the mailing list[1] for reporting the issues,
    subsequent discussion, and patches which I was able to modify as needed.
    
    [1]: https://groups.google.com/forum/#!topic/gitolite/Fcw1Et9PGmw

diff --git a/src/commands/perms b/src/commands/perms
index 0b7c5b2..30984bf 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -99,14 +99,15 @@ sub setperms {
     if ( not @_ ) {
         # legacy mode; pipe data in
         print STDERR "'batch' mode started, waiting for input (run with '-h' for details).\n";
-        print STDERR "Please hit Ctrl-C if you did not intend to do this.\n";
+        print STDERR "Please enter 'cancel' to abort if you did not intend to do this.\n";
         @ARGV = ();
         my @a;
-        for (<>) {
+        while (<>) {
+            _die "CANCELLED" if /^\s*cancel\s*$/i;
             invalid_role($1) if /(\S+)/ and not $rc{ROLES}{$1};
             push @a, $_;
         }
-        print STDERR "\n";    # make sure Ctrl-C gets caught
+
         _print( $pf, @a );
         return;
     }
@@ -167,6 +168,7 @@ sub invalid_role {
     my $role = shift;
 
     print STDERR "Invalid role '$role'; valid roles for this repo:\n";
+    open(STDOUT, '>&', \*STDERR);   # make list_roles print to STDERR
     list_roles();
     exit 1;
 }

commit bf784088e8c8330d4e25cb3d70431d364c298b52
Author: Jonathan Gray <jhg03a at acu.edu>
Date:   Wed Sep 16 15:56:01 2015 -0500

    Add script to pull and sanitize Active Directory user groups

diff --git a/contrib/utils/ad_groups.sh b/contrib/utils/ad_groups.sh
new file mode 100755
index 0000000..cc86692
--- /dev/null
+++ b/contrib/utils/ad_groups.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# author derived from: damien.nozay at gmail.com
+# author: Jonathan Gray
+
+# Given a username,
+# Provides a space-separated list of groups that the user is a member of.
+#
+# see http://gitolite.com/gitolite/conf.html#ldap
+# GROUPLIST_PGM => /path/to/ldap_groups.sh
+
+# Be sure to add your domain CA to the trusted certificates in /etc/openldap/ldap.conf using the TLS_CACERT option or you'll get certificate validation errors
+
+ldaphost='ldap://AD.DC1.local:3268,ldap://AD.DC2.local:3268,ldap://AD.DC3.local:3268'
+ldapuser='git at domain.local'
+ldappass='super.secret.password'
+binddn='dc=domain,dc=local'
+username=$1;
+
+# I don't assume your users share a common OU, so I search the entire domain
+ldap_groups() {
+        # Go fetch the full user CN as it could be anywhere inside the DN
+        usercn=$(
+                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(sAMAccountName=${username})" \
+                | grep "^dn:" \
+                | perl -pe 's|dn: (.*?)|\1|'
+        )
+
+        # Using a proprietary AD extension, let the AD Controller resolve all nested group memberships
+        # http://ddkonline.blogspot.com/2010/05/how-to-recursively-get-group-membership.html
+        # Also, substitute spaces in AD group names for '_' since gitolite expects a space separated list
+        echo $(
+                ldapsearch -ZZ -H ${ldaphost} -D ${ldapuser} -w ${ldappass} -b ${binddn} -LLL -o ldif-wrap=no "(member:1.2.840.113556.1.4.1941:=${usercn})" \
+                | grep "^dn:" \
+                | perl -pe 's|dn: CN=(.*?),.*|\1|' \
+                | sed 's/ /_/g'
+        )
+}
+
+ldap_groups $@

commit cd35b0243ab15526fcbff311c20a09d649774057
Author: Jonathan Gray <jhg03a at acu.edu>
Date:   Wed Sep 16 15:55:05 2015 -0500

    Add documentation for using Active Directory authentication via Apache

diff --git a/contrib/lib/Apache/gitolite.conf b/contrib/lib/Apache/gitolite.conf
new file mode 100644
index 0000000..87ba843
--- /dev/null
+++ b/contrib/lib/Apache/gitolite.conf
@@ -0,0 +1,47 @@
+# Apache Gitolite smart-http install Active Directory Authentication
+
+# Author: Jonathan Gray
+
+# It is assumed you already have mod_ssl, mod_ldap, & mod_authnz configured for apache
+# It is also assumed you are disabling http on port 80 and requiring the use of https on port 443
+
+# Boiler plate configuration from the smart-http deployment documentation script
+# Adjust paths if you use something other than the default
+SetEnv GIT_PROJECT_ROOT /var/www/gitolite-home/repositories
+ScriptAlias /git/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
+ScriptAlias /gitmob/ /var/www/gitolite-home/gitolite-source/src/gitolite-shell/
+SetEnv GITOLITE_HTTP_HOME /var/www/gitolite-home
+SetEnv GIT_HTTP_EXPORT_ALL
+ 
+# Setup LDAP trusted root certificate from your domain
+LDAPTrustedGlobalCert CA_BASE64 /etc/httpd/conf.d/domain.ca.cer
+
+# In case you havn't setup proper SSL certificates in ssl.conf, go ahead and do it here to save headache later with git
+SSLCertificateFile /etc/httpd/conf.d/gitolite.server.crt
+SSLCertificateKeyFile /etc/httpd/conf.d/gitolite.server.key
+SSLCertificateChainFile /etc/httpd/conf.d/DigiCertCA.crt
+ 
+<Location /git>
+        Order deny,allow
+	# In case you want to restrict access to a given ip/subnet
+        #Allow from my.ip.range/cidr
+        #Deny from All
+        AuthType Basic
+        AuthName "Git"
+        AuthBasicProvider ldap
+        AuthUserFile /dev/null
+        AuthzLDAPAuthoritative on
+        AuthLDAPURL ldaps://AD.DC1.local:3269 AD.DC2.local:3269 AD.DC3.local:3269/?sAMAccountName?sub
+        AuthLDAPBindDN git at domain.local
+        AuthLDAPBindPassword super.secret.password
+        AuthLDAPGroupAttributeIsDN on
+ 
+	# You must use one of the two following approaches to handle authentication via active directory
+	
+        # Require membership in the gitolite users group in AD
+        # The ldap-filter option is used to handle nested groups on the AD server rather than multiple calls to traverse from apache
+        # Require ldap-filter memberof:1.2.840.113556.1.4.1941:=cn=Gitolite Users,ou=Security Groups,dc=domain,dc=local
+
+	# Alternatively, require a valid user account only since you're going to control authorization in gitolite anyway
+	Require valid-user
+</Location>

commit 24171564e63d4072b2eeeb3e4dad7d2b231b31ec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Aug 14 17:24:17 2015 +0530

    minor typo in pattern...
    
    note that this does not affect anything, since the 6 extra characters that
    would be matched are not part of the base64 character set anyway, so they
    should not be *produced* by your ssh-keygen.
    
    thanks to 'ayekat' on irc pointing me to a comment on the commit on github.
    (Please don't put stuff on github and expect me to find it; I prefer plain old
    email because -- among other things -- I don't want to insist that you have a
    github account in order to discuss gitolite).

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 374bdb8..c7f0c81 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -163,7 +163,7 @@ sub fprint {
     # dbg("fp = $fp");
     close $fh;
     unlink $tempfn if $tempfn;
-    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-ZA-z0-9+/]+));
+    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-Za-z0-9+/]+));
 
     return $1;
 }

commit 4be7ac535c6135b373de70020de4d8874c7990a4
Author: gitolite tester <tester at example.com>
Date:   Sat Jun 6 06:24:06 2015 +0530

    contrib: redmine user alias

diff --git a/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm b/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
new file mode 100644
index 0000000..8fde513
--- /dev/null
+++ b/contrib/lib/Gitolite/Triggers/RedmineUserAlias.pm
@@ -0,0 +1,55 @@
+package Gitolite::Triggers::RedmineUserAlias;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# aliasing a redmine username to a more user-friendly one
+# ----------------------------------------------------------------------
+
+=for usage
+
+Why:
+
+    Redmine creates users like "redmine_alice_123"; we want the users to just
+    see "alice" instead of that.
+
+Assumption:
+
+*   Redmine does not allow duplicates in the middle bit; i.e., you can't
+    create redmine_alice_123 and redmine_alice_456 also.
+
+How:
+
+*   add this code as lib/Gitolite/Triggers/RedmineUserAlias.pm to your
+    site-local code directory; see this link for how:
+
+        http://gitolite.com/gitolite/non-core.html#ncloc
+
+*   add the following to the rc file, just before the ENABLE section (don't
+    forget the trailing comma):
+
+        INPUT   =>  [ 'RedmineUserAlias::input' ],
+
+Notes:
+
+*   http mode has not been tested and will not be.  If someone has the time to
+    test it and make it work please let me know.
+
+*   not tested with mirroring.
+
+Quote:
+
+*   "All that for what is effectively one line of code.  I need a life".
+
+=cut
+
+sub input {
+    $ARGV[0] or _die "no username???";
+    $ARGV[0] =~ s/^redmine_(\S+)_\d+$/$1/;
+}
+
+1;

commit 2b918fc3700cfc78613f91d9ea230c1f2eec4055
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 30 15:58:54 2015 +0530

    Alias.pm learns a few more tricks...
    
    (thanks to Nicholas Clark for the first requirement)
    
    -   allow replacement by "prefix"
    -   allow customised warning message (to better warn users of the new repo
        name)
    -   allow "inform and abort" -- forces users to switch to the new name

diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm
index acff44b..1fa24bb 100644
--- a/src/lib/Gitolite/Triggers/Alias.pm
+++ b/src/lib/Gitolite/Triggers/Alias.pm
@@ -26,16 +26,42 @@ Why:
 
 How:
 
+  * uncomment the line "Alias" in the "user-visible behaviour" section in the
+    rc file
+
   * add a new variable REPO_ALIASES to the rc file, with entries like:
 
         REPO_ALIASES                =>
             {
+                # if you need a more aggressive warning message than the default
+                WARNING             => "Please change your URLs to use '%new'; '%old' will not work after XXXX-XX-XX",
+
+                # prefix mapping section
+                PREFIX_MAPS         =>  {
+                    # note: NO leading slash in keys or values below
+                    'var/lib/git/'  =>  '',
+                    'var/opt/git/'  =>  'opt/',
+                },
+
+                # individual repo mapping section
                 'foo'               =>  'foo/code',
-            }
 
-  * add the following line to the INPUT section in the rc file:
+                # force users to change their URLs
+                'bar'               =>  '301/bar/code',
+                    # a target repo starting with "301/" won't actually work;
+                    # it will just produce an error message pointing the user
+                    # to the new name.  This allows admins to force users to
+                    # fix their URLs.
+            },
+
+    If a prefix map is supplied, each key is checked (in *undefined* order),
+    and the *first* key which matches the prefix of the repo will be applied.
+    If more than one key matches (for example if you specify '/abc/def' as one
+    key, and '/abc' as another), it is undefined which will get picked up.
 
-        'Alias::input',
+    The result of this, (or the original repo name if no map was found), will
+    then be subject to the individual repo mappings.  Since these are full
+    repo names, there is no possibility of multiple matches.
 
 Notes:
 
@@ -65,13 +91,34 @@ sub input {
         my $repo = $1;
         ( my $norm = $repo ) =~ s/\.git$//;    # normalised repo name
 
-        my $target;
+        my $target = $norm;
 
-        return unless $target = $rc{REPO_ALIASES}{$norm};
+        # prefix maps first
+        my $pm = $rc{REPO_ALIASES}{PREFIX_MAPS} || {};
+        while (my($k, $v) = each %$pm) {
+            last if $target =~ s/^$k/$v/;
+            # no /i, /g, etc. by design
+        }
+
+        # individual repo map next
+        $target = $rc{REPO_ALIASES}{$target} || $target;
+
+        # undocumented; don't use without discussing on mailing list
         $target = $target->{$user} if ref($target) eq 'HASH';
+
+        # if the repo name finally maps to empty, we bail, with no changes
         return unless $target;
 
-        _warn "'$norm' is an alias for '$target'";
+        # we're done.  Did we actually change anything?
+        return if $norm eq $target;
+
+        # if the new name starts with "301/", inform and abort
+        _die "please use '$target' instead of '$norm'" if $target =~ s(^301/)();
+        # otherwise print a warning and continue with the new name
+        my $wm = $rc{REPO_ALIASES}{WARNING} || "'%old' is an alias for '%new'";
+        $wm =~ s/%new/$target/g;
+        $wm =~ s/%old/$norm/g;
+        _warn $wm;
 
         $ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/;
     }

commit 4ed15ad8e5d82995b3d55cd17446fc90a3c2b7ec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 23 08:59:20 2015 +0530

    create command: remove race condition...
    
    Because of the pipe in the create command, 'gitolite git-config' and 'perms
    -c' would run simultaneously.  The problem was, if git-config ran between
    these two statements in new_wild_repo() in Store.pm:
    
        new_repo($repo);
            # 'gitolite git-config' runs here
        _print( "$repo.git/gl-creator", $user );
    
    *and* the repo pattern had CREATOR in it, it would not return the correct
    values for the default.roles options, since the repo pattern would not be
    deemed to match the actual repo (between creator() and generic_name()).
    
    Thanks to Ronald Wirth for catching this; see [1] for details.
    
    [1]: https://groups.google.com/forum/#!topic/gitolite/5Dv6ViDmfF4
    
    ----
    
    ...and while we're about it, I changed it to use /bin/sh and smoke tested it
    using "dash", which I believe is close enough.

diff --git a/src/commands/create b/src/commands/create
index 7d2a3a3..d35c4a8 100755
--- a/src/commands/create
+++ b/src/commands/create
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # Usage:    ssh git at host create <repo>
 #
@@ -12,5 +12,6 @@ usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
 [ -z "$GL_USER" ] && die GL_USER not set
 
 # ----------------------------------------------------------------------
-gitolite git-config -r $1 gitolite-options.default.roles | sort | cut -f3 |
-    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$1/' | $GL_BINDIR/commands/perms -c "$@"
+perms=$(gitolite git-config -r $1 gitolite-options.default.roles | sort | cut -f3 |
+    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$1/')
+echo "$perms" | $GL_BINDIR/commands/perms -c "$@"

commit fc8cdf0ad13d4bcdf8a885c2ecb8ebf99a1daf45
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 27 05:29:59 2015 +0530

    make single quotes around reponame optional...
    
    apparently the git client, when using ext:: transports, does not send those
    quotes
    
    https://groups.google.com/forum/#!topic/gitolite/x8rUaxJU0iM

diff --git a/src/gitolite-shell b/src/gitolite-shell
index e0bc090..d9ec01f 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -153,7 +153,7 @@ sub parse_soc {
     $soc ||= 'info';
 
     my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-    if ( $soc =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$) ) {
+    if ( $soc =~ m(^($git_commands) '?/?(.*?)(?:\.git(\d)?)?'?$) ) {
         my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
         $ENV{D} = $trace_level if $trace_level;
         _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;

commit edd979c68f6ccb362568ee6821839b79fbabaeec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 19 05:24:07 2015 +0530

    'who-pushed' learns super-efficient 'tip search'

diff --git a/src/commands/who-pushed b/src/commands/who-pushed
index 279506b..fb37607 100755
--- a/src/commands/who-pushed
+++ b/src/commands/who-pushed
@@ -15,6 +15,13 @@ my $countr = 0;
 my $countl = 0;
 migrate(@ARGV) if $ARGV[0] eq '--migrate';   # won't return; exits right there
 
+# tip search?
+my $tip_search = 0;
+if ($ARGV[0] eq '--tip') {
+    shift;
+    $tip_search = 1;
+}
+
 # the normal who-pushed
 usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
 usage() if $ARGV[1] !~ /^[0-9a-f]+$/i;
@@ -44,7 +51,11 @@ for my $logfile ( @logfiles ) {
         $old = ""       if $d_old eq ( "0" x 40 );
         $old = "$old.." if $old;
 
-        system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
+        if ($tip_search) {
+            print "$ts $pid $who $ref $d_old $new\n" if $new =~ /^$sha/;
+        } else {
+            system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
+        }
     }
 }
 
@@ -110,10 +121,12 @@ sub flush_gl_log {
 __END__
 
 =for usage
-Usage:    ssh git at host who-pushed <repo> <SHA>
+uSAge:    ssh git at host who-pushed [--tip] <repo> <SHA>
 
 Determine who pushed the given commit.  The first few hex digits of the SHA
-should suffice.
+should suffice.  If the '--tip' option is supplied, it'll only look for the
+SHA among "tip" commits (i.e., search the "new SHA"s, without running the
+expensive 'git rev-parse' for each push).
 
 Each line of the output contains the following fields: timestamp, a
 transaction ID, username, refname, and the old and new SHAs for the ref.

commit b05c94ff53adc95f68a2880a9c5c4ae40e9134f5
Author: milki <milki at rescomp.berkeley.edu>
Date:   Thu May 14 16:04:09 2015 -0700

    Don't overwrite gl-log on each flush in who-pushed
    
    _print always overwrites the target file. Empty the file at the
    beginning of the migrate run but subsequently append new entries.

diff --git a/src/commands/who-pushed b/src/commands/who-pushed
index 4f3d4a1..279506b 100755
--- a/src/commands/who-pushed
+++ b/src/commands/who-pushed
@@ -63,6 +63,10 @@ sub migrate {
         exit 1;
     }
 
+    foreach my $r (@repos) {
+        _print("$r.git/gl-log", '');
+    }
+
     my %repo_exists = map { $_ => 1 } @repos;
     @ARGV = sort ( glob("$logdir/*") );
     while (<>) {
@@ -94,7 +98,9 @@ sub gen_gl_log {
 }
 sub flush_gl_log {
     while (my ($r, $l) = each %gl_log_lines_buffer) {
-        _print("$r.git/gl-log", $l);
+        open my $fh, ">>", "$r.git/gl-log" or _die "open flush_gl_log failed: $!";
+        print $fh $l;
+        close $fh;
     }
     %gl_log_lines_buffer = ();
     say2 "flushed $countl lines to $countr repos...";

commit ac80adf953c4f533abb5d204575754e54b6b7b23
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Wed May 13 14:29:23 2015 +0200

    README.md: clean up usage

diff --git a/README.md b/README.md
index 8fdfed0..1bcb8cc 100644
--- a/README.md
+++ b/README.md
@@ -14,14 +14,9 @@ Under the hood, `runit-docker` translates `SIGTERM` and `SIGINT` to `SIGHUP`.
 
 ## Usage
 
-Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/runit-docker`.
-Debian support is included; build with `debian/rules binary` for easy package reuse.
-
-```
-CMD ["/sbin/runit-docker"]
-```
-
-Run `debian/rules clean build binary` to build a Debian package.
+* Build with `make`, install with `make install`.
+* Add `CMD ["/sbin/runit-docker"]` to your `Dockerfile`.
+* Run `debian/rules clean build binary` to build a Debian package.
 
 ## Author
 

commit 0c2fd2431ad7cd14d45a5d09503d9fa97cd2593f
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Wed May 13 14:27:29 2015 +0200

    README.md: update email address

diff --git a/README.md b/README.md
index d4f6d43..8fdfed0 100644
--- a/README.md
+++ b/README.md
@@ -25,5 +25,5 @@ Run `debian/rules clean build binary` to build a Debian package.
 
 ## Author
 
-runit-docker was written by Kosma Moczek <kosma at kosma.pl> during a single Scrum
+runit-docker was written by Kosma Moczek <kosma.moczek at pixers.pl> during a single Scrum
 planning meeting. Damn meetings.

commit 5388255e9be5996150fc179719b73a7057e0ca16
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Wed May 13 13:32:22 2015 +0200

    README.md: clean up

diff --git a/README.md b/README.md
index 54b14da..d4f6d43 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,28 @@
 # runit-docker
 
-On a scale from 1 to 10, how annoying is it that Docker uses `SIGTERM` but
-`runsvdir` expects `SIGHUP`? I guess eleven. Goddammit!
+Docker and `runsvdir` don't quite agree on what each signal means, causing
+TONS of frustration when attempting to use `runsvdir` as init under Docker.
+`runit-docker` is a plug'n'play adapter library which does signal translation
+without the overhead and nuisance of running a nanny process.
 
-No, really. You'd expect that when Docker sends SIGTERM to init (`runsvdir`
-in our case) it would behave like a proper process supervisor and kill the
-`runsv`s it manages. Except it doesn't, causing a slow, unclean container
-shutdown. I don't care whose fault this is. Docker and Runit are awesome
-and they should talk to each other.
+## Features
 
-This little wrapper fixes the interfacing SNAFU. It differs from
-the other nanny processes found on the web in that it isn't a process but
-a preloaded library - so your process list OCD isn't offended: there's only
-`runsvdir` but everything Just Works™.
+* Pressing Ctrl-C does a clean shutdown.
+* `docker stop` does a clean shutdown.
+
+Under the hood, `runit-docker` translates `SIGTERM` and `SIGINT` to `SIGHUP`.
 
 ## Usage
 
 Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/runit-docker`.
 Debian support is included; build with `debian/rules binary` for easy package reuse.
 
+```
+CMD ["/sbin/runit-docker"]
+```
+
+Run `debian/rules clean build binary` to build a Debian package.
+
 ## Author
 
 runit-docker was written by Kosma Moczek <kosma at kosma.pl> during a single Scrum

commit fc06dd1521d5eb66817414db5a1b20b69ef1694a
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Tue May 12 13:10:41 2015 +0200

    release 1.1

diff --git a/debian/changelog b/debian/changelog
index b116a08..7d8689f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+runit-docker (1.1) unstable; urgency=low
+
+  * Simplify logic.
+  * Install for SIGINT as well.
+
+ -- Kosma Moczek <kosma at kosma.pl>  Mon, 11 May 2015 12:23:59 +0000
+
 runit-docker (1.0) unstable; urgency=low
 
   * Initial release

commit cdb582b081b24e6f867850801ded874848c96d87
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Tue May 12 13:10:22 2015 +0200

    runit-docker.c: simplify + handle Ctrl-C

diff --git a/runit-docker.c b/runit-docker.c
index 4b7124d..825a35f 100644
--- a/runit-docker.c
+++ b/runit-docker.c
@@ -2,18 +2,11 @@
 #include <dlfcn.h>
 #include <stdlib.h>
 
-static void (*real_s_term)(int) = NULL;
-static void (*real_s_hangup)(int) = NULL;
-
-static void fake_s_term(int signum)
-{
-  real_s_hangup(signum);
-}
-
-static int (*real_sigaction)(int signum, const struct sigaction *act, struct sigaction *oldact) = NULL;
 
 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
 {
+  static int (*real_sigaction)(int signum, const struct sigaction *act, struct sigaction *oldact) = NULL;
+
   // Retrieve the real sigaction we just shadowed.
   if (real_sigaction == NULL) {
     real_sigaction = (void *) dlsym(RTLD_NEXT, "sigaction");
@@ -21,13 +14,15 @@ int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
     unsetenv("LD_PRELOAD");
   }
 
-  if (real_s_term == NULL) {
-    // Override installed s_term() with our own.
-    real_s_term = act->sa_handler;
-    ((struct sigaction *) act)->sa_handler = fake_s_term;
-  } else if (real_s_hangup == NULL) {
-    // Save real s_hangup() for SIGTERM handling.
-    real_s_hangup = act->sa_handler;
+  if (signum == SIGTERM) {
+    // Skip this handler, it doesn't do what we want.
+    return 0;
+  }
+
+  if (signum == SIGHUP) {
+    // Install this handler for others as well.
+    real_sigaction(SIGTERM, act, oldact);
+    real_sigaction(SIGINT, act, oldact);
   }
 
   // Forward the call the the real sigaction.

commit 10dcfdb2c56fb98695251f0d6b61f9f8939ba0ab
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Tue May 12 11:39:53 2015 +0200

    yay for generic Makefiles

diff --git a/Makefile b/Makefile
index d8e2816..9a28963 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,18 @@
 CFLAGS=-std=c99 -Wall -O2 -fPIC -D_POSIX_SOURCE -D_GNU_SOURCE
 LDLIBS=-ldl
 
-runit-docker.so: runit-docker.c
+PROGNAME=runit-docker
+
+all: $(PROGNAME).so
+
+%.so: %.c
 	gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
 
 install: runit-docker.so
 	mkdir -p $(DESTDIR)/sbin
 	mkdir -p $(DESTDIR)/lib
-	install -m 755 runit-docker $(DESTDIR)/sbin/
-	install -m 755 runit-docker.so $(DESTDIR)/lib/
+	install -m 755 $(PROGNAME) $(DESTDIR)/sbin/
+	install -m 755 $(PROGNAME).so $(DESTDIR)/lib/
 
 clean:
-	$(RM) runit-docker.so
+	$(RM) $(PROGNAME).so

commit 1fa8e169c5902bce8ff5743f02ac0dd48d99c24e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 10 19:12:51 2015 +0530

    make who-pushed more efficient...
    
    -   add (commented out) values to LOG_DEST in the default rc file to allow
        'repo-log' as a destination
    -   make gl_log() respect this and append update log records to a file called
        'gl-log' within the bare repo dir.  (This is only the update log records,
        not everything.)
    -   let 'who-pushed' use gl-log if found, and the normal ones (much slower)
        only if not
    -   add more help to who-pushed
    -   help admin migrate log records (or rather, generate the new ones from the
        normal gitolite logs)

diff --git a/src/commands/who-pushed b/src/commands/who-pushed
index 915705b..4f3d4a1 100755
--- a/src/commands/who-pushed
+++ b/src/commands/who-pushed
@@ -5,27 +5,17 @@ use warnings;
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Easy;
 
-=for usage
-Usage:    ssh git at host who-pushed <repo> <SHA>
-
-Determine who pushed the given commit.  The first few hex digits of the SHA
-should suffice.
-
-Each line of the output contains the following fields: timestamp, a
-transaction ID, username, refname, and the old and new SHAs for the ref.
+usage($ARGV[1]) if $ARGV[1] and $ARGV[1] =~ /^[\w-]+$/ and $ARGV[0] eq '-h';
 
-We assume the logfile names have been left as default, or if changed, in such
-a way that they come up oldest first when sorted.
+( my $logdir = $ENV{GL_LOGFILE} ) =~ s(/[^/]+$)();
 
-The program searches ALL the log files, in reverse sorted order (i.e., newest
-first).  This means it could take a long time if your log directory is large
-and contains lots of old log files.  Patches to limit the search to an
-optional date range are welcome.
-
-Note on the "transaction ID" field: if looking at the log file doesn't help
-you figure out what its purpose is, please just ignore it.
-=cut
+# deal with migrate
+my %gl_log_lines_buffer;
+my $countr = 0;
+my $countl = 0;
+migrate(@ARGV) if $ARGV[0] eq '--migrate';   # won't return; exits right there
 
+# the normal who-pushed
 usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
 usage() if $ARGV[1] !~ /^[0-9a-f]+$/i;
 
@@ -38,9 +28,11 @@ $ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" );
 
 my $repodir = "$ENV{GL_REPO_BASE}/$repo.git";
 chdir $repodir or die "repo '$repo' missing";
-( my $logdir = $ENV{GL_LOGFILE} ) =~ s(/[^/]+$)();
 
-for my $logfile ( reverse glob("$logdir/*") ) {
+my @logfiles = reverse glob("$logdir/*");
+ at logfiles = ( "$repodir/gl-log" ) if -f "$repodir/gl-log";
+
+for my $logfile ( @logfiles ) {
     @ARGV = ($logfile);
     for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) {
         chomp($line);
@@ -55,3 +47,106 @@ for my $logfile ( reverse glob("$logdir/*") ) {
         system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
     }
 }
+
+# ----------------------------------------------------------------------
+# migration
+
+sub migrate {
+    chdir $ENV{GL_REPO_BASE};
+    my @repos = `gitolite list-phy-repos`; chomp @repos;
+
+    my $count = scalar( grep { -f "$_.git/gl-log" } @repos );
+    if ( $count and ( $_[1] || '' ) ne '--force' ) {
+        say2 "$count repo(s) already have gl-log files.  To confirm overwriting, please re-run as:";
+        say2 "\tgitolite who-pushed --migrate --force";
+        say2 "see help ('-h', '-h logfiles', or '-h migrate') for details.";
+        exit 1;
+    }
+
+    my %repo_exists = map { $_ => 1 } @repos;
+    @ARGV = sort ( glob("$logdir/*") );
+    while (<>) {
+        say2 "processed '$ARGV'" if eof(ARGV);
+        next unless /\tupdate\t/;
+        my @f = split /\t/;
+        my $repo = $f[3];
+        if ($repo =~ m(^/)) {
+            $repo =~ s/^$ENV{GL_REPO_BASE}\///;
+            $repo =~ s/\.git$//;
+        }
+
+        gen_gl_log($repo, $_) if $repo_exists{$repo};
+    }
+    flush_gl_log();
+
+    exit 0;
+}
+sub gen_gl_log {
+    my ($repo, $l) = @_;
+
+    $countr++ unless $gl_log_lines_buffer{$repo};    # new repo, not yet seen
+    $countl++;
+    $gl_log_lines_buffer{$repo} .= $l;
+
+    # once we have buffered log lines for about 100 repos, or about 10,000 log
+    # lines, we flush them
+    flush_gl_log() if $countr >= 100 or $countl >= 10_000;
+}
+sub flush_gl_log {
+    while (my ($r, $l) = each %gl_log_lines_buffer) {
+        _print("$r.git/gl-log", $l);
+    }
+    %gl_log_lines_buffer = ();
+    say2 "flushed $countl lines to $countr repos...";
+    $countr = $countl = 0;
+}
+
+__END__
+
+=for usage
+Usage:    ssh git at host who-pushed <repo> <SHA>
+
+Determine who pushed the given commit.  The first few hex digits of the SHA
+should suffice.
+
+Each line of the output contains the following fields: timestamp, a
+transaction ID, username, refname, and the old and new SHAs for the ref.
+
+Note on the "transaction ID" field: if looking at the log file doesn't help
+you figure out what its purpose is, please just ignore it.
+
+TO SEE ADDITIONAL HELP, run with options "-h logfiles" or "-h migrate".
+=cut
+
+=for logfiles
+There are 2 places that gitolite logs to, based on the value give to the
+LOG_DEST rc variable.  By default, log files go to ~/.gitolite/logs, but you
+can choose to send them to syslog instead (in which case 'who-pushed' will not
+work), or to both syslog and the normal log files.
+
+In addition, gitolite can also be told to log just the "update" records to a
+special "gl-log" file in the bare repo directory.  This makes 'who-pushed'
+**much** faster (thanks to milki for the problem *and* the simple solution).
+
+'who-pushed' will look for that special file first and use only that if it is
+found.  Otherwise it will look in the normal gitolite log files, which will of
+course be much slower.
+=cut
+
+=for migrate
+If you installed gitolite before v3.6.4, and you wish to use the new, more
+efficient logging that helps who-pushed run faster, you should first update
+the rc file (see http://gitolite.com/gitolite/rc.html for notes on that) to
+specify a suitable value for LOG_DEST.
+
+After that you should probably do a one-time generation of the repo-specific
+'gl-log' files from the normal log files.  This can only be done from the
+server command line, even if the 'who-pushed' command has been enabled for
+remote access.
+
+To do this, just run 'gitolite who-pushed --migrate'.  If some of your repos
+already had gl-log files, it will warn you, and tell you how to override.
+You're only supposed to to use this *once* after upgrading to v3.6.4 and
+setting LOG_DEST in the rc file anyway.
+=cut
+
diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 266eea0..5d6b749 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -282,8 +282,19 @@ sub gl_log {
     my $ts = gen_ts();
     my $tid = $ENV{GL_TID} ||= $$;
 
-    # syslog
     $log_dest = $Gitolite::Rc::rc{LOG_DEST} || '' if not defined $log_dest;
+
+    # log (update records only) to "gl-log" in the bare repo dir; this is to
+    # make 'who-pushed' more efficient.  Since this is only for the update
+    # records, it is not a replacement for the other two types of logging.
+    if ($log_dest =~ /repo-log/ and $_[0] eq 'update') {
+        # if the log line is 'update', we're already in the bare repo dir
+        open my $lfh, ">>", "gl-log" or _die "open gl-log failed: $!";
+        print $lfh "$ts\t$tid\t$msg\n";
+        close $lfh;
+    }
+
+    # syslog
     if ($log_dest =~ /syslog/) {            # log_dest *includes* syslog
         if ($syslog_opened == 0) {
             require Sys::Syslog;
@@ -301,7 +312,7 @@ sub gl_log {
         # the priority/level of the syslog message.
         syslog( ( $msg =~ /^\t/ ? 'debug' : 'info' ), "%s", $msg);
 
-        return if $log_dest eq 'syslog';    # log_dest *equals* syslog
+        return if $log_dest !~ /normal/;
     }
 
     my $fh;
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index e4a0768..9fd94b5 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -509,12 +509,17 @@ __DATA__
 
     # comment out if you don't need all the extra detail in the logfile
     LOG_EXTRA                       =>  1,
-    # syslog options
-    # 1. leave this section as is for normal gitolite logging
-    # 2. uncomment this line to log only to syslog:
+    # logging options
+    # 1. leave this section as is for 'normal' gitolite logging (default)
+    # 2. uncomment this line to log ONLY to syslog:
     # LOG_DEST                      => 'syslog',
     # 3. uncomment this line to log to syslog and the normal gitolite log:
     # LOG_DEST                      => 'syslog,normal',
+    # 4. prefixing "repo-log," to any of the above will **also** log just the
+    #    update records to "gl-log" in the bare repo directory:
+    # LOG_DEST                      => 'repo-log,normal',
+    # LOG_DEST                      => 'repo-log,syslog',
+    # LOG_DEST                      => 'repo-log,syslog,normal',
 
     # roles.  add more roles (like MANAGER, TESTER, ...) here.
     #   WARNING: if you make changes to this hash, you MUST run 'gitolite

commit b6175cc8f0b671a46df21ba33197c2a9b32f647d
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:56:55 2015 +0200

    Update README.md

diff --git a/README.md b/README.md
index a5dccc4..54b14da 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,12 @@
 On a scale from 1 to 10, how annoying is it that Docker uses `SIGTERM` but
 `runsvdir` expects `SIGHUP`? I guess eleven. Goddammit!
 
+No, really. You'd expect that when Docker sends SIGTERM to init (`runsvdir`
+in our case) it would behave like a proper process supervisor and kill the
+`runsv`s it manages. Except it doesn't, causing a slow, unclean container
+shutdown. I don't care whose fault this is. Docker and Runit are awesome
+and they should talk to each other.
+
 This little wrapper fixes the interfacing SNAFU. It differs from
 the other nanny processes found on the web in that it isn't a process but
 a preloaded library - so your process list OCD isn't offended: there's only
@@ -11,7 +17,9 @@ a preloaded library - so your process list OCD isn't offended: there's only
 ## Usage
 
 Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/runit-docker`.
+Debian support is included; build with `debian/rules binary` for easy package reuse.
 
 ## Author
 
-runit-docker was written by Kosma Moczek <kosma at kosma.pl> at PIXERS.
+runit-docker was written by Kosma Moczek <kosma at kosma.pl> during a single Scrum
+planning meeting. Damn meetings.

commit abf25e13febc69df8895b88fbafca89283fcde9d
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:33:14 2015 +0200

    debianize

diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..b116a08
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+runit-docker (1.0) unstable; urgency=low
+
+  * Initial release
+
+ -- Kosma Moczek <kosma at kosma.pl>  Mon, 11 May 2015 12:23:59 +0000
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..4060915
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,14 @@
+Source: runit-docker
+Section: contrib/admin
+Priority: optional
+Maintainer: Kosma Moczek <kosma at kosma.pl>
+Build-Depends: debhelper (>= 9)
+Standards-Version: 3.9.5
+Homepage: https://github.com/kosma/runit-docker
+#Vcs-Git: git://anonscm.debian.org/collab-maint/runit-docker.git
+#Vcs-Browser: http://anonscm.debian.org/?p=collab-maint/runit-docker.git;a=summary
+
+Package: runit-docker
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: painlessly use runit in Docker containers
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..8679a6a
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,31 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: runit-docker
+Source: https://github.com/kosma/runit-docker
+
+Files: *
+Copyright: 2015 Kosma Moczek <kosma at kosma.pl>
+License: MIT
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+ 
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+ 
+ * Neither the name of runit-docker nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b43bf86
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..ce15cce
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,32 @@
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#DH_VERBOSE = 1
+
+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/default.mk
+
+# see FEATURE AREAS in dpkg-buildflags(1)
+#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+# see ENVIRONMENT in dpkg-buildflags(1)
+# package maintainers to append CFLAGS
+#export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic
+# package maintainers to append LDFLAGS
+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
+
+
+# main packaging script based on dh7 syntax
+%:
+	dh $@ 
+
+# debmake generated override targets
+# This is example for Cmake (See http://bugs.debian.org/641051 )
+#override_dh_auto_configure:
+#	dh_auto_configure -- \
+#	-DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
+
+
+
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)

commit 0d3f1856750bd62cc82cb890e5a4dc3f937de556
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:32:55 2015 +0200

    Makefile: install: create directories

diff --git a/Makefile b/Makefile
index 1eb962b..d8e2816 100644
--- a/Makefile
+++ b/Makefile
@@ -5,6 +5,8 @@ runit-docker.so: runit-docker.c
 	gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
 
 install: runit-docker.so
+	mkdir -p $(DESTDIR)/sbin
+	mkdir -p $(DESTDIR)/lib
 	install -m 755 runit-docker $(DESTDIR)/sbin/
 	install -m 755 runit-docker.so $(DESTDIR)/lib/
 

commit 9832172b44a9363191969e0fbe9d7673ef6885d1
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:29:54 2015 +0200

    Makefile: install to DESTDIR/

diff --git a/Makefile b/Makefile
index 85c0b52..1eb962b 100644
--- a/Makefile
+++ b/Makefile
@@ -5,8 +5,8 @@ runit-docker.so: runit-docker.c
 	gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
 
 install: runit-docker.so
-	install -m 755 runit-docker /sbin/
-	install -m 755 runit-docker.so /lib/
+	install -m 755 runit-docker $(DESTDIR)/sbin/
+	install -m 755 runit-docker.so $(DESTDIR)/lib/
 
 clean:
 	$(RM) runit-docker.so

commit 4d31c3a3ac8018746327986e9ff2f711bb886abe
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:17:45 2015 +0200

    README.md: more typos

diff --git a/README.md b/README.md
index 400cde6..a5dccc4 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ On a scale from 1 to 10, how annoying is it that Docker uses `SIGTERM` but
 
 This little wrapper fixes the interfacing SNAFU. It differs from
 the other nanny processes found on the web in that it isn't a process but
-a preloaded library - so your process list OCD isn't offended: there's just
+a preloaded library - so your process list OCD isn't offended: there's only
 `runsvdir` but everything Just Works™.
 
 ## Usage

commit f3db68c5a2d0e90e1fe6ecee06c9d9860fbfb52b
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:17:12 2015 +0200

    README.md: typos goddammit

diff --git a/README.md b/README.md
index f6b85f5..400cde6 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
 On a scale from 1 to 10, how annoying is it that Docker uses `SIGTERM` but
 `runsvdir` expects `SIGHUP`? I guess eleven. Goddammit!
 
-Ihis little wrapper fixes that interfacing SNAFU. It differs from
+This little wrapper fixes the interfacing SNAFU. It differs from
 the other nanny processes found on the web in that it isn't a process but
-a preloaded library - so your process list OCD isn't offended, there's just
+a preloaded library - so your process list OCD isn't offended: there's just
 `runsvdir` but everything Just Works™.
 
 ## Usage

commit 9b8ea270070f0dc44cea296b6247ebd715052e54
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:11:52 2015 +0200

    rename init-runit-docker -> runit-docker so that it fits on docker process list

diff --git a/Makefile b/Makefile
index dd2431a..85c0b52 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ runit-docker.so: runit-docker.c
 	gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
 
 install: runit-docker.so
-	install -m 755 init-runit-docker /sbin/
+	install -m 755 runit-docker /sbin/
 	install -m 755 runit-docker.so /lib/
 
 clean:
diff --git a/README.md b/README.md
index 536f421..f6b85f5 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ a preloaded library - so your process list OCD isn't offended, there's just
 
 ## Usage
 
-Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/init-runit-docker`.
+Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/runit-docker`.
 
 ## Author
 
diff --git a/init-runit-docker b/runit-docker
similarity index 100%
rename from init-runit-docker
rename to runit-docker

commit 6f2ad810015f8d8d38478caafcf54682863b3d64
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 14:01:21 2015 +0200

    initial commit

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..dd2431a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+CFLAGS=-std=c99 -Wall -O2 -fPIC -D_POSIX_SOURCE -D_GNU_SOURCE
+LDLIBS=-ldl
+
+runit-docker.so: runit-docker.c
+	gcc -shared $(CFLAGS) $(LDLIBS) -o $@ $^
+
+install: runit-docker.so
+	install -m 755 init-runit-docker /sbin/
+	install -m 755 runit-docker.so /lib/
+
+clean:
+	$(RM) runit-docker.so
diff --git a/README.md b/README.md
index 22cfb1e..536f421 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,17 @@
 # runit-docker
-painlessly use runit in Docker containers
+
+On a scale from 1 to 10, how annoying is it that Docker uses `SIGTERM` but
+`runsvdir` expects `SIGHUP`? I guess eleven. Goddammit!
+
+Ihis little wrapper fixes that interfacing SNAFU. It differs from
+the other nanny processes found on the web in that it isn't a process but
+a preloaded library - so your process list OCD isn't offended, there's just
+`runsvdir` but everything Just Works™.
+
+## Usage
+
+Build with `make`, install with `make install`. Start with `docker run {blah} /sbin/init-runit-docker`.
+
+## Author
+
+runit-docker was written by Kosma Moczek <kosma at kosma.pl> at PIXERS.
diff --git a/init-runit-docker b/init-runit-docker
new file mode 100755
index 0000000..fdbaad5
--- /dev/null
+++ b/init-runit-docker
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export LD_PRELOAD=/lib/runit-docker.so
+exec runsvdir /etc/service
diff --git a/runit-docker.c b/runit-docker.c
new file mode 100644
index 0000000..4b7124d
--- /dev/null
+++ b/runit-docker.c
@@ -0,0 +1,37 @@
+#include <signal.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+
+static void (*real_s_term)(int) = NULL;
+static void (*real_s_hangup)(int) = NULL;
+
+static void fake_s_term(int signum)
+{
+  real_s_hangup(signum);
+}
+
+static int (*real_sigaction)(int signum, const struct sigaction *act, struct sigaction *oldact) = NULL;
+
+int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
+{
+  // Retrieve the real sigaction we just shadowed.
+  if (real_sigaction == NULL) {
+    real_sigaction = (void *) dlsym(RTLD_NEXT, "sigaction");
+    // Prevent further shadowing in children.
+    unsetenv("LD_PRELOAD");
+  }
+
+  if (real_s_term == NULL) {
+    // Override installed s_term() with our own.
+    real_s_term = act->sa_handler;
+    ((struct sigaction *) act)->sa_handler = fake_s_term;
+  } else if (real_s_hangup == NULL) {
+    // Save real s_hangup() for SIGTERM handling.
+    real_s_hangup = act->sa_handler;
+  }
+
+  // Forward the call the the real sigaction.
+  return real_sigaction(signum, act, oldact);
+}
+
+// vim: ts=2 sw=2 et

commit 5216f1f20bc64466f5aacc1fb81b8ed6ad958e16
Author: Kosma Moczek <kosma at kosma.pl>
Date:   Mon May 11 12:46:52 2015 +0200

    Initial commit

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bbf313b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d158667
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2015, Kosma Moczek
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of runit-docker nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..22cfb1e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# runit-docker
+painlessly use runit in Docker containers

commit cbce2f053e75d5deb766b820c93086d6fac64a15
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 11 11:37:19 2015 +0530

    change the usage() semantics slightly...
    
    to better accommodate additional custom help messages

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 507c5bd..266eea0 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -113,13 +113,13 @@ sub _die {
 $SIG{__DIE__} = \&_die;
 
 sub usage {
-    _warn(shift) if @_;
     my $script = (caller)[1];
-    my $function = ( ( ( caller(1) )[3] ) || ( ( caller(0) )[3] ) );
+    my $function = shift if @_ and $_[0] =~ /^[\w-]+$/;
+    $function ||= ( ( ( caller(1) )[3] ) || ( ( caller(0) )[3] ) );
     $function =~ s/.*:://;
     my $code = slurp($script);
     $code =~ /^=for $function\b(.*?)^=cut/sm;
-    say2( $1 ? $1 : "...no usage message in $script" );
+    say( $1 ? $1 : "...no usage message for '$function' in $script" );
     exit 1;
 }
 

commit 47a8b3c44a76763d708b45687cd13f01c3022182
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 11 13:46:40 2015 +0530

    gitolite query-rc --dump (or -d)

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 6556ba5..e4a0768 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -215,6 +215,7 @@ sub glrc {
 }
 
 my $all   = 0;
+my $dump  = 0;
 my $nonl  = 0;
 my $quiet = 0;
 
@@ -231,6 +232,13 @@ sub query_rc {
         exit 0;
     }
 
+    if ($dump) {
+        require Data::Dumper;
+        $Data::Dumper::Sortkeys = 1;
+        print Data::Dumper::Dumper \%rc;
+        exit 0;
+    }
+
     my $cv = \%rc;    # current "value"
     while (@vars) {
         my $v = shift @vars;
@@ -350,9 +358,11 @@ sub _which {
 
 =for args
 Usage:  gitolite query-rc -a
+        gitolite query-rc -d
         gitolite query-rc [-n] [-q] rc-variable
 
     -a          print all variables and values (first level only)
+    -d          dump the entire rc structure
     -n          do not append a newline if variable is scalar
     -q          exit code only (shell truth; 0 is success)
 
@@ -379,6 +389,7 @@ Explore:
     gitolite query-rc -a
     # prints all first level variables and values, one per line.  Any that are
     # listed as HASH or ARRAY can be explored further in subsequent commands.
+    gitolite query-rc -d                # dump the entire rc structure
 =cut
 
 sub args {
@@ -387,13 +398,14 @@ sub args {
     require Getopt::Long;
     Getopt::Long::GetOptions(
         'all|a'   => \$all,
+        'dump|d'  => \$dump,
         'nonl|n'  => \$nonl,
         'quiet|q' => \$quiet,
         'help|h'  => \$help,
     ) or usage();
 
-    usage("'-a' cannot be combined with other arguments or options") if $all and ( @ARGV or $nonl or $quiet );
-    usage() if not $all and not @ARGV or $help;
+    _die("'-a' cannot be combined with other arguments or options; run with '-h' for usage") if $all and ( @ARGV or $dump or $nonl or $quiet );
+    usage() if not $all and not $dump and not @ARGV or $help;
     return @ARGV;
 }
 

commit 8bef6711fbf8225153ef251450d475a2475631a7
Author: Tony Xue <tonyxue at tonyxue.asia>
Date:   Sun May 3 21:53:35 2015 +0800

    (minor) fix typo

diff --git a/README.markdown b/README.markdown
index 24149dc..a211fab 100644
--- a/README.markdown
+++ b/README.markdown
@@ -73,7 +73,7 @@ Finally, setup gitolite with yourself as the administrator:
 
     gitolite setup -pk YourName.pub
 
-If the last command doesn't run perhaps "bin" in not in your "PATH". You can
+If the last command doesn't run perhaps "bin" is not in your "PATH". You can
 either add it, or just run:
 
     $HOME/bin/gitolite setup -pk YourName.pub

commit 5d24ae666bfd2fa9093d67c840eb8d686992083f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 26 15:08:49 2015 +0530

    v3.6.3

diff --git a/CHANGELOG b/CHANGELOG
index edf7cb9..021ebdc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,26 @@
+2015-04-26  v3.6.3  allow limited use of 'git config' using the new 'config'
+                    command
+
+                    accept openssh 6.8's new fingerprint output format
+
+                    (finally!) allow limited symlinks within ~/repositories;
+                    see commit 8e36230 for details
+
+                    perms command now lists available roles
+
+                    minor backward compat breakage: 'perms -l repo' no longer
+                    works; see 'perms -h' for new usage
+
+                    allow gitolite-shell to be used as $SHELL (experts only;
+                    no support, no docs; see commit 9cd1e37 for details)
+
+                    help with 'git push --signed' using a post-receive hook to
+                    adopt push certs into 'refs/push-certs'; for details see
+                    contrib/hooks/repo-specific/save-push-signatures
+
+                    new 'transparent proxy' feature for git repos; see
+                    src/lib/Gitolite/Triggers/TProxy.pm for details
+
 2014-11-10  v3.6.2  disable ../ everywhere (see mailing list thread for
                     details)
 

commit dd8bc1721049fd392606b9b1087e028977e86a76
Author: gitolite tester <tester at example.com>
Date:   Thu Apr 9 11:50:40 2015 +0530

    allow limited remote use of 'git config'

diff --git a/src/commands/config b/src/commands/config
new file mode 100755
index 0000000..b996066
--- /dev/null
+++ b/src/commands/config
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+use 5.10.0;
+
+# ----------------------------------------------------------------------
+# gitolite command to allow "git config" on repos (with some restrictions)
+
+# (Not to be confused with the 'git-config' command, which is used only in
+# server-side scripts, not remotely.)
+
+# setup:
+#   1.  Enable the command by adding it to the COMMANDS section in the ENABLE
+#       list in the rc file.
+#
+#   2.  Specify configs allowed to be changed by the user.  This is a space
+#       separated regex list.  For example:
+
+#           repo ...
+#               ... (various rules) ...
+#               option user-configs = hook\..* foo.bar[0-9].*
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+use Gitolite::Common;
+
+# ----------------------------------------------------------------------
+# usage
+
+=for usage
+Usage:    ssh git at host config <repo> [git config options]
+
+Runs "git config" in the repo.  Only the following 3 syntaxes are supported
+(see 'man git-config'):
+
+          --add name value
+      --get-all name
+    --unset-all name
+         --list
+
+Your administrator should tell you what keys are allowed for the "name".
+=cut
+
+# ----------------------------------------------------------------------
+# arg checks
+
+my %nargs = qw(
+          --add 3
+      --get-all 2
+    --unset-all 2
+         --list 1
+     );
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+my $repo = shift;
+
+my ($op, $key, $val) = @ARGV;
+usage() unless $op and exists $nargs{$op} and @ARGV == $nargs{$op};
+
+# ----------------------------------------------------------------------
+# authorisation checks
+
+die "sorry, you are not authorised\n" unless
+    owns($repo)
+        or
+    ( ( $op eq '--get-all' or $op eq '--list' )
+        ? can_read($repo)
+        : ( can_write($repo) and option( $repo, 'writer-is-owner' ) )
+    );
+
+# ----------------------------------------------------------------------
+# key validity checks
+
+unless ($op eq '--list') {
+    my $user_configs = option( $repo, 'user-configs' );
+    # this is a space separated list of allowed config keys
+    my @validkeys = split( ' ', ( $user_configs || '' ) );
+    my @matched = grep { $key =~ /^$_$/i } @validkeys;
+    _die "config '$key' not allowed\n" if ( @matched < 1 );
+}
+
+# ----------------------------------------------------------------------
+# go!
+
+_chdir("$rc{GL_REPO_BASE}/$repo.git");
+_system( "git", "config", @ARGV );

commit 276cf761de0522a19b0312f4466fc497a2a38b5f
Author: Benoît Knecht <knecht at ned-team.com>
Date:   Wed Apr 22 11:27:03 2015 +0200

    git-annex-shell: accept repo paths without '~/'
    
    It seems that (some versions of) git-annex use repository paths that simply
    start with '/' instead of '/~/', so make the '~/' part optional.

diff --git a/src/commands/git-annex-shell b/src/commands/git-annex-shell
index a9b29d5..572aba6 100755
--- a/src/commands/git-annex-shell
+++ b/src/commands/git-annex-shell
@@ -18,12 +18,13 @@ my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
 
 # Expect commands like:
 #   git-annex-shell 'configlist' '/~/repo'
+#   git-annex-shell 'configlist' '/repo'
 #   git-annex-shell 'sendkey' '/~/repo' 'key'
 # The parameters are always single quoted, and the repo path is always
 # the second parameter.
 # Further parameters are not validated here (see below).
 die "bad git-annex-shell command: $cmd"
-  unless $cmd =~ m#^(git-annex-shell '\w+' ')/\~/([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
+  unless $cmd =~ m#^(git-annex-shell '\w+' ')/(?:\~/)?([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
 my $start = $1;
 my $repo  = $2;
 my $end   = $3;

commit a80da842f353ebc2294ec83e0037dc5bdd850958
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 22 05:17:05 2015 +0530

    openssh 6.8 again... forgot sshkeys-lint the last time!

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 7c0f508..374bdb8 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -163,7 +163,8 @@ sub fprint {
     # dbg("fp = $fp");
     close $fh;
     unlink $tempfn if $tempfn;
-    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
+    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-ZA-z0-9+/]+));
+
     return $1;
 }
 

commit f9c0008808f2dbf986311328c2a8ba9a895128a2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 30 21:40:14 2015 +0530

    fix minor bugly in perms
    
    reminder: bugly = bug that makes something look ugly :)

diff --git a/src/commands/perms b/src/commands/perms
index 6b6bbf7..0b7c5b2 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -65,7 +65,7 @@ if ( $ARGV[0] eq '-c' ) {
 
 my $repo = shift;
 
-if ( $ARGV[0] eq '-lr' ) {
+if ( @ARGV and $ARGV[0] eq '-lr' ) {
     list_roles();
     exit 0;
 } else {

commit ed807a40c6683960e357bc995b3acf721ec088b4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 19 05:17:59 2015 +0530

    openssh 6.8 compat

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 84dda73..d5f5d8b 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -115,7 +115,7 @@ sub fp_file {
     my $f  = shift;
     my $fp = `ssh-keygen -l -f '$f'`;
     chomp($fp);
-    _die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
+    _die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/ or $fp =~ m(SHA256:([A-ZA-z0-9+/]+));
     $fp = $1;
     return $fp;
 }

commit 8e362300a6705482f0c0f4aa3de95ca6e3b0a5c2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 13 20:01:38 2015 +0530

    allow repo symlinks while still being paranoid...
    
    I don't like "find -L" in a security program.  Period.  If you MUST keep some
    of your repos somewhere else, you'll need to do it this way.
    
    Let's say all the repos under foo/bar and baz are actually on a different
    disk.  That is, ~/repositories/foo/bar is a symlink to some other disk, as is
    ~/repositories/baz.
    
    Then add this to the rc file somewhere in the "rc variables used by various
    features" section:
    
        REPO_SYMLINKS   =>  "foo/bar/ baz/",
    
    DON'T forget the trailing slash at the end of each name, and the comma
    outside.

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index b32d0d0..507c5bd 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -226,9 +226,11 @@ sub cleanup_conf_line {
         # receiving *any* arg invalidates cache)
         return \@phy_repos if ( @phy_repos and not @_ );
 
-        for my $repo (`find . -name "*.git" -prune`) {
+        my $cmd = 'find . ' . ($Gitolite::Rc::rc{REPO_SYMLINKS} || '') . ' -name "*.git" -prune';
+        for my $repo (`$cmd`) {
             chomp($repo);
-            $repo =~ s(\./(.*)\.git$)($1);
+            $repo =~ s/\.git$//;
+            $repo =~ s(^\./)();
             push @phy_repos, $repo;
         }
         trace( 3, scalar(@phy_repos) . " physical repos found" );
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 1d566eb..5568b3f 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -224,13 +224,12 @@ sub new_wild_repo {
 
 sub hook_repos {
     trace(3);
+
     # all repos, all hooks
     _chdir( $rc{GL_REPO_BASE} );
+    my $phy_repos = list_phy_repos(1);
 
-    for my $repo (`find . -name "*.git" -prune`) {
-        chomp($repo);
-        $repo =~ s/\.git$//;
-        $repo =~ s(^\./)();
+    for my $repo ( @{$phy_repos} ) {
         hook_1($repo);
     }
 }

commit f30c4a2bf4b4c85a3dd91c43401f2477897d1e2c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 13 13:27:37 2015 +0530

    lineup the info display fields

diff --git a/src/commands/info b/src/commands/info
index 08e9931..5079cfa 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -91,13 +91,16 @@ sub print_phy_repos {
 
 sub listem {
     my ( $repos, $lc, $ld, @aa ) = @_;
-    my $creator = '';
+    my @list;
+    my $mlr = 0;    # max length of reponame
+    my $mlc = 0;    # ...and creator
     for my $repo (@$repos) {
         next unless $repo =~ /$patt/;
-        my $perm = '';
+        my $creator = '';
+        my $desc    = '';
+        my $perm    = '';
         $creator = creator($repo) if $lc;
 
-        my $desc;
         if ($ld) {
             # use config value first, else 'description' file as second choice
             my $k = 'gitweb.description';
@@ -120,15 +123,16 @@ sub listem {
             $out{repos}{$repo}{creator}     = $creator if $lc;
             $out{repos}{$repo}{description} = $desc    if $ld;
             $out{repos}{$repo}{perms}       = _hash($perm);
-
-            next;
+        } else {
+            $mlr = length($repo) if ( $lc or $ld ) and $mlr < length($repo);
+            $mlc = length($creator) if $lc and $ld and $mlc < length($creator);
+            push @list, [ $perm, $repo, $creator, $desc ];
         }
-
-        print "$perm\t$repo";
-        print "\t$creator" if $lc;
-        print "\t$desc"    if $ld;
-        print "\n";
     }
+    return if $json;
+
+    my $fmt = "%s\t%-${mlr}s\t%-${mlc}s\t%s\n";
+    map { s/\t\t/\t/; s/\s*$/\n/; print } map { sprintf $fmt, @$_ } @list;
 }
 
 sub _hash {

commit 8786e256e24b3365999fce058249af525ed34655
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 11 22:26:05 2015 +0530

    perms: list available roles, plus several other changes...
    
    (much thanks to Tony Finch!)
    
    -   show valid roles and their access rights, for the given repo ("-lr")
    -   check a role not just against the rc, but against the roles that this
        specific repo actually uses, which may be a smaller subset.  (This is the
        most important improvement in this patch; the rest is just fluff!)
    -   fixup usage message
    
    WARNING: minor backward compat breakage: 'ssh ... perms -l repo' no longer
    works; the '-l' goes *after* the repo name now.

diff --git a/src/commands/perms b/src/commands/perms
index c9b8946..6b6bbf7 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -8,18 +8,22 @@ use Gitolite::Common;
 use Gitolite::Easy;
 
 =for usage
-Usage:  ssh git at host perms -l <repo>
-        ssh git at host perms <repo> - <rolename> <username>
-        ssh git at host perms <repo> + <rolename> <username>
+perms -- list or set permissions for user-created ("wild") repo.
 
-List or set permissions for user-created ("wild") repo.  The first usage shown
-will list the current contents of the permissions file.  The other two will
-change permissions, adding or removing a user from a role.
+Usage summary:
+    ssh git at host perms <repo> -l
+        # list current permissions on repo
+    ssh git at host perms <repo> -lr
+        # list available roles and their access rights
+
+    ssh git at host perms <repo> + <rolename> <username>
+        # change permissions: add a user to a role
+    ssh git at host perms <repo> - <rolename> <username>
+        # change permissions: remove a user from a role
 
 Examples:
-    ssh git at host perms foo + READERS user1
-    ssh git at host perms foo + READERS user2
-    ssh git at host perms foo + READERS user3
+    ssh git at host perms my/repo + READERS alice
+    ssh git at host perms my/repo + WRITERS bob
 
 ----
 There is also a batch mode useful for scripting and bulk loading.  Do not
@@ -38,11 +42,8 @@ $ENV{GL_USER} or _die "GL_USER not set";
 
 my $generic_error = "repo does not exist, or you are not authorised";
 
-my $list = 0;
-if ( $ARGV[0] eq '-l' ) {
-    $list++;
-    shift;
-    getperms(@ARGV);    # doesn't return
+if ( @ARGV >= 2 and $ARGV[1] eq '-l' ) {
+    getperms($ARGV[0]);    # doesn't return
 }
 
 # auto-create the repo if -c passed and repo doesn't exist
@@ -63,7 +64,13 @@ if ( $ARGV[0] eq '-c' ) {
 }
 
 my $repo = shift;
-setperms(@ARGV);
+
+if ( $ARGV[0] eq '-lr' ) {
+    list_roles();
+    exit 0;
+} else {
+    setperms(@ARGV);
+}
 
 # cache control
 if ($rc{CACHE}) {
@@ -96,7 +103,7 @@ sub setperms {
         @ARGV = ();
         my @a;
         for (<>) {
-            _die "Invalid role '$1'; check the rc file" if /(\S+)/ and not $rc{ROLES}{$1};
+            invalid_role($1) if /(\S+)/ and not $rc{ROLES}{$1};
             push @a, $_;
         }
         print STDERR "\n";    # make sure Ctrl-C gets caught
@@ -107,7 +114,6 @@ sub setperms {
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if @_ != 3;
     my ( $op, $role, $user ) = @_;
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
-    _die "Invalid role '$role'; check the rc file"                     if not $rc{ROLES}{$role};
     _die "Invalid user '$user'"                                        if not $user =~ $USERNAME_PATT;
 
     my $text = '';
@@ -123,6 +129,7 @@ sub setperms {
             _print( $pf, @text );
         }
     } else {
+        invalid_role($role) unless grep { $_->[3] eq $role } load_roles();
         if ($present) {
             _warn "'$role $user' already present in file";
         } else {
@@ -132,3 +139,50 @@ sub setperms {
         }
     }
 }
+
+my @rules;
+
+sub load_roles {
+    return @rules if @rules;
+
+    require Gitolite::Conf::Load;
+    Gitolite::Conf::Load::load($repo);
+
+    my %repos = %Gitolite::Conf::Load::repos;
+    my @repo_memberships = Gitolite::Conf::Load::memberships('repo', $repo);
+
+    for my $rp (@repo_memberships) {
+        my $hr = $repos{$rp};
+        for my $r ( keys %$hr ) {
+            next unless $r =~ s/^@//;
+            next unless $rc{ROLES}{$r};
+            map { $_->[3] = $r } @{ $hr->{"\@$r"} };
+            push @rules, @{ $hr->{"\@$r"} };
+        }
+    }
+    return @rules;
+}
+
+sub invalid_role {
+    my $role = shift;
+
+    print STDERR "Invalid role '$role'; valid roles for this repo:\n";
+    list_roles();
+    exit 1;
+}
+
+sub list_roles {
+
+    my @rules = sort { $a->[0] <=> $b->[0] } load_roles();
+
+    for (@rules) {
+        $_->[2] =~ s(^refs/heads/)();
+        $_->[2] = '--any--' if $_->[2] eq 'refs/.*';
+    }
+
+    my $max = 0;
+    map { $max = $_ if $_ > $max } map { length($_->[2]) } @rules;
+    printf("\t%s\t%*s\t \t%s\n", "perm",  -$max, "ref",   "role");
+    printf("\t%s\t%*s\t \t%s\n", "----",  -$max, "---",   "----");
+    printf("\t%s\t%*s\t=\t%s\n", $_->[1], -$max, $_->[2], $_->[3]) for @rules;
+}
diff --git a/t/0-me-first.t b/t/0-me-first.t
index 822d1af..8c9d12b 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -22,6 +22,7 @@ confreset;confadd '
     repo cc/..*
         C       =   u4
         RW+     =   CREATOR u5
+        R       =   READERS
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
diff --git a/t/daemon-gitweb-via-perms.t b/t/daemon-gitweb-via-perms.t
index 0d19371..8707e19 100755
--- a/t/daemon-gitweb-via-perms.t
+++ b/t/daemon-gitweb-via-perms.t
@@ -53,7 +53,7 @@ try "
     glt perms u1 bar/u1/try1 + READERS daemon
     !/./
 
-    glt perms u1 -l bar/u1/try1
+    glt perms u1 bar/u1/try1 -l
     /READERS daemon/
 
     find $h/repositories -name git-daemon-export-ok
@@ -65,7 +65,7 @@ try "
 
     glt perms u1 bar/u1/try2 + READERS gitweb
 
-    glt perms u1 -l bar/u1/try2
+    glt perms u1 bar/u1/try2 -l
     /READERS gitweb/
 
     find $h/repositories -name git-daemon-export-ok
diff --git a/t/perm-roles.t b/t/perm-roles.t
index a1c4f85..03403d6 100755
--- a/t/perm-roles.t
+++ b/t/perm-roles.t
@@ -64,7 +64,7 @@ glt push u1 file:///foo/u1/u1r1 t1
 
 # add u2 to WRITERS
 echo WRITERS \@g2 | glt perms u1 foo/u1/u1r1
-glt perms u1 -l foo/u1/u1r1
+glt perms u1 foo/u1/u1r1 -l
         /WRITERS \@g2/
 
 glt fetch u1
@@ -96,7 +96,7 @@ glt push u2 file:///foo/u1/u1r1 t2
 
 # change u2 to READERS
 echo READERS u2 | glt perms u1 foo/u1/u1r1
-glt perms u1 -l foo/u1/u1r1
+glt perms u1 foo/u1/u1r1 -l
         /READERS u2/
 
 glt fetch u1
@@ -122,7 +122,7 @@ try "
     gitolite compile;   ok or die compile failed
     /usr/bin/printf 'READERS u6\\nMANAGERS u2\\n' | glt perms u1 foo/u1/u1r1
                             ok;    !/Invalid role 'MANAGERS'/
-    glt perms u1 -l foo/u1/u1r1
+    glt perms u1 foo/u1/u1r1 -l
 ";
 
 cmp 'READERS u6
@@ -169,7 +169,7 @@ gitolite compile;   ok or die compile failed
 # add u2 to now valid TESTERS
 echo TESTERS u2 | glt perms u1 foo/u1/u1r1
         !/Invalid role 'TESTERS'/
-glt perms u1 -l foo/u1/u1r1
+glt perms u1 foo/u1/u1r1 -l
 ";
 
 cmp 'TESTERS u2
diff --git a/t/perms-groups.t b/t/perms-groups.t
index a4b6839..5de75be 100755
--- a/t/perms-groups.t
+++ b/t/perms-groups.t
@@ -51,7 +51,7 @@ try "
 
     # \@devs can R try1
     echo READERS \@devs | glt perms u1 bar/u1/try1; ok
-    glt perms u1 -l bar/u1/try1
+    glt perms u1 bar/u1/try1 -l
         /READERS \@devs/
         !/WRITERS \@leads/
 
@@ -68,7 +68,7 @@ try "
 
 # combo of previous 2
     /usr/bin/printf 'READERS \@devs\\nWRITERS \@leads\\n' | glt perms u1 bar/u1/try1; ok
-    glt perms u1 -l bar/u1/try1
+    glt perms u1 bar/u1/try1 -l
         /READERS \@devs/
         /WRITERS \@leads/
     glt info u1 -lc
diff --git a/t/sequence.t b/t/sequence.t
index 87f3731..81fabfc 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -34,7 +34,7 @@ try "
         /To file:///foo/u1/bar/
         /\\[new branch\\]      master -> master/
     echo WRITERS u2 | glt perms u1 foo/u1/bar
-    glt perms u1 -l foo/u1/bar
+    glt perms u1 foo/u1/bar -l
         /WRITERS u2/
     # expand
     glt info u2
@@ -78,7 +78,7 @@ try "
         /To file:///foo/u1/bar/
         /\\[new branch\\]      master -> master/
     echo WRITERS u2 | glt perms u1 foo/u1/bar
-    glt perms u1 -l foo/u1/bar
+    glt perms u1 foo/u1/bar -l
         /WRITERS u2/
     # expand
     glt info u2
@@ -106,7 +106,7 @@ try "
     echo READERS u2 | glt perms u1 -c foo/u1/baz
         /Initialized empty Git repository in .*/foo/u1/baz.git/
 
-    glt perms u1 -l foo/u1/baz
+    glt perms u1 foo/u1/baz -l
         /READERS u2/
     # expand
     glt info u2
diff --git a/t/wild-1.t b/t/wild-1.t
index c957798..7a8f766 100755
--- a/t/wild-1.t
+++ b/t/wild-1.t
@@ -93,7 +93,7 @@ glt clone u5 file:///foo/u4/a12 u5a12;  !ok;    /R any foo/u4/a12 u5 DENIED by f
 glt perms u4 foo/u4/a12 + READERS u5
 glt perms u4 foo/u4/a12 + WRITERS u6
 
-glt perms u4 -l foo/u4/a12
+glt perms u4 foo/u4/a12 -l
 ";
 
 cmp 'READERS u5
diff --git a/t/wild-2.t b/t/wild-2.t
index 7f5d338..cbba4f8 100755
--- a/t/wild-2.t
+++ b/t/wild-2.t
@@ -94,7 +94,7 @@ glt perms u4 foo/u4/a12 + READERS u5
 glt perms u4 foo/u4/a12 + WRITERS u6
 
 # getperms
-glt perms u4 -l foo/u4/a12
+glt perms u4 foo/u4/a12 -l
 ";
 
 cmp 'READERS u5

commit 5b38096062af167c25809ba51dde5ee026d04d9a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 4 05:39:37 2015 +0530

    prevent info leak when gitolite-shell is used as $SHELL...
    
    The error message is explicitly the same as you get when you do send in an
    invalid git/gitolite command; this is by design.

diff --git a/src/gitolite-shell b/src/gitolite-shell
index e5e6f9c..e0bc090 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -39,7 +39,7 @@ _die "I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMA
 # allow gitolite-shell to be used as "$SHELL".  Experts only; no support, no docs
 if (@ARGV and $ARGV[0] eq '-c') {
     shift;
-    $ARGV[0] =~ s/^$0 //;
+    $ARGV[0] =~ s/^$0 // or _die "unknown git/gitolite command: '$ARGV[0]'";
 }
 
 # the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed

commit 78fc24088c216686dd03d6aeba72f86559dfddf5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Feb 21 18:54:47 2015 +0530

    authkeys file need not be 0700...
    
    0600 is sufficient.
    
    Caught by Christoph.  I don't actually think there is any possibility of this
    being used to attack gitolite but it's just a 1-bit change :)
    
    ref: https://groups.google.com/forum/#!topic/gitolite/C_kzny--yP8

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index e686119..84dda73 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -82,7 +82,7 @@ sub sanity {
     _mkdir( $akdir, 0700 ) if not -d $akdir;
     if ( not -f $akfile ) {
         _print( $akfile, "" );
-        chmod 0700, $akfile;
+        chmod 0600, $akfile;
     }
 }
 

commit 9cd1e373e197e9191c6ed9b85da8a2b411e65f2f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Feb 20 14:28:53 2015 +0530

    allow gitolite-shell to be used as "$SHELL"...
    
    Experts only; no support and no docs.
    
    Idea courtesy "calestyo"; see mails on Feb 20 or so in this:
    https://groups.google.com/forum/#!msg/gitolite/eLTiK8hvijo/9dKI8YfTSecJ

diff --git a/src/gitolite-shell b/src/gitolite-shell
index e299ad2..e5e6f9c 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -36,6 +36,12 @@ my $soc = $ENV{SSH_ORIGINAL_COMMAND};
 $soc =~ s/[\n\r]+/<<newline>>/g;
 _die "I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
 
+# allow gitolite-shell to be used as "$SHELL".  Experts only; no support, no docs
+if (@ARGV and $ARGV[0] eq '-c') {
+    shift;
+    $ARGV[0] =~ s/^$0 //;
+}
+
 # the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed
 trigger('INPUT');
 

commit b6050a818ecfd394d8e02b81c8bab266f3d44cbc
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Feb 8 06:08:58 2015 +0530

    test suite: write user.* settings to ~/.gitconfig.local...
    
    ...instead of ~/.gitconfig, and add an include.path in it

diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index 291eace..904abbf 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -63,8 +63,9 @@ try "
     ln -sf $ENV{PWD}/t/glt ~/bin
     ./install -ln
     cd; rm -vrf .gito* repositories
-    git config --global user.name \"gitolite tester\"
-    git config --global user.email \"tester\@example.com\"
+    git config --file $ENV{HOME}/.gitconfig.local user.name \"gitolite tester\"
+    git config --file $ENV{HOME}/.gitconfig.local user.email \"tester\@example.com\"
+    git config --global                           include.path \"~/.gitconfig.local\"
 
     # setup
     gitolite setup -a admin

commit d500cb7da94ea66ddc46b5cb50bce3779684e0cf
Author: Robin H. Johnson <robbat2 at gentoo.org>
Date:   Tue Feb 3 09:53:20 2015 -0800

    sshkeys-lint: refactor keytype and accept ed25519
    
    sshkeys-lint was rejecting Ed25519 type keys, and also not detecting
    ecdsa keys for shell users; refactor the key type detection code to use
    a single variable and introduce Ed25519 into the new variable.
    
    Also explicitly matches the ECDSA key types now, rather than leaving it
    open-ended.
    
    Signed-off-by: Robin H. Johnson <robbat2 at gentoo.org>

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 3b2689d..7c0f508 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -17,6 +17,7 @@ $|++;
 
 my $in_gl_section = 0;
 my $warnings      = 0;
+my $KEYTYPE_REGEX = qr/\b(?:ssh-(?:rsa|dss|ed25519)|ecdsa-sha2-nistp(?:256|384|521))\b/;
 
 sub dbg {
     use Data::Dumper;
@@ -126,7 +127,7 @@ sub user {
     my $user = '';
     $user ||= "user $1"         if /^command=.*gitolite-shell (.*?)"/;
     $user ||= "unknown command" if /^command/;
-    $user ||= "shell access"    if /^ssh-(rsa|dss)/;
+    $user ||= "shell access"    if /$KEYTYPE_REGEX/;
 
     return $user;
 }
@@ -142,10 +143,10 @@ sub ak_comment {
 sub fprint {
     local $_ = shift;
     my ( $fh, $tempfn, $in );
-    if ( /ssh-(dss|rsa) / || /ecdsa-/ ) {
+    if ( /$KEYTYPE_REGEX/ ) {
         # an actual key was passed.  Since ssh-keygen requires an actual file,
         # make a temp file to take the data and pass on to ssh-keygen
-        s/^.* (ssh-dss|ssh-rsa|ecdsa-\S+)/$1/;
+        s/^.* ($KEYTYPE_REGEX)/$1/;
         use File::Temp qw(tempfile);
         ( $fh, $tempfn ) = tempfile();
         $in = $tempfn;

commit 346b1324b4bcf854f8aba8af575f4ae238db812b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Feb 7 12:14:06 2015 +0530

    fix ugliness in http output when only base URL is given

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 879da54..e299ad2 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -67,6 +67,7 @@ sub in_http {
     # fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http,
     # so the rest of the code stays the same (except the exec at the end).
     http_simulate_ssh_connection();
+    $ENV{SSH_ORIGINAL_COMMAND} ||= '';
 
     $ENV{REMOTE_USER} ||= $rc{HTTP_ANON_USER};
     @ARGV = ( $ENV{REMOTE_USER} );

commit af84e178a75e95ec965a0a7c3e067031b7513b26
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Feb 4 05:13:39 2015 +0530

    info: get description from the 'gitweb.description'

diff --git a/src/commands/info b/src/commands/info
index 267e4f6..08e9931 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -97,11 +97,16 @@ sub listem {
         my $perm = '';
         $creator = creator($repo) if $lc;
 
-        my $desc = '';
-        for my $d ("$ENV{GL_REPO_BASE}/$repo.git/description") {
-            next unless $ld and -r $d;
-            $desc = slurp($d);
-            chomp($desc);
+        my $desc;
+        if ($ld) {
+            # use config value first, else 'description' file as second choice
+            my $k = 'gitweb.description';
+            my $d = "$ENV{GL_REPO_BASE}/$repo.git/description";
+            $desc = git_config( $repo, $k )->{$k} || '';
+            if ( !$desc and -r $d ) {
+                $desc = slurp($d);
+                chomp($desc);
+            }
         }
 
         for my $aa (@aa) {

commit ef91d974047e2f1231db9c5837ccc950eb9b6dc7
Author: Robert Schiele <rschiele at gmail.com>
Date:   Fri Jan 23 07:30:15 2015 +0100

    add missing include path to GL_LIBDIR
    
    In src/triggers/post-compile/update-description-file and
    src/triggers/post-compile/update-gitweb-daemon-from-options the perl
    module Gitolite::Easy is directly loaded from the command line but
    the include path was not set.  This change also sets this missing
    include path.
    
    (a typo in one of the arguments was also fixed -- sitaram)

diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
index c5e76cc..e5b7c6a 100755
--- a/src/triggers/post-compile/update-description-file
+++ b/src/triggers/post-compile/update-description-file
@@ -10,7 +10,7 @@
 # adding this program to the POST_COMPILE trigger list).
 
 cd $GL_REPO_BASE
-gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -MGitolite::Easy -lne '
+gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne '
             my @F = split /\t/,$_,3;
             textfile( file => "description", repo => $F[0], text => $F[2] );
     '
diff --git a/src/triggers/post-compile/update-gitweb-daemon-from-options b/src/triggers/post-compile/update-gitweb-daemon-from-options
index 0edbb87..9b499b2 100755
--- a/src/triggers/post-compile/update-gitweb-daemon-from-options
+++ b/src/triggers/post-compile/update-gitweb-daemon-from-options
@@ -51,4 +51,4 @@ trap "rm -rf $tmp" 0
 gitolite list-phy-repos | sort | tee $tmp/all | gitolite git-config % gitolite-options.daemon | cut -f1 > $tmp/daemon
 
 comm -23 $tmp/all $tmp/daemon | perl -lne 'unlink "$ENV{RB}/$_.git/$ENV{EO}"'
-cat               $tmp/daemon | perl -MGitolite::Easy -lne 'textfile( file => $ENV{EO}, repo => $_, text = "");'
+cat               $tmp/daemon | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne 'textfile( file => $ENV{EO}, repo => $_, text => "");'

commit e79f335b9519ee0f5866a28f2fe26e1228f9c820
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jan 25 10:32:32 2015 +0530

    writable: 'status' subcommand changes...
    
    -   allow users with RW to use it
    -   fix bug where, when a repo's writable status is checked, the global status
        was not checked
    
    Thanks to Michel Bourget for discussion and ideas.

diff --git a/src/commands/writable b/src/commands/writable
index e84020e..3e97f0b 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -29,7 +29,7 @@ my $op   = shift;    # on|off|status
 if ( $repo eq '@all' ) {
     _die "you are not authorized" if $ENV{GL_USER} and not is_admin();
 } else {
-    _die "you are not authorized" if $ENV{GL_USER} and not( owns($repo) or is_admin() );
+    _die "you are not authorized" if $ENV{GL_USER} and not( owns($repo) or is_admin() or ( can_write($repo) and $op eq 'status' ) );
 }
 
 my $msg = join( " ", @ARGV );
@@ -46,13 +46,15 @@ if ( $repo eq '@all' ) {
     target( $ENV{HOME} );
 } else {
     target("$rb/$repo.git");
+    target( $ENV{HOME} ) if $op eq 'status';
 }
 
+exit 0;
+
 sub target {
     my $repodir = shift;
     if ( $op eq 'status' ) {
         exit 1 if -e "$repodir/$sf";
-        exit 0;
     } elsif ( $op eq 'on' ) {
         unlink "$repodir/$sf";
     } elsif ( $op eq 'off' ) {

commit 1c61d578ce07715ef5bf54eec25acb5fdd654210
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jan 3 13:30:17 2015 +0530

    ban intermediate components of a repo path ending in ".git"
    
    Thanks to Tim Nordell for catching this...
    
    With wild repos, you can create a repo like foo/bar.git/baz (whose actual path
    becomes $HOME/repositories/foo/bar.git/baz.git), and after that funny things
    happen.
    
    The reason is that, for efficiency reasons, when running the unix 'find'
    command, gitolite prunes the find when it encounters a directory whose name
    ends in ".git".
    
    I have no intention of changing that, and I think it's easier to ban this type
    of repo than worry about enabling an odd corner case that has no real use.
    
    While doing that, I realised I have too many subroutines called "sanity()" or
    similar names, so... got some sanity into *them*!

diff --git a/src/gitolite-shell b/src/gitolite-shell
index f17345a..879da54 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -102,7 +102,7 @@ sub main {
 
     # set up the repo and the attempted access
     my ( $verb, $repo ) = parse_soc();    # returns only for git commands
-    sanity($repo);
+    Gitolite::Conf::Load::sanity($repo, $REPONAME_PATT);
     $ENV{GL_REPO} = $repo;
     my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
 
@@ -171,13 +171,6 @@ sub parse_soc {
     _die "unknown git/gitolite command: '$soc'";
 }
 
-sub sanity {
-    my $repo = shift;
-    _die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT;
-    _die "'$repo' ends with a '/'"         if $repo =~ m(/$);
-    _die "'$repo' contains '..'"           if $repo =~ m(\.\.);
-}
-
 # ----------------------------------------------------------------------
 # helper functions for "in_http"
 
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 70f22ce..88d1612 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -233,11 +233,13 @@ sub option {
 }
 
 sub sanity {
-    my $repo = shift;
+    my ($repo, $patt) = @_;
+    $patt ||= $REPOPATT_PATT;
 
-    _die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
-    _die "'$repo' ends with a '/'" if $repo =~ m(/$);
-    _die "'$repo' contains '..'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
+    _die "invalid repo '$repo'" if not( $repo and $repo =~ $patt );
+    _die "'$repo' ends with a '/'"  if $repo =~ m(/$);
+    _die "'$repo' contains '..'"    if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
+    _die "'$repo' contains '.git/'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.git/);
 }
 
 sub repo_missing {
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index c7bd8cc..8f530f2 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -195,7 +195,7 @@ sub textfile {
     # target file
     _die "need file" unless $h{file};
     _die "'$h{file}' contains a '/'" if $h{file} =~ m(/);
-    _sanity($h{file});
+    Gitolite::Conf::Load::sanity($h{file}, $REPONAME_PATT);
 
     # target file's location.  This can come from one of two places: dir
     # (which comes from our code, so does not need to be sanitised), or repo,
@@ -203,7 +203,7 @@ sub textfile {
     _die "need exactly one of repo or dir" unless $h{repo} xor $h{dir};
     _die "'$h{dir}' does not exist" if $h{dir} and not -d $h{dir};
     if ($h{repo}) {
-        _sanity($h{repo});
+        Gitolite::Conf::Load::sanity($h{repo}, $REPONAME_PATT);
         $h{dir} = "$rc{GL_REPO_BASE}/$h{repo}.git";
         _die "repo '$h{repo}' does not exist" if not -d $h{dir};
 
@@ -232,14 +232,6 @@ sub textfile {
 
 # ----------------------------------------------------------------------
 
-sub _sanity {
-    my $name = shift;
-    _die "'$name' contains bad characters" if $name !~ $REPONAME_PATT;
-    _die "'$name' ends with a '/'"         if $name =~ m(/$);
-    _die "'$name' contains '..'"           if $name =~ m(\.\.);
-}
-
-
 sub valid_user {
     _die "GL_USER not set" unless exists $ENV{GL_USER};
     $user = $ENV{GL_USER};

commit cf062b8bb6b21a52f7c5002d33fbc950762c1aa7
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Dec 31 06:17:55 2014 +0530

    fixups to the "save-push-signatures" program
    
    (both thanks to Junio's review)
    
    -   detect/discard replayed certs in handling 'git push --signed'
    -   make the commit message also contain the blob.  It's kinda redundant to
        have it in both the commit message *and* the individual files, but is
        easier to process in terms of checking the entire cert chain.
    
    links to threads:
        https://groups.google.com/forum/#!topic/gitolite/7cSrU6JorEY
        http://article.gmane.org/gmane.comp.version-control.git/261928

diff --git a/contrib/hooks/repo-specific/save-push-signatures b/contrib/hooks/repo-specific/save-push-signatures
index 0ff9a11..2470491 100755
--- a/contrib/hooks/repo-specific/save-push-signatures
+++ b/contrib/hooks/repo-specific/save-push-signatures
@@ -9,6 +9,11 @@
 # that allows searching for all the certs pertaining to one specific branch
 # (thanks to Junio Hamano for this idea plus general brainstorming).
 
+# The "collection" happens only if $GIT_PUSH_CERT_NONCE_STATUS = OK; again,
+# thanks to Junio for pointing this out; see [1]
+#
+# [1]: https://groups.google.com/forum/#!topic/gitolite/7cSrU6JorEY
+
 # WARNINGS:
 #   Does not check that GIT_PUSH_CERT_STATUS = "G".  If you want to check that
 #   and FAIL the push, you'll have to write a simple pre-receive hook
@@ -26,12 +31,14 @@
 #       http://gitolite.com/gitolite/cookbook.html#v3.6-variation-repo-specific-hooks
 
 # Environment:
+#   GIT_PUSH_CERT_NONCE_STATUS should be "OK" (as mentioned above)
+#
 #   GL_OPTIONS_GPC_PENDING (optional; defaults to 1).  This is the number of
 #   git push certs that should be waiting in order to trigger the post
 #   processing.  You can set it within gitolite like so:
 #
-#   repo foo bar    # or maybe just 'repo @all'
-#       option ENV.GPC_PENDING = 5
+#       repo foo bar    # or maybe just 'repo @all'
+#           option ENV.GPC_PENDING = 5
 
 # Setup:
 #   Set up this code as a post-receive hook for whatever repos you need to.
@@ -82,6 +89,11 @@ warn() { echo "$@" >&2; }
 # if there are no arguments, we're running as a "post-receive" hook
 if [ -z "$1" ]
 then
+    # ignore if it may be a replay attack
+    [ "$GIT_PUSH_CERT_NONCE_STATUS" = "OK" ] || exit 1
+    # I don't think "exit 1" does anything in a post-receive anyway, so that's
+    # just a symbolic gesture!
+
     # note the lock file used
     _lock .gpc.lock $0 cat_blob
 
@@ -161,21 +173,12 @@ then
             # we're using the ref name as a "fake" filename, so people can,
             # for example, 'git log refs/push-certs -- refs/heads/master', to
             # see all the push certs pertaining to the master branch.  This
-            # idea came from Junio Hamano, the git maintanier (I certainly
+            # idea came from Junio Hamano, the git maintainer (I certainly
             # don't deal with git plumbing enough to have thought of it!)
         done
 
         T=$(git write-tree)
-        C=$(
-            (
-                echo "git push cert blob $b"
-                echo
-                cat $cf | grep ^pusher | perl -pe 's/\d{10}.*/localtime $&/e'
-                cat $cf | grep ^pushee
-                echo
-                cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/'
-            ) | git commit-tree -p $PUSH_CERTS $T
-        )
+        C=$( git commit-tree -p $PUSH_CERTS $T < $cf )
         git update-ref $PUSH_CERTS $C
 
         rm -f $cf

commit 90e47e1ba386b5a3eb6db2beb96682ea30c73b20
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Dec 30 08:55:19 2014 +0530

    new command: option

diff --git a/src/commands/option b/src/commands/option
new file mode 100644
index 0000000..de49aab
--- /dev/null
+++ b/src/commands/option
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+# ----------------------------------------------------------------------
+# gitolite command to allow repo "owners" to set "options" on repos
+
+# This command can be run by a user to set "options" for any repo that she
+# owns.
+#
+# However, gitolite does *not* have the concept of an incremental "compile",
+# and options are only designed to be specified in the gitolite.conf file
+# (which a user should not be able to even see!).  Therefore, we allow one
+# specific file (conf/options.conf) to be manipulated by a remote user in a
+# *controlled* fashion, and this file is "include"d in the main gitolite.conf
+# file.
+
+# WARNINGS:
+#   1.  Runs "gitolite compile" at the end.  On really huge systems (where the
+#       sum total of the conf files is in the order of tens of thousands of
+#       lines) this may take a second or two :)
+#   2.  Since "options.conf" is not part of the admin repo, you may need to
+#       back it up separately, just like you currently back up gl-creator and
+#       gl-perms files from individual repos.
+#   3.  "options.conf" is formatted very strictly because it's not meant to be
+#       human edited.  If you edit it directly on the server, be careful.
+
+# Relevant gitolite doc links:
+#   "wild" repos and "owners"
+#       http://gitolite.com/gitolite/wild.html
+#       http://gitolite.com/gitolite/wild.html#specifying-owners
+#       http://gitolite.com/gitolite/wild.html#appendix-1-owner-and-creator
+#   gitolite "options"
+#       http://gitolite.com/gitolite/options.html
+#   the "include" statement
+#       http://gitolite.com/gitolite/conf.html#include
+
+# setup:
+#   1.  Enable the command by adding it to the ENABLE list in the rc file.
+#
+#   2.  Make sure your gitolite.conf has this line at the end:
+#
+#           include "options.conf"
+#
+#       then add/commit/push.
+#
+#       Do NOT add a file called "options.conf" to your gitolite-admin repo!
+#       This means every time you compile (push the admin repo) you will get a
+#       warning about the missing file.
+#
+#       You can either "touch ~/.gitolite/conf/options.conf" on the server, or
+#       take *any* wild repo and add *any* option to create it.
+#
+#   3.  Specify options allowed to be changed by the user.  For example:
+#
+#           repo foo/..*
+#               C   =   blah blah
+#               ...other rules...
+#               option user-options = hook\..* foo bar[0-9].*
+#
+#       Users can then set any of these options, but no others.
+
+# ----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+use Gitolite::Common;
+
+# ----------------------------------------------------------------------
+# usage and arg checks
+
+=for usage
+Usage:    ssh git at host option <repo> add <key> <val>
+          ssh git at host option <repo> del <key>
+          ssh git at host option <repo> list
+
+Add, delete, or list options for wild repos.  Keys must match one of the
+allowed patterns; your system administrator will tell you what they are.
+
+Doesn't check things like adding a key that already exists (simply overwrites
+without warning), deleting a key that doesn't, etc.
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+my $OPTIONS = "$ENV{HOME}/.gitolite/conf/options.conf";
+
+my $repo = shift;
+die "sorry, you are not authorised\n" unless owns($repo);
+
+my $op = shift; usage() unless $op =~ /^(add|del|list)$/;
+my $key = shift; usage() if not $key and $op ne 'list';
+my $val = shift; usage() if not $val and $op eq 'add';
+
+_print( $OPTIONS, "" ) unless -f $OPTIONS;    # avoid error on first run
+my $options = slurp($OPTIONS);
+
+# ----------------------------------------------------------------------
+# get 'list' out of the way first
+if ( $op eq 'list' ) {
+    print "$1\t$2\n" while $options =~ /^repo $repo\n    option (\S+) = (.*)/mg;
+    exit 0;
+}
+
+# ----------------------------------------------------------------------
+# that leaves 'add' or 'del'
+
+# NOTE: sanity check on characters in key and val not needed;
+# REMOTE_COMMAND_PATT is more restrictive than UNSAFE_PATT anyway!
+
+# check if the key is allowed
+my $user_options = option( $repo, 'user-options' );
+# this is a space separated list of allowed option keys
+my @validkeys = split( ' ', ( $user_options || '' ) );
+my @matched = grep { $key =~ /^$_$/i } @validkeys;
+_die "option '$key' not allowed\n" if ( @matched < 1 );
+
+# delete anyway
+$options =~ s/^repo $repo\n    option $key = .*\n//m;
+# then re-add if needed
+$options .= "repo $repo\n    option $key = $val\n" if $op eq 'add';
+
+# ----------------------------------------------------------------------
+# save and compile
+_print( $OPTIONS, $options );
+system("gitolite compile");

commit cb273f3ea54873c52c6bac09da95901f2f7348d1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Dec 29 13:31:55 2014 +0530

    help with 'git push --signed'

diff --git a/contrib/hooks/repo-specific/save-push-signatures b/contrib/hooks/repo-specific/save-push-signatures
new file mode 100755
index 0000000..0ff9a11
--- /dev/null
+++ b/contrib/hooks/repo-specific/save-push-signatures
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+# ----------------------------------------------------------------------
+# post-receive hook to adopt push certs into 'refs/push-certs'
+
+# Collects the cert blob on push and saves it, then, if a certain number of
+# signed pushes have been seen, processes all the "saved" blobs in one go,
+# adding them to the special ref 'refs/push-certs'.  This is done in a way
+# that allows searching for all the certs pertaining to one specific branch
+# (thanks to Junio Hamano for this idea plus general brainstorming).
+
+# WARNINGS:
+#   Does not check that GIT_PUSH_CERT_STATUS = "G".  If you want to check that
+#   and FAIL the push, you'll have to write a simple pre-receive hook
+#   (post-receive is not the place for that; see 'man githooks').
+#
+#   Gitolite users: failing the hook cannot be done as a VREF because git does
+#   not set those environment variables in the update hook.  You'll have to
+#   write a trivial pre-receive hook and add that in.
+
+# Relevant gitolite doc links:
+#   repo-specific environment variables
+#       http://gitolite.com/gitolite/dev-notes.html#rsev
+#   repo-specific hooks
+#       http://gitolite.com/gitolite/non-core.html#rsh
+#       http://gitolite.com/gitolite/cookbook.html#v3.6-variation-repo-specific-hooks
+
+# Environment:
+#   GL_OPTIONS_GPC_PENDING (optional; defaults to 1).  This is the number of
+#   git push certs that should be waiting in order to trigger the post
+#   processing.  You can set it within gitolite like so:
+#
+#   repo foo bar    # or maybe just 'repo @all'
+#       option ENV.GPC_PENDING = 5
+
+# Setup:
+#   Set up this code as a post-receive hook for whatever repos you need to.
+#   Then arrange to have the environment variable GL_OPTION_GPC_PENDING set to
+#   some number, as shown above.  (This is only required if you need it to be
+#   greater than 1.)  It could of course be different for different repos.
+#   Also see "Invocation" section below.
+
+# Invocation:
+#   Normally via git (see 'man githooks'), once it is setup as a post-receive
+#   hook.
+#
+#   However, if you set the "pending" limit high, and want to periodically
+#   "clean up" pending certs without necessarily waiting for the counter to
+#   trip, do the following (untested):
+#
+#       RB=$(gitolite query-rc GL_REPO_BASE)
+#       for r in $(gitolite list-phy-repos)
+#       do
+#           cd $RB/$repo.git
+#           unset GL_OPTIONS_GPC_PENDING    # if it is set higher up
+#           hooks/post-receive post_process
+#       done
+#
+#   That will take care of it.
+
+# Using without gitolite:
+#   Just set GL_OPTIONS_GPC_PENDING within the script (maybe read it from git
+#   config).  Everything else is independent of gitolite.
+
+# ----------------------------------------------------------------------
+# make it work on BSD also (but NOT YET TESTED on FreeBSD!)
+uname_s=`uname -s`
+if [ "$uname_s" = "Linux" ]
+then
+    _lock() { flock "$@"; }
+else
+    _lock() { lockf -k "$@"; }
+    # I'm assuming other BSDs also have this; I only have FreeBSD.
+fi
+
+# ----------------------------------------------------------------------
+# standard stuff
+die() { echo "$@" >&2; exit 1; }
+warn() { echo "$@" >&2; }
+
+# ----------------------------------------------------------------------
+# if there are no arguments, we're running as a "post-receive" hook
+if [ -z "$1" ]
+then
+    # note the lock file used
+    _lock .gpc.lock $0 cat_blob
+
+    # if you want to initiate the post-processing ONLY from outside (for
+    # example via cron), comment out the next line.
+    exec $0 post_process
+fi
+
+# ----------------------------------------------------------------------
+# the 'post_process' part; see "Invocation" section in the doc at the top
+if [ "$1" = "post_process" ]
+then
+    # this is the same lock file as above
+    _lock .gpc.lock $0 count_and_rotate $$
+
+    [ -d git-push-certs.$$ ] || exit 0
+
+    # but this is a different one
+    _lock .gpc.ref.lock $0 update_ref $$
+
+    exit 0
+fi
+
+# ----------------------------------------------------------------------
+# other values for "$1" are internal use only
+
+if [ "$1" = "cat_blob" ]
+then
+    mkdir -p git-push-certs
+    git cat-file blob $GIT_PUSH_CERT > git-push-certs/$GIT_PUSH_CERT
+    echo $GIT_PUSH_CERT >> git-push-certs/.blob.list
+fi
+
+if [ "$1" = "count_and_rotate" ]
+then
+    count=$(ls git-push-certs | wc -l)
+    if test $count -ge ${GL_OPTIONS_GPC_PENDING:-1}
+    then
+        # rotate the directory
+        mv git-push-certs git-push-certs.$2
+    fi
+fi
+
+if [ "$1" = "update_ref" ]
+then
+    # use a different index file for all this
+    GIT_INDEX_FILE=push_certs_index; export GIT_INDEX_FILE
+
+    # prepare the special ref to receive commits
+    PUSH_CERTS=refs/push-certs
+    if git rev-parse -q --verify $PUSH_CERTS >/dev/null
+    then
+        git read-tree $PUSH_CERTS
+    else
+        git read-tree --empty
+        T=$(git write-tree)
+        C=$(echo 'start' | git commit-tree $T)
+        git update-ref $PUSH_CERTS $C
+    fi
+
+    # for each cert blob...
+    for b in `cat git-push-certs.$2/.blob.list`
+    do
+        cf=git-push-certs.$2/$b
+
+        # it's highly unlikely that the blob got GC-ed already but write it
+        # back anyway, just in case
+        B=$(git hash-object -w $cf)
+
+        # bit of a sanity check
+        [ "$B" = "$b" ] || warn "this should not happen: $B is not equal to $b"
+
+        # for each ref described within the cert, update the index
+        for ref in `cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/' | cut -f3 -d' '`
+        do
+            git update-index --add --cacheinfo 100644,$b,$ref
+            # we're using the ref name as a "fake" filename, so people can,
+            # for example, 'git log refs/push-certs -- refs/heads/master', to
+            # see all the push certs pertaining to the master branch.  This
+            # idea came from Junio Hamano, the git maintanier (I certainly
+            # don't deal with git plumbing enough to have thought of it!)
+        done
+
+        T=$(git write-tree)
+        C=$(
+            (
+                echo "git push cert blob $b"
+                echo
+                cat $cf | grep ^pusher | perl -pe 's/\d{10}.*/localtime $&/e'
+                cat $cf | grep ^pushee
+                echo
+                cat $cf | egrep '^[a-f0-9]+ [a-f0-9]+ refs/'
+            ) | git commit-tree -p $PUSH_CERTS $T
+        )
+        git update-ref $PUSH_CERTS $C
+
+        rm -f $cf
+    done
+    rm -f git-push-certs.$2/.blob.list
+    rmdir git-push-certs.$2
+fi

commit e512db824a649f4f3bc42bee25687e583600b22a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Dec 28 09:20:13 2014 +0530

    amendments to previous commit about honoring repoumask
    
    -   convert git-daemon access list trigger from sh to perl.  There's not much
        shell code in there now anyway
    
    -   (minor) use \t as the separator in the description file update code

diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
index 49bc2a4..c5e76cc 100755
--- a/src/triggers/post-compile/update-description-file
+++ b/src/triggers/post-compile/update-description-file
@@ -11,6 +11,6 @@
 
 cd $GL_REPO_BASE
 gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -MGitolite::Easy -lne '
-            my @F = split /\s+/,$_,3;
+            my @F = split /\t/,$_,3;
             textfile( file => "description", repo => $F[0], text => $F[2] );
     '
diff --git a/src/triggers/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
index 414b375..446b0da 100755
--- a/src/triggers/post-compile/update-git-daemon-access-list
+++ b/src/triggers/post-compile/update-git-daemon-access-list
@@ -1,26 +1,36 @@
-#!/bin/sh
+#!/usr/bin/perl
 
-# this is probably the *fastest* git-daemon update possible.
+# update git-daemon-export-ok files in each repo
+# ----------------------------------------------------------------------
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Easy;
+use Gitolite::Common;
+
+use strict;
+use warnings;
 
 # ----------------------------------------------------------------------
-# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
+# skip if arg-0 is POST_CREATE and no arg-2 (user name) exists; this means
 # it's been triggered by a *normal* (not "wild") repo creation, which in turn
 # means a POST_COMPILE should be following so there's no need to waste time
 # running this once for each new repo
-[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+exit 0 if @ARGV and $ARGV[0] eq 'POST_CREATE' and not $ARGV[2];
 
-EO=git-daemon-export-ok
-RB=`gitolite query-rc GL_REPO_BASE`
-export EO RB
+my $EO = "git-daemon-export-ok";
+my $RB = $rc{GL_REPO_BASE};
 
-gitolite list-phy-repos | gitolite access % daemon R any |
-    perl -MGitolite::Easy -lane '
-        unlink "$ENV{RB}/$F[0].git/$ENV{EO}" if /DENIED/;
-        textfile( file => $ENV{EO}, repo => $F[0], text => "" ) unless /DENIED/;
-    '
+for my $d (`gitolite list-phy-repos | gitolite access % daemon R any`) {
+    my @F = split "\t", $d;
+    if ($F[2] =~ /DENIED/) {
+        unlink "$RB/$F[0].git/$EO";
+    } else {
+        textfile( file => $EO, repo => $F[0], text => "" );
+    }
+}
 
-# A bit of explanation may be in order.  The gitolite output looks somewhat
-# like this:
+# As a quick recap, the gitolite output looks somewhat like this:
 
 #   bar^Idaemon^IR any bar daemon DENIED by fallthru$
 #   foo^Idaemon^Irefs/.*$
@@ -28,17 +38,4 @@ gitolite list-phy-repos | gitolite access % daemon R any |
 #   gitolite-admin^Idaemon^IR any gitolite-admin daemon DENIED by fallthru$
 #   testing^Idaemon^Irefs/.*$
 
-# where I've type "^I" to denote a tab.
-
-# Shell has to fork 'rm' to delete a file but perl doesn't.  So removing the
-# export-ok file from repos where needed is done in perl.
-
-# On the other hand, perls requires a bit more *code* to even create an empty
-# file.  Shell can do it with just "> file", and it doesn't fork for this.  So
-# that part is handled in shell.
-
-# You'll also see that the perl part is taking what it needs from the input
-# and passing the rest on, so the shell part doesn't have to do any grepping,
-# which would be a horrible slowdown.
-
-# $F and the rest is the magic of perl's flags (man perlrun).
+# where I've typed "^I" to denote a tab.

commit 36e667bef118b1d76d70f5368c6a2e42e417320c
Author: Jason Zaman <perfinion at gentoo.org>
Date:   Sat Dec 27 12:50:25 2014 -0800

    RepoUmask fixes
    
    Writing the description and git-daemon-export-ok files needs to be done
    using textfile() which will honour the RepoUmask.
    
    If you have global umask 0077 and a specific repo with 0022, then the
    description and git-daemon-export-ok files would previously get written
    with mode 0600, when they should be 0644.
    
    Signed-off-by: Robin H. Johnson <robbat2 at gentoo.org>
    Tested-by: Robin H. Johnson <robbat2 at gentoo.org>

diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
index 06cbc58..49bc2a4 100755
--- a/src/triggers/post-compile/update-description-file
+++ b/src/triggers/post-compile/update-description-file
@@ -10,8 +10,7 @@
 # adding this program to the POST_COMPILE trigger list).
 
 cd $GL_REPO_BASE
-gitolite list-phy-repos | gitolite git-config % gitweb.description |
-    while read a b c
-    do
-        echo "$c" > $GL_REPO_BASE/$a.git/description
-    done
+gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -MGitolite::Easy -lne '
+            my @F = split /\s+/,$_,3;
+            textfile( file => "description", repo => $F[0], text => $F[2] );
+    '
diff --git a/src/triggers/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
index cc865f1..414b375 100755
--- a/src/triggers/post-compile/update-git-daemon-access-list
+++ b/src/triggers/post-compile/update-git-daemon-access-list
@@ -14,14 +14,10 @@ RB=`gitolite query-rc GL_REPO_BASE`
 export EO RB
 
 gitolite list-phy-repos | gitolite access % daemon R any |
-    perl -lane '
+    perl -MGitolite::Easy -lane '
         unlink "$ENV{RB}/$F[0].git/$ENV{EO}" if /DENIED/;
-        print $F[0] unless /DENIED/
-    ' |
-    while read r
-    do
-        > $RB/$r.git/$EO
-    done
+        textfile( file => $ENV{EO}, repo => $F[0], text => "" ) unless /DENIED/;
+    '
 
 # A bit of explanation may be in order.  The gitolite output looks somewhat
 # like this:
diff --git a/src/triggers/post-compile/update-gitweb-daemon-from-options b/src/triggers/post-compile/update-gitweb-daemon-from-options
index a3627c9..0edbb87 100755
--- a/src/triggers/post-compile/update-gitweb-daemon-from-options
+++ b/src/triggers/post-compile/update-gitweb-daemon-from-options
@@ -51,7 +51,4 @@ trap "rm -rf $tmp" 0
 gitolite list-phy-repos | sort | tee $tmp/all | gitolite git-config % gitolite-options.daemon | cut -f1 > $tmp/daemon
 
 comm -23 $tmp/all $tmp/daemon | perl -lne 'unlink "$ENV{RB}/$_.git/$ENV{EO}"'
-cat               $tmp/daemon | while read repo
-do
-    > $RB/$repo.git/$EO
-done
+cat               $tmp/daemon | perl -MGitolite::Easy -lne 'textfile( file => $ENV{EO}, repo => $_, text = "");'

commit b8eb687840f1b30cb6c99ef846035c8d5e7b92e4
Author: gitolite tester <tester at example.com>
Date:   Wed Dec 10 12:37:11 2014 +0530

    test setup should now work in FreeBSD also...
    
    -   git 2.x
    -   some umask/perm issues in mirror test setup
    -   some paths and system commands in smart http setup

diff --git a/t/info-json.t b/t/info-json.t
index a78b79f..74fbdf4 100755
--- a/t/info-json.t
+++ b/t/info-json.t
@@ -171,7 +171,7 @@ sub test_gs {
     try 'perl $_ = "' . $res  . '"; /1/';
     $res = ( $href->{gitolite_version} =~ /^v3.[5-9]/ ? 1 : 'undef' );
     try 'perl $_ = "' . $res  . '"; /1/';
-    $res = ( $href->{git_version} =~ /^1.[6-9]/ ? 1 : 'undef' );
+    $res = ( $href->{git_version} =~ /^1.[6-9]|^2./ ? 1 : 'undef' );
     try 'perl $_ = "' . $res  . '"; /1/';
 }
 
diff --git a/t/mirror-test-setup.sh b/t/mirror-test-setup.sh
index 02a286a..b35364c 100755
--- a/t/mirror-test-setup.sh
+++ b/t/mirror-test-setup.sh
@@ -30,6 +30,7 @@ cd /tmp/g3
     done
     cp $bd/../t/mirror-test-ssh-config ssh-config
 }
+chmod -R go+rX /tmp/g3
 
 for h in $hosts
 do
@@ -96,6 +97,7 @@ RW  =   u1
 for h in $hosts
 do
     cat $bd/../t/mirror-test-rc | perl -pe "s/%HOSTNAME/$h/" > /tmp/g3/temp
+    chmod go+rX /tmp/g3/temp
     sudo -u $h -i cp /tmp/g3/temp .gitolite.rc
     echo "$lines"  | sudo -u $h -i sh -c 'cat >> .gitolite/conf/gitolite.conf'
     echo "$lines2" | sudo -u $h -i sh -c "cat >> .gitolite/conf/$h.conf"
diff --git a/t/smart-http.root-setup b/t/smart-http.root-setup
index 310efa3..7a46dda 100755
--- a/t/smart-http.root-setup
+++ b/t/smart-http.root-setup
@@ -17,13 +17,32 @@ die() { echo "$@"; exit 1; }
     exit 1
 }
 
-id | grep '=0(root)' || die "you must run this as root"
+# ----------------------------------------------------------------------
+# are we *BSD or Linux?
+uname_s=`uname -s`  # could be Linux or FreeBSD or some other BSD
+if [ "$uname_s" = "Linux" ]
+then
+    bsd=:
+else
+    lnx=:
+fi
+
+# ----------------------------------------------------------------------
+# main
+
+[ $EUID = 0 ] || die "you must run this as root"
 
 # delete any existing apache conf for gitolite
-rm /etc/httpd/conf.d/gitolite.conf
+$lnx rm /etc/httpd/conf.d/gitolite.conf
+$bsd rm /usr/local/etc/apache24/Includes/gitolite.conf
 
 # build your "home within a home"
-cd ~apache
+$lnx cd ~apache
+$bsd rm -rf /tmp/usr.share.httpd
+$bsd mkdir -p /tmp/usr.share.httpd
+$bsd chown www:www /tmp/usr.share.httpd
+$bsd cd /tmp/usr.share.httpd
+
 rm -rf gitolite-home
 mkdir gitolite-home
 export GITOLITE_HTTP_HOME=$PWD/gitolite-home
@@ -52,12 +71,13 @@ cat .gitolite.rc >> 1
 \mv 1 .gitolite.rc
 
 # fix up ownership
-chown -R apache.apache $GITOLITE_HTTP_HOME
+$lnx chown -R apache:apache $GITOLITE_HTTP_HOME
+$bsd chown -R www:www $GITOLITE_HTTP_HOME
 
 # create the apache config.  Note the trailing slashes on the 2 ScriptAlias
 # lines.  (The second one is optional for most sites).  NOTE: you also need to
 # give the AuthUserFile a better name/location than what I have below.
-cat <<EOF1 > /etc/httpd/conf.d/gitolite.conf
+cat <<EOF1 > 1
 SetEnv GIT_PROJECT_ROOT $GITOLITE_HTTP_HOME/repositories
 ScriptAlias /git/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
 ScriptAlias /gitmob/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
@@ -71,12 +91,15 @@ SetEnv GIT_HTTP_EXPORT_ALL
     AuthUserFile $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
 </Location>
 EOF1
+$lnx mv 1 /etc/httpd/conf.d/gitolite.conf
+$bsd mv 1 /usr/local/etc/apache24/Includes/gitolite.conf
 
 # NOTE: this is for testing only
 htpasswd -bc $GITOLITE_HTTP_HOME/gitolite-http-authuserfile admin admin
 map "htpasswd -b $GITOLITE_HTTP_HOME/gitolite-http-authuserfile % %" u{1..6}
-chown apache.apache $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
+$lnx chown apache:apache $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
+$bsd chown www:www $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
 
 # restart httpd to make it pick up all the new stuff
-service httpd restart
-
+$lnx service httpd restart
+$bsd /usr/local/etc/rc.d/apache24 restart

commit e1c3bda6598a87b857dd7cf558285ad5418b3503
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Dec 8 20:57:42 2014 +0530

    new transparent proxy feature; see comments in source

diff --git a/src/lib/Gitolite/Triggers/TProxy.pm b/src/lib/Gitolite/Triggers/TProxy.pm
new file mode 100644
index 0000000..8cf0e8d
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/TProxy.pm
@@ -0,0 +1,97 @@
+package Gitolite::Triggers::TProxy;
+
+# ----------------------------------------------------------------------
+# transparent proxy for git repos, hosted on a gitolite server
+
+# ----------------------------------------------------------------------
+# WHAT
+
+#   1.  user runs a git command (clone, fetch, push) against a gitolite
+#       server.
+#   2.  if that server has the repo, it will serve it up.  Else it will
+#       *transparently* forward the git operation to a designated upstream
+#       server.  The user does not have to do anything, and in fact may not
+#       even know this has happened.
+
+# can be combined with, but does not *require*, gitolite mirroring.
+
+# ----------------------------------------------------------------------
+# SECURITY
+#
+#   1.  Most of the issues that apply to "redirected push" in mirroring.html
+#       also apply here.  In particular, you had best make sure the two
+#       servers use the same authentication data (i.e., "alice" here should be
+#       "alice" there!)
+#
+#   2.  Also, do not add keys for servers you don't trust!
+
+# ----------------------------------------------------------------------
+# HOW
+
+# on transparent proxy server (the one that is doing the redirect):
+#   1.  add
+#           INPUT => ['TProxy::input'],
+#       just before the ENABLE list in the rc file
+#   2.  add an RC variable to tell gitolite where to go; this is also just
+#       before the ENABLE list:
+#           TPROXY_FORWARDS_TO => 'git at upstream',
+
+# on upstream server (the one redirected TO):
+#   1.  add
+#           INPUT => ['TProxy::input'],
+#       just before the ENABLE list in the rc file
+#   2.  add the pubkey of the proxy server (the one that will be redirecting
+#       to us) to this server's gitolite-admin "keydir" as
+#       "server-<something>.pub", and push the change.
+
+# to use in combination with gitolite mirroring
+#   1.  just follow the same instructions as above.  Server names and
+#       corresponding pub keys would already be set ok so step 2 in the
+#       upstream server setup (above) will not be needed.
+#   2.  needless to say, **don't** declare the repos you want to be
+#       transparently proxied in the gitolite.conf for the slave.
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+
+# ----------------------------------------------------------------------
+
+sub input {
+    # are we the upstream, getting something from a tproxy server?
+    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+    if ( $ARGV[0] =~ /^server-/ and $soc =~ /^TPROXY_FOR=(\S+) SOC=(($git_commands) '\S+')$/ ) {
+        @ARGV = ($1);
+        # you better make sure you read the security warnings up there!
+
+        $ENV{SSH_ORIGINAL_COMMAND} = $2;
+        delete $ENV{GL_BYPASS_ACCESS_CHECKS};
+        # just in case we somehow end up running before Mirroring::input!
+
+        return;
+    }
+
+    # well we're not upstream; are we a tproxy?
+    return unless $rc{TPROXY_FORWARDS_TO};
+
+    # is it a normal git command?
+    return unless $ENV{SSH_ORIGINAL_COMMAND} =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$);
+
+    # ...get the repo name from $ENV{SSH_ORIGINAL_COMMAND}
+    my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
+    $ENV{D} = $trace_level if $trace_level;
+    _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
+
+    # nothing to do if the repo exists locally
+    return if -d "$ENV{GL_REPO_BASE}/$repo.git";
+
+    my $user = shift @ARGV;
+    # redirect to upstream
+    exec( "ssh", $rc{TPROXY_FORWARDS_TO}, "TPROXY_FOR=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
+}

commit 2471e184f5305826419b611f490e0404a5cb712a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 12 04:57:00 2014 +0530

    (minor) delete extra line in test 0

diff --git a/t/0-me-first.t b/t/0-me-first.t
index 921d3ac..822d1af 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -89,7 +89,5 @@ try "
 
     glt perms u4 -c cc/bar/baz/../frob + READERS u2
                                     !ok;    /FATAL: no relative paths allowed anywhere!/
-                                    'cc/bar/baz/\\.\\./frob' contains '\\.\\.'/
-
 
 ";

commit e7752fc7c9b015ea8a5762083219be37667abd01
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 10 08:51:00 2014 +0530

    (minor) README fixup

diff --git a/README.markdown b/README.markdown
index deb9c66..24149dc 100644
--- a/README.markdown
+++ b/README.markdown
@@ -216,7 +216,7 @@ you; try running "gitolite help".
 Please see <http://gitolite.com/gitolite/#contact> for mailing list and IRC
 info.
 
-# license {#license -}
+# license
 
 The gitolite software is copyright Sitaram Chamarty and is licensed under the
 GPL v2; please see the file called COPYING in the source distribution.

commit e3f3c267f4c7cd2d97f40e7b483ef8560a87edc7
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 10 08:49:57 2014 +0530

    (minor) another test fixup
    
    why was I doing this manually till now?

diff --git a/t/mirror-test-setup.sh b/t/mirror-test-setup.sh
index 23f382d..02a286a 100755
--- a/t/mirror-test-setup.sh
+++ b/t/mirror-test-setup.sh
@@ -6,6 +6,7 @@ mainhost=frodo
 
 # setup software
 bd=`gitolite query-rc -n GL_BINDIR`
+mkdir -p /tmp/g3
 rm -rf /tmp/g3/src
 cp -a $bd /tmp/g3/src
 chmod -R go+rX /tmp/g3

commit d73f54432b1e37d42b7ea8edaa2ea484129aa7e8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 10 08:49:15 2014 +0530

    (minor) fixup due to some (fedora??) path change

diff --git a/t/smart-http b/t/smart-http
index 2ce6afc..98fc8c0 100755
--- a/t/smart-http
+++ b/t/smart-http
@@ -62,7 +62,7 @@ tsh "
         /1 file.*changed/
     git push
         ok
-        /Initialized.*var.www.gitolite-home.repositories.t2.git/
+        /Initialized.*usr.share.httpd.gitolite-home.repositories.t2.git/
         /To http:..admin:admin.localhost.git.gitolite-admin.git/
         /master -. master/
     ## various ls-remotes

commit b5649570420bc112f7c8dcaf1d3c0275de93b464
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 10 08:41:59 2014 +0530

    v3.6.2

diff --git a/CHANGELOG b/CHANGELOG
index 3666ba0..edf7cb9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,12 @@
+2014-11-10  v3.6.2  disable ../ everywhere (see mailing list thread for
+                    details)
+
+                    VREF/NAME_NC -- like VREF/NAME but for new commits only.
+                    Details within src/VREF/NAME_NC.
+
+                    allow gitolite.conf to be tested locally; details within
+                    contrib/utils/gitolite-local
+
 2014-06-22  v3.6.1  experimental rc format convertor for "<= 3.3" users who
                     have already upgraded the *code* to ">= v3.4".  Program is
                     in contrib/utils.

commit 3bad56be3a3589b241112df3fd598e142c0be623
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 10 05:03:21 2014 +0530

    disallow ../ everywhere
    
    This should have been done as soon as I rolled in support for "ua"
    (unrestricted arguments; see comments in src/commands/git-annex-shell for
    details).
    
    Please also see mailing list thread around this date for more.  In particular,
    I need to ackowledge someone who pointed this out in the context of
    "symbolic-ref", but whose name/email I appear to have misplaced!

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 276bd34..f17345a 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -157,11 +157,12 @@ sub parse_soc {
     # after this we should not return; caller expects us to handle it all here
     # and exit out
 
-
     my @words = split ' ', $soc;
     if ( $rc{COMMANDS}{ $words[0] } ) {
-        _die "suspicious characters loitering about '$soc'"
-          if $rc{COMMANDS}{ $words[0] } ne 'ua' and $soc !~ $REMOTE_COMMAND_PATT;
+        if ( $rc{COMMANDS}{ $words[0] } ne 'ua' ) {
+            _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
+            _die "no relative paths allowed anywhere!" if $soc =~ m(\.\./);
+        }
         trace( 2, "gitolite command", $soc );
         _system( "gitolite", @words );
         exit 0;
diff --git a/t/0-me-first.t b/t/0-me-first.t
index 12668f6..921d3ac 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -88,7 +88,8 @@ try "
                                     !ok;    /FATAL: 'cc/../../../../../..$rb/gitolite-admin' contains '\\.\\.'/
 
     glt perms u4 -c cc/bar/baz/../frob + READERS u2
-                                    !ok;    /FATAL: 'cc/bar/baz/\\.\\./frob' contains '\\.\\.'/
+                                    !ok;    /FATAL: no relative paths allowed anywhere!/
+                                    'cc/bar/baz/\\.\\./frob' contains '\\.\\.'/
 
 
 ";

commit 1577e06b5acc0fc3214492f8185cbadfcd042554
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu May 15 14:01:56 2014 +0530

    VREF/NAME_NC -- like VREF/NAME but for "new commits" only

diff --git a/src/VREF/NAME_NC b/src/VREF/NAME_NC
new file mode 100755
index 0000000..1a81714
--- /dev/null
+++ b/src/VREF/NAME_NC
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# ----------------------------------------------------------------------
+# VREF/NAME_NC
+#   Like VREF/NAME, but only considers "new commits" -- i.e., commits that
+#   don't already exist in the repo as part of some other ref.
+
+# ----------------------------------------------------------------------
+# WHY
+#   VREF/NAME doesn't deal well with tag creation (or new branch creation),
+#   since then all files in the project look like they are being created (due
+#   to comparison with an empty tree).
+
+#   Use this instead of VREF/NAME when you need to make that distinction.
+
+newsha=$3
+
+[ $newsha = "0000000000000000000000000000000000000000" ] && {
+    echo "we don't currently handle deletions" >&2
+    exit 1
+}
+
+git log --name-only --format=%n $newsha --not --all |
+    sort -u | grep . | sed -e 's.^.VREF/NAME_NC/.'
+
+# ----------------------------------------------------------------------
+# OTHER NOTES
+#   The built-in NAME does have a wee bit of a performance advantage.  I plan
+#   to ignore this until someone notices this enough to be a problem :)
+#
+#   I could explain it here at least, but I fear that any explanation will
+#   only add to the already rampant confusion about how VREFs work.  I'm not
+#   rocking THAT boat again, sorry!

commit 513f1a70a00ba4b3fc45b79f2210417f64cbec7c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Sep 10 07:19:14 2014 +0530

    allow gitolite.conf to be tested locally!
    
    please see documentation in the code

diff --git a/contrib/utils/gitolite-local b/contrib/utils/gitolite-local
new file mode 100755
index 0000000..5faf0c7
--- /dev/null
+++ b/contrib/utils/gitolite-local
@@ -0,0 +1,136 @@
+#!/bin/bash
+
+# ----------------------------------------------------------------------
+# change these lines to suit
+testconf=$HOME/GITOLITE-TESTCONF
+gitolite_url=git://github.com/sitaramc/gitolite
+    # change it to something local for frequent use
+    # gitolite_url=file:///tmp/gitolite.git
+
+# ----------------------------------------------------------------------
+# Usage: gitolite-local <options>
+#
+# Test your gitolite.conf rule lists on your LOCAL machine (without even
+# pushing to the server!)
+#
+# (one-time)
+#
+#   1.  put this code somewhere in your $PATH if you wish
+#   2.  edit the line near the top of the script if you want to use some other
+#       directory than the default, for "testconf".
+#   2.  prepare the "testconf" directory by running:
+#           gitolite-local prep
+#
+# (lather, rinse, repeat)
+#
+#   1.  edit the conf (see notes below for more)
+#           gitolite-local edit
+#   2.  compile the conf
+#           gitolite-local compile
+#   3.  check permissions using "info" command:
+#           gitolite-local info USERNAME
+#   4.  check permissions using "access" command:
+#           gitolite-local access <options for gitolite access command>
+#   5.  clone, fetch, and push if you like!
+#           gitolite-local clone <username> <reponame> <other options for clone>
+#           gitolite-local fetch <username> <options for fetch>
+#           gitolite-local push  <username> <options for push>
+#
+# note on editing the conf: you don't have to use the edit command; you can
+# also directly edit '.gitolite/conf/gitolite.conf' in the 'testconf'
+# directory.  You'll need to do that if your gitolite conf consists of more
+# than just one file (like if you have includes, etc.)
+#
+# note on the clone command: most of the options won't work for clone, unless
+# git is ok with them being placed *after* the repo name.
+
+# ----------------------------------------------------------------------
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+
+# ----------------------------------------------------------------------
+if [ $1 == prep ]
+then
+    set -e
+
+    [ -d $testconf ] && die "directory '$testconf' already exists"
+
+    mkdir $testconf
+    cd $testconf
+
+    export HOME=$PWD
+
+    echo getting gitolite source...
+    git clone $gitolite_url gitolite
+    echo
+
+    echo installing gitolite...
+    gitolite/install >/dev/null
+    echo
+
+    echo setting up gitolite...
+    export PATH=$PWD/gitolite/src:$PATH
+    gitolite setup -a admin
+    echo
+
+    exit 0
+fi
+
+od=$PWD
+cd $testconf
+export HOME=$PWD
+export PATH=$PWD/gitolite/src:$PATH
+
+if [ $1 = edit ]
+then
+    editor=${EDITOR:-vim}
+    $editor .gitolite/conf/gitolite.conf
+elif [ $1 = compile ]
+then
+    gitolite compile
+elif [ $1 = compile+ ]
+then
+    gitolite compile\; gitolite trigger POST_COMPILE
+elif [ $1 = info ]
+then
+    shift
+    user=$1
+    shift
+
+    GL_USER=$user gitolite info "$@"
+elif [ $1 = access ]
+then
+    shift
+
+    gitolite access "$@"
+elif [ $1 = clone ]
+then
+    shift
+    export G3T_USER=$1
+    shift
+
+    cd $od
+    export GL_BINDIR=$HOME/gitolite/t
+        # or you could do it the long way, using 'gitolite query-rc GL_BINDIR'
+    repo=$1; shift
+    git clone --upload-pack=$GL_BINDIR/gitolite-upload-pack file:///$repo "$@"
+elif [ $1 = fetch ]
+then
+    shift
+    export G3T_USER=$1
+    shift
+
+    cd $od
+    export GL_BINDIR=$HOME/gitolite/t
+    git fetch --upload-pack=$GL_BINDIR/gitolite-upload-pack "$@"
+elif [ $1 = push ]
+then
+    shift
+    export G3T_USER=$1
+    shift
+
+    cd $od
+    export GL_BINDIR=$HOME/gitolite/t
+    git push --receive-pack=$GL_BINDIR/gitolite-receive-pack "$@"
+fi

commit 0bcd962df3e69f5d1050c51753473d3d7cc4dcff
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Aug 19 18:05:47 2014 +0530

    ssh-authkeys: update authkeys when keydir is present but empty
    
    When ~/.gitolite/keydir is present but empty, and someone runs 'gitolite
    trigger POST_COMPILE', gitolite does not clear out the corresponding lines in
    ~/.ssh/authorized_keys.
    
    ----
    
    NOTE that this can only happen when you're managing gitolite directly on the
    server, per http://gitolite.com/gitolite/odds-and-ends.html#server-side-admin.
    
    When you're managing gitolite remotely, and you 'git rm -rf keydir' in your
    admin clone, then commit and push, this behaviour (of not updating the
    authkeys file in ~/.ssh) will remain.
    
    However, it's not by design -- it's more of a lucky accident!  What's
    happening here is, when ~/.gitolite is updated via a "git checkout -f", the
    (now empty) 'keydir' directory is *completely deleted*.  This makes
    ssh-authkeys bail much earlier, because now it looks like a system that is not
    even *using* ssh keys (for example, an http-mode installation).
    
    The end result is the same -- the authkeys file is left untouched.  However, I
    will not be "fixing" that, for obvious reasons.

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 4533a4c..e686119 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -60,13 +60,13 @@ for my $f (@pubkeys) {
 }
 
 # dump it out
-if (@gl_keys) {
-    my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
+my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
 
-    my $ak = slurp($akfile);
-    _die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
-    _print( $akfile, $out );
-}
+my $ak = slurp($akfile);
+_die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
+_print( $akfile, $out );
+
+_warn "you have no keys left; I hope you intended to do that!" unless @gl_keys;
 
 # ----------------------------------------------------------------------
 
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index a9b1457..46b9413 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -28,7 +28,7 @@ try "
     cd $kd;                     ok
     $pgm;                       ok;     /authorized_keys missing/
                                         /creating/
-    wc < $ak;                   ok;     /0 *0 *0/
+    wc < $ak;                   ok;     /2 *6 *32/
     # some gl keys
     ssh-keygen -N '' -q -f alice -C alice
     ssh-keygen -N '' -q -f bob   -C bob

commit 76127e3a52690b47575f2f66dfc258b73c059d22
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Aug 18 10:54:13 2014 +0530

    enforce the full path requirement in 'install'

diff --git a/install b/install
index 513dd71..98d8aee 100755
--- a/install
+++ b/install
@@ -54,7 +54,12 @@ GetOptions(
 usage() if $to and $ln or $help;
 $ln = "$ENV{HOME}/bin" if defined($ln) and not $ln;
 for my $d ($ln, $to) {
-    if ($d and not -d $d) {
+    next unless $d;    # ignore empty values
+    unless ( $d =~ m(^/) ) {
+        print STDERR "FATAL: please use an absolute path, not a relative path\n";
+        usage();
+    }
+    if ( not -d $d ) {
         print STDERR "FATAL: '$d' does not exist.\n";
         usage();
     }

commit dc8b590a0562ab7dc4bdab9511fe6c31cb422ae8
Author: Sebastian Koslowski <koslowski at kit.edu>
Date:   Fri Aug 8 12:51:02 2014 +0200

    fixing expand-deny-messages

diff --git a/src/triggers/expand-deny-messages b/src/triggers/expand-deny-messages
index 10adf66..a8b2289 100755
--- a/src/triggers/expand-deny-messages
+++ b/src/triggers/expand-deny-messages
@@ -50,7 +50,7 @@ if ( $ref =~ m((^VREF/[^/]+)) ) {
     my $vref_text = slurp( _which( $vref, 'x' ) );
     my $etag      = '(?:help|explain|explanation)';
     $vref_text =~ m(^\s*# $etag.start\n(.*)^\s*# $etag.end\n)sm
-      and print STDERR "Explanation for $vr:\n$1";
+      and print STDERR "Explanation for $vref:\n$1";
 }
 
 print STDERR "\n";

commit 8f4a899e57a207628969049a4cc19ebec2c55bd1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Aug 2 06:11:22 2014 +0530

    mirroring to arbitrary external servers using arbitrary commands
    
    (This was started off by someone on irc wanting to backup his repos to s3
    using jgit.)

diff --git a/contrib/triggers/file_mirror b/contrib/triggers/file_mirror
new file mode 100755
index 0000000..e3d083b
--- /dev/null
+++ b/contrib/triggers/file_mirror
@@ -0,0 +1,172 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# Use an external (non-gitolite) mirror to backup gitolite repos.  They will
+# be automatically kept uptodate as people push to your gitolite server.  If
+# your server should die and you create a new one, you can quickly and easily
+# get everything back from the external mirror with a few simple commands.
+
+#       -------------------------------------------------------------
+#       SEE WARNINGS/CAVEATS AND INSTRUCTIONS AT THE END OF THIS FILE
+#       -------------------------------------------------------------
+
+# ----------------------------------------------------------------------
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+my ( $trigger, $repo, $dummy, $op ) = @ARGV;
+exit 0 unless $trigger eq 'POST_GIT' or $trigger eq 'POST_CREATE';
+exit 0 if     $trigger eq 'POST_GIT' and $op ne 'W';
+
+chdir("$rc{GL_REPO_BASE}/$repo.git") or _die "chdir failed: $!\n";
+
+my %config = config( $repo, "gitolite-options\\.mirror\\.extslave" );
+for my $slave ( values %config ) {
+    _do($slave);
+
+    # processing one slave is sufficient for restoring!
+    last if $trigger eq 'POST_CREATE';
+}
+
+# in shell, that would be something like:
+#   gitolite git-config -r $repo gitolite-options\\.mirror\\.extslave | cut -f3 | while read slave
+#   do
+#       ...
+
+# ----------------------------------------------------------------------
+
+sub _do {
+    my $url = shift;
+
+    if ( $trigger eq 'POST_CREATE' ) {
+        # brand new repo just created; needs to be populated from mirror
+
+        # For your urls you will need a way to somehow query the server and
+        # ask if the repo is present; it's upto you how you do it.
+        my $path = $url;
+        $path =~ s(^file://)();
+        return unless -d $path;
+
+        # now fetch.  Maybe we can put a "-q" in there?
+        system( "git", "fetch", $url, "+refs/*:refs/*" );
+
+    } elsif ( $trigger eq 'POST_GIT' ) {
+        # someone just pushed; we need to update our mirrors
+
+        # need to create the repo on the mirror.  Again, it's upto you how you
+        # make sure there's a repo on the mirror that can receive the push.
+        make_repo($url);    # in case it doesn't already exist
+
+        # now push
+        system( "git", "push", "--mirror", $url );
+    }
+}
+
+sub make_repo {
+    my $url = shift;
+    # in this example, the URL is 'file:///...'; for other urls, presumably
+    # the url tells you enough about how to *create* a repo.
+
+    my $path = $url;
+    $path =~ s(^file://)();
+    return if -d $path;
+    system( "git", "init", "--bare", $path );
+}
+
+__END__
+
+WARNINGS
+--------
+
+1.  THIS IS SAMPLE CODE.  You will AT LEAST have to customise the _do() and
+    make_repo() functions above based on what your remote URLs are.  For
+    example, I don't even know how to create a repo from the command line if
+    your external store is, say, github!
+
+2.  THIS DOES NOT WORK FOR WILD REPOs.  It can be made to work, with a few
+    extra steps to backup and restore the "gl-perms" and "gl-creator" files.
+
+    "Left as an exercise for the reader!"
+
+DESIGN NOTES
+------------
+
+This is really just a combination of "upstream" (see src/triggers/upstream)
+and mirroring (gitolite mirroring does allow a slave to be non-gitolite, as
+long as the ssh stuff is done the same way).
+
+The main difference is that gitolite mirroring expects peers to all talk ssh,
+whereas this method lets you use other protocols.  Specifically, since this
+whole thing was started off by someone wanting to put his repos on s3
+(apparently jgit can talk to s3 directly), you can modify the two functions to
+deal with whatever remote server you have.
+
+LANGUAGE
+--------
+
+This doesn't have to be in perl.  Shell equivalent for the only gitolite
+specific code is supplied; the rest of the code is fairly straightforward.
+
+SETUP
+-----
+
+1.  Put this code into your LOCAL_CODE directory under "triggers"; see
+    non-core.html for details.
+
+2.  Add these lines to your rc file, just before the ENABLE line.  (I'm
+    assuming a v3.4 or later installation here).
+
+        POST_CREATE => [ 'file_mirror' ],
+        POST_GIT => [ 'file_mirror' ],
+
+3.  Backup your rc file, since you may have other changes in it that you'll
+    want to preserve.
+
+4.  Do something like this in your gitolite.conf file:
+
+        repo @all
+            option mirror.extslave-1    =   file:///tmp/he1/%GL_REPO.git
+            option mirror.extslave-2    =   file:///tmp/he2/%GL_REPO.git
+
+    As you can see, since this is just for demo/test, we're using a couple of
+    temp directories to serve as our "remotes" using the file:// protocol.
+
+5.  Do a one-time manual sync of all the repos (subsequent syncs happen on
+    each push):
+
+        gitolite list-phy-repos | xargs -I xx gitolite trigger POST_GIT xx admin W
+
+    (This is a little trick we're playing on the trigger stuff, but it should
+    work fine.  Just make sure that, if you have other things in your POST_GIT
+    trigger list, they're not affected in some way.  'gitolite query-rc
+    POST_GIT' will tell you what else you have.)
+
+That takes care of the "setup" and "regular backup".
+
+RESTORE
+-------
+
+1.  Install gitolite normally.  You'll get the usual two repos.
+
+2.  Restore the previously backed up rc file to replace the default one that
+    gitolite created.  At the very least, the rc file should have the
+    POST_CREATE and POST_GIT entries.
+
+        ---------------------------------------------------------
+        IF YOU FORGET THIS STEP, NASTY THINGS WILL HAPPEN TO YOU!
+        ---------------------------------------------------------
+
+3.  Clone the admin repo from one of your backup servers to some temp dir.  In
+    our example,
+
+        git clone /tmp/he1/gitolite-admin.git old-ga
+
+4.  'cd' to that clone and force push to your *new* admin repo:
+
+        cd old-ga
+        git push -f admin:gitolite-admin
+
+That's it.  As each repo gets created by the admin push, they'll get populated
+by the backed up stuff due to the POST_CREATE trigger.

commit 3154e46c93f8561633908e5c6b6e35003ae6c25e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jul 24 04:34:23 2014 +0530

    expand-deny-messages learns to print more info for VREFs

diff --git a/src/triggers/expand-deny-messages b/src/triggers/expand-deny-messages
index 78d138d..10adf66 100755
--- a/src/triggers/expand-deny-messages
+++ b/src/triggers/expand-deny-messages
@@ -8,6 +8,7 @@ use warnings;
 
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
+use Gitolite::Common;
 
 my %attempted_access = (
     # see triggers.html
@@ -44,6 +45,14 @@ _info( "Stage",     ( $a12 eq 'ACCESS_1' ? "Before git was called" : "From git's
 _info( "Ref",       _ref($ref) ) if $a12 eq 'ACCESS_2';
 _info( "Operation", _op( $a12, $aa, $oldsha, $newsha ) );
 
+if ( $ref =~ m((^VREF/[^/]+)) ) {
+    my $vref      = $1;
+    my $vref_text = slurp( _which( $vref, 'x' ) );
+    my $etag      = '(?:help|explain|explanation)';
+    $vref_text =~ m(^\s*# $etag.start\n(.*)^\s*# $etag.end\n)sm
+      and print STDERR "Explanation for $vr:\n$1";
+}
+
 print STDERR "\n";
 print STDERR "$ENV{GL_OPTION_EDM_EXTRA_INFO}\n\n" if $ENV{GL_OPTION_EDM_EXTRA_INFO};
 
@@ -122,8 +131,8 @@ Or you can also disable it for all repos, then enable it for some:
 
 [1]: http://gitolite.com/gitolite/options.html
 
-# SUPPLYING EXTRA INFORMATION
-# ---------------------------
+SUPPLYING EXTRA INFORMATION
+---------------------------
 
 You can also supply some extra information to be printed, by adding a line
 like this to each repository in the gitolite.conf file:
@@ -131,3 +140,46 @@ like this to each repository in the gitolite.conf file:
         option ENV.EDM_EXTRA_INFO = "please contact alice at example.com"
 
 You could of course add it under a "repo @all" section if you like.
+
+SUPPLYING EXTRA INFORMATION FOR VREFs
+-------------------------------------
+
+If you have VREFs that do funky things and you want to **lecture** your users
+when they screw up, add something like the following to your VREF code.
+
+    # help start
+
+    Some help text.
+
+    Some more help text.  This can be
+    multi-line.
+
+    (etc etc etc)
+
+    # help end
+
+Then everything between the "# help start" line and the "# help end" line will
+get printed if a users falls afoul of this VREF.  If any of the lines shown
+are not valid syntax for your language, figure out some way to put the whole
+thing in a comment block.  Here a C example:
+
+    /*
+    # help start
+    line 1
+    line 2
+    ...
+    last line
+    # help end
+    */
+
+Even if your language does not support multi-line comments like C does, there
+may be other ways to specify those lines.  Here's an example in shell:
+
+    cat << EOF > /dev/null
+    # help start
+    line 1
+    line 2
+    ...
+    last line
+    # help end
+    EOF

commit 344a880860aa6a713853a7de535f15733369bed1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jul 23 19:04:55 2014 +0530

    (minor) error message detail

diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
index efa9c6f..5d52a47 100755
--- a/src/triggers/repo-specific-hooks
+++ b/src/triggers/repo-specific-hooks
@@ -39,7 +39,8 @@ while (<>) {
     $hook =~ s/^gitolite-options\.hook\.//;
 
     unless ( $hook =~ /^(pre-receive|post-receive|post-update)$/ ) {
-        _warn "repo-specific-hooks: '$hook' is not one of the 3 hooks allowed, ignoring";
+        _warn "repo-specific-hooks: '$hook' is not allowed, ignoring";
+        _warn "    (only pre-receive, post-receive, and post-update are allowed)";
         next;
     }
 

commit 8de5e9f6aef1699a124afb90a8cdc747515f0650
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jul 17 20:49:52 2014 +0530

    allow mirror command to take physical reponames

diff --git a/src/commands/mirror b/src/commands/mirror
index 96b985d..dbdc952 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -46,6 +46,7 @@ usage() if not @ARGV or $ARGV[0] eq '-h';
 _die "HOSTNAME not set" if not $rc{HOSTNAME};
 
 my ( $cmd, $host, $repo ) = @ARGV;
+$repo =~ s/\.git$//;
 usage() if not $repo;
 
 if ( $cmd eq 'push' ) {

commit d288b90f63ab791d832fe2ce1058a70d108d105a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jul 13 19:19:14 2014 +0530

    (changes caused by doc revamp)

diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..deb9c66
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,231 @@
+Gitolite README
+===============
+
+## about this README
+
+**(Github-users: click the "wiki" link before sending me anything via github.)**
+
+**This is a minimal README for gitolite**, so you can quickly get started with:
+
+*   installing gitolite on a fresh userid on a Unix(-like) machine
+*   learning enough to do some basic access control
+
+**For anything more, you need to look at the complete documentation, at:
+<http://gitolite.com/gitolite>**.  Please go there for what/why/how, concepts,
+background, troubleshooting, more details on what is covered here, advanced
+features not covered here, migration from older gitolite, and many more
+topics.
+
+<!-- --------------------------------------------------------------------- -->
+
+## Assumptions
+
+*   You are familiar with:
+    *   OS: at least one Unix-like OS
+    *   ssh: ssh, ssh keys, ssh authorized keys file
+    *   git: basic use of git, bare and non-bare remotes
+
+*   You are setting up a fresh, ssh-based, installation of gitolite on a Unix
+    machine of some sort.
+
+*   You have root access, or someone has created a userid called "git" for you
+    to use and given you a password for it.  This is a brand new userid (or
+    you have deleted everything but `.bashrc` and similar files to make it
+    look like one!)
+
+*   If your server is not connected to the internet, you know how to clone the
+    gitolite source code by using some in-between server or "git bundle".
+
+<!-- --------------------------------------------------------------------- -->
+
+## Installation and setup
+
+### server requirements
+
+*   any unix system
+*   sh
+*   git 1.6.6 or later
+*   perl 5.8.8 or later
+*   openssh 5.0 or later
+*   a dedicated userid to host the repos (in this document, we assume it is
+    "git", but it can be anything; substitute accordingly)
+*   this user id does NOT currently have any ssh pubkey-based access
+    *   ideally, this user id has shell access ONLY by "su - git" from some
+        other userid on the same server (this ensure minimal confusion for ssh
+        newbies!)
+
+### steps to install
+
+First, prepare the ssh key:
+
+*   login to "git" on the server
+*   make sure `~/.ssh/authorized_keys` is empty or non-existent
+*   make sure your ssh public key from your workstation has been copied as
+    $HOME/YourName.pub
+
+Next, install gitolite by running these commands:
+
+    git clone git://github.com/sitaramc/gitolite
+    mkdir -p $HOME/bin
+    gitolite/install -to $HOME/bin
+
+Finally, setup gitolite with yourself as the administrator:
+
+    gitolite setup -pk YourName.pub
+
+If the last command doesn't run perhaps "bin" in not in your "PATH". You can
+either add it, or just run:
+
+    $HOME/bin/gitolite setup -pk YourName.pub
+
+If you get any other errors please refer to the online documentation whose URL
+was given at the top of this file.
+
+## adding users and repos
+
+*Do NOT add new repos or users manually on the server.*  Gitolite users,
+repos, and access rules are maintained by making changes to a special repo
+called "gitolite-admin" and *pushing* those changes to the server.
+
+To administer your gitolite installation, start by doing this on your
+workstation (if you have not already done so):
+
+    git clone git at host:gitolite-admin
+
+>   -------------------------------------------------------------------------
+
+>   **NOTE: if you are asked for a password, something went wrong.**.  Go hit
+>   the link for the complete documentation earlier in this file.
+
+>   -------------------------------------------------------------------------
+
+Now if you "cd gitolite-admin", you will see two subdirectories in it: "conf"
+and "keydir".
+
+To add new users alice, bob, and carol, obtain their public keys and add them
+to "keydir" as alice.pub, bob.pub, and carol.pub respectively.
+
+To add a new repo "foo" and give different levels of access to these
+users, edit the file "conf/gitolite.conf" and add lines like this:
+
+    repo foo
+        RW+         =   alice
+        RW          =   bob
+        R           =   carol
+
+Once you have made these changes, do something like this:
+
+    git add conf
+    git add keydir
+    git commit -m "added foo, gave access to alice, bob, carol"
+    git push
+
+When the push completes, gitolite will add the new users to
+`~/.ssh/authorized_keys` on the server, as well as create a new, empty, repo
+called "foo".
+
+## help for your users
+
+Once a user has sent you their public key and you have added them as
+specified above and given them access, you have to tell them what URL to
+access their repos at.  This is usually "git clone git at host:reponame"; see
+man git-clone for other forms.
+
+**NOTE**: again, if they are asked for a password, something is wrong.
+
+If they need to know what repos they have access to, they just have to run
+"ssh git at host info".
+
+## access rule examples
+
+Gitolite's access rules are very powerful.  The simplest use was already
+shown above.  Here is a slightly more detailed example:
+
+    repo foo
+        RW+                     =   alice
+        -   master              =   bob
+        -   refs/tags/v[0-9]    =   bob
+        RW                      =   bob
+        RW  refs/tags/v[0-9]    =   carol
+        R                       =   dave
+
+Here's what these example rules say:
+
+  * alice can do anything to any branch or tag -- create, push,
+    delete, rewind/overwrite etc.
+
+  * bob can create or fast-forward push any branch whose name does
+    not start with "master" and create any tag whose name does not
+    start with "v"+digit.
+
+  * carol can create tags whose names start with "v"+digit.
+
+  * dave can clone/fetch.
+
+Please see the main documentation linked above for all the gory details, as
+well as more features and examples.
+
+## groups
+
+Gitolite allows you to group users or repos for convenience.  Here's an
+example that creates two groups of users:
+
+    @staff      =   alice bob carol
+    @interns    =   ashok
+
+    repo secret
+        RW      =   @staff
+
+    repo foss
+        RW+     =   @staff
+        RW      =   @interns
+
+Group lists accumulate.  The following two lines have the same effect as
+the earlier definition of @staff above:
+
+    @staff      =   alice bob
+    @staff      =   carol
+
+You can also use group names in other group names:
+
+    @all-devs   =   @staff @interns
+
+Finally, @all is a special group name that is often convenient to use if
+you really mean "all repos" or "all users".
+
+## commands
+
+Users can run certain commands remotely, using ssh.  Running
+
+    ssh git at host help
+
+prints a list of available commands.
+
+The most commonly used command is "info".  All commands respond to a
+single argument of "-h" with suitable information.
+
+If you have shell on the server, you have a lot more commands available to
+you; try running "gitolite help".
+
+<!-- --------------------------------------------------------------------- -->
+
+## LICENSE
+
+# contact and support
+
+Please see <http://gitolite.com/gitolite/#contact> for mailing list and IRC
+info.
+
+# license {#license -}
+
+The gitolite software is copyright Sitaram Chamarty and is licensed under the
+GPL v2; please see the file called COPYING in the source distribution.
+
+Please see <http://gitolite.com/gitolite/#license> for more.
+
+>   -------------------------------------------------------------------------
+
+>   **NOTE**: GIT is a trademark of Software Freedom Conservancy and my use of
+>   "Gitolite" is under license.
+
+>   -------------------------------------------------------------------------
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 88f0661..0000000
--- a/README.txt
+++ /dev/null
@@ -1,439 +0,0 @@
-Github-users: click the 'wiki' link before sending me anything via github.
-
-Existing users: this is gitolite v3.x.  If you are upgrading from v2.x this
-file will not suffice; you *must* check the online docs (see below for URL).
-
-------------------------------------------------------------------------
-
-
-This file contains BASIC DOCUMENTATION ONLY.
-
-  * It is suitable for a fresh, ssh-based, installation of gitolite and basic
-    usage of its most important features.
-
-  * It is NOT meant to be exhaustive or detailed.
-
-The COMPLETE DOCUMENTATION is at:
-
-    http://gitolite.com/gitolite/master-toc.html
-
-Please go there for what/why/how, concepts, background, troubleshooting, more
-details on what is covered here, or advanced features not covered here.
-
-------------------------------------------------------------------------
-
-
-BASIC DOCUMENTATION FOR GITOLITE
-================================
-
-This file contains the following sections:
-
-    INSTALLATION AND SETUP
-    ADDING USERS AND REPOS
-    HELP FOR YOUR USERS
-    BASIC SYNTAX
-    ACCESS RULES
-    GROUPS
-    COMMANDS
-    THE 'rc' FILE
-    GIT-CONFIG
-    GIT-DAEMON
-    GITWEB
-
-    MIGRATING FROM v2
-
-    CONTACT AND SUPPORT
-    LICENSE
-
-------------------------------------------------------------------------
-
-
-INSTALLATION AND SETUP
-----------------------
-
-    Server requirements:
-
-      * any unix system
-      * sh
-      * git 1.6.6+
-      * perl 5.8.8+
-      * openssh 5.0+
-      * a dedicated userid to host the repos (in this document, we assume it
-        is 'git'), with shell access ONLY by 'su - git' from some other userid
-        on the same server.
-
-    Steps to install:
-
-      * login as 'git' as described above
-
-      * make sure ~/.ssh/authorized_keys is empty or non-existent
-
-      * make sure your ssh public key from your workstation is available at
-        $HOME/YourName.pub
-
-      * run the following commands:
-
-            git clone git://github.com/sitaramc/gitolite
-            mkdir -p $HOME/bin
-            gitolite/install -to $HOME/bin
-            gitolite setup -pk YourName.pub
-
-        If the last command doesn't run perhaps 'bin' in not in your 'PATH'.
-        You can either add it, or just run:
-
-            $HOME/bin/gitolite setup -pk YourName.pub
-
-
-ADDING USERS AND REPOS
-----------------------
-
-    Do NOT add new repos or users manually on the server.  Gitolite users,
-    repos, and access rules are maintained by making changes to a special repo
-    called 'gitolite-admin' and pushing those changes to the server.
-
-    ----
-
-    To administer your gitolite installation, start by doing this on your
-    workstation (if you have not already done so):
-
-        git clone git at host:gitolite-admin
-
-    **NOTE**: if you are asked for a password, something has gone wrong.
-
-    Now if you 'cd gitolite-admin', you will see two subdirectories in it:
-    'conf' and 'keydir'.
-
-    To add new users alice, bob, and carol, obtain their public keys and add
-    them to 'keydir' as alice.pub, bob.pub, and carol.pub respectively.
-
-    To add a new repo 'foo' and give different levels of access to these
-    users, edit the file 'conf/gitolite.conf' and add lines like this:
-
-        repo foo
-            RW+         =   alice
-            RW          =   bob
-            R           =   carol
-
-    See the 'ACCESS RULES' section later for more details.
-
-    Once you have made these changes, do something like this:
-
-        git add conf
-        git add keydir
-        git commit -m 'added foo, gave access to alice, bob, carol'
-        git push
-
-    When the push completes, gitolite will add the new users to
-    ~/.ssh/authorized_keys on the server, as well as create a new, empty, repo
-    called 'foo'.
-
-
-HELP FOR YOUR USERS
--------------------
-
-    Once a user has sent you their public key and you have added them as
-    specified above and given them access, you have to tell them what URL to
-    access their repos at.  This is usually 'git clone git at host:reponame'; see
-    man git-clone for other forms.
-
-    **NOTE**: again, if they are asked for a password, something is wrong.
-
-    If they need to know what repos they have access to, they just have to run
-    'ssh git at host info'; see 'COMMANDS' section later for more on this.
-
-
-BASIC SYNTAX
-------------
-
-    The basic syntax of the conf file is very simple.
-
-      * Everything is space separated; there are no commas, semicolons, etc.,
-        in the syntax.
-
-      * Comments are in the usual perl/shell style.
-
-      * User and repo names are as simple as possible; they must start with an
-        alphanumeric, but after that they can also contain '.', '_', or '-'.
-
-        Usernames can optionally be followed by an '@' and a domainname
-        containing at least one '.'; this allows you to use an email address
-        as someone's username.
-
-        Reponames can contain '/' characters; this allows you to put your
-        repos in a tree-structure for convenience.
-
-      * There are no continuation lines.
-
-
-ACCESS RULES
-------------
-
-    This section is mostly 'by example'.
-
-    Gitolite's access rules are very powerful.  The simplest use was already
-    shown above.  Here is a slightly more detailed example:
-
-        repo foo
-            RW+                     =   alice
-            -   master              =   bob
-            -   refs/tags/v[0-9]    =   bob
-            RW                      =   bob
-            RW  refs/tags/v[0-9]    =   carol
-            R                       =   dave
-
-    For clones and fetches, as long as the user is listed with an R, RW
-    or RW+ in at least one rule, he is allowed to read the repo.
-
-    For pushes, rules are processed in sequence until a rule is found
-    where the user, the permission (see note 1), and the refex (note 2)
-    *all* match.  At that point, if the permission on the matched rule
-    was '-', the push is denied, otherwise it is allowed.  If no rule
-    matches, the push is denied.
-
-    Note 1: permission matching:
-
-      * a permission of RW matches only a fast-forward push or create
-      * a permission of RW+ matches any type of push
-      * a permission of '-' matches any type of push
-
-    Note 2: refex matching:
-    (refex = optional regex to match the ref being pushed)
-
-      * an empty refex is treated as 'refs/.*'
-      * a refex that does not start with 'refs/' is prefixed with 'refs/heads/'
-      * finally, a '^' is prefixed
-      * the ref being pushed is matched against this resulting refex
-
-    With all that background, here's what the example rules say:
-
-      * alice can do anything to any branch or tag -- create, push,
-        delete, rewind/overwrite etc.
-
-      * bob can create or fast-forward push any branch whose name does
-        not start with 'master' and create any tag whose name does not
-        start with 'v'+digit.
-
-      * carol can create tags whose names start with 'v'+digit.
-
-      * dave can clone/fetch.
-
-
-GROUPS
-------
-
-    Gitolite allows you to group users or repos for convenience.  Here's an
-    example that creates two groups of users:
-
-        @staff      =   alice bob carol
-        @interns    =   ashok
-
-        repo secret
-            RW      =   @staff
-
-        repo foss
-            RW+     =   @staff
-            RW      =   @interns
-
-    Group lists accumulate.  The following two lines have the same effect as
-    the earlier definition of @staff above:
-
-        @staff      =   alice bob
-        @staff      =   carol
-
-    You can also use group names in other group names:
-
-        @all-devs   =   @staff @interns
-
-    Finally, @all is a special group name that is often convenient to use if
-    you really mean 'all repos' or 'all users'.
-
-
-COMMANDS
---------
-
-    Users can run certain commands remotely, using ssh.  For example:
-
-        ssh git at host help
-
-    prints a list of available commands.
-
-    The most commonly used command is 'info'.  All commands respond to a
-    single argument of '-h' with suitable information.
-
-    If you have shell on the server, you have a lot more commands available to
-    you; try running 'gitolite help'.
-
-
-THE 'rc' FILE
---------------
-
-    Some of the instructions below may require you to edit the rc file
-    (~/.gitolite.rc on the server).
-
-    The rc file is perl code, but you do NOT need to know perl to edit it.
-    Just mind the commas, use single quotes unless you know what you're doing,
-    and make sure the brackets and braces stay matched up.
-
-
-GIT-CONFIG
-----------
-
-    Gitolite lets you set git-config values for individual repos without
-    having to log on to the server and run 'git config' commands:
-
-        repo foo
-            config hooks.mailinglist = foo-commits at example.tld
-            config hooks.emailprefix = '[foo] '
-            config foo.bar = ''
-            config foo.baz =
-
-    **WARNING**
-
-        The last two syntaxes shown above are the *only* way to *delete*
-        a config variable once you have added it.  Merely removing it from
-        the conf file will *not* delete it from the repo.git/config file.
-
-    **SECURITY NOTE**
-
-        Some git-config keys allow arbitrary code to be run on the server.
-
-        If all of your gitolite admins already have shell access to the server
-        account hosting it, you can edit the rc file (~/.gitolite.rc) on the
-        server, and change the GIT_CONFIG_KEYS line to look like this:
-
-            GIT_CONFIG_KEYS     =>  '.*',
-
-        Otherwise, give it a space-separated list of regular expressions that
-        define what git-config keys are allowed.  For example, this one allows
-        only variables whose names start with 'gitweb' or with 'gc' to be
-        defined:
-
-            GIT_CONFIG_KEYS     =>  'gitweb\..* gc\..*',
-
-
-GIT-DAEMON
-----------
-
-    Gitolite creates the 'git-daemon-export-ok' file for any repo that is
-    readable by a special user called 'daemon', like so:
-
-        repo foo
-            R   =   daemon
-
-
-GITWEB
-------
-
-    Any repo that is readable by a special user called 'gitweb' will be added
-    to the projects.list file.
-
-        repo foo
-            R   =   gitweb
-
-    Or you can set one or more of the following config variables instead:
-
-        repo foo
-            config gitweb.owner         =   some person's name
-            config gitweb.description   =   some description
-            config gitweb.category      =   some category
-
-    **NOTE**
-
-        You will probably need to change the UMASK in the rc file from the
-        default (0077) to 0027 and add whatever user your gitweb is running as
-        to the 'git' group.  After that, you need to run a one-time 'chmod -R'
-        on the already created files and directories.
-
-
-------------------------------------------------------------------------
-
-
-MIGRATING FROM v2
------------------
-
-    This section describes how to migrate a basic install of v2 to v3.
-
-    However, if you have used any of the following features:
-
-      * any non-default settings in the rc file
-      * NAME/ rules
-      * subconf and delegation
-      * mirroring
-      * wild repos (user-created repos)
-      * any custom hooks of your own
-
-    you should go through the full set of migration instructions at
-    http://gitolite.com/gitolite/migr.html
-
-    The steps to follow to migrate a simple v2 setup to v3 are as follows:
-
-    0.  take a backup :-)
-
-    1.  remove old gitolite
-
-        1.1 Remove (or rename)
-
-          * the directories named in the rc variables GL_PACKAGE_CONF and
-            GL_PACKAGE_HOOKS (look in ~/.gitolite.rc)
-
-          * ~/.gitolite.rc
-
-          * the gitolite v2 code, whose location you can find in the
-            "command=" parameter in any of the gitolite keys in
-            ~/.ssh/authorized_keys
-
-          * ~/.gitolite (preserve ~/.gitolite/logs if you wish)
-
-        1.2 Edit ~/.ssh/authorized_keys and delete all lines pertaining to
-            gitolite (they will have a "command=" option pointing to
-            gl-auth-command)
-
-        1.3 Clone ~/repositories/gitolite-admin.git to some safe location on
-            the same server.
-
-            NOTE: please clone using the file system directly, not via ssh.
-
-        1.4 Delete ~/repositories/gitolite-admin.git (the repo you just
-            cloned).
-
-            NOTE: DO NOT delete any other repo in ~/repositories.  Leave them
-            all as they are.
-
-    2.  install gitolite as normal.  It doesn't matter what pubkey you use in
-        the "gitolite setup" step; in fact you may even choose to just run
-        "gitolite setup -a admin".  The admin repo created in this step will
-        get wiped out in the next step anyway.
-
-    3.  go to the clone you made in step 1.3 and run 'gitolite push -f'.
-
-        NOTE: that is 'gitolite push -f', not 'git push -f' :-)
-
-
-------------------------------------------------------------------------
-
-
-CONTACT AND SUPPORT
--------------------
-
-    Mailing list for support and general discussion:
-        gitolite at googlegroups.com
-        subscribe address: gitolite+subscribe at googlegroups.com
-
-    Mailing list for announcements and notices:
-        subscribe address: gitolite-announce+subscribe at googlegroups.com
-
-    IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
-    time zone).
-
-    Author: sitaramc at gmail.com, but please DO NOT use this for general support
-    questions.  Subscribe to the list and ask there instead.
-
-
-LICENSE
--------
-
-    The gitolite *code* is released under GPL v2.  See COPYING for details.
-
-    This documentation, which is part of the source code repository, is
-    provided under a Creative Commons Attribution-ShareAlike 3.0 Unported
-    License -- see http://creativecommons.org/licenses/by-sa/3.0/
diff --git a/contrib/utils/ldap_groups.sh b/contrib/utils/ldap_groups.sh
index 0192565..01bf5ee 100755
--- a/contrib/utils/ldap_groups.sh
+++ b/contrib/utils/ldap_groups.sh
@@ -5,7 +5,7 @@
 # Given a username,
 # Provides a space-separated list of groups that the user is a member of.
 #
-# see http://gitolite.com/gitolite/auth.html#ldap
+# see http://gitolite.com/gitolite/conf.html#ldap
 # GROUPLIST_PGM => /path/to/ldap_groups.sh
 
 ldap_groups() {
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index afc10dd..6556ba5 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -67,7 +67,7 @@ if ( -r $rc and -s $rc ) {
 }
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
-    say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://gitolite.com/gitolite/g2migr.html)";
+    say2 "FATAL: '$rc' seems to be for older gitolite; please see\nhttp://gitolite.com/gitolite/migr.html";
 
     exit 1;
 }
@@ -543,7 +543,7 @@ __DATA__
 
         # or you can use this, which lets you put everything in a subdirectory
         # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
-        # on this, see http://gitolite.com/gitolite/cust.html#pushcode
+        # on this, see http://gitolite.com/gitolite/non-core.html#pushcode
         # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
 
     # ------------------------------------------------------------------
diff --git a/t/README b/t/README
index 5053260..4ecdfb7 100644
--- a/t/README
+++ b/t/README
@@ -9,5 +9,6 @@ On such a userid, clone gitolite then run this command in the clone:
 
     GITOLITE_TEST=y prove
 
-http://gitolite.com/gitolite/testing.html has more details.  It will
-also help you try out gitolite if you want to go beyond just the test suite.
+http://gitolite.com/gitolite/testing.html has more details.  Alternatively,
+http://gitolite.com/gitolite/req.html#trying will help you try out gitolite if
+you want to play with gitolite safely.

commit 3455375e69ffd357adf96566f79e91407af8b532
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Jun 23 12:22:13 2014 +0530

    v3.6.1

diff --git a/CHANGELOG b/CHANGELOG
index a89528d..3666ba0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+2014-06-22  v3.6.1  experimental rc format convertor for "<= 3.3" users who
+                    have already upgraded the *code* to ">= v3.4".  Program is
+                    in contrib/utils.
+
+                    giving shell access to a few users got a lot easier (see
+                    comments in the rc file).
+
+                    allow logging to syslog as well (see comments in the rc
+                    file)
+
+                    new 'motd' command
+
+                    redis caching redone and now in core; see
+                    http://gitolite.com/gitolite/cache.html
+
 2014-05-09  v3.6    (cool stuff) the access command can now help you debug
                     your rules / understand how a specific access decision was
                     arrived at.

commit a0203fe04436b67dbe41befbe54dcbe7a137f1c2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Jun 23 11:03:42 2014 +0530

    (experimental) util to upgrade rc file format

diff --git a/contrib/utils/rc-format-v3.4 b/contrib/utils/rc-format-v3.4
new file mode 100755
index 0000000..1a11737
--- /dev/null
+++ b/contrib/utils/rc-format-v3.4
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+
+# help with rc file format change at v3.4 -- help upgrade v3 rc files from
+# v3.3 and below to the new v3.4 and above format
+
+# once you upgrade gitolite past 3.4, you may want to use the new rc file
+# format, because it's really much nicer (just to recap: the old format will
+# still work, in fact internally the new format gets converted to the old
+# format before actually being used.  However, the new format makes it much
+# easier to enable and disable features).
+
+# PLEASE SEE WARNINGS BELOW
+
+# this program helps you upgrade your rc file.
+
+# STEPS
+#   cd gitolite-source-repo-clone
+#   contrib/utils/upgrade-rc33 /path/to/old.gitolite.rc > new.gitolite.rc
+
+# WARNINGS
+#   make sure you also READ ALL ERROR/WARNING MESSAGES GENERATED
+#   make sure you EXAMINE THE FILE AND CHECK THAT EVERYTHING LOOKS GOOD before using it
+#   be especially careful about
+#       variables which contains single/double quotes or other special characters
+#       variables that stretch across multiple lines
+#       features which take arguments (like 'renice')
+#       new features you've enabled which don't exist in the default rc
+
+# ----------------------------------------------------------------------
+
+use strict;
+use warnings;
+use 5.10.0;
+use Cwd;
+use Data::Dumper;
+$Data::Dumper::Terse    = 1;
+$Data::Dumper::Indent   = 1;
+$Data::Dumper::Sortkeys = 1;
+
+BEGIN {
+    $ENV{HOME} = getcwd;
+    $ENV{HOME} .= "/.home.rcupgrade.$$";
+    mkdir $ENV{HOME} or die "mkdir '$ENV{HOME}': $!\n";
+}
+
+END {
+    system("rm -rf ./.home.rcupgrade.$$");
+}
+
+use lib "./src/lib";
+use Gitolite::Rc;
+{
+    no warnings 'redefine';
+    sub Gitolite::Common::gl_log { }
+}
+
+# ----------------------------------------------------------------------
+
+# everything happens inside a fresh v3.6.1+ gitolite clone; no other
+# directories are used.
+
+# the old rc file to be migrated is somewhere *else* and is supplied as a
+# command line argument.
+
+# ----------------------------------------------------------------------
+
+my $oldrc = shift or die "need old rc filename as arg-1\n";
+
+{
+
+    package rcup;
+    do $oldrc;
+}
+
+my %oldrc;
+{
+    no warnings 'once';
+    %oldrc = %rcup::RC;
+}
+
+delete $rcup::{RC};
+{
+    my @extra = sort keys %rcup::;
+    warn "**** WARNING ****\nyou have variables declared outside the %RC hash; you must handle them manually\n" if @extra;
+}
+
+# this is the new rc text being built up
+my $newrc = glrc('default-text');
+
+# ----------------------------------------------------------------------
+
+# default disable all features in newrc
+map { disable( $_, 'sq' ) } (qw(help desc info perms writable ssh-authkeys git-config daemon gitweb));
+# map { disable($_, '') } (qw(GIT_CONFIG_KEYS));
+
+set_s('HOSTNAME');
+set_s( 'UMASK',               'num' );
+set_s( 'GIT_CONFIG_KEYS',     'sq' );
+set_s( 'LOG_EXTRA',           'num' );
+set_s( 'DISPLAY_CPU_TIME',    'num' );
+set_s( 'CPU_TIME_WARN_LIMIT', 'num' );
+set_s('SITE_INFO');
+
+set_s('LOCAL_CODE');
+
+if ( $oldrc{WRITER_CAN_UPDATE_DESC} ) {
+    die "tell Sitaram he changed the default rc too much" unless $newrc =~ /rc variables used by various features$/m;
+    $newrc =~ s/(rc variables used by various features\n)/$1\n    # backward compat\n        WRITER_CAN_UPDATE_DESC      =>  1,\n/;
+
+    delete $oldrc{WRITER_CAN_UPDATE_DESC};
+}
+
+if ( $oldrc{ROLES} ) {
+    my $t = '';
+    for my $r ( sort keys %{ $oldrc{ROLES} } ) {
+        $t .= ( " " x 8 ) . $r . ( " " x ( 28 - length($r) ) ) . "=>  1,\n";
+    }
+    $newrc =~ s/(ROLES *=> *\{\n).*?\n( *\},)/$1$t$2/s;
+
+    delete $oldrc{ROLES};
+}
+
+if ( $oldrc{DEFAULT_ROLE_PERMS} ) {
+    warn "DEFAULT_ROLE_PERMS has been replaced by per repo option\nsee http://gitolite.com/gitolite/wild.html\n";
+    delete $oldrc{DEFAULT_ROLE_PERMS};
+}
+
+# the following is a bit like the reverse of what the new Rc.pm does...
+
+for my $l ( split /\n/, $Gitolite::Rc::non_core ) {
+    next if $l =~ /^ *#/ or $l !~ /\S/;
+
+    my ( $name, $where, $module ) = split ' ', $l;
+    $module = $name if $module eq '.';
+    ( $module = $name ) .= "::" . lc($where) if $module eq '::';
+
+    # if you find $module as an element of $where, enable $name
+    enable($name) if miw( $module, $where );
+}
+
+# now deal with commands
+if ( $oldrc{COMMANDS} ) {
+    for my $c ( sort keys %{ $oldrc{COMMANDS} } ) {
+        if ( $oldrc{COMMANDS}{$c} == 1 ) {
+            enable($c);
+            # we don't handle anything else right (and so far only git-annex
+            # is affected, as far as I remember)
+
+            delete $oldrc{COMMANDS}{$c};
+        }
+    }
+}
+
+print $newrc;
+
+for my $w (qw(INPUT POST_COMPILE PRE_CREATE ACCESS_1 POST_GIT PRE_GIT ACCESS_2 POST_CREATE SYNTACTIC_SUGAR)) {
+    delete $oldrc{$w} unless scalar( @{ $oldrc{$w} } );
+}
+delete $oldrc{COMMANDS} unless scalar keys %{ $oldrc{COMMANDS} };
+
+exit 0 unless %oldrc;
+
+warn "the following parts of the old rc were NOT converted:\n";
+print STDERR Dumper \%oldrc;
+
+# ----------------------------------------------------------------------
+
+# set scalars that the new file defaults to "commented out"
+sub set_s {
+    my ( $key, $type ) = @_;
+    $type ||= '';
+    return unless exists $oldrc{$key};
+
+    # special treatment for UMASK
+    $oldrc{$key} = substr( "00" . sprintf( "%o", $oldrc{$key} ), -4 ) if ( $key eq 'UMASK' );
+
+    $newrc =~ s/# $key /$key   /;    # uncomment if needed
+    if ( $type eq 'num' ) {
+        $newrc =~ s/$key ( *=> *).*/$key $1$oldrc{$key},/;
+    } elsif ( $type eq 'sq' ) {
+        $newrc =~ s/$key ( *=> *).*/$key $1'$oldrc{$key}',/;
+    } else {
+        $newrc =~ s/$key ( *=> *).*/$key $1"$oldrc{$key}",/;
+    }
+
+    delete $oldrc{$key};
+}
+
+sub disable {
+    my ( $key, $type ) = @_;
+    if ( $type eq 'sq' ) {
+        $newrc =~ s/^( *)'$key'/$1# '$key'/m;
+    } else {
+        $newrc =~ s/^( *)$key\b/$1# $key/m;
+    }
+}
+
+sub enable {
+    my $key = shift;
+    $newrc =~ s/^( *)# *'$key'/$1'$key'/m;
+    return if $newrc =~ /^ *'$key'/m;
+    $newrc =~ s/(add new commands here.*\n)/$1            '$key',\n/;
+}
+
+sub miw {
+    my ( $m, $w ) = @_;
+    return 0 unless $oldrc{$w};
+    my @in = @{ $oldrc{$w} };
+    my @out = grep { !/^$m$/ } @{ $oldrc{$w} };
+    $oldrc{$w} = \@out;
+    return not scalar(@in) == scalar(@out);
+}

commit c1530bbc3ce4f74ab7b67bbb0bf757eb0bddb34a
Author: gitolite tester <tester at example.com>
Date:   Mon Jun 9 07:22:50 2014 +0530

    git-annex should accept trailing .git in reponame

diff --git a/src/commands/git-annex-shell b/src/commands/git-annex-shell
index 17a5f26..a9b29d5 100755
--- a/src/commands/git-annex-shell
+++ b/src/commands/git-annex-shell
@@ -27,6 +27,7 @@ die "bad git-annex-shell command: $cmd"
 my $start = $1;
 my $repo  = $2;
 my $end   = $3;
+$repo =~ s/\.git$//;
 die "I dont like some of the characters in $repo\n" unless $repo =~ $Gitolite::Rc::REPONAME_PATT;
 die "I dont like absolute paths in $cmd\n" if $repo =~ /^\//;
 die "I dont like '..' paths in $cmd\n"     if $repo =~ /\.\./;

commit 4fefd3f93254b775afa7ed72ca95f83c26c68724
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 15 07:28:47 2014 +0530

    new way to list shell-capable users

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 9f53474..afc10dd 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -531,9 +531,6 @@ __DATA__
     # the Mirroring feature needs this
         # HOSTNAME                  =>  "foo",
 
-    # if you enabled 'Shell', you need this
-        # SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",
-
     # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
         # CACHE_TTL                 =>  600,
 
@@ -599,8 +596,10 @@ __DATA__
             # access a repo by another (possibly legacy) name
             # 'Alias',
 
-            # give some users direct shell access
-            # 'Shell',
+            # give some users direct shell access.  See documentation in
+            # sts.html for details on the following two choices.
+            # "Shell $ENV{HOME}/.gitolite.shell-users",
+            # 'Shell alice bob',
 
             # set default roles from lines like 'option default.roles-1 = ...', etc.
             # 'set-default-roles',
diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
index f516269..2dd6643 100755
--- a/src/triggers/post-compile/ssh-authkeys-shell-users
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -9,8 +9,6 @@ use Gitolite::Common;
 $|++;
 
 my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
-my $sufile = $rc{SHELL_USERS_LIST} or exit 0;
--r $sufile or _die "'$sufile' not readable";
 
 # ----------------------------------------------------------------------
 
@@ -22,8 +20,32 @@ for my $su ( shell_users() ) {
 
 _print( $akfile, $aktext );
 
+# two methods to specify list of shell-capable users.  (1) list of usernames
+# as arguments to 'Shell' in rc file, (2) list of usernames in a plain text
+# file whose name is the first argument to 'Shell' in the rc file.  Or both!
 sub shell_users {
-    my @ret = grep { not /^#/ } slurp($sufile);
-    chomp(@ret);
+    my ($sufile, @ret);
+
+    # backward compat for 3.6 and below.  This code will be removed in 3.7.
+    # Also, the variable is ignored if you end up using the new variant (i.e.,
+    # put a file name on the 'Shell' line itself).
+    $sufile = $rc{SHELL_USERS_LIST} if $rc{SHELL_USERS_LIST} and -r $rc{SHELL_USERS_LIST};
+
+    $sufile = shift @ARGV if @ARGV and -r $ARGV[0];
+
+    if ($sufile) {
+        @ret = grep { not /^#/ } slurp($sufile);
+        chomp(@ret);
+    }
+
+    for my $u (@ARGV) {
+        # arguments placed in the rc file appear before the trigger name
+        last if $u eq 'POST_COMPILE';
+
+        push @ret, $u;
+        # no sanity checking, since the rc file can only be created by someone
+        # who already has shell access
+    }
+    _die "'Shell': enabled but no usernames supplied" unless @ret;
     return @ret;
 }

commit 93960a9f751c8540011c954e09ca3f3f873f7f43
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 14 21:27:10 2014 +0530

    mirror command learns a couple of server side commands

diff --git a/src/commands/mirror b/src/commands/mirror
index 6aca45f..96b985d 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -34,6 +34,11 @@ the same command except with 'status' instead of 'push'.  With usage 1, you
 can use the special name "all" to get the status of all slaves for the given
 repo.  (Admins wishing to find the status of all slaves for ALL repos will
 have to script it using the output of "gitolite list-phy-repos".)
+
+SERVER LIST: 'gitolite mirror list master <reponame>' and 'gitolite mirror
+list slaves <reponame>' will show you the name of the master server, and list
+the slave servers, for the repo.  They only work on the server command line
+(any server), but not remotely (from a normal user).
 =cut
 
 usage() if not @ARGV or $ARGV[0] eq '-h';
@@ -92,16 +97,42 @@ if ( $cmd eq 'push' ) {
 
     $host = '*' if $host eq 'all';
     map { print_status($_) } sort glob("gl-slave-$host.status");
+} else {
+    # strictly speaking, we could allow some of the possible commands remotely
+    # also, at least for admins.  However, these commands are mainly intended
+    # for server-side scripting so I don't care.
+    usage() if $ENV{GL_USER};
+
+    server_side_commands(@ARGV);
 }
 
+# ----------------------------------------------------------------------
+
 sub valid_slave {
     my ( $host, $repo ) = @_;
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
+    my %list = repo_slaves($repo);
+    _die "'$host' not a valid slave for '$repo'" unless $list{$host};
+}
+
+sub repo_slaves {
+    my $repo = shift;
+
     my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
     my %list = map { $_ => 1 } map { split } values %$ref;
 
-    _die "'$host' not a valid slave for '$repo'" unless $list{$host};
+    return %list;
+}
+
+sub repo_master {
+    my $repo = shift;
+
+    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.master\$" );
+    my @list = map { split } values %$ref;
+    _die "'$repo' seems to have more than one master" if @list > 1;
+
+    return $list[0] || '';
 }
 
 sub print_status {
@@ -113,3 +144,23 @@ sub print_status {
     print slurp($file);
     print "----------\n";
 }
+
+# ----------------------------------------------------------------------
+# server side commands.  Very little error checking.
+#   gitolite mirror list master <repo>
+#   gitolite mirror list slaves <repo>
+
+sub server_side_commands {
+    if ( $cmd eq 'list' ) {
+        if ( $host eq 'master' ) {
+            say repo_master($repo);
+        } elsif ( $host eq 'slaves' ) {
+            my %list = repo_slaves($repo);
+            say join( " ", sort keys %list );
+        } else {
+            _die "gitolite mirror list master|slaves <reponame>";
+        }
+    } else {
+        _die "invalid command";
+    }
+}

commit 6daa7923eba02b37660932a9671930356e512ea1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 19 04:15:16 2014 +0530

    comment change on _mkdir; thanks to trobinso on IRC

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 47c1d02..b32d0d0 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -124,8 +124,9 @@ sub usage {
 }
 
 sub _mkdir {
-    # it's not an error if the directory exists, but it is an error if it
-    # doesn't exist and we can't create it
+    # It's not an error if the directory exists, but it is an error if it
+    # doesn't exist and we can't create it. This includes not guaranteeing
+    # dead symlinks or if mkpath traversal is blocked by a file.
     my $dir  = shift;
     my $perm = shift;    # optional
     return if -d $dir;

commit 646c133aa0ece981ae5f62ca69a4b75261a88464
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jun 3 07:42:13 2014 +0530

    allow logging to syslog as well

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index d8221ce..47c1d02 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -264,6 +264,9 @@ sub gen_lfn {
     return $template;
 }
 
+my $log_dest;
+my $syslog_opened = 0;
+END { closelog() if $syslog_opened; }
 sub gl_log {
     # the log filename and the timestamp come from the environment.  If we get
     # called even before they are set, we have no choice but to dump to STDERR
@@ -276,6 +279,28 @@ sub gl_log {
     my $ts = gen_ts();
     my $tid = $ENV{GL_TID} ||= $$;
 
+    # syslog
+    $log_dest = $Gitolite::Rc::rc{LOG_DEST} || '' if not defined $log_dest;
+    if ($log_dest =~ /syslog/) {            # log_dest *includes* syslog
+        if ($syslog_opened == 0) {
+            require Sys::Syslog;
+            Sys::Syslog->import(qw(:standard));
+
+            openlog("gitolite" . ( $ENV{GL_TID} ? "[$ENV{GL_TID}]" : "" ), "pid", "local0");
+            $syslog_opened = 1;
+        }
+
+        # gl_log is called either directly, or, if the rc variable LOG_EXTRA
+        # is set, from trace(1, ...).  The latter use is considered additional
+        # info for troubleshooting.  Trace prefixes a tab to the arguments
+        # before calling gl_log, to visually set off such lines in the log
+        # file.  Although syslog eats up that leading tab, we use it to decide
+        # the priority/level of the syslog message.
+        syslog( ( $msg =~ /^\t/ ? 'debug' : 'info' ), "%s", $msg);
+
+        return if $log_dest eq 'syslog';    # log_dest *equals* syslog
+    }
+
     my $fh;
     logger_plus_stderr( "errors found before logging could be setup", "$msg" ) if not $ENV{GL_LOGFILE};
     open my $lfh, ">>", $ENV{GL_LOGFILE}
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 5651b06..9f53474 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -497,6 +497,12 @@ __DATA__
 
     # comment out if you don't need all the extra detail in the logfile
     LOG_EXTRA                       =>  1,
+    # syslog options
+    # 1. leave this section as is for normal gitolite logging
+    # 2. uncomment this line to log only to syslog:
+    # LOG_DEST                      => 'syslog',
+    # 3. uncomment this line to log to syslog and the normal gitolite log:
+    # LOG_DEST                      => 'syslog,normal',
 
     # roles.  add more roles (like MANAGER, TESTER, ...) here.
     #   WARNING: if you make changes to this hash, you MUST run 'gitolite

commit 23b6b60790a245d1b317f132142125a28a1168ff
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 13 04:58:29 2014 +0530

    textfile() must also respect repo-umask

diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index f2e4124..c7bd8cc 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -206,6 +206,10 @@ sub textfile {
         _sanity($h{repo});
         $h{dir} = "$rc{GL_REPO_BASE}/$h{repo}.git";
         _die "repo '$h{repo}' does not exist" if not -d $h{dir};
+
+        my $umask = option( $h{repo}, 'umask' );
+        # note: using option() moves us to ADMIN_BASE, but we don't care here
+        umask oct($umask) if $umask;
     }
 
     # final full file name

commit 07ce4b90342d25fdeea5b987cce2342aaa180f25
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 8 09:48:09 2014 +0530

    new motd command; see command help for details

diff --git a/src/commands/motd b/src/commands/motd
new file mode 100755
index 0000000..b56e99e
--- /dev/null
+++ b/src/commands/motd
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for usage
+Usage:    ssh git at host motd <repo> rm
+          cat <filename> | ssh git at host motd <repo> set
+
+Remove or set the motd file for repo or the whole system.
+
+For a repo: you need to have write access to the repo and the
+'writer-is-owner' option must be set for the repo, or it must be a
+user-created ('wild') repo and you must be the owner.
+
+For the whole system: you need to be an admin (have write access to the
+gitolite-admin repo).  Use @all in place of the repo name.
+
+PLEASE NOTE that if you're using http mode, the motd will only appear for
+gitolite commands, not for normal git operations.  This in turn means that
+only the system wide motd can be seen; repo level motd's never show up.
+=cut
+
+usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
+
+my $repo = shift;
+my $op = shift || '';
+usage() if $op ne 'rm' and $op ne 'set';
+my $file = "gl-motd";
+
+#<<<
+_die "you are not authorized" unless
+    ( $repo eq '@all' and is_admin() )      or
+    ( $repo ne '@all' and owns($repo) )     or
+    ( $repo ne '@all' and can_write($repo)  and option( $repo, 'writer-is-owner' ) );
+#>>>
+
+my @out =
+  $repo eq '@all'
+  ? ( dir => $rc{GL_ADMIN_BASE} )
+  : ( repo => $repo );
+
+if ( $op eq 'rm' ) {
+    $repo eq '@all'
+      ? unlink "$rc{GL_ADMIN_BASE}/$file"
+      : unlink "$rc{GL_REPO_BASE}/$repo.git/$file";
+} elsif ( $op eq 'set' ) {
+    textfile( file => $file, @out, prompt => '' );
+} else {
+    print textfile( file => $file, @out, );
+}
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 91aa36f..5651b06 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -420,6 +420,10 @@ BEGIN {
 
     Alias                   INPUT           ::
 
+    Motd                    INPUT           ::
+    Motd                    PRE_GIT         ::
+    Motd                    COMMAND         motd
+
     Mirroring               INPUT           ::
     Mirroring               PRE_GIT         ::
     Mirroring               POST_GIT        ::
@@ -598,6 +602,9 @@ __DATA__
             # show more detailed messages on deny
             # 'expand-deny-messages',
 
+            # show a message of the day
+            # 'Motd',
+
         # system admin stuff
 
             # enable mirroring (don't forget to set the HOSTNAME too!)
diff --git a/src/lib/Gitolite/Triggers/Motd.pm b/src/lib/Gitolite/Triggers/Motd.pm
new file mode 100644
index 0000000..6de80a2
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Motd.pm
@@ -0,0 +1,29 @@
+package Gitolite::Triggers::Motd;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# print a message of the day to STDERR
+# ----------------------------------------------------------------------
+
+my $file = "gl-motd";
+
+sub input {
+    # at present, we print it for every single interaction with gitolite.  We
+    # may want to change that later; if we do, get code from Kindergarten.pm
+    # to get the gitcmd+repo or cmd+args so you can filter on them
+
+    my $f = "$rc{GL_ADMIN_BASE}/$file";
+    print STDERR slurp($f) if -f $f;
+}
+
+sub pre_git {
+    my $repo = $_[1];
+    my $f    = "$rc{GL_REPO_BASE}/$repo.git/$file";
+    print STDERR slurp($f) if -f $f;
+}
+
+1;

commit 08138471e1a32cd9cc2b4567c79612ee6f711982
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 8 07:37:13 2014 +0530

    allow commands to be defined in non-core list

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 704e3d6..91aa36f 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -185,6 +185,9 @@ sub non_core_expand {
 
         push @{ $rc{$where} }, $module;
     }
+
+    # finally, add in commands that were declared in the non-core list
+    map { /^(\S+)/; $rc{COMMANDS}{$1} = 1 } @{ $rc{COMMAND} };
 }
 
 # exported functions

commit da10d6c9f5f51f3f2d275fa5210f6f881fe7fb6f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 7 19:48:12 2014 +0530

    new textfile function, changes to desc, readme
    
    make it easier to handle text files for arbitrary purposes.  Switch desc and
    readme to use that instead of winging it on their own.

diff --git a/src/commands/desc b/src/commands/desc
index f096012..4a4bf20 100755
--- a/src/commands/desc
+++ b/src/commands/desc
@@ -1,41 +1,49 @@
-#!/bin/sh
-
-# Usage:    ssh git at host desc <repo>
-#           ssh git at host desc <repo> <description string>
-#
-# Show or set description for user-created ("wild") repo.
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ "$1" = "-h" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-repo=$1; shift
-
-# this shell script takes arguments that are completely under the user's
-# control, so make sure you quote those suckers!
-
-descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
-
-# Allow users with "R" access to read the repo's description.
-if [ -z "$1" ]
-then
-    gitolite access -q "$repo" $GL_USER R any || die You are not authorised
-    [ -r "$descfile" ] && cat "$descfile"
-    exit 0
-fi
-
-# kernel.org needs 'desc' to be available to people who have "RW" or above,
-# not just the "creator".  In fact they need it for non-wild repos so there
-# *is* no creator.
-
-if gitolite query-rc -q WRITER_CAN_UPDATE_DESC
-then
-    gitolite access -q "$repo" $GL_USER W any || die You are not authorised
-else
-    gitolite creator "$repo" $GL_USER || die You are not authorised
-fi
-
-echo "$*" > "$descfile"
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for usage
+Usage:    ssh git at host desc <repo>
+          ssh git at host desc <repo> <description string>
+
+Show or set description for repo.  You need to have write access to the repo
+and the 'writer-is-owner' option must be set for the repo, or it must be a
+user-created ('wild') repo and you must be the owner.
+=cut
+
+usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
+
+my $repo = shift;
+my $text = join( " ", @ARGV );
+my $file = 'description';
+
+#<<<
+_die "you are not authorized" unless
+    ( not $text and can_read($repo) )   or
+    (     $text and owns($repo) )       or
+    (     $text and can_write($repo)    and ( $rc{WRITER_CAN_UPDATE_DESC} or option( $repo, 'writer-is-owner' ) ) );
+#>>>
+
+$text
+  ? textfile( file => $file, repo => $repo, text => $text )
+  : print textfile( file => $file, repo => $repo );
+
+__END__
+
+kernel.org needs 'desc' to be available to people who have "RW" or above, not
+just the "creator".  In fact they need it for non-wild repos so there *is* no
+creator.  To accommodate this, we created the WRITER_CAN_UPDATE_DESC rc
+variable.
+
+However, that has turned out to be a bit of a blunt instrument for people with
+different types of wild repos -- they don't want to apply this to all of them.
+It seems easier to do this as an option, so you may have it for one set of
+"repo ..." and not have it for others.  And if you want it for the whole
+system you'd just put it under "repo @all".
+
+The new 'writer-is-owner' option is meant to cover desc, readme, and any other
+repo-specific text file, so it's also a blunt instrument, though in a
+different dimension :-)
diff --git a/src/commands/readme b/src/commands/readme
index 358ce3e..cd9632f 100755
--- a/src/commands/readme
+++ b/src/commands/readme
@@ -1,45 +1,54 @@
-#!/bin/sh
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
 
 # README.html files work similar to "description" files. For further
 # information see
-# 	https://www.kernel.org/pub/software/scm/git/docs/gitweb.html
+#   https://www.kernel.org/pub/software/scm/git/docs/gitweb.html
 # under "Per-repository gitweb configuration".
 
-# Usage:    ssh git at host readme <repo>
-#           ssh git at host readme <repo> rm
-#           cat <filename> | ssh git at host readme <repo> set
-#
-# Show, remove or set the README.html file for user-created ("wild") repo.
-
-die() { echo "$@" >&2; exit 1; }
-usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
-[ "$1" = "-h" ] && usage
-[ $# -gt 1 ] && [ "$2" != "set" ] && [ "$2" != "rm" ] && usage
-[ -z "$GL_USER" ] && die GL_USER not set
-
-# ----------------------------------------------------------------------
-repo=$1; shift
-
-if gitolite query-rc -q WRITER_CAN_UPDATE_README
-then
-    gitolite access -q "$repo" $GL_USER W any || die You are not authorised
-else
-    gitolite creator "$repo" $GL_USER || die You are not authorised
-fi
-
-readmefile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/README.html
-
-if [ -z "$1" ]
-then
-    [ -r "$readmefile" ] && cat "$readmefile"
-    exit 0
-fi
-
-if [ "$1" = "rm" ]
-then
-    rm -f "$readmefile"
-    exit 0
-fi
-
-cat >"$readmefile"
+=for usage
+Usage:    ssh git at host readme <repo>
+          ssh git at host readme <repo> rm
+          cat <filename> | ssh git at host readme <repo> set
+
+Show, remove or set the README.html file for repo.
+
+You need to have write access to the repo and the 'writer-is-owner' option
+must be set for the repo, or it must be a user-created ('wild') repo and you
+must be the owner.
+=cut
+
+usage() if not @ARGV or @ARGV < 1 or $ARGV[0] eq '-h';
+
+my $repo = shift;
+my $op = shift || '';
+usage() if $op and $op ne 'rm' and $op ne 'set';
+my $file = 'README.html';
+
+#<<<
+_die "you are not authorized" unless
+    ( not $op and can_read($repo) )   or
+    (     $op and owns($repo) )       or
+    (     $op and can_write($repo)    and option( $repo, 'writer-is-owner' ) );
+#>>>
+
+if ( $op eq 'rm' ) {
+    unlink "$rc{GL_REPO_BASE}/$repo.git/$file";
+} elsif ( $op eq 'set' ) {
+    textfile( file => $file, repo => $repo, prompt => '' );
+} else {
+    print textfile( file => $file, repo => $repo );
+}
+
+__END__
+
+The WRITER_CAN_UPDATE_README option is gone now; it applies to all the repos
+in the system.  Much better to add 'option writer-is-owner = 1' to repos or
+repo groups that you want this to apply to.
+
+This option is meant to cover desc, readme, and any other repo-specific text
+file, so it's also a blunt instrument, though in a different dimension :-)
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 308e94d..f2e4124 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -43,6 +43,8 @@ package Gitolite::Easy;
 
   config
 
+  textfile
+
   %rc
   say
   say2
@@ -50,6 +52,8 @@ package Gitolite::Easy;
   _warn
   _print
   usage
+
+  option
 );
 #>>>
 use Exporter 'import';
@@ -182,6 +186,56 @@ sub config {
 
 # ----------------------------------------------------------------------
 
+# maintain a textfile; see comments in code for details, and calls in various
+# other programs (like 'motd', 'desc', and 'readme') for how to call
+sub textfile {
+    my %h = @_;
+    my $repodir;
+
+    # target file
+    _die "need file" unless $h{file};
+    _die "'$h{file}' contains a '/'" if $h{file} =~ m(/);
+    _sanity($h{file});
+
+    # target file's location.  This can come from one of two places: dir
+    # (which comes from our code, so does not need to be sanitised), or repo,
+    # which may come from the user
+    _die "need exactly one of repo or dir" unless $h{repo} xor $h{dir};
+    _die "'$h{dir}' does not exist" if $h{dir} and not -d $h{dir};
+    if ($h{repo}) {
+        _sanity($h{repo});
+        $h{dir} = "$rc{GL_REPO_BASE}/$h{repo}.git";
+        _die "repo '$h{repo}' does not exist" if not -d $h{dir};
+    }
+
+    # final full file name
+    my $f = "$h{dir}/$h{file}";
+
+    # operation
+    _die "can't have both prompt and text" if defined $h{prompt} and defined $h{text};
+    if (defined $h{prompt}) {
+        print STDERR $h{prompt};
+        my $t = join( "", <> );
+        _print($f, $t);
+    } elsif (defined $h{text}) {
+        _print($f, $h{text});
+    } else {
+        return slurp($f) if -f $f;
+    }
+
+    return '';
+}
+
+# ----------------------------------------------------------------------
+
+sub _sanity {
+    my $name = shift;
+    _die "'$name' contains bad characters" if $name !~ $REPONAME_PATT;
+    _die "'$name' ends with a '/'"         if $name =~ m(/$);
+    _die "'$name' contains '..'"           if $name =~ m(\.\.);
+}
+
+
 sub valid_user {
     _die "GL_USER not set" unless exists $ENV{GL_USER};
     $user = $ENV{GL_USER};
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index f120265..704e3d6 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -509,11 +509,6 @@ __DATA__
     # the 'info' command prints this as additional info, if it is set
         # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
 
-    # the 'desc' command uses this
-        # WRITER_CAN_UPDATE_DESC    =>  1,
-    # the 'readme' command uses this
-        # WRITER_CAN_UPDATE_README  =>  1,
-
     # the CpuTime feature uses these
         # display user, system, and elapsed times to user after each git operation
         # DISPLAY_CPU_TIME          =>  1,

commit ec0fd62c95426d829ff01274a65a8a84e4802ebd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 6 05:10:06 2014 +0530

    make 'help' and 'info' print consistent greeting lines

diff --git a/src/commands/help b/src/commands/help
index 3ab60d8..cf54084 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -17,9 +17,9 @@ Each command has its own help, accessed by passing it '-h' again.
 
 usage() if @ARGV;
 
-my $user = $ENV{GL_USER} || '';
-print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
+print greeting();
 
+my $user = $ENV{GL_USER} || '';
 print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
 
 my %list = ( list_x( $ENV{GL_BINDIR} ), list_x( $rc{LOCAL_CODE} || '' ) );
diff --git a/src/commands/info b/src/commands/info
index 3a2d463..267e4f6 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -28,7 +28,12 @@ than a few thousand repos.
 my ( $lc, $ld, $json, $patt ) = args();
 my %out;    # holds info to be json'd
 
-print_version();
+$ENV{GL_USER} or _die "GL_USER not set";
+if ($json) {
+    greeting(\%out);
+} else {
+    print greeting();
+}
 
 print_patterns();     # repos he can create for himself
 print_phy_repos();    # repos already created
@@ -62,23 +67,6 @@ sub args {
     return ( $lc, $ld, $json, $patt );
 }
 
-sub print_version {
-    chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
-    my $gv = substr( `git --version`, 12 );
-    $ENV{GL_USER} or _die "GL_USER not set";
-
-    if ($json) {
-        $out{GL_USER}          = $ENV{GL_USER};
-        $out{USER}             = ( $ENV{USER} || "httpd" ) . "\@$hn";
-        $out{gitolite_version} = version();
-        chomp( $out{git_version} = $gv );    # this thing has a newline at the end
-        return;
-    }
-
-    # normal output
-    print "hello $ENV{GL_USER}, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
-}
-
 sub print_patterns {
     my ( $repos, @aa );
 
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index c2cb87d..f120265 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -8,6 +8,7 @@ package Gitolite::Rc;
   glrc
   query_rc
   version
+  greeting
   trigger
   _which
 
@@ -269,6 +270,26 @@ sub version {
     return $version;
 }
 
+sub greeting {
+    my $json = shift;
+
+    chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
+    my $gv = substr( `git --version`, 12 );
+    my $gl_user = $ENV{GL_USER} || '';
+    $gl_user = " $gl_user" if $gl_user;
+
+    if ($json) {
+        $json->{GL_USER}          = $ENV{GL_USER};
+        $json->{USER}             = ( $ENV{USER} || "httpd" ) . "\@$hn";
+        $json->{gitolite_version} = version();
+        chomp( $json->{git_version} = $gv );    # this thing has a newline at the end
+        return;
+    }
+
+    # normal output
+    return "hello$gl_user, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
+}
+
 sub trigger {
     my $rc_section = shift;
 

commit f980b29dbd6c77a8805c234e7cf8052c12480517
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 5 07:06:33 2014 +0530

    Kindergarten module...
    
    https://groups.google.com/forum/#!topic/gitolite/cS34Vxix0Us

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index cad6e9c..c2cb87d 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -387,6 +387,8 @@ BEGIN {
 
     renice                  PRE_GIT         .
 
+    Kindergarten            INPUT           ::
+
     CpuTime                 INPUT           ::
     CpuTime                 POST_GIT        ::
 
@@ -616,6 +618,10 @@ __DATA__
             # allow simple line-oriented macros
             # 'macros',
 
+        # Kindergarten mode
+
+            # disallow various things that sensible people shouldn't be doing anyway
+            # 'Kindergarten',
     ],
 
 );
diff --git a/src/lib/Gitolite/Triggers/Kindergarten.pm b/src/lib/Gitolite/Triggers/Kindergarten.pm
new file mode 100755
index 0000000..6274c3d
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Kindergarten.pm
@@ -0,0 +1,99 @@
+package Gitolite::Triggers::Kindergarten;
+
+# http://www.great-quotes.com/quote/424177
+#   "Doctor, it hurts when I do this."
+#   "Then don't do that!"
+
+# Prevent various things that sensible people shouldn't be doing anyway. List
+# of things it prevents is at the end of the program.
+
+# If you were forced to enable this module because someone is *constantly*
+# doing things that need to be caught, consider getting rid of that person.
+# Because, really, who knows what *else* he/she is doing that can't be caught
+# with some clever bit of code?
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+my %active;
+sub active {
+    # in rc, you either see just 'Kindergarten' to activate all features, or
+    # 'Kindergarten U0 CREATOR' (i.e., a space sep list of features after the
+    # word Kindergarten) to activate only those named features.
+
+    # no features specifically activated; implies all of them are active
+    return 1 if not %active;
+    # else check if this specific feature is active
+    return 1 if $active{ +shift };
+
+    return 0;
+}
+
+my ( $verb, $repo, $cmd, $args );
+sub input {
+    # get the features to be activated, if supplied
+    while ( $_[0] ne 'INPUT' ) {
+        $active{ +shift } = 1;
+    }
+
+    # generally fill up variables you might use later
+    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /($git_commands) '\/?(\S+)'$/ ) {
+        $verb = $1;
+        $repo = $2;
+    } elsif ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^(\S+) (.*)$/ ) {
+        $cmd  = $1;
+        $args = $2;
+    }
+
+    prevent_CREATOR($repo) if active('CREATOR') and $verb;
+    prevent_0(@ARGV)       if active('U0')      and @ARGV;
+}
+
+sub prevent_CREATOR {
+    my $repo = shift;
+    _die "'CREATOR' not allowed as part of reponame" if $repo =~ /\bCREATOR\b/;
+}
+
+sub prevent_0 {
+    my $user = shift;
+    _die "'0' is not a valid username" if $user eq '0';
+}
+
+1;
+
+__END__
+
+CREATOR
+
+    prevent literal 'CREATOR' from being part of a repo name
+
+    a quirk deep inside gitolite would let this config
+
+        repo foo/CREATOR/..*
+            C   =   ...
+
+    allow the creation of repos like "foo/CREATOR/bar", i.e., the word CREATOR is
+    literally used.
+
+    I consider this a totally pathological situation to check for.  The worst that
+    can happen is someone ends up cluttering the server with useless repos.
+
+    One solution could be to prevent this only for wild repos, but I can't be
+    bothered to fine tune this, so this module prevents even normal repos from
+    having the literal CREATOR in them.
+
+    See https://groups.google.com/forum/#!topic/gitolite/cS34Vxix0Us for more.
+
+U0
+
+    prevent a user from being called literal '0'
+
+    Ideally we should prevent keydir/0.pub (or variants) from being created,
+    but for "Then don't do that" purposes it's enough to prevent the user from
+    logging in.
+
+    See https://groups.google.com/forum/#!topic/gitolite/F1IBenuSTZo for more.

commit 766057a60e69be9a49b759713b7cf0d22d430e07
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 5 05:43:39 2014 +0530

    http: stderr should dup stdout for non-git command output

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 39a3438..276bd34 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -223,6 +223,9 @@ sub http_simulate_ssh_connection {
         $ENV{SSH_ORIGINAL_COMMAND} = $verb;
         $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
         http_print_headers();    # in preparation for the eventual output!
+
+        # we also need to pipe STDERR out via STDOUT, else the user doesn't see those messages!
+        open(STDERR, ">&STDOUT") or _die "Can't dup STDOUT: $!";
     }
     $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
 }

commit b5df869c1ae8233f8b84cc086347c5d0662fcf1c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 30 14:22:18 2014 +0530

    mirroring: hush mirror status if asked to

diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 3ff2792..c88fc92 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -72,7 +72,7 @@ sub pre_git {
     details($repo);
 
     # print mirror status if at least one slave status file is present
-    print_status( $repo ) if $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-slave-*.status");
+    print_status( $repo ) if not $rc{HUSH_MIRROR_STATUS} and $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-slave-*.status");
 
     # we don't deal with any reads.  Note that for pre-git this check must
     # happen *after* getting details, to give mode() a chance to die on "known

commit 949c45c1c649f5c66d46b77b47d614dbaf3bf21e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 16 15:21:24 2014 +0530

    redis caching: 'set-default-roles' fix...
    
    flush repo from cache

diff --git a/src/triggers/set-default-roles b/src/triggers/set-default-roles
index a8a2b4d..18ac28b 100755
--- a/src/triggers/set-default-roles
+++ b/src/triggers/set-default-roles
@@ -13,3 +13,8 @@ die() { echo "$@" >&2; exit 1; }
 cd $GL_REPO_BASE/$2.git || die "could not cd to $GL_REPO_BASE/$2.git"
 gitolite git-config -r $2 gitolite-options.default.roles | sort | cut -f3 |
     perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$2/' > gl-perms
+
+# cache control, if rc says caching is on
+gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$2')";
+
+exit 0
diff --git a/t/perm-default-roles.t b/t/perm-default-roles.t
index 8535609..1a56ff8 100755
--- a/t/perm-default-roles.t
+++ b/t/perm-default-roles.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # permissions using role names
 # ----------------------------------------------------------------------
 
-try "plan 27";
+try "plan 33";
 try "DEF POK = !/DENIED/; !/failed to push/";
 
 my $rb = `gitolite query-rc -n GL_REPO_BASE`;
@@ -107,10 +107,20 @@ try "
 
 cd ..
 
+gitolite access foo/u1/u1r3 u4 W
+        !ok
+        !/refs/../
+        /W any foo/u1/u1r3 u4 DENIED by fallthru/
+
 # make foo/u1/u1r3
 glt clone u1 file:///foo/u1/u1r3
         /Initialized empty Git repository in .*/foo/u1/u1r3.git//
 
+gitolite access foo/u1/u1r3 u4 W
+        ok
+        /refs/../
+        !/W any foo/u1/u1r3 u4 DENIED by fallthru/
+
 # make bar/u3/u3r3
 glt clone u3 file:///bar/u3/u3r3
         /Initialized empty Git repository in .*/bar/u3/u3r3.git//

commit 4a0728e1bbe51a13ab50dd2a69ad50b2b605c8b9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 20 11:13:22 2014 +0530

    warn about 'gitolite setup' losing split keys...
    
    thanks to Jephte Clain for catching this (thread "ssh-authkeys-split not
    enabled in gitolite setup" on the mailing list)

diff --git a/src/triggers/post-compile/ssh-authkeys-split b/src/triggers/post-compile/ssh-authkeys-split
index e538fff..d96d2e9 100755
--- a/src/triggers/post-compile/ssh-authkeys-split
+++ b/src/triggers/post-compile/ssh-authkeys-split
@@ -15,9 +15,18 @@
 #
 # - assumes no "@" sign in basenames of any multi-key files (single line file
 #   may still have them)
+
 # - assumes you don't have a subdir in keydir called "__split_keys__"
+
 # - God help you if you try to throw in a putty key in there.
 
+# - RUNNING "GITOLITE SETUP" WILL LOSE ALL THESE KEYS.  So if you ever do
+#   that, you will then need to make a dummy push to the admin repo to add
+#   them back.  If all your **admin** keys were in split keys, then you lost
+#   remote access.  If that happens, log on to the server using "su - git" or
+#   such, then use the methods described in the "bypassing gitolite" section
+#   in "emergencies.html" instead of a remote push.
+
 # SUPPORT
 # -------
 #

commit 2d5c69066bafc20201e02b510251e3c5e59ac2e6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu May 15 11:34:55 2014 +0530

    (oops!) new mirror status code assumes too recent perl
    
    As a result, a push fails if *mirroring* for the previous push failed!
    
    If you cannot apply this patch asap, a work around is to remove all files
    called "gl-slave-*.status" in repo.git on the server.

diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index b1e4455..3ff2792 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -244,8 +244,10 @@ sub push_to_slaves {
 
 sub print_status {
     my $repo = shift;
-    delete local $ENV{GL_USER};
+    my $u = $ENV{GL_USER};
+    delete $ENV{GL_USER};
     system("gitolite mirror status all $repo >&2");
+    $ENV{GL_USER} = $u;
 }
 
 1;

commit 8b7c50a6ca992af70187cfac20a15a9f23548069
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 14 11:44:16 2014 +0530

    redis caching: please read below
    
    This commit brings in redis caching to Gitolite.
    
    Documentation is not yet done, but briefly:
    1.  make sure you have Redis available ('perl -MRedis -e 0' should succeed)
    2.  enable caching by uncommenting the "CACHE => 'redis'" line in the rc file
    
    However, this code is not the same as the old "redis" branch (which has now
    been deleted).  It is a complete rewrite, much simpler, and much more elegant.
    It uses the ideas of memoisation rather than explicit "cache_set" and
    "cache_get", and at present memoizes just one function: the access() function
    in Load.pm.  That seems to be sufficient for most purposes anyway.

diff --git a/src/commands/fork b/src/commands/fork
index 1795e03..49994fc 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -50,5 +50,8 @@ ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
 # record where you came from
 echo "$from" > gl-forked-from
 
+# cache control, if rc says caching is on
+gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$to')";
+
 # trigger post_create
 gitolite trigger POST_CREATE $to $GL_USER fork
diff --git a/src/commands/perms b/src/commands/perms
index f61057d..c9b8946 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -64,6 +64,13 @@ if ( $ARGV[0] eq '-c' ) {
 
 my $repo = shift;
 setperms(@ARGV);
+
+# cache control
+if ($rc{CACHE}) {
+    require Gitolite::Cache;
+    Gitolite::Cache::cache_control('flush', $repo);
+}
+
 _system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
 
 # ----------------------------------------------------------------------
diff --git a/src/lib/Gitolite/Cache.pm b/src/lib/Gitolite/Cache.pm
new file mode 100644
index 0000000..351a13e
--- /dev/null
+++ b/src/lib/Gitolite/Cache.pm
@@ -0,0 +1,161 @@
+package Gitolite::Cache;
+
+# cache stuff using an external database (redis)
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  cache_control
+  cache_wrap
+);
+
+use Exporter 'import';
+
+use Gitolite::Common;
+use Gitolite::Rc;
+use Storable qw(freeze thaw);
+use Redis;
+
+my $redis;
+
+my $redis_sock = "$ENV{HOME}/.redis-gitolite.sock";
+if ( -S $redis_sock ) {
+    _connect_redis();
+} else {
+    _start_redis();
+    _connect_redis();
+
+    # this redis db is a transient, caching only, db, so let's not
+    # accidentally use any stale data when if we're just starting up
+    cache_control('stop');
+    cache_control('start');
+}
+
+# ----------------------------------------------------------------------
+
+my %wrapped;
+my $ttl = ( $rc{CACHE_TTL} || ( $rc{GROUPLIST_PGM} ? 900 : 90000 ) );
+
+sub cache_control {
+    my $op = shift;
+    if ( $op eq 'stop' ) {
+        $redis->flushall();
+    } elsif ( $op eq 'start' ) {
+        $redis->set( 'cache-up', 1 );
+    } elsif ( $op eq 'flush' ) {
+        flush_repo(@_);
+    }
+}
+
+sub cache_wrap {
+    my $sub   = shift;
+    my $tname = $sub;    # this is what will show up in the trace output
+    trace( 3, "wrapping '$sub'" );
+    $sub = ( caller 1 )[0] . "::" . $sub if $sub !~ /::/;
+    return if $wrapped{$sub}++;    # in case somehow it gets called twice for the same sub!
+
+    # collect names of wrapped subs into a redis 'set'
+    $redis->sadd( "SUBWAY", $sub );    # subway?  yeah well they wrap subs don't they?
+
+    my $cref = eval '\&' . $sub;
+    my %opt  = @_;
+        # rest of the options come in as a hash.  'list' says this functions
+        # returns a list.  'ttl' is a number to override the default ttl for
+        # the cached value.
+
+    no strict 'refs';
+    no warnings 'redefine';
+    *{$sub} = sub {                    # the wrapper function
+        my $key = join( ", ", @_ );
+        trace( 2, "$tname.args", @_ );
+
+        if ( cache_up() and defined( my $val = $redis->get("$sub: $key") ) ) {
+            # cache is up and we got a hit, return value from cache
+            if ( $opt{list} ) {
+                trace( 2, "$tname.getl", @{ thaw($val) } );
+                return @{ thaw($val) };
+            } else {
+                trace( 2, "$tname.get", $val );
+                return $val;
+            }
+        } else {
+            # cache is down or we got a miss, compute
+            my ( $r, @r );
+            if ( $opt{list} ) {
+                @r = $cref->(@_);    # provide list context
+                trace( 2, "$tname.setl", @r );
+            } else {
+                $r = $cref->(@_);    # provide scalar context
+                trace( 2, "$tname.set", $r );
+            }
+
+            # store computed value in cache if cache is up
+            if ( cache_up() ) {
+                $redis->set( "$sub: $key", ( $opt{list} ? freeze( \@r ) : $r ) );
+                $redis->expire( "$sub: $key", $opt{ttl} || $ttl );
+                trace( 2, "$tname.ttl", ( $opt{ttl} || $ttl ) );
+            }
+
+            return @r if $opt{list};
+            return $r;
+        }
+    };
+    trace( 3, "wrapped '$sub'" );
+}
+
+sub cache_up {
+    return $redis->exists('cache-up');
+}
+
+sub flush_repo {
+    my $repo = shift;
+
+    my @wrapped = $redis->smembers("SUBWAY");
+    for my $func (@wrapped) {
+        # if we wrap any more functions, make sure they're functions where the
+        # first argument is 'repo'
+        my @keys = $redis->keys("$func: $repo, *");
+        $redis->del( @keys ) if @keys;
+    }
+}
+
+# ----------------------------------------------------------------------
+
+sub _start_redis {
+    my $conf = join( "", <DATA> );
+    $conf =~ s/%HOME/$ENV{HOME}/g;
+
+    open( REDIS, "|-", "/usr/sbin/redis-server", "-" ) or die "start redis server failed: $!";
+    print REDIS $conf;
+    close REDIS;
+
+    # give it a little time to come up
+    select( undef, undef, undef, 0.2 );
+}
+
+sub _connect_redis {
+    $redis = Redis->new( sock => $redis_sock, encoding => undef ) or die "redis new failed: $!";
+    $redis->ping or die "redis ping failed: $!";
+}
+
+1;
+
+__DATA__
+# resources
+maxmemory 50MB
+port 0
+unixsocket %HOME/.redis-gitolite.sock
+unixsocketperm 700
+timeout 0
+databases 1
+
+# daemon
+daemonize yes
+pidfile %HOME/.redis-gitolite.pid
+dbfilename %HOME/.redis-gitolite.rdb
+dir %HOME
+
+# feedback
+loglevel notice
+logfile %HOME/.redis-gitolite.log
+
+# we don't save
diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index ef22adb..ce7adca 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -32,8 +32,21 @@ sub compile {
     # the order matters; new repos should be created first, to give store a
     # place to put the individual gl-conf files
     new_repos();
+
+    # cache control
+    if ($rc{CACHE}) {
+        require Gitolite::Cache;
+        Gitolite::Cache->import(qw(cache_control));
+
+        cache_control('stop');
+    }
+
     store();
 
+    if ($rc{CACHE}) {
+        cache_control('start');
+    }
+
     for my $repo ( @{ $rc{NEW_REPOS_CREATED} } ) {
         trigger( 'POST_CREATE', $repo );
     }
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 5d4766c..70f22ce 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -141,6 +141,12 @@ sub access {
     return "$aa $safe_ref $repo $user DENIED by fallthru";
 }
 
+# cache control
+if ($rc{CACHE}) {
+    require Gitolite::Cache;
+    Gitolite::Cache::cache_wrap('Gitolite::Conf::Load::access');
+}
+
 sub git_config {
     my ( $repo, $key, $empty_values_OK ) = @_;
     $key ||= '.';
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index e6d4052..cad6e9c 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -476,6 +476,9 @@ __DATA__
         WRITERS                     =>  1,
     },
 
+    # enable caching (currently only Redis).  PLEASE RTFM BEFORE USING!!!
+    # CACHE                         =>  'Redis',
+
     # ------------------------------------------------------------------
 
     # rc variables used by various features
@@ -500,6 +503,9 @@ __DATA__
     # if you enabled 'Shell', you need this
         # SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",
 
+    # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
+        # CACHE_TTL                 =>  600,
+
     # ------------------------------------------------------------------
 
     # suggested locations for site-local gitolite code (see cust.html)
diff --git a/t/mirror-test-rc b/t/mirror-test-rc
index d06fbc0..1d76783 100644
--- a/t/mirror-test-rc
+++ b/t/mirror-test-rc
@@ -37,6 +37,8 @@
     # uncomment (and change) this if you wish
     # DEFAULT_ROLE_PERMS            =>  'READERS @all',
 
+    # CACHE => 'Redis',
+
     # ------------------------------------------------------------------
 
     # rc variables used by various features

commit 522cc1fc1af530ef9c82e01d89f11022adf4b355
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 7 19:23:41 2014 +0530

    v3.6

diff --git a/CHANGELOG b/CHANGELOG
index cf012fc..a89528d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,41 @@
+2014-05-09  v3.6    (cool stuff) the access command can now help you debug
+                    your rules / understand how a specific access decision was
+                    arrived at.
+
+                    mirroring: since mirroring is asynchronous (by default
+                    anyway), when a 'git push --mirror' fails, you may not
+                    know it unless you look in the log file on the server.
+                    Now gitolite captures the info and -- if the word 'fatal'
+                    appears anywhere within it, it saves the entire output and
+                    prints it to STDERR for anyone who reads or writes the
+                    repo on the *master* server, until the error condition
+                    clears up.
+
+                    mirroring: allow 'nosync' slaves -- no attempt to
+                    automatically push to these slaves will be made.  Instead,
+                    you have to manually (or via cron, etc) trigger pushes.
+
+                    (backward compat breakage) the old v2 syntax for
+                    specifying gitweb owner and description is no longer
+                    supported.
+
+                    macros now allow strings as arguments (thanks to Jason
+                    Donenfeld for the idea/problem).
+
+                    the 'info' command can print in JSON format if asked to.
+
+                    repo-specific hooks: now you can specify more than one,
+                    and gitolite runs all of them in sequence.
+
+                    new trigger 'expand-deny-messages' to show more details
+                    when access is denied.
+
+                    git-annex support is finally in master, yaaay!
+
+                    new 'readme' command, modelled after 'desc'.  Apparently
+                    gitweb can use a README.html file in the *bare* repo
+                    directory -- who knew!
+
 2013-10-14  v3.5.3  catch undefined groupnames (when possible)
 
                     mirroring: async push to slaves

commit c36fe36f0e32ec4d79d13b7997895f22659bd6b8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 10 17:16:30 2014 +0530

    writable: status can now be interrogated by script
    
    (also, admin can set individual wild repos' writable status)
    
    code courtesy Michel Bourget (in thread
        writable : adding a 'status' option
    on the mailing list)

diff --git a/src/commands/writable b/src/commands/writable
index 828f569..e84020e 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -6,12 +6,14 @@ use lib $ENV{GL_LIBDIR};
 use Gitolite::Easy;
 
 =for usage
-Usage: gitolite writable <reponame>|@all on|off
+Usage: gitolite writable <reponame>|@all on|off|status
 
 Disable/re-enable pushes to all repos or named repo.  Useful to run
 non-git-aware backups and so on.
 
 'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
+'status' returns the current status as shell truth (i.e., exit code 0 for
+writable, 1 for not writable).
 
 With 'off', any subsequent text is taken to be the message to be shown to
 users when their pushes get rejected.  If it is not supplied, it will take it
@@ -19,20 +21,20 @@ from STDIN; this allows longer messages.
 =cut
 
 usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
-usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off';
+usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off' and $ARGV[1] ne 'status';
 
 my $repo = shift;
-my $on   = ( shift eq 'on' );
+my $op   = shift;    # on|off|status
 
 if ( $repo eq '@all' ) {
     _die "you are not authorized" if $ENV{GL_USER} and not is_admin();
 } else {
-    _die "you are not authorized" if $ENV{GL_USER} and not owns($repo);
+    _die "you are not authorized" if $ENV{GL_USER} and not( owns($repo) or is_admin() );
 }
 
 my $msg = join( " ", @ARGV );
 # try STDIN only if no msg found in args *and* it's an 'off' command
-if ( not $msg and not $on ) {
+if ( not $msg and $op eq 'off' ) {
     say2 "...please type the message to be shown to users:";
     $msg = join( "", <> );
 }
@@ -48,9 +50,12 @@ if ( $repo eq '@all' ) {
 
 sub target {
     my $repodir = shift;
-    if ($on) {
+    if ( $op eq 'status' ) {
+        exit 1 if -e "$repodir/$sf";
+        exit 0;
+    } elsif ( $op eq 'on' ) {
         unlink "$repodir/$sf";
-    } else {
+    } elsif ( $op eq 'off' ) {
         _print( "$repodir/$sf", $msg );
     }
 }

commit 145831b5e707a69ee8ce81f9a90ad238287d8cbf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 9 18:41:24 2014 +0530

    unnecessary 'use Getopt::Long' changed to 'require'
    
    the one in Rc.pm must be particularly significant; the test suite was about 12
    percent faster after this change!!
    
    (and I found this thanks to mucking around with sysdig ... neat tool!)

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 05341b9..ef22adb 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -10,7 +10,6 @@ package Gitolite::Conf;
 );
 
 use Exporter 'import';
-use Getopt::Long;
 
 use Gitolite::Rc;
 use Gitolite::Common;
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 61e0363..e6d4052 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -20,7 +20,6 @@ package Gitolite::Rc;
 );
 
 use Exporter 'import';
-use Getopt::Long;
 
 use Gitolite::Common;
 
@@ -361,7 +360,8 @@ Explore:
 sub args {
     my $help = 0;
 
-    GetOptions(
+    require Getopt::Long;
+    Getopt::Long::GetOptions(
         'all|a'   => \$all,
         'nonl|n'  => \$nonl,
         'quiet|q' => \$quiet,
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 06b2409..43de5d9 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -39,7 +39,6 @@ Subsequent runs:
 );
 
 use Exporter 'import';
-use Getopt::Long;
 
 use Gitolite::Rc;
 use Gitolite::Common;
@@ -73,7 +72,8 @@ sub args {
     my $help   = 0;
     my $argv   = join( " ", @ARGV );
 
-    GetOptions(
+    require Getopt::Long;
+    Getopt::Long::GetOptions(
         'admin|a=s'     => \$admin,
         'pubkey|pk=s'   => \$pubkey,
         'hooks-only|ho' => \$h_only,

commit 8e915ff2676dfc666749ba5e86c74966601adf6a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 7 20:13:56 2014 +0530

    make Time::HiRes optional

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index d8554ab..d8221ce 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -71,9 +71,12 @@ sub dd {
 }
 
 {
-    use Time::HiRes;
     my %start_times;
 
+    eval "require Time::HiRes";
+    # we just ignore any errors from this; nothing needs to be done as long as
+    # no code *calls* either of the next two functions.
+
     sub t_start {
         my $name = shift || 'default';
         $start_times{$name} = [ Time::HiRes::gettimeofday() ];

commit 0a2e0384d148025d941a7e4b19262cbfad85b088
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 4 12:57:33 2014 +0530

    access command and rule tracing changes...
    
    *   enable rule tracing by default (it's not an option anymore)
    *   make the access command accept non-fq ref names (and fq them internally
        just like parse_refs() does)
    *   make continuation-lines compatible with rule tracing

diff --git a/src/commands/access b/src/commands/access
index e605444..e8b9446 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -22,9 +22,12 @@ DENIED in it if access was denied.  With '-q', returns only an exit code
   - ref:  defauts to 'any'.  See notes below
 
 Notes:
-  - ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine.
-    The 'any' ref is special -- it ignores deny rules (see docs for what this
-    means and exceptions).
+  - ref: something like 'master', or 'refs/tags/v1.0', or even a VREF if you
+    know what they look like.
+
+    The 'any' ref is special -- it ignores deny rules, thus simulating
+    gitolite's behaviour during the pre-git access check (see 'deny-rules'
+    section in rules.html for details).
 
   - batch mode: see src/triggers/post-compile/update-git-daemon-access-list
     for a good example that shows how to test several repos in one invocation.
@@ -32,18 +35,18 @@ Notes:
     times; you'll notice if you have more than a hundred or so repos.
 
   - '-s' shows the rules (conf file name, line number, and rule) that were
-    considered and how they fared.  Admin must add 'RULE_INFO => 1,' to the rc
-    file, then push the gitolite-admin repo (or 'gitolite compile').
-
-    WARNING: may not be compatible with some syntactic sugar helpers (like
-    'continuation-lines').
+    considered and how they fared.
 =cut
 
 usage() if not @ARGV or $h;
 
 my ( $repo, $user, $aa, $ref ) = @ARGV;
+# default access is '+'
 $aa  ||= '+';
+# default ref is 'any'
 $ref ||= 'any';
+# fq the ref if needed
+$ref =~ s(^)(refs/heads/) if $ref and $ref ne 'any' and $ref !~ m(^(refs|VREF)/);
 _die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ );
 _die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
 
@@ -78,8 +81,6 @@ while (<>) {
 }
 
 sub show {
-    die "rc variable RULE_INFO not set; see help text\n" unless $rc{RULE_INFO};
-
     my $in = $rc{RULE_TRACE} or die "this should not happen!";
 
     print STDERR "legend:";
diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index 26189f2..cf89620 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -43,7 +43,7 @@ sub explode {
             incsub( $1, $2, $3, $subconf, $out );
         } else {
             # normal line, send it to the callback function
-            push @{$out}, "# $file $." if $rc{RULE_INFO};
+            push @{$out}, "# $file $.";
             push @{$out}, $line;
         }
     }
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 0cf1ea5..1d566eb 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -124,7 +124,7 @@ sub add_rule {
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
     $nextseq++;
-    store_rule_info( $nextseq, $fname, $lnum ) if $rc{RULE_INFO};
+    store_rule_info( $nextseq, $fname, $lnum );
     for my $repo (@repolist) {
         push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
     }
@@ -255,7 +255,7 @@ sub parse_done {
         _warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
     }
 
-    close_rule_info() if $rc{RULE_INFO};
+    close_rule_info();
 }
 
 # ----------------------------------------------------------------------
diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines
index 3c28f20..d63475f 100644
--- a/src/syntactic-sugar/continuation-lines
+++ b/src/syntactic-sugar/continuation-lines
@@ -21,6 +21,8 @@ sub sugar_script {
     my @out  = ();
     my $keep = '';
     for my $l (@$lines) {
+        # skip RULE_INFO lines if in continuation mode
+        next if $keep and $l =~ /^ *#/;
         if ( $l =~ s/\\$// ) {
             $keep .= $l;
         } else {

commit 0e1d33f4a87535fa862ba7bbfcba916c812e093c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 4 10:16:44 2014 +0530

    ssh-authkeys: make authkey creation warning less scary

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 9e0f059..4533a4c 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -75,8 +75,9 @@ sub sanity {
     _die "'$glshell' found but not readable; this should NOT happen..."   if not -r $glshell;
     _die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;
 
-    _warn "$akdir missing; creating a new one"  if not -d $akdir;
-    _warn "$akfile missing; creating a new one" if not -f $akfile;
+    my $n = "    (this is normal on a brand new install)";
+    _warn "$akdir missing; creating a new one\n$n"  if not -d $akdir;
+    _warn "$akfile missing; creating a new one\n$n" if not -f $akfile;
 
     _mkdir( $akdir, 0700 ) if not -d $akdir;
     if ( not -f $akfile ) {

commit 5238e526ac0fc90965bf77b9e734171e581d4d77
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 3 12:31:05 2014 +0530

    mirroring: save and print mirror-push status...
    
    -   save 'git push --mirror' output if it contains the word 'fatal'
    -   allow users and admins to query/print it using 'gitolite mirror status ...'
    -   print it if ANY operation on the repo is performed on the master server

diff --git a/src/commands/mirror b/src/commands/mirror
index 205145c..6aca45f 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -28,6 +28,12 @@ need real-time updates or have bandwidth/connectivity issues.
 Usage 2 can be initiated by *any* user who has *any* gitolite access to the
 master server, but it checks that the slave is in one of the slaves options
 before doing the push.
+
+MIRROR STATUS: To find the status of the last mirror push to any slave, run
+the same command except with 'status' instead of 'push'.  With usage 1, you
+can use the special name "all" to get the status of all slaves for the given
+repo.  (Admins wishing to find the status of all slaves for ALL repos will
+have to script it using the output of "gitolite list-phy-repos".)
 =cut
 
 usage() if not @ARGV or $ARGV[0] eq '-h';
@@ -52,9 +58,11 @@ if ( $cmd eq 'push' ) {
     }
 
     my $errors = 0;
+    my $glss = '';
     for (`git push --mirror $host:$repo 2>&1`) {
         $errors = 1 if $?;
         print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
+        $glss .= $_;
         chomp;
         if (/FATAL/) {
             $errors = 1;
@@ -63,7 +71,27 @@ if ( $cmd eq 'push' ) {
             trace( 1, "mirror: $_" );
         }
     }
+    # save the mirror push status for this slave if the word 'fatal' is found,
+    # else remove the status file.  We don't store "success" output messages;
+    # you can always get those from the log files if you really need them.
+    if ( $glss =~ /fatal/i ) {
+        my $glss_prefix = Gitolite::Common::gen_ts() . "\t$ENV{GL_TID}\t";
+        $glss =~ s/^/$glss_prefix/gm;
+        _print("gl-slave-$host.status", $glss);
+    } else {
+        unlink "gl-slave-$host.status";
+    }
+
     exit $errors;
+} elsif ($cmd eq 'status') {
+    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
+    # will die if host not in slaves for repo
+
+    _chdir( $rc{GL_REPO_BASE} );
+    _chdir("$repo.git");
+
+    $host = '*' if $host eq 'all';
+    map { print_status($_) } sort glob("gl-slave-$host.status");
 }
 
 sub valid_slave {
@@ -75,3 +103,13 @@ sub valid_slave {
 
     _die "'$host' not a valid slave for '$repo'" unless $list{$host};
 }
+
+sub print_status {
+    my $file = shift;
+    return unless -f $file;
+    my $slave = $1 if $file =~ /^gl-slave-(.+)\.status$/;
+    print "----------\n";
+    print "WARNING: previous mirror push to host '$slave' failed, status is:\n";
+    print slurp($file);
+    print "----------\n";
+}
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 7d55692..b1e4455 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -71,6 +71,9 @@ sub pre_git {
     # now you know the repo, get its mirroring details
     details($repo);
 
+    # print mirror status if at least one slave status file is present
+    print_status( $repo ) if $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-slave-*.status");
+
     # we don't deal with any reads.  Note that for pre-git this check must
     # happen *after* getting details, to give mode() a chance to die on "known
     # unknown" repos (repos that are in the config, but mirror settings
@@ -239,4 +242,10 @@ sub push_to_slaves {
     $ENV{GL_USER} = $u;
 }
 
+sub print_status {
+    my $repo = shift;
+    delete local $ENV{GL_USER};
+    system("gitolite mirror status all $repo >&2");
+}
+
 1;

commit b607f55bb273f56a0d9148031ce67cd5a9280f37
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 2 11:12:14 2014 +0530

    repo-specific hooks: make them upgradable from 3.5.3.1,
    
    also fix a buglet
    
    (tests courtesy milki)

diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
index 1bc4bc5..efa9c6f 100755
--- a/src/triggers/repo-specific-hooks
+++ b/src/triggers/repo-specific-hooks
@@ -9,17 +9,19 @@ use Gitolite::Rc;
 use Gitolite::Common;
 
 _die "repo-specific-hooks: LOCAL_CODE not defined in rc" unless $rc{LOCAL_CODE};
-_die "repo-specific-hooks: '$rc{LOCAL_CODE}' does not exist or is not a directory" unless -d $rc{LOCAL_CODE};
+_die "repo-specific-hooks: '$rc{LOCAL_CODE}/hooks/repo-specific' does not exist or is not a directory" unless -d "$rc{LOCAL_CODE}/hooks/repo-specific";
 
 _chdir( $ENV{GL_REPO_BASE} );
 
 @ARGV = ("gitolite list-phy-repos | gitolite git-config -ev -r % gitolite-options\\.hook\\. |");
 
+my $driver = "$rc{LOCAL_CODE}/hooks/multi-hook-driver";
 # Hook Driver
-my $hook_text = '';
 {
     local $/ = undef;
-    $hook_text = <DATA>;
+    my $hook_text = <DATA>;
+    _print( $driver, $hook_text );
+    chmod 0755, $driver;
 }
 
 while (<>) {
@@ -41,8 +43,10 @@ while (<>) {
         next;
     }
 
-    my @codes = split / /, $codes;
-    unlink( glob("$repo.git/hooks/$hook.*") );
+    my @codes = split /\s+/, $codes;
+
+    my $dst = "$repo.git/hooks/$hook";
+    unlink( glob("$dst.*") );
 
     my $counter = "h00";
     foreach my $code (@codes) {
@@ -63,9 +67,9 @@ while (<>) {
 
         # no sanity checks for multiple overwrites of the same hook
     }
-    my $dst = "$repo.git/hooks/$hook";
-    _print( $dst, $hook_text ) if not -f $dst;
-    chmod 0755, $dst
+
+    unlink $dst;
+    symlink $driver, $dst or die "could not symlink '$driver' to '$dst'";
 }
 
 __DATA__
diff --git a/t/repo-specific-hooks.t b/t/repo-specific-hooks.t
index b68d958..88976ca 100755
--- a/t/repo-specific-hooks.t
+++ b/t/repo-specific-hooks.t
@@ -9,8 +9,9 @@ use Gitolite::Test;
 # test script for partial copy feature
 # ----------------------------------------------------------------------
 
-try "plan 99";
+try "plan 117";
 my $h = $ENV{HOME};
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
 
 try 'cd tsh_tempdir; mkdir -p local/hooks/repo-specific';
 
@@ -40,35 +41,66 @@ try "# Enable LOCAL_CODE and repo-specific-hooks
 confreset;confadd '
     repo foo
             RW+                 =   @all
-            option hook.post-receive =  first
 
     repo bar
             RW+                 =   @all
-            option hook.pre-receive =  first second
 
     repo baz
             RW+                 =   @all
-            option hook.post-receive =  first
-            option hook.post-update =  first second
 ';
 
-
-try "ADMIN_PUSH repo-specific-hooks; !/FATAL/" or die text();
+try "ADMIN_PUSH repo-specific-hooks-0; !/FATAL/" or die text();
 
 try "
     /Init.*empty.*foo\\.git/
     /Init.*empty.*bar\\.git/
+    /Init.*empty.*baz\\.git/
 ";
 
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-try "
-    ls $rb/foo.git/hooks/*;  ok;     /post-receive.h00-first/
-                                    !/post-receive.h01/
-    ls $rb/bar.git/hooks/*;  ok;     /pre-receive.h00-first/
-                                     /pre-receive.h01-second/
-    ls $rb/baz.git/hooks/*;  ok;     /post-receive.h00-first/
-                                     /post-update.h00-first/
-                                     /post-update.h01-second/
+my $failing_hook = "#!/bin/sh
+exit 1
+";
+
+# Place a existing hooks in repos
+put "$rb/foo.git/hooks/post-recieve", $failing_hook;
+put "$rb/bar.git/hooks/pre-recieve", $failing_hook;
+put "$rb/baz.git/hooks/post-update", $failing_hook;
+
+try "# Verify hooks
+    ls -l $rb/foo.git/hooks/*;  ok;     !/post-receive -. .*local/hooks/multi-hook-driver/
+    ls -l $rb/bar.git/hooks/*;  ok;     !/pre-receive -. .*local/hooks/multi-hook-driver/
+    ls -l $rb/baz.git/hooks/*;  ok;     !/post-update -. .*local/hooks/multi-hook-driver/
+";
+
+confreset;confadd '
+    repo foo
+            RW+                 =   @all
+            option hook.post-receive =  first
+
+    repo bar
+            RW+                 =   @all
+            option hook.pre-receive =  first second
+
+    repo baz
+            RW+                 =   @all
+            option hook.post-receive =  first
+            option hook.post-update =  first second
+';
+
+
+try "ADMIN_PUSH repo-specific-hooks-1; !/FATAL/" or die text();
+
+try "# Verify hooks
+    ls -l $rb/foo.git/hooks/*;  ok;     /post-receive.h00-first/
+                                       !/post-receive.h01/
+                                        /post-receive -. .*local/hooks/multi-hook-driver/
+    ls -l $rb/bar.git/hooks/*;  ok;     /pre-receive.h00-first/
+                                        /pre-receive.h01-second/
+                                        /pre-receive -. .*local/hooks/multi-hook-driver/
+    ls -l $rb/baz.git/hooks/*;  ok;     /post-receive.h00-first/
+                                        /post-update.h00-first/
+                                        /post-update.h01-second/
+                                        /post-update -. .*local/hooks/multi-hook-driver/
 ";
 
 try "

commit 6d71af741f5db17e4d9380c1be8dc0da0c2d77ad
Author: Damien NOZAY <damien.nozay at gmail.com>
Date:   Tue May 6 17:43:01 2014 -0700

    get ldap groups
    
    Add sample script for getting group membership from LDAP.
    based on ldapsearch.
    
    (committer added author name as comment in case of questions)

diff --git a/contrib/utils/ldap_groups.sh b/contrib/utils/ldap_groups.sh
new file mode 100755
index 0000000..0192565
--- /dev/null
+++ b/contrib/utils/ldap_groups.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# author: damien.nozay at gmail.com
+
+# Given a username,
+# Provides a space-separated list of groups that the user is a member of.
+#
+# see http://gitolite.com/gitolite/auth.html#ldap
+# GROUPLIST_PGM => /path/to/ldap_groups.sh
+
+ldap_groups() {
+    username=$1;
+    # this relies on openldap / pam_ldap to be configured properly on your
+    # system. my system allows anonymous search.
+    echo $(
+        ldapsearch -x -LLL "(&(objectClass=posixGroup)(memberUid=${username}))" cn \
+        | grep "^cn" \
+        | cut -d' ' -f2
+    );
+}
+
+ldap_groups $@

commit 14d75c65444aacc2a316847432997a79c894a5f8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 23 17:35:33 2014 +0530

    mirroring: allow 'nosync' option for slaves
    
    (people who look at the *code* will realise there's also a 'sync' option!)
    
    diff --git c/src/lib/Gitolite/Triggers/Mirroring.pm i/src/lib/Gitolite/Triggers/Mirroring.pm
    index 014408d..7d55692 100644
    --- c/src/lib/Gitolite/Triggers/Mirroring.pm
    +++ i/src/lib/Gitolite/Triggers/Mirroring.pm
    @@ -189,8 +189,17 @@ sub post_git {
         }
    
         sub slaves {
    -        my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.slaves.*" );
    -        my %out = map { $_ => 1 } map { split } values %$ref;
    +        my $repo = shift;
    +
    +        my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
    +        my %out = map { $_ => 'async' } map { split } values %$ref;
    +
    +        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.sync.*" );
    +        map { $out{$_} = 'sync' } map { split } values %$ref;
    +
    +        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.nosync.*" );
    +        map { $out{$_} = 'nosync' } map { split } values %$ref;
    +
             return %out;
         }
    
    @@ -222,7 +231,9 @@ sub push_to_slaves {
         delete $ENV{GL_USER};    # why?  see src/commands/mirror
    
         for my $s ( sort keys %slaves ) {
    -        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &");
    +        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $slaves{$s} eq 'async';
    +        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1")   if $slaves{$s} eq 'sync';
    +        _warn "manual mirror push pending for '$s'"                          if $slaves{$s} eq 'nosync';
         }
    
         $ENV{GL_USER} = $u;

diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 014408d..7d55692 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -189,8 +189,17 @@ sub post_git {
     }
 
     sub slaves {
-        my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.slaves.*" );
-        my %out = map { $_ => 1 } map { split } values %$ref;
+        my $repo = shift;
+
+        my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
+        my %out = map { $_ => 'async' } map { split } values %$ref;
+
+        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.sync.*" );
+        map { $out{$_} = 'sync' } map { split } values %$ref;
+
+        $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves\\.nosync.*" );
+        map { $out{$_} = 'nosync' } map { split } values %$ref;
+
         return %out;
     }
 
@@ -222,7 +231,9 @@ sub push_to_slaves {
     delete $ENV{GL_USER};    # why?  see src/commands/mirror
 
     for my $s ( sort keys %slaves ) {
-        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &");
+        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $slaves{$s} eq 'async';
+        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1")   if $slaves{$s} eq 'sync';
+        _warn "manual mirror push pending for '$s'"                          if $slaves{$s} eq 'nosync';
     }
 
     $ENV{GL_USER} = $u;

commit 0f8b2f57a9d3d465fe0caaeae45dd404957b0d36
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 23 11:25:45 2014 +0530

    minor backward compat breakage in specifying owner/desc,
    
    see https://groups.google.com/forum/#!topic/gitolite/uyoHYZ1Oyvg

diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index fdfbac9..986494b 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -124,12 +124,6 @@ sub owner_desc {
     # category = "whatever..."
     #   ->  config gitweb.category = whatever...
 
-    # older formats:
-    # repo = "some long description"
-    # repo "owner name" = "some long description"
-    #   ->  config gitweb.owner = owner name
-    #   ->  config gitweb.description = some long description
-
     for my $line (@$lines) {
         if ( $line =~ /^desc = (\S.*)/ ) {
             push @ret, "config gitweb.description = $1";
@@ -137,11 +131,6 @@ sub owner_desc {
             push @ret, "config gitweb.owner = $1";
         } elsif ( $line =~ /^category = (\S.*)/ ) {
             push @ret, "config gitweb.category = $1";
-        } elsif ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
-            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
-            push @ret, "repo $repo";
-            push @ret, "config gitweb.description = $desc";
-            push @ret, "config gitweb.owner = $owner" if $owner;
         } else {
             push @ret, $line;
         }

commit e300165e4883aed940362cd9c329b14b9b8d9de9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 23 06:42:24 2014 +0530

    add warning when ref supplied for R rule
    
    thanks to Tom Robinson; see
    https://groups.google.com/d/msg/gitolite/nz-vdyVVSkg/bT6FeBOdPgoJ

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 77cfa9f..0cf1ea5 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -113,6 +113,8 @@ sub parse_users {
 
 sub add_rule {
     my ( $perm, $ref, $user, $fname, $lnum ) = @_;
+    _warn "doesn't make sense to supply a ref ('$ref') for 'R' rule"
+      if $perm eq 'R' and $ref ne 'refs/.*';
     _warn "possible undeclared group '$user'"
       if $user =~ /^@/
       and not $groups{$user}

commit e492c03dcc3f3d73a22b006574ae4ab49c5ba564
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 17 14:54:11 2014 +0530

    allow strings as macro arguments
    
    thanks to Jason Donenfeld [1] as well as apologies for not being able to push
    this out till now!
    
    [1]: https://groups.google.com/forum/#!searchin/gitolite/macros/gitolite/3ypDcm9jTGU/PoVCoIVscaUJ

diff --git a/src/syntactic-sugar/macros b/src/syntactic-sugar/macros
index a0ddf90..a3493a4 100644
--- a/src/syntactic-sugar/macros
+++ b/src/syntactic-sugar/macros
@@ -25,7 +25,14 @@ sub sugar_script {
 
 sub expand {
     my $l = shift;
-    my ( $word, @arg ) = split ' ', $l;
+    my ( $word, @arg );
+
+    eval "require Text::ParseWords";
+    if ($@) {
+        ( $word, @arg ) = split ' ', $l;
+    } else {
+        ( $word, @arg ) = Text::ParseWords::shellwords($l);
+    }
     my $v = $macro{$word};
     $v =~ s/%(\d+)/$arg[$1-1] or die "macro '$word' needs $1 arguments at '$l'\n"/gem;
     return $v;

commit 5d1119c210ba930b7b315bf22f9075352c2a4760
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 16 19:32:00 2014 +0530

    (experimental) trace rules using access command

diff --git a/src/commands/access b/src/commands/access
index 7254bc6..e605444 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -1,4 +1,4 @@
-#!/usr/bin/perl
+#!/usr/bin/perl -s
 use strict;
 use warnings;
 
@@ -7,12 +7,14 @@ use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
 
+our ( $q, $s, $h );    # quiet, show, help
+
 =for usage
-Usage:  gitolite access [-q] <repo> <user> <perm> <ref>
+Usage:  gitolite access [-q|-s] <repo> <user> <perm> <ref>
 
 Print access rights for arguments given.  The string printed has the word
 DENIED in it if access was denied.  With '-q', returns only an exit code
-(shell truth, not perl truth -- 0 is success).
+(shell truth, not perl truth -- 0 is success).  For '-s', see below.
 
   - repo: mandatory
   - user: mandatory
@@ -24,15 +26,20 @@ Notes:
     The 'any' ref is special -- it ignores deny rules (see docs for what this
     means and exceptions).
 
-Batch mode: see src/triggers/post-compile/update-git-daemon-access-list for a
-good example that shows how to test several repos in one invocation.  This is
-orders of magnitude faster than running the command multiple times; you'll
-notice if you have more than a hundred or so repos.
+  - batch mode: see src/triggers/post-compile/update-git-daemon-access-list
+    for a good example that shows how to test several repos in one invocation.
+    This is orders of magnitude faster than running the command multiple
+    times; you'll notice if you have more than a hundred or so repos.
+
+  - '-s' shows the rules (conf file name, line number, and rule) that were
+    considered and how they fared.  Admin must add 'RULE_INFO => 1,' to the rc
+    file, then push the gitolite-admin repo (or 'gitolite compile').
+
+    WARNING: may not be compatible with some syntactic sugar helpers (like
+    'continuation-lines').
 =cut
 
-usage() if not @ARGV or $ARGV[0] eq '-h';
-my $quiet = 0;
-if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
+usage() if not @ARGV or $h;
 
 my ( $repo, $user, $aa, $ref ) = @ARGV;
 $aa  ||= '+';
@@ -46,19 +53,21 @@ if ( $repo ne '%' and $user ne '%' ) {
     # single repo, single user; no STDIN
     $ret = access( $repo, $user, $aa, $ref );
 
+    show() if $s;
+
     if ( $ret =~ /DENIED/ ) {
-        print "$ret\n" unless $quiet;
+        print "$ret\n" unless $q;
         exit 1;
     }
 
-    print "$ret\n" unless $quiet;
+    print "$ret\n" unless $q;
     exit 0;
 }
 
 $repo = '' if $repo eq '%';
 $user = '' if $user eq '%';
 
-_die "'-q' doesn't go with using a pipe" if $quiet;
+_die "'-q' and '-s' meaningless in pipe mode" if $q or $s;
 @ARGV = ();
 while (<>) {
     my @in = split;
@@ -67,3 +76,80 @@ while (<>) {
     $ret = access( $r, $u, $aa, $ref );
     print "$r\t$u\t$ret\n";
 }
+
+sub show {
+    die "rc variable RULE_INFO not set; see help text\n" unless $rc{RULE_INFO};
+
+    my $in = $rc{RULE_TRACE} or die "this should not happen!";
+
+    print STDERR "legend:";
+    print STDERR "
+    d => skipped deny rule due to ref unknown or 'any',
+    r => skipped due to refex not matching,
+    p => skipped due to perm (W, +, etc) not matching,
+    D => explicitly denied,
+    A => explicitly allowed,
+    F => denied due to fallthru (no rules matched)
+
+";
+
+    my %rule_info = read_ri($in);    # get rule info data for all traced rules
+                                     # this means conf filename, line number, and content of the line
+
+    # the rule-trace info is a set of pairs of a number plus a string.  Only
+    # the last character in a string is valid (and has meanings shown above).
+    # At the end there may be a final 'f'
+    my @in = split ' ', $in;
+    while (@in) {
+        $in = shift @in;
+        if ( $in =~ /^\d+$/ ) {
+            my $res = shift @in or die "this should not happen either!";
+            my $m = chop($res);
+            printf "  %s %20s:%-6s %s\n", $m,
+                                          $rule_info{$in}{fn},
+                                          $rule_info{$in}{ln},
+                                          $rule_info{$in}{cl};
+        } elsif ( $in eq 'F' ) {
+            printf "  %s %20s\n", $in, "(fallthru)";
+        } else {
+            die "and finally, this also should not happen!";
+        }
+    }
+    print "\n";
+}
+
+sub read_ri {
+    my %rules = map { $_ => 1 } $_[0] =~ /(\d+)/g;
+    # contains a series of rule numbers, each of which we must search in
+    # $GL_ADMIN_BASE/.gitolite/conf/rule_info
+
+    my %rule_info;
+    for ( slurp( $ENV{GL_ADMIN_BASE} . "/conf/rule_info" ) ) {
+        my ( $r, $f, $l ) = split ' ', $_;
+        next unless $rules{$r};
+        $rule_info{$r}{fn} = $f;
+        $rule_info{$r}{ln} = $l;
+        $rule_info{$r}{cl} = conf_lines( $f, $l );
+
+        # a wee bit of optimisation, in case the rule_info file is huge and
+        # what we want is up near the beginning
+        delete $rules{$r};
+        last unless %rules;
+    }
+    return %rule_info;
+}
+
+{
+    my %conf_lines;
+
+    sub conf_lines {
+        my ( $file, $line ) = @_;
+        $line--;
+
+        unless ( $conf_lines{$file} ) {
+            $conf_lines{$file} = [ slurp( $ENV{GL_ADMIN_BASE} . "/conf/$file" ) ];
+            chomp( @{ $conf_lines{$file} } );
+        }
+        return $conf_lines{$file}[$line];
+    }
+}
diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 00642cb..d8554ab 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -202,6 +202,7 @@ sub sort_u {
 
 sub cleanup_conf_line {
     my $line = shift;
+    return $line if $line =~ /^# \S+ \d+$/;
 
     # kill comments, but take care of "#" inside *simple* strings
     $line =~ s/^((".*?"|[^#"])*)#.*/$1/;
diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 303550c..05341b9 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -44,7 +44,9 @@ sub parse {
     my $lines = shift;
     trace( 3, scalar(@$lines) . " lines incoming" );
 
+    my ( $fname, $lnum );
     for my $line (@$lines) {
+        ( $fname, $lnum ) = ( $1, $2 ), next if $line =~ /^# (\S+) (\d+)$/;
         # user or repo groups
         if ( $line =~ /^(@\S+) = (.*)/ ) {
             add_to_group( $1, split( ' ', $2 ) );
@@ -57,7 +59,7 @@ sub parse {
 
             for my $ref (@refs) {
                 for my $user (@users) {
-                    add_rule( $perm, $ref, $user );
+                    add_rule( $perm, $ref, $user, $fname, $lnum );
                 }
             }
         } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index 8f934fa..26189f2 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -43,6 +43,7 @@ sub explode {
             incsub( $1, $2, $3, $subconf, $out );
         } else {
             # normal line, send it to the callback function
+            push @{$out}, "# $file $." if $rc{RULE_INFO};
             push @{$out}, $line;
         }
     }
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 47cda99..5d4766c 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -102,17 +102,24 @@ sub access {
     }
 
     trace( 3, scalar(@rules) . " rules found" );
+
+    $rc{RULE_TRACE} = '';
     for my $r (@rules) {
+        $rc{RULE_TRACE} .= " " . $r->[0] . " ";
+
         my $perm = $r->[1];
         my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
         trace( 3, "perm=$perm, refex=$refex" );
 
+        $rc{RULE_TRACE} .= "d";
         # skip 'deny' rules if the ref is not (yet) known
         next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
 
+        $rc{RULE_TRACE} .= "r";
         # rule matches if ref matches or ref is any (see gitolite-shell)
         next unless $ref =~ /^$refex/ or $ref eq 'any';
 
+        $rc{RULE_TRACE} .= "D";
         trace( 2, "DENIED by $refex" ) if $perm eq '-';
         return "$aa $safe_ref $repo $user DENIED by $refex" if $perm eq '-';
 
@@ -120,9 +127,16 @@ sub access {
         # any of these followed by "M".
         ( my $aaq = $aa ) =~ s/\+/\\+/;
         $aaq =~ s/M/.*M/;
+
+        $rc{RULE_TRACE} .= "A";
+
         # as far as *this* ref is concerned we're ok
         return $refex if ( $perm =~ /$aaq/ );
+
+        $rc{RULE_TRACE} .= "p";
     }
+    $rc{RULE_TRACE} .= " F";
+
     trace( 2, "DENIED by fallthru" );
     return "$aa $safe_ref $repo $user DENIED by fallthru";
 }
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 69484d3..77cfa9f 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -112,7 +112,7 @@ sub parse_users {
 }
 
 sub add_rule {
-    my ( $perm, $ref, $user ) = @_;
+    my ( $perm, $ref, $user, $fname, $lnum ) = @_;
     _warn "possible undeclared group '$user'"
       if $user =~ /^@/
       and not $groups{$user}
@@ -122,6 +122,7 @@ sub add_rule {
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
     $nextseq++;
+    store_rule_info( $nextseq, $fname, $lnum ) if $rc{RULE_INFO};
     for my $repo (@repolist) {
         push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
     }
@@ -251,6 +252,8 @@ sub parse_done {
     for my $ig ( sort keys %ignored ) {
         _warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
     }
+
+    close_rule_info() if $rc{RULE_INFO};
 }
 
 # ----------------------------------------------------------------------
@@ -386,5 +389,19 @@ sub inside_out {
     # %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]);
 }
 
+{
+    my $ri_fh = '';
+
+    sub store_rule_info {
+        $ri_fh = _open( ">", $rc{GL_ADMIN_BASE} . "/conf/rule_info" ) unless $ri_fh;
+        # $nextseq, $fname, $lnum
+        print $ri_fh join( "\t", @_ ) . "\n";
+    }
+
+    sub close_rule_info {
+        close $ri_fh or die "close rule_info file failed: $!";
+    }
+}
+
 1;
 

commit e2c4dc103cc5605b78f5966128ad2d2e98256855
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 14 07:22:47 2014 +0530

    info: learns -json option

diff --git a/src/commands/info b/src/commands/info
index b2bc3fc..3a2d463 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -10,50 +10,72 @@ use Gitolite::Common;
 use Gitolite::Conf::Load;
 
 =for args
-Usage:  gitolite info [-lc] [-ld] [<repo name pattern>]
+Usage:  gitolite info [-lc] [-ld] [-json] [<repo name pattern>]
 
 List all existing repos you can access, as well as repo name patterns you can
 create repos from (if any).
 
     '-lc'       lists creators as an additional field at the end.
     '-ld'       lists description as an additional field at the end.
+    '-json'     produce JSON output instead of normal output
 
 The optional pattern is an unanchored regex that will limit the repos
 searched, in both cases.  It might speed up things a little if you have more
 than a few thousand repos.
 =cut
 
-# these two are globals
-my ( $lc, $ld, $patt ) = args();
+# these are globals
+my ( $lc, $ld, $json, $patt ) = args();
+my %out;    # holds info to be json'd
 
 print_version();
 
 print_patterns();     # repos he can create for himself
 print_phy_repos();    # repos already created
-print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
+
+if ( $rc{SITE_INFO} ) {
+    $json
+      ? $out{SITE_INFO} = $rc{SITE_INFO}
+      : print "\n$rc{SITE_INFO}\n";
+}
+
+print JSON::to_json( \%out, { utf8 => 1, pretty => 1 } ) if $json;
 
 # ----------------------------------------------------------------------
 
 sub args {
-    my ( $lc, $ld, $patt ) = ( '', '', '' );
+    my ( $lc, $ld, $json, $patt ) = ( '', '', '', '' );
     my $help = '';
 
     GetOptions(
-        'lc' => \$lc,
-        'ld' => \$ld,
-        'h'  => \$help,
+        'lc'   => \$lc,
+        'ld'   => \$ld,
+        'json' => \$json,
+        'h'    => \$help,
     ) or usage();
 
     usage() if @ARGV > 1 or $help;
     $patt = shift @ARGV || '.';
 
-    return ( $lc, $ld, $patt );
+    require JSON if $json;
+
+    return ( $lc, $ld, $json, $patt );
 }
 
 sub print_version {
     chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
     my $gv = substr( `git --version`, 12 );
     $ENV{GL_USER} or _die "GL_USER not set";
+
+    if ($json) {
+        $out{GL_USER}          = $ENV{GL_USER};
+        $out{USER}             = ( $ENV{USER} || "httpd" ) . "\@$hn";
+        $out{gitolite_version} = version();
+        chomp( $out{git_version} = $gv );    # this thing has a newline at the end
+        return;
+    }
+
+    # normal output
     print "hello $ENV{GL_USER}, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
 }
 
@@ -100,6 +122,15 @@ sub listem {
         }
         $perm =~ s/\^//;
         next unless $perm =~ /\S/;
+
+        if ($json) {
+            $out{repos}{$repo}{creator}     = $creator if $lc;
+            $out{repos}{$repo}{description} = $desc    if $ld;
+            $out{repos}{$repo}{perms}       = _hash($perm);
+
+            next;
+        }
+
         print "$perm\t$repo";
         print "\t$creator" if $lc;
         print "\t$desc"    if $ld;
@@ -107,3 +138,8 @@ sub listem {
     }
 }
 
+sub _hash {
+    my $in = shift;
+    my %out = map { $_ => 1 } ( $in =~ /(\S)/g );
+    return \%out;
+}
diff --git a/t/info-json.t b/t/info-json.t
new file mode 100755
index 0000000..a78b79f
--- /dev/null
+++ b/t/info-json.t
@@ -0,0 +1,183 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Test;
+use JSON;
+
+# the info command
+# ----------------------------------------------------------------------
+
+try 'plan 162';
+
+try "## info";
+
+confreset;confadd '
+    @t1 = t1
+    repo    @t1
+        RW              =   u1
+        R               =   u2
+    repo    t2
+        RW  =               u2
+        R   =               u1
+    repo    t3
+        RW  =   u3
+        R   =   u4
+
+    repo foo/..*
+        C   =   u1
+        RW  =   CREATOR u3
+';
+
+try "ADMIN_PUSH info; !/FATAL/" or die text();
+try "
+                                        /Initialized.*empty.*t1.git/
+                                        /Initialized.*empty.*t2.git/
+                                        /Initialized.*empty.*t3.git/
+";
+
+my $href;   # semi-global (or at least file scoped lexical!)
+
+# testing for info -json is a bit unusual.  The actual tests are done within
+# this test script itself, and we send Tsh just enough for it to decide if
+# it's 'ok' or 'not ok' and print that.
+
+try "glt info u1 -json; ok";
+$href = from_json(text());
+try "## u1 test_gs";
+test_gs('u1');
+try "## u1";
+perm('foo/..*', 'r w C');
+perm('testing', 'R W c');
+perm('t1', 'R W c');
+perm('t2', 'R w c');
+perm('t3', 'r w c');
+
+try "## u2";
+try "glt info u2 -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('testing', 'R W c');
+perm('t1', 'R w c');
+perm('t2', 'R W c');
+perm('t3', 'r w c');
+
+try "## u3";
+try "glt info u3 -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'R W c');
+perm('testing', 'R W c');
+perm('t1', 'r w c');
+perm('t2', 'r w c');
+perm('t3', 'R W c');
+
+try "## u4";
+try "glt info u4 -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('testing', 'R W c');
+perm('t1', 'r w c');
+perm('t2', 'r w c');
+perm('t3', 'R w c');
+
+try "## u5";
+try "glt info u5 -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('testing', 'R W c');
+perm('t1', 'r w c');
+perm('t2', 'r w c');
+perm('t3', 'r w c');
+
+try "## u6";
+try "glt info u6 -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('testing', 'R W c');
+perm('t1', 'r w c');
+perm('t2', 'r w c');
+perm('t3', 'r w c');
+
+try "## ls-remote foo/one";
+try "glt ls-remote u1 file:///foo/one;   ok";
+
+try "## u1";
+try "glt info u1 -json; ok; !/creator..:/";
+$href = from_json(text());
+perm('foo/..*', 'r w C');
+perm('foo/one', 'R W c');
+test_creator('foo/one', 'u1', 'undef');
+
+try "## u2";
+try "glt info u2 -json; ok; !/creator..:/";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('foo/one', 'r w c');
+test_creator('foo/one', 'u1', 'undef');
+
+try "## u3";
+try "glt info u3 -json; ok; !/creator..:/";
+$href = from_json(text());
+perm('foo/..*', 'R W c');
+perm('foo/one', 'R W c');
+test_creator('foo/one', 'u1', 'undef');
+
+try("## with -lc now");
+
+try "## u1";
+try "glt info u1 -lc -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w C');
+perm('foo/one', 'R W c');
+test_creator('foo/one', 'u1', 1);
+
+try "## u2";
+try "glt info u2 -lc -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'r w c');
+perm('foo/one', 'r w c');
+test_creator('foo/one', 'u1', 'undef');
+
+try "## u3";
+try "glt info u3 -lc -json; ok";
+$href = from_json(text());
+perm('foo/..*', 'R W c');
+perm('foo/one', 'R W c');
+test_creator('foo/one', 'u1', 1);
+
+# ----------------------------------------------------------------------
+
+# test perms given repo and expected perms.  (lowercase r/w/c means NOT
+# expected, uppercase means expected)
+sub perm {
+    my ($repo, $aa) = @_;
+    for my $aa1 (split ' ', $aa) {
+        my $exp = 1;
+        if ($aa1 =~ /[a-z]/) {
+            $exp = 'undef';     # we can't use 0, though I'd like to
+            $aa1 = uc($aa1);
+        }
+        my $perm = $href->{repos}{$repo}{perms}{$aa1} || 'undef';
+        try 'perl $_ = "' . $perm  . '"; /' . $exp . '/';
+    }
+}
+
+# test versions in greeting string
+sub test_gs {
+    my $glu = shift;
+    my $res = ( $href->{GL_USER} eq $glu ? 1 : 'undef' );
+    try 'perl $_ = "' . $res  . '"; /1/';
+    $res = ( $href->{gitolite_version} =~ /^v3.[5-9]/ ? 1 : 'undef' );
+    try 'perl $_ = "' . $res  . '"; /1/';
+    $res = ( $href->{git_version} =~ /^1.[6-9]/ ? 1 : 'undef' );
+    try 'perl $_ = "' . $res  . '"; /1/';
+}
+
+# test creator
+sub test_creator {
+    my ($r, $c, $exp) = @_;
+    my $res = ( ($href->{repos}{$r}{creator} || '') eq $c ? 1 : 'undef' );
+    try 'perl $_ = "' . $res  . '"; /' . $exp . '/';
+}

commit 17459c1a83606bc5bc06bdb8c27099cec15ab41b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 13 17:59:29 2014 +0530

    SAFE_CONFIG fixup should happen in core...
    
    otherwise it doesn't help 'option' lines, nor gitolite git-config queries
    (which see the "safe" value, which is mostly useless).

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 21c060c..303550c 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -68,6 +68,9 @@ sub parse {
             my @matched = grep { $key =~ /^$_$/i } @validkeys;
             _die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
             _die "bad config value '$value'" if $value =~ $UNSAFE_PATT;
+            while ( my ( $mk, $mv ) = each %{ $rc{SAFE_CONFIG} } ) {
+                $value =~ s/%$mk/$mv/g;
+            }
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
             trace( 3, $line );
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 106791a..a58a85d 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -49,9 +49,6 @@ sub fixup_config {
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
-            while ( my ( $mk, $mv ) = each %{ $rc{SAFE_CONFIG} } ) {
-                $value =~ s/%$mk/$mv/g;
-            }
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {
             system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );

commit e3c85adaa2f284e2838c8f0677780bc493c3e60a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 12 20:18:52 2014 +0530

    git 1.9.0 changed the rules on file:// urls
    
    used to allow "file://foo" to mean "foo in current directory", but now it's
    not allowed!

diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index 32beb43..291eace 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -52,7 +52,7 @@ try "
     DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/
     DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
 
-    DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
+    DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file:///gitolite-admin/
     DEF CHECK_SETUP = CS_1; git log; ok; /fa7564c1b903ea3dce49314753f25b34b9e0cea0/
 
     DEF CLONE = glt clone %1 file:///%2
@@ -71,7 +71,7 @@ try "
 
     # clone admin repo
     cd tsh_tempdir
-    glt clone admin --progress file://gitolite-admin
+    glt clone admin --progress file:///gitolite-admin
     cd gitolite-admin
 " or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
 
diff --git a/t/0-me-first.t b/t/0-me-first.t
index 47784ae..12668f6 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -31,18 +31,18 @@ try "
 
     # basic clone
     cd ..
-    glt clone u1 file://aa u1aa;    ok;     /Cloning into 'u1aa'.../
+    glt clone u1 file:///aa u1aa;    ok;     /Cloning into 'u1aa'.../
                                             /warning: You appear to have cloned an empty repository/
     [ -d u1aa ];                    ok
 
     # basic clone deny
-    glt clone u4 file://aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
+    glt clone u4 file:///aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
     [ -d u4aa ];                    !ok
 
     # basic push
     cd u1aa;                        ok
     tc z-507;                       ok;     /master .root-commit. 7cf7624. z-507/
-    glt push u1 origin HEAD;        ok;     /To file://aa/
+    glt push u1 origin HEAD;        ok;     /To file:///aa/
                                             /\\[new branch\\] *HEAD -> master/
 
     # basic rewind
@@ -61,7 +61,7 @@ try "
 
     # basic rewind deny
     cd ..
-    glt clone u2 file://aa u2aa;    ok;     /Cloning into 'u2aa'.../
+    glt clone u2 file:///aa u2aa;    ok;     /Cloning into 'u2aa'.../
     cd u2aa;                        ok
     tc g-776 g-777 g-778;           ok;     /master 9cbc181. g-778/
     glt push u2 origin HEAD;        ok;     /284951d..9cbc181  HEAD -> master/
diff --git a/t/branch-perms.t b/t/branch-perms.t
index 64b2fcb..e59baea 100755
--- a/t/branch-perms.t
+++ b/t/branch-perms.t
@@ -31,11 +31,11 @@ try "ADMIN_PUSH set1; !/FATAL/" or die text();
 
 try "
     cd ..;                          ok
-    glt clone u1 file://aa;         ok
+    glt clone u1 file:///aa;         ok
     cd aa;                          ok
     tc l-995 l-996 l-997 l-998 l-999 l-1000 l-1001 l-1002 l-1003;
                                     ok;     /master a788db9. l-1003/
-    glt push u1 origin HEAD;        ok;     /To file://aa/
+    glt push u1 origin HEAD;        ok;     /To file:///aa/
                                             /\\* \\[new branch\\]      HEAD -> master/
 
     git branch dev;                 ok
@@ -49,27 +49,27 @@ try "
     # u2 rewind master fail
     git reset --hard HEAD^;         ok;     /HEAD is now at 65d5f4a l-1002/
     tc s-361;                       ok;     /master b331651. s-361/
-    glt push u2 file://aa +master;  !ok;    reject
+    glt push u2 file:///aa +master;  !ok;    reject
                                             /\\+ refs/heads/master aa u2 DENIED by fallthru/
 
     # u3 rewind master succeed
     git reset --hard HEAD^;         ok
     tc m-508;                       ok
-    glt push u3 file://aa +master;  ok;     /\\+ .* master -> master \\(forced update\\)/
+    glt push u3 file:///aa +master;  ok;     /\\+ .* master -> master \\(forced update\\)/
 
     # u4 push master succeed
     tc f-526;                       ok;
-    glt push u4 file://aa master;   ok;     /master -> master/
+    glt push u4 file:///aa master;   ok;     /master -> master/
 
     # u4 rewind master fail
     git reset --hard HEAD^;         ok;
-    glt push u4 file://aa +master;  !ok;    /\\+ refs/heads/master aa u4 DENIED by fallthru/
+    glt push u4 file:///aa +master;  !ok;    /\\+ refs/heads/master aa u4 DENIED by fallthru/
 
     # u3 and u4 / dev foo -- all 4 fail
-    glt push u3 file://aa dev;      !ok;    /W refs/heads/dev aa u3 DENIED by fallthru/
-    glt push u4 file://aa dev;      !ok;    /W refs/heads/dev aa u4 DENIED by fallthru/
-    glt push u3 file://aa foo;      !ok;    /W refs/heads/foo aa u3 DENIED by fallthru/
-    glt push u4 file://aa foo;      !ok;    /W refs/heads/foo aa u4 DENIED by fallthru/
+    glt push u3 file:///aa dev;      !ok;    /W refs/heads/dev aa u3 DENIED by fallthru/
+    glt push u4 file:///aa dev;      !ok;    /W refs/heads/dev aa u4 DENIED by fallthru/
+    glt push u3 file:///aa foo;      !ok;    /W refs/heads/foo aa u3 DENIED by fallthru/
+    glt push u4 file:///aa foo;      !ok;    /W refs/heads/foo aa u4 DENIED by fallthru/
 
     # clean up for next set
     glt push u1 -f origin master dev foo
@@ -77,14 +77,14 @@ try "
 
     # u5 push master fail
     tc l-417;                       ok
-    glt push u5 file://aa master;   !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
+    glt push u5 file:///aa master;   !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
 
     # u5 rewind dev succeed
-    glt push u5 file://aa +dev^:dev
+    glt push u5 file:///aa +dev^:dev
                                     ok;     /\\+ .* dev\\^ -> dev \\(forced update\\)/
 
     # u5 rewind foo fail
-    glt push u5 file://aa +foo^:foo
+    glt push u5 file:///aa +foo^:foo
                                     !ok;    /\\+ refs/heads/foo aa u5 DENIED by fallthru/
 
     # u5 tries to push foo; succeeds
@@ -92,7 +92,7 @@ try "
 
     # u5 push foo succeed
     tc e-530;                       ok;
-    glt push u5 file://aa foo;      ok;     /foo -> foo/
+    glt push u5 file:///aa foo;      ok;     /foo -> foo/
 
     # u1 delete branch dev succeed
     glt push u1 origin :dev;        ok;     / - \\[deleted\\] *dev/
@@ -117,6 +117,6 @@ try "
     glt push u1 origin :dev;        !ok;    /D refs/heads/dev aa u1 DENIED by fallthru/
 
     # u4 delete branch dev succeed
-    glt push u4 file://aa :dev;     ok;     / - \\[deleted\\] *dev/
+    glt push u4 file:///aa :dev;     ok;     / - \\[deleted\\] *dev/
 
 ";

commit c0e36b3e7f11bb67397d10f771e8b13e63bb605d
Author: milki <milki at rescomp.berkeley.edu>
Date:   Wed Apr 9 11:21:36 2014 -0700

    Test for repo-specific-hooks-trigger

diff --git a/t/repo-specific-hooks.t b/t/repo-specific-hooks.t
new file mode 100755
index 0000000..b68d958
--- /dev/null
+++ b/t/repo-specific-hooks.t
@@ -0,0 +1,178 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Test;
+
+# test script for partial copy feature
+# ----------------------------------------------------------------------
+
+try "plan 99";
+my $h = $ENV{HOME};
+
+try 'cd tsh_tempdir; mkdir -p local/hooks/repo-specific';
+
+foreach my $h (qw/first second/) {
+    put "local/hooks/repo-specific/$h", "#!/bin/sh
+echo \$0
+if [ \$# -ne 0 ]; then
+    echo \$0 has args: \$@
+else
+    echo \$0 has stdin: `cat`
+fi
+";
+}
+try 'chmod +x local/hooks/repo-specific/*';
+
+try 'pwd';
+my $tempdir = join("\n", sort (lines()));
+try 'cd gitolite-admin';
+
+try "# Enable LOCAL_CODE and repo-specific-hooks
+    cat $h/.gitolite.rc
+    perl s/# 'repo-specific-hooks'/'repo-specific-hooks'/
+    perl s%# LOCAL_CODE%LOCAL_CODE => '$tempdir/local', #%
+    put $h/.gitolite.rc
+";
+
+confreset;confadd '
+    repo foo
+            RW+                 =   @all
+            option hook.post-receive =  first
+
+    repo bar
+            RW+                 =   @all
+            option hook.pre-receive =  first second
+
+    repo baz
+            RW+                 =   @all
+            option hook.post-receive =  first
+            option hook.post-update =  first second
+';
+
+
+try "ADMIN_PUSH repo-specific-hooks; !/FATAL/" or die text();
+
+try "
+    /Init.*empty.*foo\\.git/
+    /Init.*empty.*bar\\.git/
+";
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+try "
+    ls $rb/foo.git/hooks/*;  ok;     /post-receive.h00-first/
+                                    !/post-receive.h01/
+    ls $rb/bar.git/hooks/*;  ok;     /pre-receive.h00-first/
+                                     /pre-receive.h01-second/
+    ls $rb/baz.git/hooks/*;  ok;     /post-receive.h00-first/
+                                     /post-update.h00-first/
+                                     /post-update.h01-second/
+";
+
+try "
+    cd ..
+
+    # Single hook still works
+    [ -d foo ];            !ok;
+    CLONE admin foo;        ok; /empty/; /cloned/
+    cd foo
+    tc a1;                  ok; /ee47f8b/
+    PUSH admin master;      ok; /new.*master -. master/
+                                /hooks/post-receive.h00-first/
+                                !/post-receive.*has args:/
+                                /post-receive.h00-first has stdin: 0000000000000000000000000000000000000000 ee47f8b6be2160ad1a3f69c97a0cb3d488e6657e refs/heads/master/
+
+    cd ..
+
+    # Multiple hooks fired
+    [ -d bar ];            !ok;
+    CLONE admin bar;        ok; /empty/; /cloned/
+    cd bar
+    tc a2;                  ok; /cfc8561/
+    PUSH admin master;      ok; /new.*master -. master/
+                                /hooks/pre-receive.h00-first/
+                                !/hooks/pre-recieve.*has args:/
+                                /hooks/pre-receive.h00-first has stdin: 0000000000000000000000000000000000000000 cfc8561c7827a8b94df6c5dad156383d4cb210f5 refs/heads/master/
+                                /hooks/pre-receive.h01-second/
+                                !/hooks/pre-receive.h01.*has args:/
+                                /hooks/pre-receive.h01-second has stdin: 0000000000000000000000000000000000000000 cfc8561c7827a8b94df6c5dad156383d4cb210f5 refs/heads/master/
+
+    cd ..
+
+    # Post-update has stdin instead of arguments
+    [ -d baz ];            !ok;
+    CLONE admin baz;        ok; /empty/; /cloned/
+    cd baz
+    tc a3;                  ok; /2863617/
+    PUSH admin master;      ok; /new.*master -. master/
+                                /hooks/post-receive.h00-first/
+                                !/hooks/post-receive.h00.*has args:/
+                                /hooks/post-receive.h00-first has stdin: 0000000000000000000000000000000000000000 28636171ae703f42fb17c312c6b6a078ed07a2cd refs/heads/master/
+                                /hooks/post-update.h00-first/
+                                /hooks/post-update.h00-first has args: refs/heads/master/
+                                !/hooks/post-update.h00.*has stdin:/
+                                /hooks/post-update.h01-second/
+                                /hooks/post-update.h01-second has args: refs/heads/master/
+                                !/hooks/post-update.h01.*has stdin:/
+";
+
+# Verify hooks are removed properly
+
+confreset;confadd '
+    repo foo
+            RW+                 =   @all
+            option hook.post-receive =
+
+    repo bar
+            RW+                 =   @all
+            option hook.pre-receive =  second
+
+    repo baz
+            RW+                 =   @all
+            option hook.post-receive =
+            option hook.post-update =  second
+';
+
+try "ADMIN_PUSH repo-specific-hooks-02; !/FATAL/" or die text();
+
+try "
+    ls $rb/foo.git/hooks/*;  ok;    !/post-receive/
+    ls $rb/bar.git/hooks/*;  ok;    !/pre-receive.*first/
+                                     /pre-receive.h00-second/
+    ls $rb/baz.git/hooks/*;  ok;    !/post-receive/
+                                    !/post-update.*first/
+                                     /post-update.h00-second/
+";
+
+try "
+    cd ..
+
+    # Foo has no hooks
+    cd foo
+    tc b1;                  ok; /7ef69de/
+    PUSH admin master;      ok; /master -. master/
+                                !/hooks/post-receive/
+
+    cd ..
+
+    # Bar only has the second hook
+    cd bar
+    tc b2;                  ok; /cc7808f/
+    PUSH admin master;      ok; /master -. master/
+                                /hooks/pre-receive.h00-second/
+                                !/hooks/pre-receive.*has args:/
+                                /hooks/pre-receive.h00-second has stdin: 0000000000000000000000000000000000000000 cc7808f77c7c7d705f82dc54dc3152146175768f refs/heads/master/
+
+    cd ..
+
+    # Baz has no post-receive and keeps the second hook for post-update
+    cd baz
+    tc b3;                  ok; /8d20101/
+    PUSH admin master;      ok; /master -. master/
+                                !/hooks/post-receive.*/
+                                /hooks/post-update.h00-second/
+                                /hooks/post-update.h00-second has args: refs/heads/master/
+                                !/hooks/post-update.*has stdin/
+";

commit 9d3eaef904a98d971d16e5823c58ac4be8e8b901
Author: milki <milki at rescomp.berkeley.edu>
Date:   Thu Apr 3 00:12:48 2014 -0700

    repo-specific hook: allow specifying more than one
    
    hooks are specified as a space delimited list for each "git" hook.

diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
index 79e87e7..1bc4bc5 100755
--- a/src/triggers/repo-specific-hooks
+++ b/src/triggers/repo-specific-hooks
@@ -4,10 +4,6 @@ use warnings;
 
 # setup repo-specific hooks
 
-# code is too long, but if you take out all the error/safety/sanity checks,
-# it's basically just creating a symlink in <repo.git>/hooks pointing to some
-# file inside $rc{LOCAL_CODE}/hooks/repo-specific
-
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
@@ -17,11 +13,19 @@ _die "repo-specific-hooks: '$rc{LOCAL_CODE}' does not exist or is not a director
 
 _chdir( $ENV{GL_REPO_BASE} );
 
- at ARGV = ("gitolite list-phy-repos | gitolite git-config -r % gitolite-options\\.hook\\. |");
+ at ARGV = ("gitolite list-phy-repos | gitolite git-config -ev -r % gitolite-options\\.hook\\. |");
+
+# Hook Driver
+my $hook_text = '';
+{
+    local $/ = undef;
+    $hook_text = <DATA>;
+}
 
 while (<>) {
     chomp;
-    my ( $repo, $hook, $code ) = split /\t/, $_;
+    my ( $repo, $hook, $codes ) = split /\t/, $_;
+    $codes ||= '';
 
     # we don't allow fiddling with the admin repo
     if ( $repo eq 'gitolite-admin' ) {
@@ -37,18 +41,51 @@ while (<>) {
         next;
     }
 
-    if ( $code =~ m(^/|\.\.) ) {
-        _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
-        next;
-    }
+    my @codes = split / /, $codes;
+    unlink( glob("$repo.git/hooks/$hook.*") );
 
-    my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
-    my $dst = "$repo.git/hooks/$hook";
-    unless ( -x $src ) {
-        _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
-        next;
+    my $counter = "h00";
+    foreach my $code (@codes) {
+        if ( $code =~ m(^/|\.\.) ) {
+            _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
+            next;
+        }
+
+        my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
+        my $dst = "$repo.git/hooks/$hook.$counter-$code";
+        unless ( -x $src ) {
+            _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
+            next;
+        }
+        unlink $dst;
+        symlink $src, $dst or _warn "could not symlink '$src' to '$dst'";
+        $counter++;
+
+        # no sanity checks for multiple overwrites of the same hook
     }
-    unlink $dst;
-    symlink $src, $dst or _warn "could not symlink '$src' to '$dst'";
-    # no sanity checks for multiple overwrites of the same hook
+    my $dst = "$repo.git/hooks/$hook";
+    _print( $dst, $hook_text ) if not -f $dst;
+    chmod 0755, $dst
 }
+
+__DATA__
+#/bin/sh
+
+# Determine what input the hook needs
+# post-update takes args, pre/post-receive take stdin
+type=args
+stdin=''
+[ $0 != hooks/post-update ] && {
+    type=stdin
+    stdin=`cat`
+}
+
+for h in $0.*; do
+    [ -x $h ] || continue
+    if [ $type = args ]
+    then
+        $h $@
+    else
+        echo "$stdin" | $h
+    fi
+done

commit ee2421d9208b7e35ee226f1e13ff8257f904f73e
Author: Marcos Santos <msantos.dev at gmail.com>
Date:   Wed Apr 9 23:47:41 2014 +0100

    Allow users with "R" access to run 'desc' in read mode
    
    (i.e., read a repo's description using the "desc" command)

diff --git a/src/commands/desc b/src/commands/desc
index 4fa3060..f096012 100755
--- a/src/commands/desc
+++ b/src/commands/desc
@@ -17,9 +17,20 @@ repo=$1; shift
 # this shell script takes arguments that are completely under the user's
 # control, so make sure you quote those suckers!
 
+descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
+
+# Allow users with "R" access to read the repo's description.
+if [ -z "$1" ]
+then
+    gitolite access -q "$repo" $GL_USER R any || die You are not authorised
+    [ -r "$descfile" ] && cat "$descfile"
+    exit 0
+fi
+
 # kernel.org needs 'desc' to be available to people who have "RW" or above,
 # not just the "creator".  In fact they need it for non-wild repos so there
 # *is* no creator.
+
 if gitolite query-rc -q WRITER_CAN_UPDATE_DESC
 then
     gitolite access -q "$repo" $GL_USER W any || die You are not authorised
@@ -27,17 +38,4 @@ else
     gitolite creator "$repo" $GL_USER || die You are not authorised
 fi
 
-# if it passes, $repo is a valid repo name so it is known to contain only sane
-# characters.  This is because 'gitolite creator' return true only if there
-# *is* a repo of that name and it has a gl-creator file that contains the same
-# text as $GL_USER.
-
-descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
-
-if [ -z "$1" ]
-then
-    [ -r "$descfile" ] && cat "$descfile"
-    exit 0
-fi
-
 echo "$*" > "$descfile"

commit b3d4e7843a0bfdafc35dfb5811cf2d2771c6a2b6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 9 09:56:06 2014 +0530

    git-config learned '-ev' option to print empty-valued keys also

diff --git a/src/commands/git-config b/src/commands/git-config
index d996575..94211de 100755
--- a/src/commands/git-config
+++ b/src/commands/git-config
@@ -17,32 +17,43 @@ key, or, if '-r' is supplied, a regex that is applied to all available keys.
     -q          exit code only (shell truth; 0 is success)
     -n          suppress trailing newline when used as key (not pattern)
     -r          treat key as regex pattern (unanchored)
+    -ev         print keys with empty values also (see below)
 
 Examples:
     gitolite git-config repo gitweb.owner
     gitolite git-config -q repo gitweb.owner
     gitolite git-config -r repo gitweb
 
-When the key is treated as a pattern, prints:
+Notes:
 
-    reponame<tab>key<tab>value<newline>
+1.  When the key is treated as a pattern, prints:
 
-Otherwise the output is just the value.
+        reponame<tab>key<tab>value<newline>
 
-Finally, see the advanced use section of 'gitolite access -h' -- you can do
-something similar here also:
+    Otherwise the output is just the value.
 
-    gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
+2.  By default, keys with empty values (specified as "" in the conf file) are
+    treated as non-existant.  Using '-ev' will print those keys also.  Note
+    that this only makes sense when the key is treated as a pattern, where
+    such keys are printed as:
+
+        reponame<tab>key<tab><newline>
+
+3.  Finally, see the advanced use section of 'gitolite access -h' -- you can
+    do something similar here also:
+
+        gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
 =cut
 
 usage() if not @ARGV;
 
-my ( $help, $nonl, $quiet, $regex ) = (0) x 4;
+my ( $help, $nonl, $quiet, $regex, $ev ) = (0) x 5;
 GetOptions(
-    'n' => \$nonl,
-    'q' => \$quiet,
-    'r' => \$regex,
-    'h' => \$help,
+    'n'  => \$nonl,
+    'q'  => \$quiet,
+    'r'  => \$regex,
+    'h'  => \$help,
+    'ev' => \$ev,
 ) or usage();
 
 my ( $repo, $key ) = @ARGV;
@@ -54,7 +65,7 @@ if ( $repo ne '%' and $key ne '%' ) {
     # single repo, single key; no STDIN
     $key = "^\Q$key\E\$" unless $regex;
 
-    $ret = git_config( $repo, $key );
+    $ret = git_config( $repo, $key, $ev );
 
     # if the key is not a regex, it should match at most one item
     _die "found more than one entry for '$key'" if not $regex and scalar( keys %$ret ) > 1;
@@ -80,7 +91,7 @@ while (<>) {
     my $r  = $repo || shift @in;
     my $k  = $key || shift @in;
     $k = "^\Q$k\E\$" unless $regex;
-    $ret = git_config( $r, $k );
+    $ret = git_config( $r, $k, $ev );
     next unless %$ret;
     map { print "$r\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret;
 }

commit 2893b148f806636f958c46703d70815314921bc2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 4 18:49:45 2014 +0530

    expand-deny-messages: trigger to show more detail on errors

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 3d1af57..61e0363 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -400,6 +400,9 @@ BEGIN {
 
     refex-expr              ACCESS_2        RefexExpr::access_2
 
+    expand-deny-messages    ACCESS_1        .
+    expand-deny-messages    ACCESS_2        .
+
     RepoUmask               PRE_GIT         ::
     RepoUmask               POST_CREATE     ::
 
@@ -565,6 +568,9 @@ __DATA__
             # set default roles from lines like 'option default.roles-1 = ...', etc.
             # 'set-default-roles',
 
+            # show more detailed messages on deny
+            # 'expand-deny-messages',
+
         # system admin stuff
 
             # enable mirroring (don't forget to set the HOSTNAME too!)
diff --git a/src/triggers/expand-deny-messages b/src/triggers/expand-deny-messages
new file mode 100755
index 0000000..78d138d
--- /dev/null
+++ b/src/triggers/expand-deny-messages
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# program name: expand-deny-messages
+
+# DOCUMENTATION IS AT THE BOTTOM OF THIS FILE; PLEASE READ
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+
+my %attempted_access = (
+    # see triggers.html
+    'ACCESS_1' => {
+        'R' => 'Repo read',
+        'W' => 'Repo write',
+    },
+    'ACCESS_2' => {
+        'W' => "Fast forward push",
+        '+' => "Rewind push branch or overwrite tag",
+        'C' => "Create ref",
+        'D' => "Delete ref",
+    }
+);
+
+# env var to disable is set?
+exit 0 if $ENV{GL_OPTION_EDM_DISABLE};
+
+# argument 1
+my $a12 = shift;    # ACCESS_1 or ACCESS_2
+exit 0 if $a12 !~ /^ACCESS_[12]$/;    # shouldn't happen; error in rc file?
+
+# the rest of the arguments
+my ( $repo, $user, $aa, $ref, $msg, $oldsha, $newsha ) = @ARGV;
+
+# we're only interested in deny messages
+exit 0 if $msg !~ /DENIED/;
+
+print STDERR "\nFATAL -- ACCESS DENIED\n";
+
+_info( "Repo",      $repo );
+_info( "User",      $user );
+_info( "Stage",     ( $a12 eq 'ACCESS_1' ? "Before git was called" : "From git's update hook" ) );
+_info( "Ref",       _ref($ref) ) if $a12 eq 'ACCESS_2';
+_info( "Operation", _op( $a12, $aa, $oldsha, $newsha ) );
+
+print STDERR "\n";
+print STDERR "$ENV{GL_OPTION_EDM_EXTRA_INFO}\n\n" if $ENV{GL_OPTION_EDM_EXTRA_INFO};
+
+# ------------------------------------------------------------------------
+
+sub _ref {
+    my $r = shift;
+    return "VREF '$r'"   if $r =~ s(^VREF/)();
+    return "Branch '$r'" if $r =~ s(^refs/heads/)();
+    return "Tag '$r'"    if $r =~ s(^refs/tags/)();
+    return "Non-standard ref '$r'";
+}
+
+sub _info {
+    printf STDERR "%-14s  %-60s\n", @_;
+}
+
+sub _op {
+    my ( $a12, $aa, $oldsha, $newsha ) = @_;
+
+    # first remove the M part and save the text for later addition if needed
+    my $merge = ( $aa =~ s/M// ? " with merge commit" : "" );
+
+    # next, the attempted access is modified to reflect the actual operation being
+    # attempted.  NOTE: this no longer necessarily reflects what the gitolite log
+    # file stores; it's more granular and truly distinguishes a branch create from
+    # an ff push, etc.  Could help when user typos a branch name I suppose
+    $aa = 'C' if $oldsha and $oldsha eq '0' x 40;
+    $aa = 'D' if $newsha and $newsha eq '0' x 40;
+
+    # then we map it, add merge text if any
+    my $op = $attempted_access{$a12}{$aa} || "Unknown operation '$aa'";
+    $op .= $merge;
+
+    return $op;
+}
+
+__END__
+
+ENABLING THE FEATURE
+--------------------
+
+To enable this feature, uncomment the line in the rc file if your gitolite was
+installed recently enough.  Otherwise you will need to add these lines to the
+end of your rc file, just before the "%RC" block ends:
+
+    ACCESS_1 => [
+        'expand-deny-messages',
+    ],
+
+    ACCESS_2 => [
+        'expand-deny-messages',
+    ],
+
+Please don't miss the trailing commas!
+
+DISABLING IT FOR SPECIFIC REPOS
+-------------------------------
+
+Once it is enabled at the rc file level, if you wish to disable it for
+specific repositories just add a line like this to those repos:
+
+        option ENV.EDM_DISABLE = 1
+
+Or you can also disable it for all repos, then enable it for some:
+
+    repo @all
+        option ENV.EDM_DISABLE = 1
+
+    # ... then later ...
+
+    repo foo bar @baz
+        option ENV.EDM_DISABLE = 0
+
+(options.html[1] and pages linked from it will explain how that works).
+
+[1]: http://gitolite.com/gitolite/options.html
+
+# SUPPLYING EXTRA INFORMATION
+# ---------------------------
+
+You can also supply some extra information to be printed, by adding a line
+like this to each repository in the gitolite.conf file:
+
+        option ENV.EDM_EXTRA_INFO = "please contact alice at example.com"
+
+You could of course add it under a "repo @all" section if you like.

commit d7d987dd903628031ec405b6ba0c22a5ed996f8b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 28 07:28:53 2014 +0530

    avoid triggering ACCESS_2 on fallthru

diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index f6b3f1b..32cd6e0 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -87,10 +87,12 @@ sub check_vref {
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+    if ( $ret =~ /by fallthru/ ) {
+        trace( 3, "remember, fallthru is success here!" );
+        return;
+    }
     trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
-    _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
-      if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
-    trace( 3, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
+    _die "$ret" . ( $deny_message ? "\n$deny_message" : '' ) if $ret =~ /DENIED/;
 }
 
 {

commit 90de6c84bf14bed0535228cfb745e603375e17b8
Author: Lars Gullik Bjønnes <larsbj at gullik.org>
Date:   Wed Mar 26 02:38:21 2014 +0100

    ssh-authkeys-shell-users: remove unused "use File::Temp"

diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
index 176e450..f516269 100755
--- a/src/triggers/post-compile/ssh-authkeys-shell-users
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -2,8 +2,6 @@
 use strict;
 use warnings;
 
-use File::Temp qw(tempfile);
-
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;

commit b23aed9c6d1786bec3617d89db0ad0c9261ff2dc
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 20 05:55:54 2014 +0530

    git-annex support, finally in master!

diff --git a/src/commands/git-annex-shell b/src/commands/git-annex-shell
new file mode 100755
index 0000000..17a5f26
--- /dev/null
+++ b/src/commands/git-annex-shell
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+# This command requires unrestricted arguments, so add it to the ENABLE list
+# like this:
+#   'git-annex-shell ua',
+
+# This requires git-annex version 20111016 or newer. Older versions won't
+# be secure.
+
+use strict;
+use warnings;
+
+# ignore @ARGV and look at the original unmodified command
+my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
+
+# Expect commands like:
+#   git-annex-shell 'configlist' '/~/repo'
+#   git-annex-shell 'sendkey' '/~/repo' 'key'
+# The parameters are always single quoted, and the repo path is always
+# the second parameter.
+# Further parameters are not validated here (see below).
+die "bad git-annex-shell command: $cmd"
+  unless $cmd =~ m#^(git-annex-shell '\w+' ')/\~/([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
+my $start = $1;
+my $repo  = $2;
+my $end   = $3;
+die "I dont like some of the characters in $repo\n" unless $repo =~ $Gitolite::Rc::REPONAME_PATT;
+die "I dont like absolute paths in $cmd\n" if $repo =~ /^\//;
+die "I dont like '..' paths in $cmd\n"     if $repo =~ /\.\./;
+
+# Modify $cmd, fixing up the path to the repo to include GL_REPO_BASE.
+my $newcmd = "$start$rc{GL_REPO_BASE}/$repo$end";
+
+# Rather than keeping track of which git-annex-shell commands
+# require write access and which are readonly, we tell it
+# when readonly access is needed.
+if ( can_write($repo) ) {
+} elsif ( can_read($repo) ) {
+    $ENV{GIT_ANNEX_SHELL_READONLY} = 1;
+} else {
+    die "$repo $ENV{GL_USER} DENIED\n";
+}
+# Further limit git-annex-shell to safe commands (avoid it passing
+# unknown commands on to git-shell)
+$ENV{GIT_ANNEX_SHELL_LIMITED} = 1;
+
+# Note that $newcmd does *not* get evaluated by the unix shell.
+# Instead it is passed as a single parameter to git-annex-shell for
+# it to parse and handle the command. This is why we do not need to
+# fully validate $cmd above.
+Gitolite::Common::gl_log( $ENV{SSH_ORIGINAL_COMMAND} );
+exec "git-annex-shell", "-c", $newcmd;
+
+__END__
+
+INSTRUCTIONS... (NEED TO BE VALIDATED BY SOMEONE WHO KNOWS GIT-ANNEX WELL).
+
+based on http://git-annex.branchable.com/tips/using_gitolite_with_git-annex/
+ONLY VARIATIONS FROM THAT PAGE ARE WRITTEN HERE.
+
+setup
+
+  * in the ENABLE list in the rc file, add an entry like this:
+        'git-annex-shell ua',
+
+That should be it; everything else should be as in that page.

commit 6d29d626793516dfdd092038b2902a72e3c473f4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 20 05:55:26 2014 +0530

    allow some commands to have unrestricted arguments

diff --git a/src/gitolite-shell b/src/gitolite-shell
index bbfbfc6..39a3438 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -157,10 +157,11 @@ sub parse_soc {
     # after this we should not return; caller expects us to handle it all here
     # and exit out
 
-    _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
 
     my @words = split ' ', $soc;
     if ( $rc{COMMANDS}{ $words[0] } ) {
+        _die "suspicious characters loitering about '$soc'"
+          if $rc{COMMANDS}{ $words[0] } ne 'ua' and $soc !~ $REMOTE_COMMAND_PATT;
         trace( 2, "gitolite command", $soc );
         _system( "gitolite", @words );
         exit 0;

commit 8f1fd8481aaa338a02f5eb2f41dff4f8f1bc96fa
Author: Martin Erik Werner <martinerikwerner at gmail.com>
Date:   Tue Dec 17 13:11:38 2013 +0100

    D: Allow repo owners in addition to creator
    
    Modify the D command to allow owners, in additon to creators,
    unlocking/trashing/deleting the repo. This is similar to the change to
    the perms command in 797a81f.

diff --git a/src/commands/D b/src/commands/D
index 1a8c2b5..016a365 100755
--- a/src/commands/D
+++ b/src/commands/D
@@ -62,7 +62,7 @@ TRASH_SUFFIX=`gitolite query-rc TRASH_SUFFIX`;  tsuf=`date +%Y-%m-%d_%H:%M:%S`;
 # ----------------------------------------------------------------------
 
 owner_or_die() {
-    gitolite creator "$repo" $GL_USER || die You are not authorised
+    gitolite owns "$repo" || die You are not authorised
 }
 
 # ----------------------------------------------------------------------

commit 249916ca64468a8342dd95a3cbed43a3825202b6
Author: Markus Mayer <code at mmayer.net>
Date:   Mon Feb 3 11:45:56 2014 -0800

    Add "readme" command to gitolite
    
    The "readme" command allows users of wild repos to view, add or remove
    a README.html page.
    
    The code is largely modelled after the "desc" script.

diff --git a/src/commands/readme b/src/commands/readme
new file mode 100755
index 0000000..358ce3e
--- /dev/null
+++ b/src/commands/readme
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# README.html files work similar to "description" files. For further
+# information see
+# 	https://www.kernel.org/pub/software/scm/git/docs/gitweb.html
+# under "Per-repository gitweb configuration".
+
+# Usage:    ssh git at host readme <repo>
+#           ssh git at host readme <repo> rm
+#           cat <filename> | ssh git at host readme <repo> set
+#
+# Show, remove or set the README.html file for user-created ("wild") repo.
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ $# -gt 1 ] && [ "$2" != "set" ] && [ "$2" != "rm" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+repo=$1; shift
+
+if gitolite query-rc -q WRITER_CAN_UPDATE_README
+then
+    gitolite access -q "$repo" $GL_USER W any || die You are not authorised
+else
+    gitolite creator "$repo" $GL_USER || die You are not authorised
+fi
+
+readmefile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/README.html
+
+if [ -z "$1" ]
+then
+    [ -r "$readmefile" ] && cat "$readmefile"
+    exit 0
+fi
+
+if [ "$1" = "rm" ]
+then
+    rm -f "$readmefile"
+    exit 0
+fi
+
+cat >"$readmefile"
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 9b15f23..3d1af57 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -482,6 +482,8 @@ __DATA__
 
     # the 'desc' command uses this
         # WRITER_CAN_UPDATE_DESC    =>  1,
+    # the 'readme' command uses this
+        # WRITER_CAN_UPDATE_README  =>  1,
 
     # the CpuTime feature uses these
         # display user, system, and elapsed times to user after each git operation
@@ -526,6 +528,7 @@ __DATA__
             # 'create',
             # 'fork',
             # 'mirror',
+            # 'readme',
             # 'sskm',
             # 'D',
 

commit fc5467c76da69c8977b8869c59aa672a7a576fdb
Author: John L. Scarfone <john at scarfone.net>
Date:   Wed Jan 22 14:09:08 2014 -0500

    change unlink to rm -f

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index d7c023c..937226b 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -23,7 +23,7 @@ plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 # since mktemp does not honor umask, we just use it to generate a temp
 # filename (note: 'mktemp -u' on some systems, this gets close enough)
 tmpfile=`mktemp $plf.tmp_XXXXXXXX`
-unlink $tmpfile;
+rm -f $tmpfile;
 
 (
     gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED

commit 29f787eeeba697df65f030ce11ba4132204572cf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jan 14 16:59:17 2014 +0530

    perl 5.10 giveth, and 5.18 taketh away...
    
    lexical $_ is suddenly "experimental"!  WTF?

diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index 4c3827c..670178f 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -227,7 +227,7 @@ sub tsh_tempdir {
 
 sub print_plan {
     return unless $ENV{HARNESS_ACTIVE};
-    my $_ = shift;
+    local $_ = shift;
     say "1..$_";
 }
 
@@ -235,7 +235,7 @@ sub rc_lines {
     my @lines = @_;
 
     while (@lines) {
-        my $_ = shift @lines;
+        local $_ = shift @lines;
         chomp; $_ = trim_ws($_);
 
         $line++;
@@ -522,13 +522,13 @@ sub sm {
 }
 
 sub trim_ws {
-    my $_ = shift;
+    local $_ = shift;
     s/^\s+//; s/\s+$//;
     return $_;
 }
 
 sub is_comment_or_empty {
-    my $_ = shift;
+    local $_ = shift;
     chomp; $_ = trim_ws($_);
     if (/^##\s(.*)/) {
         $testname = $1;
@@ -538,7 +538,7 @@ sub is_comment_or_empty {
 }
 
 sub cmds {
-    my $_ = shift;
+    local $_ = shift;
     chomp; $_ = trim_ws($_);
 
     # split on unescaped ';'s, then unescape the ';' in the results

commit 19ccab8955b72b0b6cd602c2273c0d29c39ab90a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Dec 2 12:32:00 2013 +0530

    git-config key check should ignore case

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 5e78fd0..21c060c 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -65,7 +65,7 @@ sub parse {
             $value =~ s/^['"](.*)["']$/$1/;
             my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
             push @validkeys, "gitolite-options\\..*";
-            my @matched = grep { $key =~ /^$_$/ } @validkeys;
+            my @matched = grep { $key =~ /^$_$/i } @validkeys;
             _die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
             _die "bad config value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );

commit 680a79673ae2c98946d6b46ef4cea2da7f0eab65
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Oct 19 00:30:14 2013 +0530

    (perltidy)

diff --git a/src/VREF/lock b/src/VREF/lock
index e07d8d5..0fc7681 100755
--- a/src/VREF/lock
+++ b/src/VREF/lock
@@ -24,10 +24,10 @@ _die "do '$ff' failed with '$@', contact your administrator" if $@;
 
 my ( $oldtree, $newtree, $refex ) = @ARGV[ 3, 4, 6 ];
 
-for my $file (`git diff --name-only $oldtree $newtree` ) {
+for my $file (`git diff --name-only $oldtree $newtree`) {
     chomp($file);
 
-    if ($locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER}) {
+    if ( $locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER} ) {
         print "$refex '$file' locked by '$locks{$file}{USER}'";
         last;
     }
diff --git a/src/VREF/refex-expr b/src/VREF/refex-expr
index 8403469..b788dd9 100755
--- a/src/VREF/refex-expr
+++ b/src/VREF/refex-expr
@@ -7,8 +7,8 @@ use warnings;
 
 my $rule = $ARGV[7];
 die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
-  unless exists $ENV{"GL_REFEX_EXPR_" . $rule};
-my $res = $ENV{"GL_REFEX_EXPR_" . $rule} || 0;
+  unless exists $ENV{ "GL_REFEX_EXPR_" . $rule };
+my $res = $ENV{ "GL_REFEX_EXPR_" . $rule } || 0;
 print "$ARGV[6] ($res)\n" if $res;
 
 exit 0;
diff --git a/src/commands/git-config b/src/commands/git-config
index a39da2a..d996575 100755
--- a/src/commands/git-config
+++ b/src/commands/git-config
@@ -71,7 +71,7 @@ if ( $repo ne '%' and $key ne '%' ) {
 }
 
 $repo = '' if $repo eq '%';
-$key  = '' if $key  eq '%';
+$key  = '' if $key eq '%';
 
 _die "'-q' doesn't go with using a pipe" if $quiet;
 @ARGV = ();
diff --git a/src/commands/help b/src/commands/help
index 8824a60..3ab60d8 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -22,8 +22,8 @@ print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() .
 
 print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
 
-my %list = (list_x( $ENV{GL_BINDIR}), list_x($rc{LOCAL_CODE} || ''));
-for (sort keys %list) {
+my %list = ( list_x( $ENV{GL_BINDIR} ), list_x( $rc{LOCAL_CODE} || '' ) );
+for ( sort keys %list ) {
     print "\t$list{$_}" if $ENV{D};
     print "\t$_\n" if not $user or $rc{COMMANDS}{$_};
 }
diff --git a/src/commands/htpasswd b/src/commands/htpasswd
index fc468d0..bbfacc7 100755
--- a/src/commands/htpasswd
+++ b/src/commands/htpasswd
@@ -40,5 +40,5 @@ print "new htpasswd: ";
 my $password = <>;
 $password =~ s/[\n\r]*$//;
 die "empty passwords are not allowed\n" unless $password;
-my $res = system("htpasswd", "-mb", $htpasswd_file, $ENV{GL_USER}, $password);
+my $res = system( "htpasswd", "-mb", $htpasswd_file, $ENV{GL_USER}, $password );
 die "htpasswd command seems to have failed with return code: $res.\n" if $res;
diff --git a/src/commands/info b/src/commands/info
index 654b7ed..b2bc3fc 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -54,7 +54,7 @@ sub print_version {
     chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
     my $gv = substr( `git --version`, 12 );
     $ENV{GL_USER} or _die "GL_USER not set";
-    print "hello $ENV{GL_USER}, this is " . ($ENV{USER} || "httpd") . "\@$hn running gitolite3 " . version() . " on git $gv\n";
+    print "hello $ENV{GL_USER}, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
 }
 
 sub print_patterns {
@@ -102,7 +102,7 @@ sub listem {
         next unless $perm =~ /\S/;
         print "$perm\t$repo";
         print "\t$creator" if $lc;
-        print "\t$desc" if $ld;
+        print "\t$desc"    if $ld;
         print "\n";
     }
 }
diff --git a/src/commands/list-dangling-repos b/src/commands/list-dangling-repos
index 6d86937..60a3592 100755
--- a/src/commands/list-dangling-repos
+++ b/src/commands/list-dangling-repos
@@ -32,18 +32,18 @@ my %phy_repos = map { chomp; $_ => 1 } `gitolite list-phy-repos`;
 
 # Remove exact matches.  But for repo names like "gtk+", you could have
 # collapsed this into the next step (the regex match).
-for my $pr (keys %phy_repos) {
+for my $pr ( keys %phy_repos ) {
     next unless exists $repos{$pr};
     delete $repos{$pr};
     delete $phy_repos{$pr};
 }
 
 # Remove regex matches.
-for my $pr (keys %phy_repos) {
+for my $pr ( keys %phy_repos ) {
     my $matched = 0;
-    my $pr2 = Gitolite::Conf::Load::generic_name($pr);
-    for my $r (keys %repos) {
-        if ($pr =~ /^$r$/ or $pr2 =~ /^$r$/) {
+    my $pr2     = Gitolite::Conf::Load::generic_name($pr);
+    for my $r ( keys %repos ) {
+        if ( $pr =~ /^$r$/ or $pr2 =~ /^$r$/ ) {
             $matched = 1;
             next;
         }
@@ -52,4 +52,4 @@ for my $pr (keys %phy_repos) {
 }
 
 # what's left in %phy_repos are dangling repos.
-print join("\n", sort keys %phy_repos), "\n";
+print join( "\n", sort keys %phy_repos ), "\n";
diff --git a/src/commands/lock b/src/commands/lock
index 7a5765b..70c2190 100755
--- a/src/commands/lock
+++ b/src/commands/lock
@@ -65,14 +65,13 @@ sub object_exists {
     my @branches = `git for-each-ref refs/heads '--format=%(refname)'`;
     foreach my $b (@branches) {
         chomp($b);
-	system("git cat-file -e $b:$file 2>/dev/null") or return 1;
-            # note that with system(), the return value is "shell truth", so
-            # you check for success with "or", not "and"
+        system("git cat-file -e $b:$file 2>/dev/null") or return 1;
+        # note that with system(), the return value is "shell truth", so
+        # you check for success with "or", not "and"
     }
-    return 0;   # report object not found
+    return 0;    # report object not found
 }
 
-
 # ----------------------------------------------------------------------
 # everything below assumes we have already chdir'd to "$repo.git".  Also, $ff
 # is used as a global.
@@ -111,7 +110,7 @@ sub f_list {
 
     my %locks = get_locks();
     print "\n# locks held:\n\n";
-    map { print "$locks{$_}{USER}\t$_\t(" . scalar(localtime($locks{$_}{TIME})) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
+    map { print "$locks{$_}{USER}\t$_\t(" . scalar( localtime( $locks{$_}{TIME} ) ) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
     print "\n# locks broken:\n\n";
     for my $b ( @{ $locks{BREAKS} } ) {
         my ( $when, $who, $whose, $how_old, $what ) = split ' ', $b;
diff --git a/src/commands/mirror b/src/commands/mirror
index d72f9ae..205145c 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -45,10 +45,10 @@ if ( $cmd eq 'push' ) {
     _chdir( $rc{GL_REPO_BASE} );
     _chdir("$repo.git");
 
-    if (-f "gl-creator") {
+    if ( -f "gl-creator" ) {
         # try to propagate the wild repo, including creator name and gl-perms
         my $creator = `cat gl-creator`; chomp($creator);
-        trace(1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null`);
+        trace( 1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null` );
     }
 
     my $errors = 0;
diff --git a/src/commands/perms b/src/commands/perms
index 90d7d2e..f61057d 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -51,7 +51,7 @@ if ( $ARGV[0] eq '-c' ) {
     my $repo = $ARGV[0] or usage();
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
-    if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
+    if ( not -d "$rc{GL_REPO_BASE}/$repo.git" ) {
         my $ret = Gitolite::Conf::Load::access( $repo, $ENV{GL_USER}, '^C', 'any' );
         _die $generic_error if $ret =~ /DENIED/;
 
@@ -92,7 +92,7 @@ sub setperms {
             _die "Invalid role '$1'; check the rc file" if /(\S+)/ and not $rc{ROLES}{$1};
             push @a, $_;
         }
-        print STDERR "\n";  # make sure Ctrl-C gets caught
+        print STDERR "\n";    # make sure Ctrl-C gets caught
         _print( $pf, @a );
         return;
     }
@@ -100,8 +100,8 @@ sub setperms {
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if @_ != 3;
     my ( $op, $role, $user ) = @_;
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
-    _die "Invalid role '$role'; check the rc file" if not $rc{ROLES}{$role};
-    _die "Invalid user '$user'" if not $user =~ $USERNAME_PATT;
+    _die "Invalid role '$role'; check the rc file"                     if not $rc{ROLES}{$role};
+    _die "Invalid user '$user'"                                        if not $user =~ $USERNAME_PATT;
 
     my $text = '';
     my @text = slurp($pf) if -f $pf;
diff --git a/src/commands/rsync b/src/commands/rsync
index 46ec167..1109ac4 100755
--- a/src/commands/rsync
+++ b/src/commands/rsync
@@ -90,7 +90,7 @@ usage();
 
 sub bundle_create {
     my ( $repo, $ttl ) = @_;
-    my $bundle   = "$repo.bundle";
+    my $bundle = "$repo.bundle";
     $bundle =~ s(.*/)();
     my $recreate = 0;
 
diff --git a/src/commands/who-pushed b/src/commands/who-pushed
index 5de7b9d..915705b 100755
--- a/src/commands/who-pushed
+++ b/src/commands/who-pushed
@@ -38,18 +38,18 @@ $ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" );
 
 my $repodir = "$ENV{GL_REPO_BASE}/$repo.git";
 chdir $repodir or die "repo '$repo' missing";
-(my $logdir = $ENV{GL_LOGFILE}) =~ s(/[^/]+$)();
+( my $logdir = $ENV{GL_LOGFILE} ) =~ s(/[^/]+$)();
 
 for my $logfile ( reverse glob("$logdir/*") ) {
     @ARGV = ($logfile);
     for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) {
         chomp($line);
         my @fields = split /\t/, $line;
-        my ($ts, $pid, $who, $ref, $d_old, $new) = @fields[ 0, 1, 4, 6, 7, 8];
+        my ( $ts, $pid, $who, $ref, $d_old, $new ) = @fields[ 0, 1, 4, 6, 7, 8 ];
 
         # d_old is what you display
         my $old = $d_old;
-        $old = "" if $d_old eq ("0" x 40);
+        $old = ""       if $d_old eq ( "0" x 40 );
         $old = "$old.." if $old;
 
         system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
diff --git a/src/commands/writable b/src/commands/writable
index d6426b2..828f569 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -22,7 +22,7 @@ usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
 usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off';
 
 my $repo = shift;
-my $on = ( shift eq 'on' );
+my $on   = ( shift eq 'on' );
 
 if ( $repo eq '@all' ) {
     _die "you are not authorized" if $ENV{GL_USER} and not is_admin();
diff --git a/src/gitolite b/src/gitolite
index b8eafd7..4a4cbf5 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -73,7 +73,7 @@ if ( $command eq 'setup' ) {
 } elsif ( $command eq 'trigger' ) {
     trigger(@args);
 
-} elsif ( my $c = _which("commands/$command", 'x' ) ) {
+} elsif ( my $c = _which( "commands/$command", 'x' ) ) {
     trace( 2, "attempting gitolite command $c" );
     _system( $c, @args );
 
diff --git a/src/gitolite-shell b/src/gitolite-shell
index c8c0bb5..bbfbfc6 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -130,7 +130,7 @@ sub main {
     }
 
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
-    if ($ENV{REQUEST_URI}) {
+    if ( $ENV{REQUEST_URI} ) {
         _system( "git", "http-backend" );
     } else {
         my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 92e0dbc..00642cb 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -54,7 +54,7 @@ sub trace {
         $sub =~ s(.*/(.*))(($1));
     }
     $sub .= ' ' x ( 31 - length($sub) );
-    say2 "$level\t$sub\t", join("\t", @_);
+    say2 "$level\t$sub\t", join( "\t", @_ );
 }
 
 sub dbg {
@@ -282,7 +282,7 @@ sub gl_log {
 
 sub logger_plus_stderr {
     open my $fh, "|-", "logger" or confess "it's really not my day is it...?\n";
-    for ( @_ ) {
+    for (@_) {
         print STDERR "FATAL: $_\n";
         print $fh "FATAL: $_\n";
     }
diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index 74ba933..8f934fa 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -56,7 +56,7 @@ sub incsub {
 
     _die "invalid include/subconf file/glob '$include_glob'"
       unless $include_glob =~ /^"(.+)"$/
-          or $include_glob =~ /^'(.+)'$/;
+      or $include_glob =~ /^'(.+)'$/;
     $include_glob = $1;
 
     trace( 3, $is_subconf, $include_glob );
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 4ce8b32..47cda99 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -70,7 +70,7 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
-    trace( 2, $repo, $user, $aa, $ref);
+    trace( 2, $repo, $user, $aa, $ref );
     _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
     sanity($repo);
 
@@ -85,7 +85,7 @@ sub access {
     _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ m(^VREF/NAME/) or $ref =~ $REF_OR_FILENAME_PATT;
     # apparently we can't always force sanity; at least what we *return*
     # should be sane/safe.  This pattern is based on REF_OR_FILENAME_PATT.
-    (my $safe_ref = $ref) =~ s([^-0-9a-zA-Z._\@/+ :,])(.)g;
+    ( my $safe_ref = $ref ) =~ s([^-0-9a-zA-Z._\@/+ :,])(.)g;
     trace( 3, "safe_ref", $safe_ref ) if $ref ne $safe_ref;
 
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
@@ -131,7 +131,7 @@ sub git_config {
     my ( $repo, $key, $empty_values_OK ) = @_;
     $key ||= '.';
 
-    if (repo_missing($repo)) {
+    if ( repo_missing($repo) ) {
         load_common();
     } else {
         load($repo);
@@ -169,36 +169,36 @@ sub git_config {
     # now some of these will have an empty key; we need to delete them unless
     # we're told empty values are OK
     unless ($empty_values_OK) {
-        my($k, $v);
-        while (($k, $v) = each %ret) {
+        my ( $k, $v );
+        while ( ( $k, $v ) = each %ret ) {
             delete $ret{$k} if not $v;
         }
     }
 
-    my($k, $v);
+    my ( $k, $v );
     my $creator = creator($repo);
-    while (($k, $v) = each %ret) {
+    while ( ( $k, $v ) = each %ret ) {
         $v =~ s/%GL_REPO/$repo/g;
         $v =~ s/%GL_CREATOR/$creator/g if $creator;
         $ret{$k} = $v;
     }
 
-    map { trace( 3, "$_", "$ret{$_}") } ( sort keys %ret ) if $ENV{D};
+    map { trace( 3, "$_", "$ret{$_}" ) } ( sort keys %ret ) if $ENV{D};
     return \%ret;
 }
 
 sub env_options {
     return unless -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf-compiled.pm";
-        # prevent catch-22 during initial install
+    # prevent catch-22 during initial install
 
     my $cwd = getcwd();
 
     my $repo = shift;
     map { delete $ENV{$_} } grep { /^GL_OPTION_/ } keys %ENV;
     my $h = git_config( $repo, '^gitolite-options.ENV\.' );
-    while (my ($k, $v) = each %$h) {
+    while ( my ( $k, $v ) = each %$h ) {
         next unless $k =~ /^gitolite-options.ENV\.(\w+)$/;
-        $ENV{"GL_OPTION_" . $1} = $v;
+        $ENV{ "GL_OPTION_" . $1 } = $v;
     }
 
     chdir($cwd);
@@ -584,7 +584,7 @@ sub list_memberships {
     load_common();
     my @m;
 
-    if ($user and $repo) {
+    if ( $user and $repo ) {
         # unsupported/undocumented except via "in_role()" in Easy.pm
         @m = memberships( 'user', $user, $repo );
     } elsif ($user) {
@@ -594,7 +594,7 @@ sub list_memberships {
     }
 
     @m = grep { $_ ne '@all' and $_ ne ( $user || $repo ) } @m;
-    return ( sort_u(\@m) );
+    return ( sort_u( \@m ) );
 }
 
 =for list_members
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index fd09670..69484d3 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -69,9 +69,9 @@ sub set_repolist {
     while (@in) {
         $_ = shift @in;
         if ( check_subconf_repo_disallowed( $subconf, $_ ) ) {
-            if (exists $groups{$_}) {
+            if ( exists $groups{$_} ) {
                 # groupname disallowed; try individual members now
-                (my $g = $_) =~ s/^\@$subconf\./\@/;
+                ( my $g = $_ ) =~ s/^\@$subconf\./\@/;
                 _warn "expanding '$g'; this *may* slow down compilation";
                 unshift @in, keys %{ $groups{$_} };
                 next;
@@ -114,9 +114,11 @@ sub parse_users {
 sub add_rule {
     my ( $perm, $ref, $user ) = @_;
     _warn "possible undeclared group '$user'"
-        if $user =~ /^@/ and not $groups{$user} and not $rc{GROUPLIST_PGM}
-            and not special_group($user);
-    _die "bad ref '$ref'"   unless $ref  =~ $REPOPATT_PATT;
+      if $user =~ /^@/
+      and not $groups{$user}
+      and not $rc{GROUPLIST_PGM}
+      and not special_group($user);
+    _die "bad ref '$ref'"   unless $ref =~ $REPOPATT_PATT;
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
     $nextseq++;
@@ -186,7 +188,7 @@ sub new_repos {
         # use gl-conf as a sentinel
         hook_1($repo) if -d "$repo.git" and not -f "$repo.git/gl-conf";
 
-        if (not -d "$repo.git") {
+        if ( not -d "$repo.git" ) {
             push @{ $rc{NEW_REPOS_CREATED} }, $repo;
             trigger( 'PRE_CREATE', $repo );
             new_repo($repo);
@@ -285,7 +287,7 @@ sub store_1 {
     open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
 
     my $dumped_data = '';
-    if ($repos{$repo}) {
+    if ( $repos{$repo} ) {
         $one_repo{$repo} = $repos{$repo};
         delete $repos{$repo};
         $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
@@ -326,7 +328,7 @@ sub store_common {
 
         # save patterns in %groups for faster handling of multiple repos, such
         # as happens in the various POST_COMPILE scripts
-        for my $k (keys %groups) {
+        for my $k ( keys %groups ) {
             $patterns{groups}{$k} = 1 unless $k =~ $REPONAME_PATT;
         }
     }
diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index d02bfa0..fdfbac9 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -52,7 +52,7 @@ sub sugar {
                 # perl-ism; apart from keeping the full path separate from the
                 # simple name, this also protects %rc from change by implicit
                 # aliasing, which would happen if you touched $s itself
-                my $sfp = _which("syntactic-sugar/$s", 'r');
+                my $sfp = _which( "syntactic-sugar/$s", 'r' );
 
                 _warn("skipped sugar script '$s'"), next if not -r $sfp;
                 $lines = SugarBox::run_sugar_script( $sfp, $lines );
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 6311897..308e94d 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -102,7 +102,7 @@ sub in_group {
     my $g = shift;
     $g =~ s/^\@?/@/;
 
-    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships('-u', $user) };
+    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships( '-u', $user ) };
 }
 
 # in_role()
@@ -117,7 +117,7 @@ sub in_role {
     $r =~ s/^\@?/@/;
     my $repo = shift;
 
-    return grep { $_ eq $r } @{ Gitolite::Conf::Load::list_memberships("-u", $user, "-r", $repo) };
+    return grep { $_ eq $r } @{ Gitolite::Conf::Load::list_memberships( "-u", $user, "-r", $repo ) };
 }
 
 # owns()
@@ -156,8 +156,8 @@ sub can_read {
 #   if gitolite access -q $REPONAME $GL_USER W; then ...
 sub can_write {
     valid_user();
-    my ($r, $aa, $ref) = @_;
-    $aa ||= 'W';
+    my ( $r, $aa, $ref ) = @_;
+    $aa  ||= 'W';
     $ref ||= 'any';
     return not( access( $r, $user, $aa, $ref ) =~ /DENIED/ );
 }
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 7b86a1a..f6b3f1b 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -34,7 +34,7 @@ sub update {
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
 
-    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, $ret);
+    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, $ret );
     exit 0;
 }
 
@@ -60,7 +60,7 @@ sub check_vrefs {
             }
         } else {
             my ( $dummy, $pgm, @args ) = split '/', $vref;
-            $pgm = _which("VREF/$pgm", 'x');
+            $pgm = _which( "VREF/$pgm", 'x' );
             $pgm or _die "'$vref': helper program missing or unexecutable";
 
             open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
@@ -120,7 +120,7 @@ sub args {
     # for branch create or delete, merge_base stays at '0'x40
     chomp( $merge_base = `git merge-base $oldsha $newsha` )
       unless $oldsha eq '0' x 40
-          or $newsha eq '0' x 40;
+      or $newsha eq '0' x 40;
 
     $aa = 'W';
     # tag rewrite
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 2d1cede..9b15f23 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -62,7 +62,7 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 my $current_data_version = "3.2";
 
 my $rc = glrc('filename');
-if (-r $rc and -s $rc) {
+if ( -r $rc and -s $rc ) {
     do $rc or die $@;
 }
 if ( defined($GL_ADMINDIR) ) {
@@ -132,7 +132,7 @@ sub non_core_expand {
     my %enable;
 
     for my $e ( @{ $rc{ENABLE} } ) {
-        my ($name, $arg) = split ' ', $e, 2;
+        my ( $name, $arg ) = split ' ', $e, 2;
         # store args as the hash value for the name
         $enable{$name} = $arg || '';
 
@@ -142,8 +142,8 @@ sub non_core_expand {
     }
 
     # bring in additional non-core specs from the rc file, if given
-    if (my $nc2 = $rc{NON_CORE}) {
-        for ($non_core, $nc2) {
+    if ( my $nc2 = $rc{NON_CORE} ) {
+        for ( $non_core, $nc2 ) {
             # beat 'em into shape :)
             s/#.*//g;
             s/[ \t]+/ /g; s/^ //mg; s/ $//mg;
@@ -152,8 +152,8 @@ sub non_core_expand {
 
         for ( split "\n", $nc2 ) {
             next unless /\S/;
-            my ($name, $where, $module, $before, $name2) = split ' ', $_;
-            if (not $before) {
+            my ( $name, $where, $module, $before, $name2 ) = split ' ', $_;
+            if ( not $before ) {
                 $non_core .= "$name $where $module\n";
                 next;
             }
@@ -165,7 +165,7 @@ sub non_core_expand {
     my @data = split "\n", $non_core || '';
     for (@data) {
         next if /^\s*(#|$)/;
-        my ($name, $where, $module) = split ' ', $_;
+        my ( $name, $where, $module ) = split ' ', $_;
 
         # if it appears here, it's not a command, so delete it.  At the end of
         # this loop, what's left in $rc{COMMANDS} will be those names in the
@@ -228,17 +228,17 @@ sub query_rc {
         exit 0;
     }
 
-    my $cv = \%rc;  # current "value"
+    my $cv = \%rc;    # current "value"
     while (@vars) {
         my $v = shift @vars;
 
         # dig into the rc hash, using each var as a component
-        if (not ref($cv)) {
+        if ( not ref($cv) ) {
             _warn "unused arguments...";
             last;
-        } elsif (ref($cv) eq 'HASH') {
+        } elsif ( ref($cv) eq 'HASH' ) {
             $cv = $cv->{$v} || '';
-        } elsif (ref($cv) eq 'ARRAY') {
+        } elsif ( ref($cv) eq 'ARRAY' ) {
             $cv = $cv->[$v] || '';
         } else {
             _die "dont know what to do with " . ref($cv) . " item in the rc file";
@@ -247,17 +247,17 @@ sub query_rc {
 
     # we've run out of arguments so $cv is what we have.  If we're supposed to
     # be quiet, we don't have to print anything so let's get that done first:
-    exit ( $cv ? 0 : 1 ) if $quiet;     # shell truth
+    exit( $cv ? 0 : 1 ) if $quiet;    # shell truth
 
     # print values (notice we ignore the '-n' option if it's a ref)
-    if (ref($cv) eq 'HASH') {
-        print join("\n", sort keys %$cv), "\n" if %$cv;
-    } elsif (ref($cv) eq 'ARRAY') {
-        print join("\n", @$cv), "\n" if @$cv;
+    if ( ref($cv) eq 'HASH' ) {
+        print join( "\n", sort keys %$cv ), "\n" if %$cv;
+    } elsif ( ref($cv) eq 'ARRAY' ) {
+        print join( "\n", @$cv ), "\n" if @$cv;
     } else {
         print $cv . ( $nonl ? '' : "\n" ) if $cv;
     }
-    exit ( $cv ? 0 : 1 );   # shell truth
+    exit( $cv ? 0 : 1 );              # shell truth
 }
 
 sub version {
@@ -277,7 +277,7 @@ sub trigger {
     # name, so setup env from options
     require Gitolite::Conf::Load;
     Gitolite::Conf::Load->import('env_options');
-    env_options($_[0]) if $_[0];
+    env_options( $_[0] ) if $_[0];
 
     if ( exists $rc{$rc_section} ) {
         if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
@@ -293,7 +293,7 @@ sub trigger {
                     Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
 
                 } else {
-                    $pgm = _which("triggers/$pgm", 'x');
+                    $pgm = _which( "triggers/$pgm", 'x' );
 
                     _warn("skipped trigger '$s' (not found or not executable)"), next if not $pgm;
                     trace( 2, 'trigger command', $s );
@@ -310,12 +310,12 @@ sub _which {
     # looks for a file in LOCAL_CODE or GL_BINDIR.  Returns whichever exists
     # (LOCAL_CODE preferred if defined) or 0 if not found.
     my $file = shift;
-    my $mode = shift;   # could be 'x' or 'r'
+    my $mode = shift;    # could be 'x' or 'r'
 
     my @files = ("$rc{GL_BINDIR}/$file");
     unshift @files, ("$rc{LOCAL_CODE}/$file") if $rc{LOCAL_CODE};
 
-    for my $f ( @files ) {
+    for my $f (@files) {
         return $f if -x $f;
         return $f if -r $f and $mode eq 'r';
     }
@@ -375,7 +375,8 @@ sub args {
 
 # ----------------------------------------------------------------------
 
-BEGIN { $non_core = "
+BEGIN {
+    $non_core = "
     # No user-servicable parts inside.  Warranty void if seal broken.  Refer
     # servicing to authorised service center only.
 
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index e59977f..06b2409 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -81,10 +81,10 @@ sub args {
     ) or usage();
 
     usage() if $help or ( $pubkey and $admin );
-    usage() if $h_only and ($admin or $pubkey);
+    usage() if $h_only and ( $admin or $pubkey );
 
     if ($pubkey) {
-        $pubkey =~ /\.pub$/ or _die "'$pubkey' name does not end in .pub";
+        $pubkey =~ /\.pub$/                 or _die "'$pubkey' name does not end in .pub";
         tsh_try("cat $pubkey")              or _die "'$pubkey' not a readable file";
         tsh_lines() == 1                    or _die "'$pubkey' must have exactly one line";
         tsh_try("ssh-keygen -l -f $pubkey") or _die "'$pubkey' does not seem to be a valid ssh pubkey file";
diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index f42c463..32beb43 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -25,11 +25,11 @@ use Gitolite::Common;
 
 BEGIN {
     require Gitolite::Test::Tsh;
-    *{'try'}  = \&Tsh::try;
-    *{'put'}  = \&Tsh::put;
-    *{'text'} = \&Tsh::text;
+    *{'try'}   = \&Tsh::try;
+    *{'put'}   = \&Tsh::put;
+    *{'text'}  = \&Tsh::text;
     *{'lines'} = \&Tsh::lines;
-    *{'cmp'}  = \&Tsh::cmp;
+    *{'cmp'}   = \&Tsh::cmp;
 }
 
 use strict;
@@ -38,7 +38,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 # make sure the user is ready for it
-if (not $ENV{GITOLITE_TEST} or $ENV{GITOLITE_TEST} ne 'y') {
+if ( not $ENV{GITOLITE_TEST} or $ENV{GITOLITE_TEST} ne 'y' ) {
     print "Bail out! See t/README for information on how to run the tests.\n";
     exit 255;
 }
@@ -113,7 +113,7 @@ sub confadd {
 sub md5sum {
     my $out = '';
     for my $file (@_) {
-        $out .= md5_hex(slurp($file)) . "  $file\n";
+        $out .= md5_hex( slurp($file) ) . "  $file\n";
     }
     return $out;
 }
diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index b3c43e0..4c3827c 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -315,12 +315,12 @@ sub def {
     my $e;    # the expanded value
     if ( $e = $def{$c} ) {    # starting value
         for my $i ( 1 .. 9 ) {
-            last unless $e =~ /%$i/;    # no more %N's (we assume sanity)
+            last unless $e =~ /%$i/;                              # no more %N's (we assume sanity)
             die "$def{$c} requires more arguments\n" unless @d;
-            my $f = shift @d;           # get the next datum
-            $e =~ s/%$i/$f/g;           # and substitute %N all over
+            my $f = shift @d;                                     # get the next datum
+            $e =~ s/%$i/$f/g;                                     # and substitute %N all over
         }
-        return join( " ", $e, @d );     # join up any remaining data
+        return join( " ", $e, @d );                               # join up any remaining data
     }
     return '';
 }
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 366660f..014408d 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -15,7 +15,7 @@ my ( $mode, $master, %slaves, %trusted_slaves );
 # ----------------------------------------------------------------------
 
 sub input {
-    unless ($ARGV[0] =~ /^server-(\S+)$/) {
+    unless ( $ARGV[0] =~ /^server-(\S+)$/ ) {
         _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
         return;
     }
@@ -27,13 +27,13 @@ sub input {
 
     # custom peer-to-peer commands.  At present the only one is 'perms -c',
     # sent from a mirror command
-    if ($ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/) {
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/ ) {
         $ENV{GL_USER} = $1;
 
         my $repo = $2;
         details($repo);
-        _die "$hn: '$repo' is local"  if $mode eq 'local';
-        _die "$hn: '$repo' is native" if $mode eq 'master';
+        _die "$hn: '$repo' is local"                        if $mode eq 'local';
+        _die "$hn: '$repo' is native"                       if $mode eq 'master';
         _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
 
         # this expects valid perms content on STDIN
@@ -96,8 +96,8 @@ sub pre_git {
     # case 2: we're slave, master pushing to us
     if ( $sender and not $rc{REDIRECTED_PUSH} ) {
         trace( 3, "case 2, master push" );
-        _die "$hn: '$repo' is local"  if $mode eq 'local';
-        _die "$hn: '$repo' is native" if $mode eq 'master';
+        _die "$hn: '$repo' is local"                        if $mode eq 'local';
+        _die "$hn: '$repo' is native"                       if $mode eq 'master';
         _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
         return;
     }
@@ -106,8 +106,8 @@ sub pre_git {
     # case 3: we're master, slave sending a redirected push to us
     if ( $sender and $rc{REDIRECTED_PUSH} ) {
         trace( 3, "case 2, slave redirect" );
-        _die "$hn: '$repo' is local"      if $mode eq 'local';
-        _die "$hn: '$repo' is not native" if $mode eq 'slave';
+        _die "$hn: '$repo' is local"                           if $mode eq 'local';
+        _die "$hn: '$repo' is not native"                      if $mode eq 'slave';
         _die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
         _die "$hn: redirection not allowed from '$sender'"     if not $trusted_slaves{$sender};
         return;
diff --git a/src/lib/Gitolite/Triggers/RefexExpr.pm b/src/lib/Gitolite/Triggers/RefexExpr.pm
index 5e2114f..e913665 100644
--- a/src/lib/Gitolite/Triggers/RefexExpr.pm
+++ b/src/lib/Gitolite/Triggers/RefexExpr.pm
@@ -17,7 +17,7 @@ sub access_2 {
     return if $init_done and not %rules;
 
     # but we don't really know that the first time, heh!
-    if (not $init_done) {
+    if ( not $init_done ) {
         my $repo = $_[1];
         init($repo);
         return unless %rules;
@@ -29,8 +29,8 @@ sub access_2 {
     $passed{$refex}++;
 
     # evaluate the rules each time; it's not very expensive
-    for my $k (sort keys %rules) {
-        $ENV{"GL_REFEX_EXPR_" . $k} = eval_rule($rules{$k});
+    for my $k ( sort keys %rules ) {
+        $ENV{ "GL_REFEX_EXPR_" . $k } = eval_rule( $rules{$k} );
     }
 }
 
@@ -42,7 +42,7 @@ sub eval_rule {
 
     my $ret = eval $e;
     _die "eval '$e' -> '$@'" if $@;
-    Gitolite::Common::trace(1, "RefexExpr", "'$rule' -> '$e' -> '$ret'");
+    Gitolite::Common::trace( 1, "RefexExpr", "'$rule' -> '$e' -> '$ret'" );
 
     return "'$rule' -> '$e'" if $ret;
 }
@@ -68,10 +68,10 @@ sub init {
     my $repo = shift;
 
     # find all the rule expressions
-    my %t = config($repo, "^gitolite-options\\.refex-expr\\.");
-    my ($k, $v);
+    my %t = config( $repo, "^gitolite-options\\.refex-expr\\." );
+    my ( $k, $v );
     # get rid of the cruft and store just the rule name as the key
-    while ( ($k, $v) = each %t) {
+    while ( ( $k, $v ) = each %t ) {
         $k =~ s/^gitolite-options\.refex-expr\.//;
         $rules{$k} = $v;
     }
diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm
index 828e1e2..109cb31 100644
--- a/src/lib/Gitolite/Triggers/RepoUmask.pm
+++ b/src/lib/Gitolite/Triggers/RepoUmask.pm
@@ -30,14 +30,14 @@ use warnings;
 sub post_create {
     my $repo = $_[1];
 
-    my $umask = option($repo, 'umask');
-    _chdir($rc{GL_REPO_BASE});  # because using option() moves us to ADMIN_BASE!
+    my $umask = option( $repo, 'umask' );
+    _chdir( $rc{GL_REPO_BASE} );    # because using option() moves us to ADMIN_BASE!
 
     return unless $umask;
 
     # unlike the one in the rc file, this is a string
     $umask = oct($umask);
-    my $mode = "0" . sprintf("%o", $umask ^ 0777);
+    my $mode = "0" . sprintf( "%o", $umask ^ 0777 );
 
     system("chmod -R $mode $repo.git >&2");
     system("find $repo.git -type f -exec chmod a-x '{}' \\;");
@@ -46,8 +46,8 @@ sub post_create {
 sub pre_git {
     my $repo = $_[1];
 
-    my $umask = option($repo, 'umask');
-    _chdir($rc{GL_REPO_BASE});  # because using option() moves us to ADMIN_BASE!
+    my $umask = option( $repo, 'umask' );
+    _chdir( $rc{GL_REPO_BASE} );    # because using option() moves us to ADMIN_BASE!
 
     return unless $umask;
 
diff --git a/src/syntactic-sugar/macros b/src/syntactic-sugar/macros
index 37a4b22..a0ddf90 100644
--- a/src/syntactic-sugar/macros
+++ b/src/syntactic-sugar/macros
@@ -7,24 +7,25 @@
 # see documentation at the end of this script
 
 my %macro;
+
 sub sugar_script {
     my $lines = shift;
-    my @out  = ();
+    my @out   = ();
 
-    my $l = join("\n", @$lines);
-    while ($l =~ s/^macro (\w+)\b(.*?)\nend//ms) {
+    my $l = join( "\n", @$lines );
+    while ( $l =~ s/^macro (\w+)\b(.*?)\nend//ms ) {
         $macro{$1} = $2;
     }
 
     $l =~ s/^((\w+)\b.*)/$macro{$2} ? expand($1) : $1/gem;
 
-    $lines = [split "\n", $l];
+    $lines = [ split "\n", $l ];
     return $lines;
 }
 
 sub expand {
     my $l = shift;
-    my ($word, @arg) = split ' ', $l;
+    my ( $word, @arg ) = split ' ', $l;
     my $v = $macro{$word};
     $v =~ s/%(\d+)/$arg[$1-1] or die "macro '$word' needs $1 arguments at '$l'\n"/gem;
     return $v;
diff --git a/src/syntactic-sugar/refex-expr b/src/syntactic-sugar/refex-expr
index 3565c99..f9e7706 100644
--- a/src/syntactic-sugar/refex-expr
+++ b/src/syntactic-sugar/refex-expr
@@ -7,6 +7,7 @@
 my $perm = qr(-|R|RW\+?C?D?M?);
 
 my $seq = 1;
+
 sub sugar_script {
     my $lines = shift;
 
@@ -18,11 +19,11 @@ sub sugar_script {
         next unless $l =~ /^($perm) /;
         # more detailed check
         next unless $l =~ /^($perm) (\S.*) = (\S.*)$/;
-        my ($perm, $refexes, $users) = ($1, $2, $3);
+        my ( $perm, $refexes, $users ) = ( $1, $2, $3 );
         next unless $refexes =~ / (and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne) /;
 
         print STDERR ">>>> $l\n";
-        pop @out;   # we need to replace that last line
+        pop @out;    # we need to replace that last line
 
         push @out, "option refex-expr.sugar$seq = $refexes";
         push @out, "$perm VREF/refex-expr/sugar$seq = $users";
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index e91df20..106791a 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -25,7 +25,7 @@ exit 0 if @ARGV and $ARGV[0] eq 'POST_CREATE' and not $ARGV[2];
 
 # ----------------------------------------------------------------------
 # if called from POST_CREATE, we have only a single repo to worry about
-if (@ARGV and $ARGV[0] eq 'POST_CREATE') {
+if ( @ARGV and $ARGV[0] eq 'POST_CREATE' ) {
     my $repo = $ARGV[1];
     fixup_config($repo);
 
@@ -42,14 +42,14 @@ for my $pr (@$lpr) {
 }
 
 sub fixup_config {
-    my $pr = shift;
+    my $pr      = shift;
     my $creator = creator($pr);
 
     my $gc = git_config( $pr, '.', 1 );
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
-            while ( my ($mk, $mv) = each %{ $rc{SAFE_CONFIG} } ) {
+            while ( my ( $mk, $mv ) = each %{ $rc{SAFE_CONFIG} } ) {
                 $value =~ s/%$mk/$mv/g;
             }
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
index 6baa508..79e87e7 100755
--- a/src/triggers/repo-specific-hooks
+++ b/src/triggers/repo-specific-hooks
@@ -15,16 +15,16 @@ use Gitolite::Common;
 _die "repo-specific-hooks: LOCAL_CODE not defined in rc" unless $rc{LOCAL_CODE};
 _die "repo-specific-hooks: '$rc{LOCAL_CODE}' does not exist or is not a directory" unless -d $rc{LOCAL_CODE};
 
-_chdir($ENV{GL_REPO_BASE});
+_chdir( $ENV{GL_REPO_BASE} );
 
 @ARGV = ("gitolite list-phy-repos | gitolite git-config -r % gitolite-options\\.hook\\. |");
 
 while (<>) {
     chomp;
-    my ($repo, $hook, $code) = split /\t/, $_;
+    my ( $repo, $hook, $code ) = split /\t/, $_;
 
     # we don't allow fiddling with the admin repo
-    if ($repo eq 'gitolite-admin') {
+    if ( $repo eq 'gitolite-admin' ) {
         _warn "repo-specific-hooks: ignoring attempts to set hooks for the admin repo";
         next;
     }
@@ -32,19 +32,19 @@ while (<>) {
     # get the hook name
     $hook =~ s/^gitolite-options\.hook\.//;
 
-    unless ($hook =~ /^(pre-receive|post-receive|post-update)$/) {
+    unless ( $hook =~ /^(pre-receive|post-receive|post-update)$/ ) {
         _warn "repo-specific-hooks: '$hook' is not one of the 3 hooks allowed, ignoring";
         next;
     }
 
-    if ($code =~ m(^/|\.\.)) {
+    if ( $code =~ m(^/|\.\.) ) {
         _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
         next;
     }
 
     my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
     my $dst = "$repo.git/hooks/$hook";
-    unless (-x $src) {
+    unless ( -x $src ) {
         _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
         next;
     }

commit 21f4f0263ce174f0d6c6dfa89a42b4a62d639060
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Oct 18 23:51:14 2013 +0530

    logging level changes

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 7a27cc0..c8c0bb5 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -123,7 +123,6 @@ sub main {
     # more information.
     unless ( $ENV{GL_BYPASS_ACCESS_CHECKS} ) {
         my $ret = access( $repo, $user, $aa, 'any' );
-        trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
         trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
         _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
 
diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index f7171ff..92e0dbc 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -48,15 +48,13 @@ sub trace {
     return unless defined( $ENV{D} );
 
     my $level = shift; return if $ENV{D} < $level;
-    my $args = ''; $args = join( ", ", @_ ) if @_;
     my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://;
     if ( not $sub ) {
         $sub = (caller)[1];
         $sub =~ s(.*/(.*))(($1));
     }
-    $sub .= ' ' x ( 32 - length($sub) );
-    say2 "TRACE $level $sub", ( @_ ? shift : () );
-    say2( "TRACE $level " . ( " " x 32 ), $_ ) for @_;
+    $sub .= ' ' x ( 31 - length($sub) );
+    say2 "$level\t$sub\t", join("\t", @_);
 }
 
 sub dbg {
@@ -228,7 +226,7 @@ sub cleanup_conf_line {
             $repo =~ s(\./(.*)\.git$)($1);
             push @phy_repos, $repo;
         }
-        trace( 2, scalar(@phy_repos) . " physical repos found" );
+        trace( 3, scalar(@phy_repos) . " physical repos found" );
         return sort_u( \@phy_repos );
     }
 }
diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 4703736..5e78fd0 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -42,7 +42,7 @@ sub compile {
 
 sub parse {
     my $lines = shift;
-    trace( 2, scalar(@$lines) . " lines incoming" );
+    trace( 3, scalar(@$lines) . " lines incoming" );
 
     for my $line (@$lines) {
         # user or repo groups
@@ -70,7 +70,7 @@ sub parse {
             _die "bad config value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
-            trace( 2, $line );
+            trace( 3, $line );
             set_subconf($1);
         } else {
             _warn "syntax error, ignoring: '$line'";
diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index ec78973..74ba933 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -59,7 +59,7 @@ sub incsub {
           or $include_glob =~ /^'(.+)'$/;
     $include_glob = $1;
 
-    trace( 2, $is_subconf, $include_glob );
+    trace( 3, $is_subconf, $include_glob );
 
     for my $file ( glob($include_glob) ) {
         _warn("included file not found: '$file'"), next unless -f $file;
@@ -103,7 +103,7 @@ sub already_included {
     return 0 unless $included{$file_id}++;
 
     _warn("$file already included");
-    trace( 2, "$file already included" );
+    trace( 3, "$file already included" );
     return 1;
 }
 
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index c66bdf1..4ce8b32 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -70,6 +70,7 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
+    trace( 2, $repo, $user, $aa, $ref);
     _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
     sanity($repo);
 
@@ -85,7 +86,7 @@ sub access {
     # apparently we can't always force sanity; at least what we *return*
     # should be sane/safe.  This pattern is based on REF_OR_FILENAME_PATT.
     (my $safe_ref = $ref) =~ s([^-0-9a-zA-Z._\@/+ :,])(.)g;
-    trace( 2, "safe_ref $safe_ref created from $ref") if $ref ne $safe_ref;
+    trace( 3, "safe_ref", $safe_ref ) if $ref ne $safe_ref;
 
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
     # check to give valid results.
@@ -100,7 +101,7 @@ sub access {
         return "$aa $safe_ref $repo $user DENIED by existence";
     }
 
-    trace( 2, scalar(@rules) . " rules found" );
+    trace( 3, scalar(@rules) . " rules found" );
     for my $r (@rules) {
         my $perm = $r->[1];
         my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
@@ -182,7 +183,7 @@ sub git_config {
         $ret{$k} = $v;
     }
 
-    trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
+    map { trace( 3, "$_", "$ret{$_}") } ( sort keys %ret ) if $ENV{D};
     return \%ret;
 }
 
@@ -289,7 +290,7 @@ sub load_1 {
 
     sub rules {
         my ( $repo, $user ) = @_;
-        trace( 3, "repo=$repo, user=$user" );
+        trace( 3, $repo, $user );
 
         return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
 
diff --git a/src/lib/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
index 961db1a..2eeefcc 100644
--- a/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -19,7 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub post_update {
-    trace( 1, 'post-up', @ARGV );
+    trace( 3, 'post-up', @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 92eb625..7b86a1a 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -26,7 +26,7 @@ sub update {
 
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
 
-    trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
+    trace( 2, $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
@@ -90,7 +90,7 @@ sub check_vref {
     trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
       if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
-    trace( 2, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
+    trace( 3, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
 }
 
 {
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 75ff21a..2d1cede 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -289,21 +289,21 @@ sub trigger {
                 if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
 
                     require Gitolite::Triggers;
-                    trace( 1, 'trigger', $module, $sub, @args, $rc_section, @_ );
+                    trace( 2, 'trigger module', $module, $sub, @args, $rc_section, @_ );
                     Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
 
                 } else {
                     $pgm = _which("triggers/$pgm", 'x');
 
                     _warn("skipped trigger '$s' (not found or not executable)"), next if not $pgm;
-                    trace( 2, "command: $s" );
+                    trace( 2, 'trigger command', $s );
                     _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
                 }
             }
         }
         return;
     }
-    trace( 2, "'$rc_section' not found in rc" );
+    trace( 3, "'$rc_section' not found in rc" );
 }
 
 sub _which {
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 6778bc1..366660f 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -60,7 +60,7 @@ sub input {
 sub pre_git {
     return unless $hn;
     # nothing, and I mean NOTHING, happens if HOSTNAME is not set
-    trace( 1, "pre_git() on $hn" );
+    trace( 3, "pre_git() on $hn" );
 
     my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
 
@@ -85,7 +85,6 @@ sub pre_git {
         trace( 3, "case 1, user push" );
         return if $mode eq 'local' or $mode eq 'master';
         if ( $trusted_slaves{$hn} ) {
-            trace( 3, "redirecting to $master" );
             trace( 1, "redirect to $master" );
             exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
         } else {
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index e588c6a..9e0f059 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -28,7 +28,7 @@ tsh_try("sestatus");
 my $selinux = ( tsh_text() =~ /enabled/ );
 
 my $ab = $rc{GL_ADMIN_BASE};
-trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
+trace( 1, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
 my $akdir        = "$ENV{HOME}/.ssh";
 my $akfile       = "$ENV{HOME}/.ssh/authorized_keys";
 my $glshell      = $rc{GL_BINDIR} . "/gitolite-shell";

commit 806ebe5c0e337be982b3af2be230ff0f6aa06b78
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Oct 23 05:03:08 2013 +0530

    other minor fixups

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 809e4f8..4703736 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -67,7 +67,7 @@ sub parse {
             push @validkeys, "gitolite-options\\..*";
             my @matched = grep { $key =~ /^$_$/ } @validkeys;
             _die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
-            _die "bad value '$value'" if $value =~ $UNSAFE_PATT;
+            _die "bad config value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
             trace( 2, $line );

commit de8155870eed23d4a0c7494f8c4345475aef052b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Oct 17 08:50:22 2013 +0530

    (minor) log END line for commands also

diff --git a/src/gitolite b/src/gitolite
index 2498737..b8eafd7 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -76,7 +76,6 @@ if ( $command eq 'setup' ) {
 } elsif ( my $c = _which("commands/$command", 'x' ) ) {
     trace( 2, "attempting gitolite command $c" );
     _system( $c, @args );
-    exit 0;
 
 } elsif ( $command eq 'list-phy-repos' ) {
     _chdir( $rc{GL_REPO_BASE} );
@@ -95,6 +94,8 @@ if ( $command eq 'setup' ) {
 
 gl_log('END') if $$ == $ENV{GL_TID};
 
+exit 0;
+
 sub args {
     usage() if not $command or $command eq '-h';
 }

commit fac58070128e9af85b0fb4cc282dae343da32935
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Oct 16 11:53:14 2013 +0530

    (minor) use Rc before Common consistently

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index a2823e7..809e4f8 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -12,8 +12,8 @@ package Gitolite::Conf;
 use Exporter 'import';
 use Getopt::Long;
 
-use Gitolite::Common;
 use Gitolite::Rc;
+use Gitolite::Common;
 use Gitolite::Conf::Sugar;
 use Gitolite::Conf::Store;
 
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 295e888..c66bdf1 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -21,8 +21,8 @@ package Gitolite::Conf::Load;
 use Exporter 'import';
 use Cwd;
 
-use Gitolite::Common;
 use Gitolite::Rc;
+use Gitolite::Common;
 
 use strict;
 use warnings;
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index f5f9a66..fd09670 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -26,8 +26,8 @@ use Data::Dumper;
 $Data::Dumper::Indent   = 1;
 $Data::Dumper::Sortkeys = 1;
 
-use Gitolite::Common;
 use Gitolite::Rc;
+use Gitolite::Common;
 use Gitolite::Hooks::Update;
 use Gitolite::Hooks::PostUpdate;
 

commit f8776f57b8abdf78fe82667259553f43fc8d4c21
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Oct 29 11:31:39 2013 +0530

    allow repos to be created when only config entries present
    
    specifically, 16f2d9b doesn't cut it.  That only works when you migrated a
    whole bunch of repos from elsewhere, gave them all access using @all or some
    pattern, then give individual config lines to each.
    
    In this case the repo doesn't even exist, so the same logic (of treating
    config lines as equivalent to rule lines) must extend to the *creation* of a
    repo.

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index a5ffe9b..f5f9a66 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -175,7 +175,7 @@ sub new_repos {
     _chdir( $rc{GL_REPO_BASE} );
 
     # normal repos
-    my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
+    my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } ( sort keys %repos, sort keys %configs );
     # add in members of repo groups
     map { push @repos, keys %{ $groups{$_} } } grep { /^@/ and $_ ne '@all' } keys %repos;
 

commit 3dad4f8e3214d6ab5f71823019a624fa48b055a3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Oct 21 07:01:11 2013 +0530

    oops; fa06a34 had a nasty bug for fresh installs
    
    thanks to Nathan Ferch for catching this.  See thread with subject line "World
    writable files and directories" on the mailing list.

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index b327637..75ff21a 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -93,7 +93,7 @@ do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 # setup some perl/rc/env vars, plus umask
 # ----------------------------------------------------------------------
 
-umask $rc{UMASK};
+umask ( $rc{UMASK} || 0077 );
 
 unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
 

commit aae41621001dc2079d915c0e298682ed3c3404d1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Oct 14 18:24:16 2013 +0530

    v3.5.3

diff --git a/CHANGELOG b/CHANGELOG
index c3a3c81..cf012fc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,21 @@
+2013-10-14  v3.5.3  catch undefined groupnames (when possible)
+
+                    mirroring: async push to slaves
+
+                    (some portability fixes)
+
+                    (a couple of contrib scripts - querying IPA based LDAP
+                    servers for group membership, and user key management)
+
+                    allow groups in subconf files (this *may* slow down
+                    compilation in extreme cases)
+
+                    make adding repo-specific hooks easier (see cust.mkd or
+                    cust.html online for docs)
+
+                    smart http now supports git 1.8.2 and above (which changed
+                    the protocol requirements a wee bit)
+
 2013-07-10  v3.5.2  allow ENV vars to be set from repo options, for use in
                     triggers and hooks
 

commit 5c137a59b307d7347d6a671a18e69c69a8979e5e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Oct 14 16:30:15 2013 +0530

    catch undefined groupnames (when possible)

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 8220316..a5ffe9b 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -113,6 +113,9 @@ sub parse_users {
 
 sub add_rule {
     my ( $perm, $ref, $user ) = @_;
+    _warn "possible undeclared group '$user'"
+        if $user =~ /^@/ and not $groups{$user} and not $rc{GROUPLIST_PGM}
+            and not special_group($user);
     _die "bad ref '$ref'"   unless $ref  =~ $REPOPATT_PATT;
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
@@ -120,6 +123,17 @@ sub add_rule {
     for my $repo (@repolist) {
         push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
     }
+
+    sub special_group {
+        # ok perl doesn't really have lexical subs (at least not the older
+        # perls I want to support) but let's pretend...
+        my $g = shift;
+        $g =~ s/^\@//;
+        return 1 if $g eq 'all' or $g eq 'CREATOR';
+        return 1 if $rc{ROLES}{$g};
+        return 0;
+    }
+
 }
 
 sub add_config {

commit e4ee1233952533362b6437cba5d38f95eeb552e9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Sep 29 10:27:30 2013 +0530

    mirroring: async push to slaves...
    
    tested by a few people independently, on ML and on #gitolite
    
    Note that the changes to the (private) test suite use an arbitrary "sleep 5"
    to allow the other sides to settle.  Without this, there were too many errors
    because the test suite was trying the next command before the previous one had
    effectively finished.

diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 80a2448..6778bc1 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -223,7 +223,7 @@ sub push_to_slaves {
     delete $ENV{GL_USER};    # why?  see src/commands/mirror
 
     for my $s ( sort keys %slaves ) {
-        system("gitolite mirror push $s $repo &");
+        system("gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &");
     }
 
     $ENV{GL_USER} = $u;
diff --git a/t/mirror-test b/t/mirror-test
index 6f605b9..627dc74 100755
--- a/t/mirror-test
+++ b/t/mirror-test
@@ -93,6 +93,7 @@ try "
     git push;                           ok
         /To frodo\@localhost:gitolite-admin/
         /master -> master/
+    sleep 5
     git rev-parse HEAD
 ";
 
@@ -119,6 +120,7 @@ try "
     git push;                           ok
         /To sam\@localhost:gitolite-admin/
         /master -> master/
+    sleep 5
     git rev-parse HEAD
 ";
 
@@ -193,6 +195,7 @@ try "
     git push;                           ok
         /To frodo\@localhost:gitolite-admin/
         /master -> master/
+    sleep 5
 
     sudo -u gollum -i gitolite git-config -r gitolite-admin .
         ok
@@ -235,6 +238,7 @@ try "
     empty
     git push origin master
         /new branch/;   /master -> master/
+    sleep 5
     git rev-parse HEAD
 ";
 chomp($t = text());
@@ -261,6 +265,7 @@ try "
     git push sam\@localhost:r2 master
         /new branch/;   /master -> master/
         /master -> master/
+    sleep 5
     git ls-remote frodo\@localhost:r2
         !/$t/
         /$t2/
@@ -292,6 +297,7 @@ try "
     empty
     git push;                               ok
         /master -> master/
+    sleep 5
     git rev-parse HEAD
 ";
 chomp($t = text());
@@ -314,6 +320,7 @@ try "
 
     git push -f
         /\\+ .......\\.\\.\\........ master -> master .forced update/
+    sleep 5
 ";
 
 swk("u2");
@@ -335,6 +342,7 @@ try "
     tc c
     git push
         /master -> master/
+    sleep 5
     git rev-parse HEAD
 ";
 chomp($t = text());
@@ -354,6 +362,7 @@ try "
 
     git push -f
         /\\+ .......\\.\\.\\........ master -> master .forced update/
+    sleep 5
 
     cd ..
     rm -rf fr1 fr2
@@ -367,6 +376,7 @@ try "
     cd fr2
     tc e
     git push;                               ok
+    sleep 5
 
     git rev-parse HEAD
 ";

commit b3dd62bd5ef18da7cb4d3aed3c28362b00aa7cfe
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Sep 29 10:28:40 2013 +0530

    emulate 'mktemp -u' because that's not portable (uggh!)
    
    It *is* harder to port a shell script than a shell!

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index d173667..d7c023c 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -20,8 +20,11 @@
 
 plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 [ -z "$plf" ] && plf=$HOME/projects.list
-# since mktemp does not honor umask, we just use it to generate a temp filename
-tmpfile=`mktemp -u $plf.tmp_XXXXXXXX`
+# since mktemp does not honor umask, we just use it to generate a temp
+# filename (note: 'mktemp -u' on some systems, this gets close enough)
+tmpfile=`mktemp $plf.tmp_XXXXXXXX`
+unlink $tmpfile;
+
 (
     gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
     gitolite list-phy-repos | gitolite git-config -r % gitweb\\.

commit a30a8f622d75baef7c7f74985376cee5837b9c8c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Sep 29 10:26:18 2013 +0530

    (minor) better error message for missing trigger script

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 5f351f8..b327637 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -295,7 +295,7 @@ sub trigger {
                 } else {
                     $pgm = _which("triggers/$pgm", 'x');
 
-                    _warn("skipped command '$s'"), next if not $pgm;
+                    _warn("skipped trigger '$s' (not found or not executable)"), next if not $pgm;
                     trace( 2, "command: $s" );
                     _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
                 }

commit 3ee8987fe5068056ae72be11cbc1f511be8accf3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Sep 29 10:25:18 2013 +0530

    (minor) make install command slightly more portable

diff --git a/install b/install
index c355f7a..513dd71 100755
--- a/install
+++ b/install
@@ -69,7 +69,7 @@ unless ($version =~ /^v\d/) {
 
 if ($to) {
     _mkdir($to);
-    system("cp -a * $to");
+    system("cp -RpP * $to");
     _print( "$to/VERSION", $version );
 } elsif ($ln) {
     ln_sf( $ENV{GL_BINDIR}, "gitolite", $ln );

commit 437b49718320e4898c5f86bfb6f8ee0b150ca2a4
Author: Richard Clark <richard at fohnet.co.uk>
Date:   Sun Sep 8 15:56:59 2013 +0100

    Add script for querying IPA-based LDAP servers for group membership

diff --git a/contrib/utils/ipa_groups.pl b/contrib/utils/ipa_groups.pl
new file mode 100755
index 0000000..9cffa40
--- /dev/null
+++ b/contrib/utils/ipa_groups.pl
@@ -0,0 +1,229 @@
+#!/usr/bin/env perl
+#
+# ipa_groups.pl
+#
+# See perldoc for usage
+#
+use Net::LDAP;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED);
+use strict;
+use warnings;
+
+my $usage = <<EOD;
+Usage: $0 \$uid
+This script returns a list of groups that \$uid is a member of
+EOD
+
+my $uid = shift or die $usage;
+
+## CONFIG SECTION
+
+# If you want to do plain-text LDAP, then set ldap_opts to an empty hash and
+# then set protocols of ldap_hosts to ldap://
+my @ldap_hosts = [
+  'ldaps://auth-ldap-001.prod.example.net',
+  'ldaps://auth-ldap-002.prod.example.net',
+];
+my %ldap_opts = (
+    verify => 'require',
+    cafile => '/etc/pki/tls/certs/prod.example.net_CA.crt'
+);
+
+# Base DN to search
+my $base_dn = 'dc=prod,dc=example,dc=net';
+
+# User for binding to LDAP server with
+my $user = 'uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net';
+my $pass = 'reallysecurepasswordstringhere';
+
+## Below variables should not need to be changed under normal circumstances
+
+# OU where groups are located. Anything return that is not within this OU is
+# removed from results. This OU is static on FreeIPA so will only need updating
+# if you want to support other LDAP servers. This is a regex so can be set to
+# anything you want (E.G '.*').
+my $groups_ou = qr/cn=groups,cn=accounts,${base_dn}$/;
+
+# strip path - if you want to return the full path of the group object then set
+# this to 0
+my $strip_group_paths = 1;
+
+# Number of seconds before timeout (for each query)
+my $timeout=5;
+
+# user object class
+my $user_oclass = 'person';
+
+# group attribute
+my $group_attrib = 'memberOf';
+
+## END OF CONFIG SECTION
+
+# Catch timeouts here
+$SIG{'ALRM'} = sub {
+  die "LDAP queries timed out";
+};
+
+alarm($timeout);
+
+# try each server until timeout is reached, has very fast failover if a server
+# is totally unreachable
+my $ldap = Net::LDAP->new(@ldap_hosts, %ldap_opts) ||
+  die "Error connecting to specified servers: $@ \n";
+
+my $mesg = $ldap->bind(
+    dn       => $user,
+    password => $pass
+);
+
+if ($mesg->code()) {
+  die ("error:",      $mesg->code(),"\n",
+       "error name: ",$mesg->error_name(),"\n",
+       "error text: ",$mesg->error_text(),"\n");
+}
+
+# How many LDAP query results to grab for each paged round
+# Set to under 1000 to limit load on LDAP server
+my $page = Net::LDAP::Control::Paged->new(size => 500);
+
+# @queries is an array or array references. We initially fill it up with one
+# arrayref (The first LDAP search) and then add more during the execution.
+# First start by resolving the group.
+my @queries = [ ( base    => $base_dn,
+                  filter  => "(&(objectClass=${user_oclass})(uid=${uid}))",
+                  control => [ $page ],
+) ];
+
+# array to store groups matching $groups_ou
+my @verified_groups;
+
+# Loop until @queries is empty...
+foreach my $queryref (@queries) {
+
+  # set cookie for paged querying
+  my $cookie;
+  alarm($timeout);
+  while (1) {
+    # Perform search
+    my $mesg = $ldap->search( @{$queryref} );
+
+    foreach my $entry ($mesg->entries) {
+      my @groups = $entry->get_value($group_attrib);
+      # find any groups matching $groups_ou  regex and push onto $verified_groups array
+      foreach my $group (@groups) {
+        if ($group =~ /$groups_ou/) {
+          push @verified_groups, $group;
+        }
+      }
+    }
+
+    # Only continue on LDAP_SUCCESS
+    $mesg->code and last;
+
+    # Get cookie from paged control
+    my($resp)  = $mesg->control(LDAP_CONTROL_PAGED) or last;
+    $cookie    = $resp->cookie or last;
+
+    # Set cookie in paged control
+    $page->cookie($cookie);
+  } # END: while(1)
+
+  # Reset the page control for the next query
+  $page->cookie(undef);
+
+  if ($cookie) {
+    # We had an abnormal exit, so let the server know we do not want any more
+    $page->cookie($cookie);
+    $page->size(0);
+    $ldap->search( @{$queryref} );
+    # Then die
+    die("LDAP query unsuccessful");
+  }
+
+} # END: foreach my $queryref (...)
+
+# we're assuming that the group object looks something like
+# cn=name,cn=groups,cn=accounts,dc=X,dc=Y and there are no ',' chars in group
+# names
+if ($strip_group_paths) {
+  for (@verified_groups) { s/^cn=([^,]+),.*$/$1/g };
+}
+
+foreach my $verified (@verified_groups) {
+  print $verified . "\n";
+}
+
+alarm(0);
+
+__END__
+
+=head1 NAME
+
+ipa_groups.pl
+
+=head2 VERSION
+
+0.1.1
+
+=head2 DESCRIPTION
+
+Connects to one or more FreeIPA-based LDAP servers in a first-reachable fashion and returns a newline separated list of groups for a given uid. Uses memberOf attribute and thus supports nested groups.
+
+=head2 AUTHOR
+
+Richard Clark <rclark at telnic.org>
+
+=head2 FreeIPA vs Generic LDAP
+
+This script uses regular LDAP, but is focussed on support for FreeIPA, where users and groups are generally contained within single OUs, and memberOf attributes within the user object are enumerated with a recursive list of groups that the user is a member of.
+
+It is mostly impossible to provide generic out of the box LDAP support due to varying schemas, supported extensions and overlays between implementations.
+
+=head2 CONFIGURATION
+
+=head3  LDAP Bind Account 
+
+To setup an LDAP bind user in FreeIPA, create a svc_gitolite_bind.ldif file along the following lines:
+
+    dn: uid=svc_gitolite_bind,cn=sysaccounts,cn=etc,dc=prod,dc=example,dc=net
+    changetype: add
+    objectclass: account
+    objectclass: simplesecurityobject
+    uid: svc_gitolite_bind
+    userPassword: reallysecurepasswordstringhere
+    passwordExpirationTime: 20150201010101Z
+    nsIdleTimeout: 0
+
+Then create the service account user, using ldapmodify authenticating as the the directory manager account (or other acccount with appropriate privileges to the sysaccounts OU):
+
+    $ ldapmodify -h auth-ldap-001.prod.example.net -Z -x -D "cn=Directory Manager" -W -f svc_gitolite_bind.ldif
+
+=head3 Required Configuration
+
+The following variables within the C<## CONFIG SECTION ##> need to be configured before the script will work.
+
+C<@ldap_hosts> - Should be set to an array of URIs or hosts to connect to. Net::LDAP will attempt to connect to each host in this list and stop on the first reachable server. The example shows TLS-supported URIs, if you want to use plain-text LDAP then set the protocol part of the URI to LDAP:// or just provide hostnames as this is the default behavior for Net::LDAP.
+
+C<%ldap_opts> - To use LDAP-over-TLS, provide the CA certificate for your LDAP servers. To use plain-text LDAP, then empty this hash of it's values or provide other valid arguments to Net::LDAP.
+
+C<%base_dn> - This can either be set to the 'true' base DN for your directory, or alternatively you can set it the the OU that your users are located in (E.G cn=users,cn=accounts,dc=prod,dc=example,dc=net).
+
+C<$user> - Provide the full Distinguished Name of your directory bind account as configured above.
+
+C<$pass> - Set to password of your directory bind account as configured above.
+
+=head3 Optional Configuration
+
+C<$groups_ou> - By default this is a regular expression matching the default groups OU. Any groups not matching this regular expression are removed from the search results. This is because FreeIPA enumerates non-user type groups (E.G system, sudoers, policy and other types) within the memberOf attribute. To change this behavior, set C<$groups_ou> to a regex matching anything you want (E.G: '.*').
+
+C<$strip_group_paths> - If this is set to perl boolean false (E.G '0') then groups will be returned in DN format. Default is true, so just the short/CN value is returned.
+
+C<$timeout> - Number of seconds to wait for an LDAP query before determining that it has failed and trying the next server in the list. This does not affect unreachable servers, which are failed immediately.
+
+C<$user_oclass> - Object class of the user to search for.
+
+C<$group_attrib> - Attribute to search for within the user object that denotes the membership of a group.
+
+=cut
+

commit 59c817d06fbee0035fa7f078be789dc3dfed6561
Author: Ralf Hemmecke <ralf at hemmecke.org>
Date:   Thu Mar 7 19:15:55 2013 +0100

    (contrib) user key management
    
    committer's notes:
    
    code here, and docs in gitolite-doc repo, fetched from
    
        git://github.com/hemmecke/gitolite
        git://github.com/hemmecke/gitolite-doc
    
    respectively.

diff --git a/contrib/commands/ukm b/contrib/commands/ukm
new file mode 100755
index 0000000..8a2d361
--- /dev/null
+++ b/contrib/commands/ukm
@@ -0,0 +1,732 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Easy;
+
+=for usage
+Usage for this command is not that simple. Please read the full
+documentation in
+https://github.com/sitaramc/gitolite-doc/blob/master/contrib/ukm.mkd
+or online at http://gitolite.com/gitolite/ukm.html.
+=cut
+
+usage() if @ARGV and $ARGV[0] eq '-h';
+
+# Terms used in this file.
+# pubkeypath: the (relative) filename of a public key starting from
+#   gitolite-admin/keydir. Examples: alice.pub, foo/bar/alice.pub,
+#   alice at home.pub, foo/alice at laptop.pub. You get more examples, if you
+#   replace "alice" by "bob at example.com".
+# userid: computed from a pubkeypath by removing any directory
+#   part, the '.pub' extension and the "old-style" @NAME classifier.
+#   The userid identifies a user in the gitolite.conf file.
+# keyid: an identifier for a key given on the command line.
+#   If the script is called by one of the super_key_managers, then the
+#   keyid is the pubkeypath without the '.pub' extension. Otherwise it
+#   is the userid for a guest.
+#   The keyid is normalized to lowercase letters.
+
+my $rb = $rc{GL_REPO_BASE};
+my $ab = $rc{GL_ADMIN_BASE};
+
+# This will be the subdirectory under "keydir" in which the guest
+# keys will be stored. To prevent denial of service, this directory
+# should better start with 'zzz'.
+# The actual value can be set through the GUEST_DIRECTORY resource.
+# WARNING: If this value is changed you must understand the consequences.
+#          There will be no support if guestkeys_dir is anything else than
+#          'zzz/guests'.
+my $guestkeys_dir = 'zzz/guests';
+
+# A guest key cannot have arbitrary names (keyid). Only keys that do *not*
+# match $forbidden_guest_pattern are allowed. Super-key-managers can add
+# any keyid.
+
+# This is the directory for additional keys of a self key manager.
+my $selfkeys_dir = 'zzz/self';
+# There is no flexibility for selfkeys. One must specify a keyid that
+# matches the regular expression '^@[a-z0-9]+$'. Note that all keyids
+# are transformed to lowercase before checking.
+my $required_self_pattern = qr([a-z0-9]+);
+my $selfkey_management = 0; # disable selfkey managment
+
+# For guest key managers the keyid must pass two tests.
+#   1) It must match the $required_guest_pattern regular expression.
+#   2) It must not match the $forbidden_guest_pattern regular expression.
+# Default for $forbidden_guest_pattern is qr(.), i.e., every keyid is
+# forbidden, or in other words, only the gitolite-admin can manage keys.
+# Default for $required_guest_pattern is such that the keyid must look
+# like an email address, i.e. must have exactly one @ and at least one
+# dot after the @.
+# Just setting 'ukm' => 1 in .gitolite.rc only allows the super-key-managers
+# (i.e., only the gitolite admin(s)) to manage keys.
+my $required_guest_pattern =
+    qr(^[0-9a-z][-0-9a-z._+]*@[-0-9a-z._+]+[.][-0-9a-z._+]+$);
+my $forbidden_guest_pattern = qr(.);
+
+die "The command 'ukm' is not enabled.\n" if ! $rc{'COMMANDS'}{'ukm'};
+
+my $km = $rc{'UKM_CONFIG'};
+if(ref($km) eq 'HASH') {
+    # If not set we only allow keyids that look like emails
+    my $rgp = $rc{'UKM_CONFIG'}{'REQUIRED_GUEST_PATTERN'} || '';
+    $required_guest_pattern = qr(^($rgp)$) if $rgp;
+    $forbidden_guest_pattern = $rc{'UKM_CONFIG'}{'FORBIDDEN_GUEST_PATTERN'}
+                            || $forbidden_guest_pattern;
+    $selfkey_management = $rc{'UKM_CONFIG'}{'SELFKEY_MANAGEMENT'} || 0;
+}
+
+# get the actual userid
+my $gl_user = $ENV{GL_USER};
+my $super_key_manager = is_admin(); # or maybe is_super_admin() ?
+
+# save arguments for later
+my $operation = shift || 'list';
+my $keyid     = shift || '';
+$keyid = lc $keyid; # normalize to lowercase ids
+
+my ($zop, $zfp, $zselector, $zuser) = get_pending($gl_user);
+# The following will only be true if a selfkey manager logs in to
+# perform a pending operation.
+my $pending_self = ($zop ne '');
+
+die "You are not a key manager.\n"
+    unless $super_key_manager || $pending_self
+           || in_group('guest-key-managers')
+           || in_group('self-key-managers');
+
+# Let's deal with the pending user first. The only allowed operations
+# that are to confirm the add operation with the random code
+# that must be provided via stdin or to undo a pending del operation.
+if ($pending_self) {
+    pending_user($gl_user, $zop, $zfp, $zselector, $zuser);
+    exit;
+}
+
+my @available_operations = ('list','add','del');
+die "unknown ukm subcommand: $operation\n"
+    unless grep {$operation eq $_} @available_operations;
+
+# get to the keydir
+_chdir("$ab/keydir");
+
+# Note that the program warns if it finds a fingerprint that maps to
+# different userids.
+my %userids = (); # mapping from fingerprint to userid
+my %fingerprints = (); # mapping from pubkeypath to fingerprint
+my %pubkeypaths = (); # mapping from userid to pubkeypaths
+                      # note that the result is a list of pubkeypaths
+
+# Guest keys are managed by people in the @guest-key-managers group.
+# They can only add/del keys in the $guestkeys_dir directory. In fact,
+# the guest key manager $gl_user has only access to keys inside
+# %guest_pubkeypaths.
+my %guest_pubkeypaths = (); # mapping from userid to pubkeypath for $gl_user
+
+# Self keys are managed by people in the @self-key-managers group.
+# They can only add/del keys in the $selfkeys_dir directory. In fact,
+# the self key manager $gl_user has only access to keys inside
+# %self_pubkeypaths.
+my %self_pubkeypaths = ();
+
+# These are the keys that are managed by a super key manager.
+my @all_pubkeypaths = `find . -type f -name "*.pub" 2>/dev/null | sort`;
+
+for my $pubkeypath (@all_pubkeypaths) {
+    chomp($pubkeypath);
+    my $fp = fingerprint($pubkeypath);
+    $fingerprints{$pubkeypath} = $fp;
+    my $userid = get_userid($pubkeypath);
+    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
+    $userid = $zuser if $zop;
+    if (! defined $userids{$fp}) {
+        $userids{$fp} = $userid;
+    } else {
+        warn "key $fp is used for different user ids\n"
+            unless $userids{$fp} eq $userid;
+    }
+    push @{$pubkeypaths{$userid}}, $pubkeypath;
+    if ($pubkeypath =~ m|^./$guestkeys_dir/([^/]+)/[^/]+\.pub$|) {
+        push @{$guest_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
+    }
+    if ($pubkeypath =~ m|^./$selfkeys_dir/([^/]+)/[^/]+\.pub$|) {
+        push @{$self_pubkeypaths{$userid}}, $pubkeypath if $gl_user eq $1;
+    }
+}
+
+###################################################################
+# do stuff according to the operation
+###################################################################
+
+if ( $operation eq 'list' ) {
+    list_pubkeys();
+    print "\n\n";
+    exit;
+}
+
+die "keyid required\n" unless $keyid;
+die "Not allowed to use '..' in keyid.\n" if $keyid =~ /\.\./;
+
+if ( $operation eq 'add' ) {
+    if ($super_key_manager) {
+        add_pubkey($gl_user, "$keyid.pub", safe_stdin());
+    } elsif (selfselector($keyid)) {
+        add_self($gl_user, $keyid, safe_stdin());
+    } else {
+        # assert ingroup('guest-key-managers');
+        add_guest($gl_user, $keyid, safe_stdin());
+    }
+} elsif ( $operation eq 'del' ) {
+    if ($super_key_manager) {
+        del_super($gl_user, "$keyid.pub");
+    } elsif (selfselector($keyid)) {
+        del_self($gl_user, $keyid);
+    } else {
+        # assert ingroup('guest-key-managers');
+        del_guest($gl_user, $keyid);
+    }
+}
+
+exit;
+
+
+###################################################################
+# only function definitions are following
+###################################################################
+
+# make a temp clone and switch to it
+our $TEMPDIR;
+BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; chomp($TEMPDIR) }
+END { my $err = $?; `/bin/rm -rf $TEMPDIR`; $? = $err; }
+
+sub cd_temp_clone {
+    chomp($TEMPDIR);
+    hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR/gitolite-admin" );
+    chdir("$TEMPDIR/gitolite-admin");
+    my $ip = $ENV{SSH_CONNECTION};
+    $ip =~ s/ .*//;
+    my ($zop, $zfp, $zselector, $zuser) = get_pending($ENV{GL_USER});
+    my $email = $zuser;
+    $email .= '@' . $ip  unless $email =~ m(@);
+    my $name = $zop ? "\@$zselector" : $zuser;
+    # Record the keymanager in the gitolite-admin repo as author of the change.
+    hushed_git( "config", "user.email", "$email" );
+    hushed_git( "config", "user.name",  "'$name from $ip'" );
+}
+
+# compute the fingerprint from the full path of a pubkey file
+sub fingerprint {
+    my $fp = `ssh-keygen -l -f $_[0]`;
+    die "does not seem to be a valid pubkey\n"
+        unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+) /i;
+    return $1;
+}
+
+
+# Read one line from STDIN and return it.
+#  If no data is available on STDIN after one second, the empty string
+# is returned.
+# If there is more than one line or there was an error in reading, the
+# function dies.
+sub safe_stdin {
+    use IO::Select;
+    my $s=IO::Select->new(); $s->add(\*STDIN);
+    return '' unless $s->can_read(1);
+    my $data;
+    my $ret = read STDIN, $data, 4096;
+    # current pubkeys are approx 400 bytes so we go a little overboard
+    die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n"
+        unless $ret;
+    die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
+    return $data;
+}
+
+# call git, be quiet
+sub hushed_git {
+    system("git " . join(" ", @_) . ">/dev/null 2>/dev/null");
+}
+
+# Extract the userid from the full path of the pubkey file (relative
+# to keydir/ and including the '.pub' extension.
+sub get_userid {
+    my ($u) = @_; # filename of pubkey relative to keydir/.
+    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
+    $u =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
+    return $u;
+}
+
+# Extract the @selector part from the full path of the pubkey file
+# (relative to keydir/ and including the '.pub' extension).
+# If there is no @selector part, the empty string is returned.
+# We also correctly extract the selector part from pending keys.
+sub get_selector {
+    my ($u) = @_; # filename of pubkey relative to keydir/.
+    $u =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
+    $u =~ s(\.pub$)();             # baz at home.pub -> baz at home
+    return $1 if $u =~ m/.\@($required_self_pattern)$/; # baz at home -> home
+    my ($zop, $zfp, $zselector, $zuser) = get_pending($u);
+    # If $u was not a pending key, then $zselector is the empty string.
+    return $zselector;
+}
+
+# Extract fingerprint, operation, selector, and true userid from a
+# pending userid.
+sub get_pending {
+    my ($gl_user) = @_;
+    return ($1, $2, $3, $4)
+       if ($gl_user=~/^zzz-(...)-([0-9a-f]{32})-($required_self_pattern)-(.*)/);
+    return ('', '', '', $gl_user)
+}
+
+# multiple / and are simplified to one / and the path is made relative
+sub sanitize_pubkeypath {
+    my ($pubkeypath) = @_;
+    $pubkeypath =~ s|//|/|g; # normalize path
+    $pubkeypath =~ s,\./,,g; # remove './' from path
+    return './'.$pubkeypath; # Don't allow absolute paths.
+}
+
+# This function is only relavant for guest key managers.
+# It returns true if the pattern is OK and false otherwise.
+sub required_guest_keyid {
+    my ($_) = @_;
+    /$required_guest_pattern/ and ! /$forbidden_guest_pattern/;
+}
+
+# The function takes a $keyid as input and returns the keyid with the
+# initial @ stripped if everything is fine. It aborts with an error if
+# selfkey management is not enabled or the function is called for a
+# non-self-key-manager.
+# If the required selfkey pattern is not matched, it returns an empty string.
+# Thus the function can be used to check whether a given keyid is a
+# proper selfkeyid.
+sub selfselector {
+    my ($keyid) = @_;
+    return '' unless $keyid =~ m(^\@($required_self_pattern)$);
+    $keyid = $1;
+    die "selfkey management is not enabled\n" unless $selfkey_management;
+    die "You are not a selfkey manager.\n" if ! in_group('self-key-managers');
+    return $keyid;
+}
+
+# Return the number of characters reserved for the userid field.
+sub userid_width {
+    my ($paths) = @_;
+    my (%pkpaths) = %{$paths};
+    my (@userid_lengths) = sort {$a <=> $b} (map {length($_)} keys %pkpaths);
+    @userid_lengths ? $userid_lengths[-1] : 0;
+}
+
+# List the keys given by a reference to a hash.
+# The regular expression $re is used to remove the initial part of the
+# keyid and replace it by what is matched inside the parentheses.
+# $format and $width are used for pretty printing
+sub list_keys {
+    my ($paths, $tokeyid, $format, $width) = @_;
+    my (%pkpaths) = %{$paths};
+    for my $userid (sort keys %pkpaths) {
+        for my $pubkeypath (sort @{$pkpaths{$userid}}) {
+            my $fp = $fingerprints{$pubkeypath};
+            my $userid = $userids{$fp};
+            my $keyid = &{$tokeyid}($pubkeypath);
+            printf $format,$fp,$userid,$width+1-length($userid),"",$keyid
+                if ($super_key_manager
+                    || required_guest_keyid($keyid)
+                    || $keyid=~m(^\@));
+        }
+    }
+}
+
+# Turn a pubkeypath into a keyid for super-key-managers, guest-keys,
+# and self-keys.
+sub superkeyid {
+    my ($keyid) = @_;
+    $keyid =~ s(\.pub$)();
+    $keyid =~ s(^\./)();
+    return $keyid;
+}
+
+sub guestkeyid {
+    my ($keyid) = @_;
+    $keyid =~ s(\.pub$)();
+    $keyid =~ s(^.*/)();
+    return $keyid;
+}
+
+sub selfkeyid {
+    my ($keyid) = @_;
+    $keyid =~ s(\.pub$)();
+    $keyid =~ s(^.*/)();
+    my ($zop, $zfp, $zselector, $zuser) = get_pending($keyid);
+    return "\@$zselector (pending $zop)" if $zop;
+    $keyid =~ s(.*@)(@);
+    return $keyid;
+}
+
+###################################################################
+
+# List public keys managed by the respective user.
+# The fingerprints, userids and keyids are printed.
+# keyids are shown in a form that can be used for add and del
+# subcommands. 
+sub list_pubkeys {
+    print "Hello $gl_user, you manage the following keys:\n";
+    my $format = "%-47s %s%*s%s\n";
+    my $width = 0;
+    if ($super_key_manager) {
+        $width = userid_width(\%pubkeypaths);
+        $width = 6 if $width < 6; # length("userid")==6
+        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
+        list_keys(\%pubkeypaths, , \&superkeyid, $format, $width);
+    } else {
+        my $widths = $selfkey_management?userid_width(\%self_pubkeypaths):0;
+        my $widthg = userid_width(\%guest_pubkeypaths);
+        $width = $widths > $widthg ? $widths : $widthg; # maximum width
+        return unless $width; # there are no keys
+        $width = 6 if $width < 6; # length("userid")==6
+        printf $format, "fingerprint", "userid", ($width-5), "", "keyid";
+        list_keys(\%self_pubkeypaths, \&selfkeyid, $format, $width)
+            if $selfkey_management;
+        list_keys(\%guest_pubkeypaths, \&guestkeyid,  $format, $width);
+    }
+}
+
+
+###################################################################
+
+# Add a public key for the user $gl_user.
+# $pubkeypath is the place where the new key will be stored.
+# If the file or its fingerprint already exists, the operation is
+# rejected.
+sub add_pubkey {
+    my ( $gl_user, $pubkeypath, $keymaterial ) = @_;
+    if(! $keymaterial) {
+        print STDERR "Please supply the new key on STDIN.\n";
+        print STDERR "Try something like this:\n";
+        print STDERR "cat FOO.pub | ssh GIT\@GITOLITESERVER ukm add KEYID\n";
+        die "missing public key data\n";
+    }
+    # clean pubkeypath a bit
+    $pubkeypath = sanitize_pubkeypath($pubkeypath);
+    # Check that there is not yet something there already.
+    die "cannot override existing key\n" if $fingerprints{$pubkeypath};
+
+    my $userid = get_userid($pubkeypath);
+    # Super key managers shouldn't be able to add a that leads to
+    # either an empty userid or to a userid that starts with @.
+    #
+    # To avoid confusion, all keyids for super key managers must be in
+    # a full path format. Having a public key of the form
+    # gitolite-admin/keydir/@foo.pub might be confusing and might lead
+    # to other problems elsewhere.
+    die "cannot add key that starts with \@\n" if (!$userid) || $userid=~/^@/;
+
+    cd_temp_clone();
+    _chdir("keydir");
+    $pubkeypath =~ m((.*)/); # get the directory part
+    _mkdir($1);
+    _print($pubkeypath, $keymaterial);
+    my $fp = fingerprint($pubkeypath);
+
+    # Maybe we are adding a selfkey.
+    my ($zop, $zfp, $zselector, $zuser) = get_pending($userid);
+    my $user = $zop ? "$zuser\@$zselector" : $userid;
+    $userid = $zuser;
+    # Check that there isn't a key with the same fingerprint under a
+    # different userid.
+    if (defined $userids{$fp}) {
+        if ($userid ne $userids{$fp}) {
+            print STDERR "Found  $fp $userids{$fp}\n" if $super_key_manager;
+            print STDERR "Same key is already available under another userid.\n";
+            die "cannot add key\n";
+        } elsif ($zop) {
+            # Because of the way a key is confirmed with ukm, it is
+            # impossible to confirm the initial key of the user as a
+            # new selfkey. (It will lead to the function list_pubkeys
+            # instead of pending_user_add, because the gl_user will
+            # not be that of a pending user.) To avoid confusion, we,
+            # therefore, forbid to add the user's initial key
+            # altogether.
+            # In fact, we here also forbid to add any key for that
+            # user that is already in the system.
+            die "You cannot add a key that already belongs to you.\n";
+        }
+    } else {# this fingerprint does not yet exist
+        my @paths = @{$pubkeypaths{$userid}} if defined $pubkeypaths{$userid};
+        if (@paths) {# there are already keys for $userid
+            if (grep {$pubkeypath eq $_} @paths) {
+                print STDERR "The keyid is already present. Nothing changed.\n";
+            } elsif ($super_key_manager) {
+                # It's OK to add new selfkeys, but here we are in the case
+                # of adding multiple keys for guests. That is forbidden.
+                print STDERR "Adding new public key for $userid.\n";
+            } elsif ($pubkeypath =~ m(^\./$guestkeys_dir/)) {
+                # Arriving here means we are about to add a *new*
+                # guest key, because the fingerprint is not yet
+                # existing. This would be for an already existing
+                # userid (added by another guest key manager). Since
+                # that effectively means to (silently) add an
+                # additional key for an existing user, it must be
+                # forbidden.
+                die "cannot add another public key for an existing user\n";
+            }
+        }
+    }
+    exit if (`git status -s` eq ''); # OK to add identical keys twice
+    hushed_git( "add", "." ) and die "git add failed\n";
+    hushed_git( "commit", "-m", "'ukm add $gl_user $userid\n\n$fp'" )
+        and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+# Guest key managers should not be allowed to add directories or
+# multiple keys via the @domain mechanism, since this might allow
+# another guest key manager to give an attacker access to another
+# user's repositories.
+#
+# Example: Alice adds bob.pub for bob at example.org. David adds eve.pub
+# (where only Eve but not Bob has the private key) under the keyid
+# bob at example.org@foo. This basically gives Eve the same rights as
+# Bob.
+sub add_guest {
+    my ( $gl_user, $keyid, $keymaterial ) = @_;
+    die "keyid not allowed: '$keyid'\n"
+        if $keyid =~ m(@.*@) or $keyid =~ m(/) or !required_guest_keyid($keyid);
+    add_pubkey($gl_user, "$guestkeys_dir/$gl_user/$keyid.pub", $keymaterial);
+}
+
+# Add a new selfkey for user $gl_user.
+sub add_self {
+    my ( $gl_user, $keyid, $keymaterial ) = @_;
+    my $selector = "";
+    $selector = selfselector($keyid); # might return empty string
+    die "keyid not allowed: $keyid\n" unless $selector;
+
+    # Check that the new selector is not already in use even not in a
+    # pending state.
+    die "keyid already in use: $keyid\n"
+        if grep {selfkeyid($_)=~/^\@$selector( .*)?$/} @{$self_pubkeypaths{$gl_user}};
+    # generate new pubkey create fingerprint
+    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
+    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
+    $sessionfp =~ s/://g;
+    my $user = "zzz-add-$sessionfp-$selector-$gl_user";
+    add_pubkey($gl_user, "$selfkeys_dir/$gl_user/$user.pub", $keymaterial);
+    print `cat "$TEMPDIR/session.pub"`;
+}
+
+###################################################################
+
+
+# Delete a key of user $gl_user.
+sub del_pubkey {
+    my ($gl_user, $pubkeypath) = @_;
+    $pubkeypath = sanitize_pubkeypath($pubkeypath);
+    my $fp = $fingerprints{$pubkeypath};
+    die "key not found\n" unless $fp;
+    cd_temp_clone();
+    chdir("keydir");
+    hushed_git( "rm", "$pubkeypath" ) and die "git rm failed\n";
+    my $userid = get_userid($pubkeypath);
+    hushed_git( "commit", "-m", "'ukm del $gl_user $userid\n\n$fp'" )
+        and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+# $gl_user is a super key manager. This function aborts if the
+# superkey manager tries to remove his last key.
+sub del_super {
+    my ($gl_user, $pubkeypath) = @_;
+    $pubkeypath = sanitize_pubkeypath($pubkeypath);
+    die "You are not managing the key $keyid.\n"
+        unless grep {$_ eq $pubkeypath} @all_pubkeypaths;
+    my $userid = get_userid($pubkeypath);
+    if ($gl_user eq $userid) {
+        my @paths = @{$pubkeypaths{$userid}};
+        die "You cannot delete your last key.\n"
+            if scalar(grep {$userid eq get_userid($_)} @paths)<2;
+    }
+    del_pubkey($gl_user, $pubkeypath);
+}
+
+sub del_guest {
+    my ($gl_user, $keyid) = @_;
+    my $pubkeypath = sanitize_pubkeypath("$guestkeys_dir/$gl_user/$keyid.pub");
+    my $userid = get_userid($pubkeypath);
+    # Check whether $gl_user actually manages $keyid.
+    my @paths = ();
+    @paths = @{$guest_pubkeypaths{$userid}}
+        if defined $guest_pubkeypaths{$userid};
+    die "You are not managing the key $keyid.\n"
+        unless grep {$_ eq $pubkeypath} @paths;
+    del_pubkey($gl_user, $pubkeypath);
+}
+
+# Delete a selfkey of $gl_user. The first delete is a preparation of
+# the deletion and only a second call will actually delete the key. If
+# the second call is done with the key that is scheduled for deletion,
+# it is basically undoing the previous del call. This last case is
+# handled in function pending_user_del.
+sub del_self {
+    my ($gl_user, $keyid) = @_;
+    my $selector = selfselector($keyid); # might return empty string
+    die "keyid not allowed: '$keyid'\n" unless $selector;
+
+    # Does $gl_user actually manage that keyid?
+    # All (non-pending) selfkeys have an @selector part in their pubkeypath.
+    my @paths = @{$self_pubkeypaths{$gl_user}};
+    die "You are not managing the key $keyid.\n"
+        unless grep {$selector eq get_selector($_)} @paths;
+
+    cd_temp_clone();
+    _chdir("keydir");
+    my $fp = '';
+    # Is it the first or the second del call? It's the second call, if
+    # there is a scheduled-for-deletion or scheduled-for-addition
+    # selfkey which has the given keyid as a selector part.
+    @paths = grep {
+        my ($zop, $zfp, $zselector, $zuser) = get_pending(get_userid($_));
+        $zselector eq $selector
+    } @paths;
+    if (@paths) {# start actual deletion of the key (second call)
+        my $pubkeypath = $paths[0];
+        $fp = fingerprint($pubkeypath);
+        my ($zop, $zf, $zs, $zu) = get_pending(get_userid($pubkeypath));
+        $zop = $zop eq 'add' ? 'undo-add' : 'confirm-del';
+        hushed_git("rm", "$pubkeypath") and die "git rm failed\n";
+        hushed_git("commit", "-m", "'ukm $zop $gl_user\@$selector\n\n$fp'")
+            and die "git commit failed\n";
+        system("gitolite push >/dev/null 2>/dev/null")
+            and die "git push failed\n";
+        print STDERR "pending keyid deleted: \@$selector\n";
+        return;
+    }
+    my $oldpubkeypath = "$selfkeys_dir/$gl_user/$gl_user\@$selector.pub";
+    # generate new pubkey and create fingerprint to get a random number
+    system("ssh-keygen -N '' -q -f \"$TEMPDIR/session\" -C $gl_user");
+    my $sessionfp = fingerprint("$TEMPDIR/session.pub");
+    $sessionfp =~ s/://g;
+    my $user = "zzz-del-$sessionfp-$selector-$gl_user";
+    my $newpubkeypath = "$selfkeys_dir/$gl_user/$user.pub";
+
+    # A key for gitolite access that is in authorized_keys and not
+    # existing in the expected place under keydir/ should actually not
+    # happen, but one never knows.
+    die "key not available\n" unless -r $oldpubkeypath;
+
+    # For some strange reason the target key already exists.
+    die "cannot override existing key\n" if -e $newpubkeypath;
+
+    $fp = fingerprint($oldpubkeypath);
+    print STDERR "prepare deletion of key \@$selector\n";
+    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
+        and die "git mv failed\n";
+    hushed_git("commit", "-m", "'ukm prepare-del $gl_user\@$selector\n\n$fp'")
+        and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null")
+        and die "git push failed\n";
+}
+
+###################################################################
+# Adding a selfkey should be done as follows.
+#
+#   cat newkey.pub | ssh git at host ukm add @selector > session
+#   cat session | ssh -i newkey git at host ukm
+#
+# The provided random data will come from a newly generated ssh key
+# whose fingerprint will be stored in $gl_user. So we compute the
+# fingerprint of the data that is given to us. If it doesn't match the
+# fingerprint, then something went wrong and the confirm operation is
+# forbidden, in fact, the pending key will be removed from the system.
+sub pending_user_add {
+    my ($gl_user, $zfp, $zselector, $zuser) = @_;
+    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
+    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";
+
+    # A key for gitolite access that is in authorized_keys and not
+    # existing in the expected place under keydir/ should actually not
+    # happen, but one never knows.
+    die "key not available\n" unless -r $oldpubkeypath;
+
+    my $keymaterial = safe_stdin();
+    # If there is no keymaterial (which corresponds to a session key
+    # for the confirm-add operation), logging in to this key, removes
+    # it from the system.
+    my $session_key_not_provided = '';
+    if (!$keymaterial) {
+        $session_key_not_provided = "missing session key";
+    } else {
+        _print("$TEMPDIR/session.pub", $keymaterial);
+        my $sessionfp = fingerprint("$TEMPDIR/session.pub");
+        $sessionfp =~ s/://g;
+        $session_key_not_provided = "session key not accepted"
+            unless ($zfp eq $sessionfp)
+    }
+    my $fp = fingerprint($oldpubkeypath);
+    if ($session_key_not_provided) {
+        print STDERR "$session_key_not_provided\n";
+        print STDERR "pending keyid deleted: \@$zselector\n";
+        hushed_git("rm", "$oldpubkeypath") and die "git rm failed\n";
+        hushed_git("commit", "-m", "'ukm del $zuser\@$zselector\n\n$fp'")
+            and die "git commit failed\n";
+        system("gitolite push >/dev/null 2>/dev/null")
+            and die "git push failed\n";
+        return;
+    }
+
+    # For some strange reason the target key already exists.
+    die "cannot override existing key\n" if -e $newpubkeypath;
+
+    print STDERR "pending keyid added: \@$zselector\n";
+    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
+        and die "git mv failed\n";
+    hushed_git("commit", "-m", "'ukm confirm-add $zuser\@$zselector\n\n$fp'")
+        and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null")
+        and die "git push failed\n";
+}
+
+# To delete a key, one must first bring the key into a pending state
+# and then truely delete it with another key. In case, the login
+# happens with the pending key (implemented below), it means that the
+# delete operation has to be undone.
+sub pending_user_del {
+    my ($gl_user, $zfp, $zselector, $zuser) = @_;
+    my $oldpubkeypath = "$selfkeys_dir/$zuser/$gl_user.pub";
+    my $newpubkeypath = "$selfkeys_dir/$zuser/$zuser\@$zselector.pub";
+    print STDERR "undo pending deletion of keyid \@$zselector\n";
+    # A key for gitolite access that is in authorized_keys and not
+    # existing in the expected place under keydir/ should actually not
+    # happen, but one never knows.
+    die "key not available\n" unless -r $oldpubkeypath;
+    # For some strange reason the target key already exists.
+    die "cannot override existing key\n" if -e $newpubkeypath;
+    my $fp = fingerprint($oldpubkeypath);
+    hushed_git("mv", "$oldpubkeypath", "$newpubkeypath")
+        and die "git mv failed\n";
+    hushed_git("commit", "-m", "'ukm undo-del $zuser\@$zselector\n\n$fp'")
+        and die "git commit failed\n";
+}
+
+# A user whose key is in pending state cannot do much. In fact,
+# logging in as such a user simply takes back the "bringing into
+# pending state", i.e. a key scheduled for adding is remove and a key
+# scheduled for deletion is brought back into its properly added state.
+sub pending_user {
+    my ($gl_user, $zop, $zfp, $zselector, $zuser) = @_;
+    cd_temp_clone();
+    _chdir("keydir");
+    if ($zop eq 'add') {
+        pending_user_add($gl_user, $zfp, $zselector, $zuser);
+    } elsif ($zop eq 'del') {
+        pending_user_del($gl_user, $zfp, $zselector, $zuser);
+    } else {
+        die "unknown operation\n";
+    }
+    system("gitolite push >/dev/null 2>/dev/null")
+        and die "git push failed\n";
+}
diff --git a/contrib/t/ukm.t b/contrib/t/ukm.t
new file mode 100644
index 0000000..da4fc0b
--- /dev/null
+++ b/contrib/t/ukm.t
@@ -0,0 +1,447 @@
+#!/usr/bin/perl
+
+# Call like this:
+# TSH_VERBOSE=1 TSH_ERREXIT=1 HARNESS_ACTIVE=1 GITOLITE_TEST=y prove t/ukm.t
+
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Common;
+use Gitolite::Test;
+
+# basic tests using ssh
+# ----------------------------------------------------------------------
+
+my $bd = `gitolite query-rc -n GL_BINDIR`;
+my $h  = $ENV{HOME};
+my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
+my $pd = "$bd/../t/keys"; # source for pubkeys
+umask 0077;
+
+_mkdir( "$h/.ssh", 0700 ) if not -d "$h/.ssh";
+
+try "plan 204";
+
+
+# Reset everything.
+# Only admin and u1, u2, and u3 keys are available initially
+# Keys u4, u5, and u6 are used as guests later.
+# For easy access, we put the keys into ~/.ssh/, though.
+try "
+    rm -f $h/.ssh/authorized_keys; ok or die 1
+    cp $pd/u[1-6]* $h/.ssh; ok or die 2
+    cp $pd/admin*  $h/.ssh; ok or die 3
+    cp $pd/config  $h/.ssh; ok or die 4
+        cat $h/.ssh/config
+        perl s/%USER/$ENV{USER}/
+        put $h/.ssh/config
+    mkdir             $ab/keydir; ok or die 5
+    cp $pd/u[1-3].pub $ab/keydir; ok or die 6
+    cp $pd/admin.pub  $ab/keydir; ok or die 7
+";
+
+# Put the keys into ~/.ssh/authorized_keys
+system("gitolite ../triggers/post-compile/ssh-authkeys");
+
+# enable user key management in a simple form.
+# Guest key managers can add keyids looking like email addresses, but
+# cannot add emails containing example.com or hemmecke.org.
+system("sed -i \"s/.*ENABLE =>.*/'UKM_CONFIG'=>{'FORBIDDEN_GUEST_PATTERN'=>'example.com|hemmecke.org'}, ENABLE => ['ukm',/\" $h/.gitolite.rc");
+
+# super-key-managers can add/del any key
+# super-key-managers should in fact agree with people having write
+# access to gitolite-admin repo.
+# guest-key-managers can add/del guest keys
+confreset; confadd '
+    @guest-key-managers = u2 u3
+    @creators = u2 u3
+    repo pub/CREATOR/..*
+        C   =   @creators
+        RW+ =   CREATOR
+        RW  =   WRITERS
+        R   =   READERS
+';
+
+# Populate the gitolite-admin/keydir in the same way as it was used for
+# the initialization of .ssh/authorized_keys above.
+try "
+    mkdir             keydir; ok or die 8
+    cp $pd/u[1-3].pub keydir; ok or die 9;
+    cp $pd/admin.pub  keydir; ok or die 10;
+    git add conf keydir; ok
+    git commit -m ukm; ok; /master.* ukm/
+";
+
+# Activate new config data.
+try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();
+
+# Check whether the above setup yields the expected behavior for ukm.
+# The admin is super-key-manager, thus can manage every key.
+try "
+    ssh admin ukm; ok; /Hello admin, you manage the following keys:/
+                       / admin +admin/
+                       / u1 +u1/
+                       / u2 +u2/
+                       / u3 +u3/
+";
+
+# u1 isn't a key manager, so shouldn't be above to manage keys.
+try "ssh u1 ukm; !ok; /FATAL: You are not a key manager./";
+
+# u2 and u3 are guest key managers, but don't yet manage any key.
+try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:\n\n\n";
+try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:\n\n\n";
+
+
+###################################################################
+# Unknows subkommands abort ukm.
+try "ssh u2 ukm fake; !ok; /FATAL: unknown ukm subcommand: fake/";
+
+
+###################################################################
+# Addition of keys.
+
+# If no data is provided on stdin, we don't block, but rather timeout
+# after one second and abort the program.
+try "ssh u2 ukm add u4\@example.org; !ok; /FATAL: missing public key data/";
+
+# If no keyid is given, we cannot add a key.
+try "ssh u2 ukm add; !ok; /FATAL: keyid required/";
+
+try "
+    DEF ADD = cat $pd/%1.pub|ssh %2 ukm add %3
+    DEF ADDOK = ADD %1 %2 %3; ok
+    DEF ADDNOK = ADD %1 %2 %3; !ok
+    DEF FP = ADDNOK u4 u2 %1
+    DEF FORBIDDEN_PATTERN = FP %1; /FATAL: keyid not allowed:/
+";
+
+# Neither a guest key manager nor a super key manager can add keys that have
+# double dot in their keyid. This is hardcoded to forbid paths with .. in it.
+try "
+    ADDNOK u4 u2    u4\@hemmecke..org; /Not allowed to use '..' in keyid./
+    ADDNOK u4 admin u4\@hemmecke..org; /Not allowed to use '..' in keyid./
+    ADDNOK u4 admin ./../.myshrc;      /Not allowed to use '..' in keyid./
+";
+
+# guest-key-managers can only add keys that look like emails.
+try "
+    FORBIDDEN_PATTERN u4
+    FORBIDDEN_PATTERN u4\@example
+    FORBIDDEN_PATTERN u4\@foo\@example.org
+
+    # No support for 'old style' multiple keys.
+    FORBIDDEN_PATTERN u4\@example.org\@foo
+
+    # No path delimiter in keyid
+    FORBIDDEN_PATTERN foo/u4\@example.org
+
+    # Certain specific domains listed in FORBIDDEN_GUEST_PATTERN are forbidden.
+    # Note that also u4\@example-com would be rejected, because MYDOMAIN
+    # contains a regular expression --> I don't care.
+    FORBIDDEN_PATTERN u4\@example.com
+    FORBIDDEN_PATTERN u4\@hemmecke.org
+";
+
+# Accept one guest key.
+try "ADDOK u4 u2 u4\@example.org";
+try "ssh u2 ukm; ok; /Hello u2, you manage the following keys:/
+                     / u4\@example.org *u4\@example.org/";
+
+# Various ways how a key must be rejected.
+try "
+    # Cannot add the same key again.
+    ADDNOK u4 u2 u4\@example.org; /FATAL: cannot override existing key/
+
+    # u2 can also not add u4.pub under another keyid
+    ADDNOK u4 u2 u4\@example.net; /FATAL: cannot add key/
+         /Same key is already available under another userid./
+
+    # u2 can also not add another key under the same keyid.
+    ADDNOK u5 u2 u4\@example.org; /FATAL: cannot override existing key/
+
+    # Also u3 cannot not add another key under the same keyid.
+    ADDNOK u5 u3 u4\@example.org
+         /FATAL: cannot add another public key for an existing user/
+
+    # And u3 cannot not add u4.pub under another keyid.
+    ADDNOK u4 u3 u4\@example.net; /FATAL: cannot add key/
+         /Same key is already available under another userid./
+
+    # Not even the admin can add the same key u4 under a different userid.
+    ADDNOK u4 admin u4\@example.net; /FATAL: cannot add key/
+         /Same key is already available under another userid./
+         /Found  .* u4\@example.org/
+
+    # Super key managers cannot add keys that start with @.
+    # We don't care about @ in the dirname, though.
+    ADDNOK u4 admin foo/\@ex.net; /FATAL: cannot add key that starts with \@/
+    ADDNOK u4 admin foo/\@ex;     /FATAL: cannot add key that starts with \@/
+    ADDNOK u4 admin     \@ex.net; /FATAL: cannot add key that starts with \@/
+    ADDNOK u4 admin     \@ex;     /FATAL: cannot add key that starts with \@/
+";
+
+# But u3 can add u4.pub under the same keyid.
+try "ADDOK u4 u3 u4\@example.org";
+
+try "ssh u3 ukm; ok; /Hello u3, you manage the following keys:/
+                     / u4\@example.org *u4\@example.org/";
+
+# The admin can add multiple keys for the same userid.
+try "
+    ADDOK u5 admin u4\@example.org
+    ADDOK u5 admin u4\@example.org\@home
+    ADDOK u5 admin laptop/u4\@example.org
+    ADDOK u5 admin laptop/u4\@example.org\@home
+";
+
+# And admin can also do this for other guest key managers. Note,
+# however, that the gitolite-admin must be told where the
+# GUEST_DIRECTORY is. But he/she could find out by cloning the
+# gitolite-admin repository and adding the same key directly.
+try "
+    ADDOK u5 admin zzz/guests/u2/u4\@example.org\@foo
+    ADDOK u6 admin zzz/guests/u3/u6\@example.org
+";
+
+try "ssh admin ukm; ok"; cmp "Hello admin, you manage the following keys:
+fingerprint                                     userid         keyid
+a4:d1:11:1d:25:5c:55:9b:5f:91:37:0e:44:a5:a5:f2 admin          admin
+00:2c:1f:dd:a3:76:5a:1e:c4:3c:01:15:65:19:a5:2e u1             u1
+69:6f:b5:8a:f5:7b:d8:40:ce:94:09:a2:b8:95:79:5b u2             u2
+26:4b:20:24:98:a4:e4:a5:b9:97:76:9a:15:92:27:2d u3             u3
+78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org
+78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org laptop/u4\@example.org\@home
+78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org
+78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org u4\@example.org\@home
+8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u2/u4\@example.org
+78:cf:7e:2b:bf:18:58:54:23:cc:4b:3d:7e:f4:63:79 u4\@example.org zzz/guests/u2/u4\@example.org\@foo
+8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org zzz/guests/u3/u4\@example.org
+fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org zzz/guests/u3/u6\@example.org
+\n\n";
+
+# Now, u2 has two keys in his directory, but u2 can manage only one of
+# them, since the one added by the admin has two @ in it. Thus the key
+# added by admin is invisible to u2.
+try "ssh u2 ukm; ok"; cmp "Hello u2, you manage the following keys:
+fingerprint                                     userid         keyid
+8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
+\n\n";
+
+# Since admin added key u6 at example.org to the directory of u2, u2 is
+# also able to see it and, in fact, to manage it.
+try "ssh u3 ukm; ok"; cmp "Hello u3, you manage the following keys:
+fingerprint                                     userid         keyid
+8c:a6:c0:a5:71:85:0b:89:d3:08:97:22:ae:95:e1:bb u4\@example.org u4\@example.org
+fc:0f:eb:52:7a:d2:35:da:89:96:f5:15:0e:85:46:e7 u6\@example.org u6\@example.org
+\n\n";
+
+###################################################################
+# Deletion of keys.
+try "
+    DEF DEL = ssh %1 ukm del %2
+    DEF DELOK  = DEL %1 %2; ok
+    DEF DELNOK = DEL %1 %2; !ok
+    DEF DELNOMGR = DELNOK %1 %2; /FATAL: You are not managing the key /
+";
+
+# Deletion requires a keyid.
+try "ssh u3 ukm del; !ok; /FATAL: keyid required/";
+
+# u3 can, of course, not remove any unmanaged key.
+try "DELNOMGR u3 u2";
+
+# But u3 can delete u4 at example.org and u6 at example.org. This will, of course,
+# not remove the key u4 at example.org that u2 manages.
+try "
+    DELOK u3 u4\@example.org
+    DELOK u3 u6\@example.org
+";
+
+# After having deleted u4 at example.org, u3 cannot remove it again,
+# even though, u2 still manages that key.
+try "DELNOMGR u3 u4\@example.org";
+
+# Of course a super-key-manager can remove any (existing) key.
+try "
+    DELOK  admin zzz/guests/u2/u4\@example.org
+    DELNOK admin zzz/guests/u2/u4\@example.org
+        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
+    DELNOK admin zzz/guests/u2/u4\@example.org\@x
+        /FATAL: You are not managing the key zzz/guests/u2/u4\@example.org./
+    DELOK  admin zzz/guests/u2/u4\@example.org\@foo
+";
+
+# As the admin could do that via pushing to the gitolite-admin manually,
+# it's also allowed to delete even non-guest keys.
+try "DELOK admin u3";
+
+# Let's clean the environment again.
+try "
+    DELOK admin laptop/u4\@example.org\@home
+    DELOK admin laptop/u4\@example.org
+    DELOK admin        u4\@example.org\@home
+    DELOK admin        u4\@example.org
+    ADDOK u3 admin u3
+ ";
+
+# Currently the admin has just one key. It cannot be removed.
+# But after adding another key, deletion should work fine.
+try "
+    DELNOK admin admin; /FATAL: You cannot delete your last key./
+    ADDOK u6 admin second/admin; /Adding new public key for admin./
+    DELOK admin admin
+    DELNOK u6 admin; /FATAL: You are not managing the key admin./
+    DELNOK u6 second/admin; /FATAL: You cannot delete your last key./
+    ADDOK admin u6 admin; /Adding new public key for admin./
+    DELOK u6 second/admin
+";
+
+###################################################################
+# Selfkey management.
+
+# If self key management is not switched on in the .gitolite.rc file,
+# it's not allowed at all.
+try "ssh u2 ukm add \@second; !ok; /FATAL: selfkey management is not enabled/";
+
+# Let's enable it.
+system("sed -i \"/'UKM_CONFIG'=>/s/=>{/=>{'SELFKEY_MANAGEMENT'=>1,/\" $h/.gitolite.rc");
+
+# And add self-key-managers to gitolite.conf
+# chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
+try "glt pull admin origin master; ok";
+put "|cut -c5- > conf/gitolite.conf", '
+    repo gitolite-admin
+        RW+ = admin
+    repo testing
+        RW+ = @all
+    @guest-key-managers = u2 u3
+    @self-key-managers = u1 u2
+    @creators = u2 u3
+    repo pub/CREATOR/..*
+        C   =   @creators
+        RW+ =   CREATOR
+        RW  =   WRITERS
+        R   =   READERS
+';
+try "
+    git add conf keydir; ok
+    git commit -m selfkey; ok; /master.* selfkey/
+";
+try "PUSH admin; ok; gsh; /master -> master/; !/FATAL/" or die text();
+
+# Now we can start with the tests.
+
+# Only self key managers are allowed to use selfkey management.
+# See variable @self-key-managers.
+try "ssh u3 ukm add \@second; !ok; /FATAL: You are not a selfkey manager./";
+
+# Cannot add keyid that are not alphanumeric.
+try "ssh u1 ukm add \@second-key; !ok; /FATAL: keyid not allowed:/";
+
+# Add a second key for u1, but leave it pending by not feeding in the
+# session key. The new user can login, but he/she lives under a quite
+# random gl_user name and thus is pretty much excluded from everything
+# except permissions given to @all. If this new id calls ukm without
+# providing the session key, this (pending) key is automatically
+# removed from the system.
+# If a certain keyid is in the system, then it cannot be added again.
+try "
+    ADDOK u4 u1 \@second
+    ssh admin ukm; ok; /u1     zzz/self/u1/zzz-add-[a-z0-9]{32}-second-u1/
+    ssh u1    ukm; ok; /u1     \@second .pending add./
+    ADDNOK u4 u1 \@second; /FATAL: keyid already in use: \@second/
+    ssh u4    ukm; ok; /pending keyid deleted: \@second/
+    ssh admin ukm; ok; !/zzz/; !/second/
+";
+
+# Not providing a proper ssh public key will abort. Providing a good
+# ssh public key, which is not a session key makes the key invalid.
+# The key will, therefore, be deleted by this operation.
+try "
+    ADDOK u4 u1 \@second
+    echo fake|ssh u4 ukm; !ok; /FATAL: does not seem to be a valid pubkey/
+    cat $pd/u5.pub | ssh u4 ukm; ok;
+        /session key not accepted/
+        /pending keyid deleted: \@second/
+";
+
+# True addition of a new selfkey is done via piping it to a second ssh
+# call that uses the new key to call ukm. Note that the first ssh must
+# have completed its job before the second ssh is able to successfully
+# log in. This can be done via sleep or via redirecting to a file and
+# then reading from it.
+try "
+    # ADDOK u4 u1 \@second | (sleep 2; ssh u4 ukm); ok
+    ADD u4 u1 \@second > session; ok
+    cat session | ssh u4 ukm; ok;  /pending keyid added: \@second/
+";
+
+# u1 cannot add his/her initial key, since that key can never be
+# confirmed via ukm, so it is forbidden altogether. In fact, u1 is not
+# allowed to add any key twice.
+try "
+    ADDNOK u1 u1 \@first
+       /FATAL: You cannot add a key that already belongs to you./
+    ADDNOK u4 u1 \@first
+       /FATAL: You cannot add a key that already belongs to you./
+";
+
+# u1 also can add more keys, but not under an existing keyid. That can
+# be done by any of his/her identities (here we choose u4).
+try "
+    ADDNOK u5 u1 \@second; /FATAL: keyid already in use: \@second/
+    ADD u5 u4 \@third > session; ok
+    cat session | ssh u5 ukm; ok;  /pending keyid added: \@third/
+";
+
+# u2 cannot add the same key, but is allowed to use the same name (@third).
+try "
+    ADDNOK u5 u2 \@third; /FATAL: cannot add key/
+        /Same key is already available under another userid./
+    ADD u6 u2 \@third > session; ok
+    cat session | ssh u6 ukm; ok;  /pending keyid added: \@third/
+";
+
+# u6 can schedule his/her own key for deletion, but cannot actually
+# remove it. Trying to do so results in bringing back the key. Actual
+# deletion must be confirmed by another key.
+try "
+    ssh u6 ukm del \@third; /prepare deletion of key \@third/
+    ssh u2 ukm; ok; /u2     \@third .pending del./
+    ssh u6 ukm; ok; /undo pending deletion of keyid \@third/
+    ssh u6 ukm del \@third; /prepare deletion of key \@third/
+    ssh u2 ukm del \@third; ok;  /pending keyid deleted: \@third/
+";
+
+# While in pending-deletion state, it's forbidden to add another key
+# with the same keyid. It's also forbidden to add a key with the same
+# fingerprint as the to-be-deleted key).
+# A new key under another keyid, is OK.
+try "
+    ssh u1 ukm del \@third; /prepare deletion of key \@third/
+    ADDNOK u4 u1 \@third; /FATAL: keyid already in use: \@third/
+    ADDNOK u5 u1 \@fourth;
+        /FATAL: You cannot add a key that already belongs to you./
+    ADD u6 u1 \@fourth > session; ok
+    ssh u1 ukm; ok;
+        /u1     \@second/
+        /u1     \@fourth .pending add./
+        /u1     \@third .pending del./
+";
+# We can remove a pending-for-addition key (@fourth) by logging in
+# with a non-pending key. Trying to do anything with key u5 (@third)
+# will just bring it back to its normal state, but not change the
+# state of any other key. As already shown above, using u6 (@fourth)
+# without a proper session key, would remove it from the system.
+# Here we want to demonstrate that key u1 can delete u6 immediately.
+try "ssh u1 ukm del \@fourth; /pending keyid deleted: \@fourth/";
+
+# The pending-for-deletion key @third can also be removed via the u4
+# (@second) key.
+try "ssh u4 ukm del \@third; ok; /pending keyid deleted: \@third/";
+
+# Non-existing selfkeys cannot be deleted.
+try "ssh u4 ukm del \@x; !ok; /FATAL: You are not managing the key \@x./";

commit 6c6153769be4d13916744edf740441985546134b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Sep 7 21:45:22 2013 +0530

    allow groups in subconf files...
    
    ...simply expand them and pretend the repos were listed individually!

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 8c8a161..8220316 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -63,13 +63,20 @@ sub add_to_group {
 }
 
 sub set_repolist {
-
+    my @in = @_;
     @repolist = ();
     # ...sanity checks
-    for (@_) {
+    while (@in) {
+        $_ = shift @in;
         if ( check_subconf_repo_disallowed( $subconf, $_ ) ) {
-            (my $repo = $_) =~ s/^\@$subconf\./locally modified \@/;
-            $ignored{$subconf}{$repo} = 1;
+            if (exists $groups{$_}) {
+                # groupname disallowed; try individual members now
+                (my $g = $_) =~ s/^\@$subconf\./\@/;
+                _warn "expanding '$g'; this *may* slow down compilation";
+                unshift @in, keys %{ $groups{$_} };
+                next;
+            }
+            $ignored{$subconf}{$_} = 1;
             next;
         }
 
diff --git a/t/deleg-1.t b/t/deleg-1.t
index 933a17f..89da137 100755
--- a/t/deleg-1.t
+++ b/t/deleg-1.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # delegation tests -- part 1
 # ----------------------------------------------------------------------
 
-try "plan 53";
+try "plan 54";
 
 try "
     DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
@@ -95,5 +95,6 @@ put   "conf/fragments/u3r.conf", '
 ';
 
 try "SUBCONF_PUSH u3 u3
-        /WARNING: subconf 'u3r' attempting to set access for locally modified \@u3r/
+        /WARNING: expanding '\@u3r'/
+        /WARNING: subconf 'u3r' attempting to set access for r2b/
 ";
diff --git a/t/deleg-2.t b/t/deleg-2.t
index ccf9cc5..98fb02e 100755
--- a/t/deleg-2.t
+++ b/t/deleg-2.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # delegation tests -- part 2
 # ----------------------------------------------------------------------
 
-try "plan 54";
+try "plan 55";
 
 try "
     DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
@@ -117,5 +117,6 @@ put   "conf/fragments/u3r.conf", '
 ';
 
 try "SUBCONF_PUSH u3 u3
-        /WARNING: subconf 'u3r' attempting to set access for locally modified \@u3r/
+        /WARNING: expanding '\@u3r'/
+        /WARNING: subconf 'u3r' attempting to set access for r2b/
 ";
diff --git a/t/include-subconf.t b/t/include-subconf.t
index 813cb93..48bdaee 100755
--- a/t/include-subconf.t
+++ b/t/include-subconf.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # include and subconf
 # ----------------------------------------------------------------------
 
-try 'plan 55';
+try 'plan 58';
 
 confreset; confadd '
     include "i1.conf"
@@ -39,7 +39,9 @@ try "ADMIN_PUSH set2; !/FATAL/" or die text();
 
 try "
                                         /i1.conf already included/
-	                                /subconf 'i2' attempting to set access for \@i1, b2, bar, i1, locally modified \@g2/
+	                                    /subconf 'i2' attempting to set access for \@i1, b2, bar, i1/
+                                        /WARNING: expanding '\@g2'/
+
                                         !/attempting to set access.*i2/
                                         /Initialized.*empty.*baz.git/
                                         /Initialized.*empty.*foo.git/
@@ -62,8 +64,10 @@ confadd 'g2.conf', '
 
 try "ADMIN_PUSH set3; !/FATAL/" or die text();
 try "
-                                        /subconf 'g2' attempting to set access for locally modified \@g2/
-                                        !/Initialized.*empty/
+                                        /WARNING: expanding '\@g2'/
+                                        /WARNING: subconf 'g2' attempting to set access for h2/
+                                        /Initialized.*empty.*g2.git/
+                                        /Initialized.*empty.*i2.git/
 ";
 
 confreset;confadd '

commit cd701c520b80f4b139b9f7abce9e45caed831cef
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Sep 7 15:55:16 2013 +0530

    fix log lines to be consistent and documentable

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 0f5bb27..7a27cc0 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -71,6 +71,11 @@ sub in_http {
     $ENV{REMOTE_USER} ||= $rc{HTTP_ANON_USER};
     @ARGV = ( $ENV{REMOTE_USER} );
 
+    my $ip;
+    ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
+
+    gl_log( 'http', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
+
     return 'http';
 }
 
@@ -122,7 +127,7 @@ sub main {
         trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
         _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
 
-        gl_log( "pre_git", $repo, $user, $aa, 'any', "-> $ret" );
+        gl_log( "pre_git", $repo, $user, $aa, 'any', $ret );
     }
 
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 5bef2ed..92eb625 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -34,8 +34,7 @@ sub update {
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
 
-    trace( 1, "-> $ret" );
-    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
+    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, $ret);
     exit 0;
 }
 

commit 79a00bef67215040bf175c3f0d618442eceaa0b5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Sep 3 12:39:21 2013 +0530

    improve error message for install from tar

diff --git a/install b/install
index ad7effa..c355f7a 100755
--- a/install
+++ b/install
@@ -61,7 +61,11 @@ for my $d ($ln, $to) {
 }
 
 chdir($ENV{GL_BINDIR});
-my $version = `git describe --tags --long --dirty=-dt`;
+my $version = `git describe --tags --long --dirty=-dt 2>/dev/null`;
+unless ($version =~ /^v\d/) {
+    print STDERR "git describe failed; cannot deduce version number\n";
+    $version = "(unknown)";
+}
 
 if ($to) {
     _mkdir($to);

commit fa06a34d1dd51e3ce786eb2c0714c8bc55d5c418
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Sep 3 07:53:56 2013 +0530

    set umask as early as possible

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 7e4a987..0f5bb27 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -92,8 +92,6 @@ sub in_ssh {
 sub main {
     my $id = shift;
 
-    umask $rc{UMASK};
-
     # set up the user
     my $user = $ENV{GL_USER} = shift @ARGV;
 
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 1d62e89..5f351f8 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -90,9 +90,11 @@ unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
 # use an env var that is highly unlikely to appear in real life :)
 do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
-# setup some perl/rc/env vars
+# setup some perl/rc/env vars, plus umask
 # ----------------------------------------------------------------------
 
+umask $rc{UMASK};
+
 unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
 
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}" unless $ENV{PATH} =~ /^$ENV{GL_BINDIR}:/;

commit 62fb31755abb7ad93e17a6fe8880b74eb17086fa
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Aug 29 16:46:54 2013 +0530

    repo-specific hooks

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 5e1f21e..1d62e89 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -423,6 +423,9 @@ BEGIN { $non_core = "
 
     daemon                  POST_CREATE     post-compile/update-git-daemon-access-list
     daemon                  POST_COMPILE    post-compile/update-git-daemon-access-list
+
+    repo-specific-hooks     POST_COMPILE    .
+    repo-specific-hooks     POST_CREATE     .
 ";
 }
 
@@ -491,6 +494,18 @@ __DATA__
 
     # ------------------------------------------------------------------
 
+    # suggested locations for site-local gitolite code (see cust.html)
+
+        # this one is managed directly on the server
+        # LOCAL_CODE                =>  "$ENV{HOME}/local",
+
+        # or you can use this, which lets you put everything in a subdirectory
+        # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
+        # on this, see http://gitolite.com/gitolite/cust.html#pushcode
+        # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
+
+    # ------------------------------------------------------------------
+
     # List of commands and features to enable
 
     ENABLE => [
@@ -561,6 +576,9 @@ __DATA__
             # updates 'description' file instead of 'gitweb.description' config item
             # 'cgit',
 
+            # allow repo-specific hooks to be added
+            # 'repo-specific-hooks',
+
         # performance, logging, monitoring...
 
             # be nice
diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
new file mode 100755
index 0000000..6baa508
--- /dev/null
+++ b/src/triggers/repo-specific-hooks
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# setup repo-specific hooks
+
+# code is too long, but if you take out all the error/safety/sanity checks,
+# it's basically just creating a symlink in <repo.git>/hooks pointing to some
+# file inside $rc{LOCAL_CODE}/hooks/repo-specific
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+_die "repo-specific-hooks: LOCAL_CODE not defined in rc" unless $rc{LOCAL_CODE};
+_die "repo-specific-hooks: '$rc{LOCAL_CODE}' does not exist or is not a directory" unless -d $rc{LOCAL_CODE};
+
+_chdir($ENV{GL_REPO_BASE});
+
+ at ARGV = ("gitolite list-phy-repos | gitolite git-config -r % gitolite-options\\.hook\\. |");
+
+while (<>) {
+    chomp;
+    my ($repo, $hook, $code) = split /\t/, $_;
+
+    # we don't allow fiddling with the admin repo
+    if ($repo eq 'gitolite-admin') {
+        _warn "repo-specific-hooks: ignoring attempts to set hooks for the admin repo";
+        next;
+    }
+
+    # get the hook name
+    $hook =~ s/^gitolite-options\.hook\.//;
+
+    unless ($hook =~ /^(pre-receive|post-receive|post-update)$/) {
+        _warn "repo-specific-hooks: '$hook' is not one of the 3 hooks allowed, ignoring";
+        next;
+    }
+
+    if ($code =~ m(^/|\.\.)) {
+        _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
+        next;
+    }
+
+    my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
+    my $dst = "$repo.git/hooks/$hook";
+    unless (-x $src) {
+        _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
+        next;
+    }
+    unlink $dst;
+    symlink $src, $dst or _warn "could not symlink '$src' to '$dst'";
+    # no sanity checks for multiple overwrites of the same hook
+}

commit 2515992d8836b2fe333860ad0ed3267efd1cf698
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jul 12 10:44:52 2013 +0530

    a couple of low level file perm changes...
    
    (all thanks to Peter Wu; see [1])
    
    1.  updating ~/projects.list (or whatever you have GITWEB_PROJECTS_LIST
        pointing to) becomes an issue when the global UMASK is too tight and
        you're loosening it for specific repos.  Those repos can be seen
        (presumably you're using repoumask or something) but the
        projects.list is invisible.
    
        This patch makes gitolite honor the perms for that file if it
        already exists.  This allows the admin to manually loosen its perms
        just once and forget about it after that.  (The alternative would be
        something ghastly like GITWEB_PROJECTS_LIST_PERMS in the rc file!)
    
    2.  while discussing this it turned out that RepoUmask had a... well
        let's call it a "problem" for argument's sake ;-)
    
        The files that get created on "git init" (usually HEAD, description,
        info/exclude, and gl-perms) were left with the executable bit turned
        on.  You can see the thread [1] below for why I don't really care
        about this, but since the fix it only one line, I may as well...
    
    [1]: https://groups.google.com/forum/#!topic/gitolite/x-bQX_W82rw

diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm
index ea675e2..828e1e2 100644
--- a/src/lib/Gitolite/Triggers/RepoUmask.pm
+++ b/src/lib/Gitolite/Triggers/RepoUmask.pm
@@ -14,8 +14,9 @@ use warnings;
 
 =for usage
 
-  * In the rc file, add 'RepoUmask::pre_git' and 'RepoUmask::post_create' to
-    the corresponding trigger lists.
+  * In the rc file, add the line
+        'RepoUmask',
+    somewhere in the ENABLE list
 
   * For each repo that is to get a different umask than the default, add a
     line like this:
@@ -39,6 +40,7 @@ sub post_create {
     my $mode = "0" . sprintf("%o", $umask ^ 0777);
 
     system("chmod -R $mode $repo.git >&2");
+    system("find $repo.git -type f -exec chmod a-x '{}' \\;");
 }
 
 sub pre_git {
diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index aa1b8ac..d173667 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -27,4 +27,6 @@ tmpfile=`mktemp -u $plf.tmp_XXXXXXXX`
     gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
 ) |
     cut -f1 | sort -u | sed -e 's/$/.git/' > $tmpfile
+
+[ -f $plf ] && perl -e "chmod ( ( (stat('$plf'))[2] & 07777 ), '$tmpfile')"
 mv $tmpfile $plf

commit f5873f492c1b210315829ce498c2e22c5019977f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jul 12 23:08:33 2013 +0530

    (minor) git version change necessitated some test changes

diff --git a/t/basic.t b/t/basic.t
index 4626f96..7579935 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -18,7 +18,7 @@ try "
     CLONE dev2 gitolite-admin ga2
                                 !ok;    gsh
                                         /DENIED by fallthru/
-                                        /fatal: The remote end hung up unexpectedly/
+                                        /fatal: Could not read from remote repository/
     glt clone admin --progress file:///gitolite-admin ga2
                                 ok;     gsh
                                         /Counting/; /Compressing/; /Total/
@@ -43,7 +43,7 @@ try "
     git commit -m t01a;         ok;     /master.*t01a/
     PUSH dev2;                  !ok;    gsh
                                         /DENIED by fallthru/
-                                        /fatal: The remote end hung up unexpectedly/
+                                        /fatal: Could not read from remote repository/
     PUSH admin;                 ok;     /master -> master/
     empty;                      ok;
     PUSH admin master:mm
@@ -74,7 +74,7 @@ try "
     cd ..;                      ok;
     CLONE u1 t1;                !ok;    gsh
                                         /DENIED by fallthru/
-                                        /fatal: The remote end hung up unexpectedly/
+                                        /fatal: Could not read from remote repository/
     CLONE u2 t1;                ok;     gsh
                                         /warning: You appear to have cloned an empty repository./
     [ -d t1/.git ];             ok
@@ -84,7 +84,7 @@ try "
     test-commit tc1 tc2 tc2;    ok;     /a530e66/
     PUSH u2;                    !ok;    gsh
                                         /DENIED by fallthru/
-                                        /fatal: The remote end hung up unexpectedly/
+                                        /fatal: Could not read from remote repository/
     PUSH u3 master;             ok;     gsh
                                         /master -> master/
 

commit 32d14d39a6d5d0b15468653a8046a3a58fad953d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jul 12 15:10:34 2013 +0530

    smart http mode in git 1.8.2 and above...
    
    ...needs a specific header at a certain point
    
    see 4656bf47fca857df51b5d6f4b7b052192b3b2317 in git.git for an
    explanation.
    
    People who use the old gitolite with newer gits will probably see error
    messages that don't spell out the user, the repo, the operation, etc
    clearly.
    
    Of course, this only applies to the case where the "first check", where
    the user has NO access to the repo and is being kicked out even before
    control passes to git-*-pack.  Any errors after that should work fine
    anyway, since they come direct from git-http-backend, not gitolite.

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 0ee48e3..7e4a987 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -184,13 +184,14 @@ sub http_setup_die_handler {
         my $message = shift; chomp($message);
         print STDERR "$message\n";
 
+        http_print_headers($service);
+
         # format the service response, then the message.  With initial
         # help from Ilari and then a more detailed email from Shawn...
         $service = "# service=$service\n"; $message = "ERR $message\n";
         $service = sprintf( "%04X", length($service) + 4 ) . "$service";    # no CRLF on this one
         $message = sprintf( "%04X", length($message) + 4 ) . "$message";
 
-        http_print_headers();
         print $service;
         print "0000";                                                       # flush-pkt, apparently
         print $message;
@@ -226,7 +227,7 @@ sub http_simulate_ssh_connection {
 my $http_headers_printed = 0;
 
 sub http_print_headers {
-    my ( $code, $text ) = @_;
+    my ( $service, $code, $text ) = @_;
 
     return if $http_headers_printed++;
     $code ||= 200;
@@ -237,6 +238,10 @@ sub http_print_headers {
     print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
     print "Pragma: no-cache\r\n";
     print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
-    print "Content-Type: text/plain\r\n";
+    if ($service) {
+        print "Content-Type: application/x-$service-advertisement\r\n";
+    } else {
+        print "Content-Type: text/plain\r\n";
+    }
     print "\r\n";
 }

commit 926bd5f89f022c6533cf6ea3be52e022b30dc6a9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jul 10 11:47:36 2013 +0530

    v3.5.2

diff --git a/CHANGELOG b/CHANGELOG
index 9fde143..c3a3c81 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+2013-07-10  v3.5.2  allow ENV vars to be set from repo options, for use in
+                    triggers and hooks
+
+                    bug-fix: the new set-default-roles feature was being
+                    invoked on every run of "perms" and overriding it!
+
 2013-03-24  v3.5    (2 minor backward compat breakages)
                     1.  'DEFAULT_ROLE_PERMS' replaced by per repo
                         'default.roles' option

commit 63865a16eb06218318f5d7c21faa4878db9b6693
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 28 09:40:22 2013 +0530

    setup some ENV vars from gitolite.conf options...
    
    makes things much easier in hooks and triggers

diff --git a/src/gitolite-shell b/src/gitolite-shell
index a3ec321..0ee48e3 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -103,6 +103,9 @@ sub main {
     $ENV{GL_REPO} = $repo;
     my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
 
+    # set up env vars from options set for this repo
+    env_options($repo);
+
     # auto-create?
     if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
         require Gitolite::Conf::Store;
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index c4051e4..295e888 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -8,6 +8,7 @@ package Gitolite::Conf::Load;
 
   access
   git_config
+  env_options
 
   option
   repo_missing
@@ -18,6 +19,7 @@ package Gitolite::Conf::Load;
 );
 
 use Exporter 'import';
+use Cwd;
 
 use Gitolite::Common;
 use Gitolite::Rc;
@@ -184,6 +186,23 @@ sub git_config {
     return \%ret;
 }
 
+sub env_options {
+    return unless -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf-compiled.pm";
+        # prevent catch-22 during initial install
+
+    my $cwd = getcwd();
+
+    my $repo = shift;
+    map { delete $ENV{$_} } grep { /^GL_OPTION_/ } keys %ENV;
+    my $h = git_config( $repo, '^gitolite-options.ENV\.' );
+    while (my ($k, $v) = each %$h) {
+        next unless $k =~ /^gitolite-options.ENV\.(\w+)$/;
+        $ENV{"GL_OPTION_" . $1} = $v;
+    }
+
+    chdir($cwd);
+}
+
 sub option {
     my ( $repo, $option ) = @_;
     $option = "gitolite-options.$option";
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index a3f0c59..5e1f21e 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -271,6 +271,12 @@ sub version {
 sub trigger {
     my $rc_section = shift;
 
+    # if arg-2 (now arg-1, due to the 'shift' above) exists, it is a repo
+    # name, so setup env from options
+    require Gitolite::Conf::Load;
+    Gitolite::Conf::Load->import('env_options');
+    env_options($_[0]) if $_[0];
+
     if ( exists $rc{$rc_section} ) {
         if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
             _die "'$rc_section' section in rc file is not a perl list";

commit 05e4093fb21150a223c1d98d69de1e39fdfa7f3e
Author: Tony Finch <dot at dotat.at>
Date:   Wed Jun 26 19:43:16 2013 +0100

    help: include SITE_INFO in output

diff --git a/src/commands/help b/src/commands/help
index 23fa6ae..8824a60 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -29,6 +29,7 @@ for (sort keys %list) {
 }
 
 print "\n";
+print "$rc{SITE_INFO}\n" if $rc{SITE_INFO};
 
 exit 0;
 

commit d8fa0681426650a1e7aa6fec2ea0c1caf25295c3
Author: Darrell Thacher <dsthacher at gmail.com>
Date:   Fri Jun 7 17:32:48 2013 -0400

    check file/dir existence before locking
    
    check for existence of the object across all branches before creating
    the lock.  Helps catch silent typos which may make people think they
    locked the file but actually did not.
    
    [code and commit message somewhat edited by committer]

diff --git a/src/commands/lock b/src/commands/lock
index a9e9073..7a5765b 100755
--- a/src/commands/lock
+++ b/src/commands/lock
@@ -41,6 +41,8 @@ usage() if $op ne 'list' and not $file;
 _chdir( $ENV{GL_REPO_BASE} );
 _chdir("$repo.git");
 
+_die "aborting, file '$file' not found in any branch" if $file and not object_exists($file);
+
 my $ff = "gl-locks";
 
 if ( $op eq 'lock' ) {
@@ -54,6 +56,24 @@ if ( $op eq 'lock' ) {
 }
 
 # ----------------------------------------------------------------------
+# For a given path, return 1 if object exists in any branch, 0 if not.
+# This is to prevent locking invalid objects.
+
+sub object_exists {
+    my $file = shift;
+
+    my @branches = `git for-each-ref refs/heads '--format=%(refname)'`;
+    foreach my $b (@branches) {
+        chomp($b);
+	system("git cat-file -e $b:$file 2>/dev/null") or return 1;
+            # note that with system(), the return value is "shell truth", so
+            # you check for success with "or", not "and"
+    }
+    return 0;   # report object not found
+}
+
+
+# ----------------------------------------------------------------------
 # everything below assumes we have already chdir'd to "$repo.git".  Also, $ff
 # is used as a global.
 

commit 046d0a137607b5a5835534b5f091c7f0f784ca42
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 5 07:56:00 2013 +0530

    create command should think of default roles also...
    
    Some code is duplicated from 'set-default-roles' trigger but it's the
    cleanest way to fix the problem.

diff --git a/src/commands/create b/src/commands/create
index adac0e3..7d2a3a3 100755
--- a/src/commands/create
+++ b/src/commands/create
@@ -12,4 +12,5 @@ usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
 [ -z "$GL_USER" ] && die GL_USER not set
 
 # ----------------------------------------------------------------------
-exec $GL_BINDIR/commands/perms -c "$@" < /dev/null
+gitolite git-config -r $1 gitolite-options.default.roles | sort | cut -f3 |
+    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$1/' | $GL_BINDIR/commands/perms -c "$@"

commit b2ebc9b076729c4b109d4ab21e4836118c6ab113
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 5 07:04:05 2013 +0530

    fork command should add hooks from LOCAL_CODE if defined

diff --git a/src/commands/fork b/src/commands/fork
index 1edd205..1795e03 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -44,6 +44,7 @@ echo "$from forked to $to" >&2
 cd $GL_REPO_BASE/$to.git
 echo $GL_USER > gl-creator
 
+gitolite query-rc -q LOCAL_CODE && ln -sf `gitolite query-rc LOCAL_CODE`/hooks/common/* hooks
 ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
 
 # record where you came from

commit 412d9ab8e19b691683c5003ce40a117aa86d74b9
Author: Jan Weitzel <j.weitzel at phytec.de>
Date:   Mon Jun 3 15:46:00 2013 +0200

    partial-copy: remove unused refs
    
    Remove all refs from partial-copy that not longer exists in main.
    Also remove all refs from partial-copy that are not longer accessible, due to
    changed rights.
    
    Signed-off-by: Jan Weitzel <j.weitzel at phytec.de>

diff --git a/src/triggers/partial-copy b/src/triggers/partial-copy
index 119c9f1..79b4d48 100755
--- a/src/triggers/partial-copy
+++ b/src/triggers/partial-copy
@@ -32,4 +32,38 @@ do
     git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
 done
 
+export GL_BYPASS_ACCESS_CHECKS=1
+
+# remove all refs not in main or accessible
+cd $GL_REPO_BASE/$repo.git
+
+for ref in `git for-each-ref refs/heads refs/tags '--format=%(refname)'`
+do
+    cd $GL_REPO_BASE/$main.git
+
+    if git show-ref --verify --quiet $ref &&
+       gitolite access -q $repo $user R $ref ; then
+        # ref is present in main and accessible in repo
+        continue
+    fi
+
+    git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
+done
+
+# remove all tags no longer reachable
+cd $GL_REPO_BASE/$repo.git
+
+for ref in `git for-each-ref refs/tags '--format=%(refname)'`
+do
+    SHA=`git rev-list -1 $ref`
+    for branch in `git for-each-ref refs/heads '--format=%(refname)'`
+    do
+       if [ "`git merge-base $SHA $branch`" = "$SHA" ]; then
+           # tag is reachable in current branch, continue higher loop
+           continue 2
+       fi
+    done
+    git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
+done
+
 exit 0

commit 2f48a3e0e169e763d63c3b017a3d2980b5225a63
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu May 16 17:26:30 2013 +0530

    add simple migration instructions to README

diff --git a/README.txt b/README.txt
index 14e04b4..88f0661 100644
--- a/README.txt
+++ b/README.txt
@@ -40,6 +40,8 @@ This file contains the following sections:
     GIT-DAEMON
     GITWEB
 
+    MIGRATING FROM v2
+
     CONTACT AND SUPPORT
     LICENSE
 
@@ -346,6 +348,70 @@ GITWEB
 ------------------------------------------------------------------------
 
 
+MIGRATING FROM v2
+-----------------
+
+    This section describes how to migrate a basic install of v2 to v3.
+
+    However, if you have used any of the following features:
+
+      * any non-default settings in the rc file
+      * NAME/ rules
+      * subconf and delegation
+      * mirroring
+      * wild repos (user-created repos)
+      * any custom hooks of your own
+
+    you should go through the full set of migration instructions at
+    http://gitolite.com/gitolite/migr.html
+
+    The steps to follow to migrate a simple v2 setup to v3 are as follows:
+
+    0.  take a backup :-)
+
+    1.  remove old gitolite
+
+        1.1 Remove (or rename)
+
+          * the directories named in the rc variables GL_PACKAGE_CONF and
+            GL_PACKAGE_HOOKS (look in ~/.gitolite.rc)
+
+          * ~/.gitolite.rc
+
+          * the gitolite v2 code, whose location you can find in the
+            "command=" parameter in any of the gitolite keys in
+            ~/.ssh/authorized_keys
+
+          * ~/.gitolite (preserve ~/.gitolite/logs if you wish)
+
+        1.2 Edit ~/.ssh/authorized_keys and delete all lines pertaining to
+            gitolite (they will have a "command=" option pointing to
+            gl-auth-command)
+
+        1.3 Clone ~/repositories/gitolite-admin.git to some safe location on
+            the same server.
+
+            NOTE: please clone using the file system directly, not via ssh.
+
+        1.4 Delete ~/repositories/gitolite-admin.git (the repo you just
+            cloned).
+
+            NOTE: DO NOT delete any other repo in ~/repositories.  Leave them
+            all as they are.
+
+    2.  install gitolite as normal.  It doesn't matter what pubkey you use in
+        the "gitolite setup" step; in fact you may even choose to just run
+        "gitolite setup -a admin".  The admin repo created in this step will
+        get wiped out in the next step anyway.
+
+    3.  go to the clone you made in step 1.3 and run 'gitolite push -f'.
+
+        NOTE: that is 'gitolite push -f', not 'git push -f' :-)
+
+
+------------------------------------------------------------------------
+
+
 CONTACT AND SUPPORT
 -------------------
 

commit a000ccd47dd730cb0b5066f07fb8ab82f19b7a48
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 27 06:39:58 2013 +0530

    set-default-roles: fix bug and add test script

diff --git a/src/triggers/set-default-roles b/src/triggers/set-default-roles
index ab633ef..a8a2b4d 100755
--- a/src/triggers/set-default-roles
+++ b/src/triggers/set-default-roles
@@ -6,7 +6,7 @@
 # skip if arg-1 is POST_CREATE and no arg-3 (user name) exists (i.e., it's not
 # a wild repo)
 [ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
-[ "$4" = "R" ] || [ "$4" = "W" ] || [ "$4" = "perms-c" ] || [ "$4" = "fork" ] exit 0
+[ "$4" = "R" ] || [ "$4" = "W" ] || [ "$4" = "perms-c" ] || [ "$4" = "fork" ] || exit 0
 
 die() { echo "$@" >&2; exit 1; }
 
diff --git a/t/perm-default-roles.t b/t/perm-default-roles.t
new file mode 100755
index 0000000..8535609
--- /dev/null
+++ b/t/perm-default-roles.t
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Test;
+
+# permissions using role names
+# ----------------------------------------------------------------------
+
+try "plan 27";
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+
+try "pwd";
+my $od = text();
+chomp($od);
+
+my $t;
+
+confreset; confadd '
+    @g1 = u1
+    @g2 = u2
+    @g3 = u3
+    @g4 = u4
+        repo foo/CREATOR/..*
+          C                 =   @g1 @g2
+          RW+               =   CREATOR
+          -     refs/tags/  =   WRITERS
+          RW                =   WRITERS
+          R                 =   READERS
+
+        repo bar/CREATOR/..*
+          C                 =   @g3 @g4
+          RW+               =   CREATOR
+          -     refs/tags/  =   WRITERS
+          RW                =   WRITERS
+          R                 =   READERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+# create repos - 1; no gl-perms files expected
+try "
+
+cd ..
+
+# make foo/u1/u1r1
+glt clone u1 file:///foo/u1/u1r1
+        /Initialized empty Git repository in .*/foo/u1/u1r1.git//
+
+# make bar/u3/u3r1
+glt clone u3 file:///bar/u3/u3r1
+        /Initialized empty Git repository in .*/bar/u3/u3r1.git//
+
+cd u3r1
+";
+
+try "cd $rb; find . -name gl-perms; cd $od"; cmp text(), '';
+
+# enable set-default-roles feature
+try "
+    cat $ENV{HOME}/.gitolite.rc
+    perl s/# 'set-default-roles'/'set-default-roles'/
+    put $ENV{HOME}/.gitolite.rc
+";
+
+# create repos - 2; empty gl-perms files expected
+try "
+
+cd ..
+
+# make foo/u1/u1r2
+glt clone u1 file:///foo/u1/u1r2
+        /Initialized empty Git repository in .*/foo/u1/u1r2.git//
+
+# make bar/u3/u3r2
+glt clone u3 file:///bar/u3/u3r2
+        /Initialized empty Git repository in .*/bar/u3/u3r2.git//
+
+cd u3r2
+";
+
+try "cd $rb; find . -name gl-perms";
+$t = md5sum(sort (lines()));
+cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
+d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
+';
+try "cd $od";
+
+# enable per repo default roles
+confadd '
+        repo foo/CREATOR/..*
+        option default.roles-1  =   READERS u3
+        option default.roles-2  =   WRITERS u4
+
+        repo bar/CREATOR/..*
+        option default.roles-1  =   READERS u5
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+# create repos - 3; filled gl-perms expected
+try "
+
+cd ..
+
+# make foo/u1/u1r3
+glt clone u1 file:///foo/u1/u1r3
+        /Initialized empty Git repository in .*/foo/u1/u1r3.git//
+
+# make bar/u3/u3r3
+glt clone u3 file:///bar/u3/u3r3
+        /Initialized empty Git repository in .*/bar/u3/u3r3.git//
+
+cd u3r3
+";
+
+try "cd $rb; find . -name gl-perms";
+$t = md5sum(sort (lines()));
+cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
+b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
+d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
+1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
+';
+try "cd $od";
+
+# add perms to an old repo
+try "
+echo WRITERS \@h1 | glt perms u1 foo/u1/u1r1
+";
+
+try "cd $rb; find . -name gl-perms";
+$t = md5sum(sort (lines()));
+cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
+b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
+f8f0fd8e139ddb64cd5572914b98750a  ./foo/u1/u1r1.git/gl-perms
+d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
+1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
+';
+try "cd $od";
+
+# add perms to a new repo
+try "
+echo WRITERS \@h2 | glt perms u1 -c foo/u1/u1r4
+";
+
+try "cd $rb; find . -name gl-perms";
+$t = md5sum(sort (lines()));
+cmp $t, 'd41d8cd98f00b204e9800998ecf8427e  ./bar/u3/u3r2.git/gl-perms
+b09856c1addc8e46f6ce0d21a666a633  ./bar/u3/u3r3.git/gl-perms
+f8f0fd8e139ddb64cd5572914b98750a  ./foo/u1/u1r1.git/gl-perms
+d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1r2.git/gl-perms
+1b5af29692fad391318573bbe633b476  ./foo/u1/u1r3.git/gl-perms
+df17cd2d47e4d99642d7c5ce4093d115  ./foo/u1/u1r4.git/gl-perms
+';
+try "cd $od";

commit 962e465079617de6c064c315ff0376f59db24ef8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 3 14:11:50 2013 +0530

    'info' should show wild patterns hiding in groups also
    
    (thanks to Sebastian Koslowski for catching this)

diff --git a/src/commands/info b/src/commands/info
index d5ff810..654b7ed 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -60,8 +60,10 @@ sub print_version {
 sub print_patterns {
     my ( $repos, @aa );
 
+    my $lm = \&Gitolite::Conf::Load::list_members;
+
     # find repo patterns only, call them with ^C flag included
-    @$repos = grep { !/$REPONAME_PATT/ } @{ lister_dispatch('list-repos')->() };
+    @$repos = grep { !/$REPONAME_PATT/ } map { /^@/ ? @{ $lm->($_) } : $_ } @{ lister_dispatch('list-repos')->() };
     @aa = qw(R W ^C);
     listem( $repos, '', '', @aa );
     # but squelch the 'lc' and 'ld' flags for these

commit 1dea1b636c8f6e7b0d7597a0bbca86c86cf1f4c2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 2 18:39:39 2013 +0530

    set-default-roles should not kick in always...
    
    should only do its thing when called from someone creating a new wild
    repo, and not just changing its perms.  (Because, don't forget, the
    POST_CREATE trigger list is invoked from perms also).

diff --git a/src/triggers/set-default-roles b/src/triggers/set-default-roles
index 752d00e..ab633ef 100755
--- a/src/triggers/set-default-roles
+++ b/src/triggers/set-default-roles
@@ -6,6 +6,7 @@
 # skip if arg-1 is POST_CREATE and no arg-3 (user name) exists (i.e., it's not
 # a wild repo)
 [ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+[ "$4" = "R" ] || [ "$4" = "W" ] || [ "$4" = "perms-c" ] || [ "$4" = "fork" ] exit 0
 
 die() { echo "$@" >&2; exit 1; }
 

commit 113657011053e8be475bc6a6e0f1c7e664a9a3f1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 25 20:47:32 2013 +0530

    (bugfix) (v3.5.1) fix permissions of projects.list file
    
    update-gitweb-access-list had a change recently (289b19d) that had a
    side-effect of making projects.list unreadable by other userids (like
    'apache' or whatever).
    
    This makes 3.5 unusable for anyone using gitweb.  (Hence the new tag,
    too, though 3.5 is only the previous commit and this is a very small
    change).

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index b6bdf16..aa1b8ac 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -20,7 +20,8 @@
 
 plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 [ -z "$plf" ] && plf=$HOME/projects.list
-tmpfile=`mktemp $plf.tmp_XXXXXXXX`
+# since mktemp does not honor umask, we just use it to generate a temp filename
+tmpfile=`mktemp -u $plf.tmp_XXXXXXXX`
 (
     gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
     gitolite list-phy-repos | gitolite git-config -r % gitweb\\.

commit 4071623727537b4e315d84d23bfc671b7cf99764
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 24 09:35:53 2013 +0530

    v3.5

diff --git a/CHANGELOG b/CHANGELOG
index cff47dd..9fde143 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+2013-03-24  v3.5    (2 minor backward compat breakages)
+                    1.  'DEFAULT_ROLE_PERMS' replaced by per repo
+                        'default.roles' option
+                    2.  'gitolite list-memberships' now requires a '-r' or a
+                        '-u' flag
+
+                    new 'gitolite owns' command (thanks to Kevin Pulo)
+
 2013-03-05  v3.4    new rc file format makes it much easier to enable specific
                     features
 

commit 289b19d7dc69a1c372464ea3ce099f76f316bf94
Author: Jeff Mitchell <mitchell at kde.org>
Date:   Sat Mar 23 12:17:46 2013 -0400

    overwrite projects.list atomically
    
    avoids any potential race conditions between triggers being run
    concurrently, and probably avoids gitweb picking up a half-done file too

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index 11e1aa6..b6bdf16 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -20,10 +20,10 @@
 
 plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 [ -z "$plf" ] && plf=$HOME/projects.list
-
+tmpfile=`mktemp $plf.tmp_XXXXXXXX`
 (
     gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
     gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
 ) |
-    cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
-
+    cut -f1 | sort -u | sed -e 's/$/.git/' > $tmpfile
+mv $tmpfile $plf

commit 0419d9958809a840a813df05547b67618b4150c5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 20 21:42:16 2013 +0530

    DEFAULT_ROLE_PERMS replaced by per repo 'default.roles' option

diff --git a/src/commands/fork b/src/commands/fork
index b381662..1edd205 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -44,12 +44,6 @@ echo "$from forked to $to" >&2
 cd $GL_REPO_BASE/$to.git
 echo $GL_USER > gl-creator
 
-touch gl-perms
-if gitolite query-rc -q DEFAULT_ROLE_PERMS
-then
-    gitolite query-rc DEFAULT_ROLE_PERMS > gl-perms
-fi
-
 ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
 
 # record where you came from
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 1d6d8e2..8c8a161 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -191,7 +191,6 @@ sub new_wild_repo {
     trigger( 'PRE_CREATE', $repo, $user, $aa );
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
-    _print( "$repo.git/gl-perms", ( $rc{DEFAULT_ROLE_PERMS} ? "$rc{DEFAULT_ROLE_PERMS}\n" : "" ) );
     trigger( 'POST_CREATE', $repo, $user, $aa );
 
     _chdir( $rc{GL_ADMIN_BASE} );
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 7d1f53d..a3f0c59 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -405,6 +405,8 @@ BEGIN { $non_core = "
     ssh-authkeys            POST_COMPILE    post-compile/ssh-authkeys
     Shell                   POST_COMPILE    post-compile/ssh-authkeys-shell-users
 
+    set-default-roles       POST_CREATE     .
+
     git-config              POST_COMPILE    post-compile/update-git-configs
     git-config              POST_CREATE     post-compile/update-git-configs
 
@@ -458,8 +460,6 @@ __DATA__
         READERS                     =>  1,
         WRITERS                     =>  1,
     },
-    # uncomment (and change) this if you wish
-    # DEFAULT_ROLE_PERMS            =>  'READERS @all',
 
     # ------------------------------------------------------------------
 
@@ -535,6 +535,9 @@ __DATA__
             # give some users direct shell access
             # 'Shell',
 
+            # set default roles from lines like 'option default.roles-1 = ...', etc.
+            # 'set-default-roles',
+
         # system admin stuff
 
             # enable mirroring (don't forget to set the HOSTNAME too!)
diff --git a/src/triggers/set-default-roles b/src/triggers/set-default-roles
new file mode 100755
index 0000000..752d00e
--- /dev/null
+++ b/src/triggers/set-default-roles
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# POST_CREATE trigger to set up default set of perms for a new wild repo
+
+# ----------------------------------------------------------------------
+# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists (i.e., it's not
+# a wild repo)
+[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+
+die() { echo "$@" >&2; exit 1; }
+
+cd $GL_REPO_BASE/$2.git || die "could not cd to $GL_REPO_BASE/$2.git"
+gitolite git-config -r $2 gitolite-options.default.roles | sort | cut -f3 |
+    perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$2/' > gl-perms
diff --git a/t/fork.t b/t/fork.t
index 99d4a41..2a7a7b7 100755
--- a/t/fork.t
+++ b/t/fork.t
@@ -5,11 +5,12 @@ use warnings;
 # this is hardcoded; change it if needed
 use lib "src/lib";
 use Gitolite::Test;
+my $h = $ENV{HOME};
 
 # fork command
 # ----------------------------------------------------------------------
 
-try "plan 30";
+try "plan 38";
 
 my $rb = `gitolite query-rc -n GL_REPO_BASE`;
 
@@ -34,7 +35,23 @@ try "
 
 # allow fork as a valid command
 $ENV{G3T_RC} = "$ENV{HOME}/g3trc";
-put "$ENV{G3T_RC}", "\$rc{COMMANDS}{fork} = 1;\n\$rc{DEFAULT_ROLE_PERMS} = 'READERS \@all';\n";
+put "$ENV{G3T_RC}", "\$rc{COMMANDS}{fork} = 1;\n";
+
+# enable set-default-roles feature, add options, push
+try "
+    cat $h/.gitolite.rc
+    perl s/# 'set-default-roles'/'set-default-roles'/
+    put $h/.gitolite.rc
+";
+try "cd gitolite-admin";
+confadd '
+    repo foo/CREATOR/..*
+        C   =   u1 u2
+        RW+ =   CREATOR
+    option default.roles-1 = READERS @all
+';
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "cd ..";
 
 try "
     # now the fork succeeds
@@ -61,8 +78,7 @@ try "
 
 my $t;
 try "cd $rb; find . -name gl-perms"; $t = md5sum(sort (lines())); cmp $t,
-'d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1a.git/gl-perms
-59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
+'59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
 59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1e.git/gl-perms
 ';
 

commit 09e3a3ca477b2569fd70c46fc5493402da30d674
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 20 13:58:34 2013 +0530

    new command 'owns' (thanks to Kevin Pulo)
    
    (See 'owns2' in
    https://groups.google.com/d/msg/gitolite/InJDugkXY0s/Lxa_gsd_WbUJ)

diff --git a/src/commands/owns b/src/commands/owns
new file mode 100755
index 0000000..d1d8757
--- /dev/null
+++ b/src/commands/owns
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for usage
+Usage:  gitolite owns <reponame>
+
+Checks if $GL_USER is an owner of the repo and returns an exit code (shell
+truth, 0 for success), which makes it possible to do this in shell:
+
+    if gitolite owns someRepo
+    then
+        ...
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+my $repo = shift;
+
+exit not owns($repo);
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 35d01ba..6311897 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -124,8 +124,8 @@ sub in_role {
 
 # return true if $ENV{GL_USER} is set and is an OWNER of the given repo.
 
-# shell equivalent
-#   if gitolite creator $REPONAME $GL_USER; then ...
+# shell equivalent (assuming GL_USER is set)
+#   if gitolite owns $REPONAME; then ...
 sub owns {
     valid_user();
     my $r = shift;

commit 797a81f3eb39023e2ed6c6ed039f8c975042efec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 17 12:50:15 2013 +0530

    list-memberships and perms changes:
    
      - list-memberships now requires a '-r' or '-u'; i.e., you have to
        explicitly state whether you are passing a reponame or a username
    
        see the new '-h' message for details.
    
      - Easy.pm now has a new 'in_role()' test that is, at present, only
        used by 'owns()', which uses that instead of checking that he is the
        creator.
    
        The role name used (I recommend "OWNER") must be set in the rc file
        like so
    
            OWNER_ROLENAME => 'OWNER',
    
        and if it is not set, defaults to 'CREATOR', which makes it behave
        as things currently do.
    
      - perms now uses this new 'owns()' function to authorise the user,
        instead of checking that she is the *creator*

diff --git a/src/commands/perms b/src/commands/perms
index 2314f00..90d7d2e 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -5,7 +5,7 @@ use warnings;
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
-use Gitolite::Conf::Load;
+use Gitolite::Easy;
 
 =for usage
 Usage:  ssh git at host perms -l <repo>
@@ -52,7 +52,7 @@ if ( $ARGV[0] eq '-c' ) {
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
     if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
-        my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
+        my $ret = Gitolite::Conf::Load::access( $repo, $ENV{GL_USER}, '^C', 'any' );
         _die $generic_error if $ret =~ /DENIED/;
 
         require Gitolite::Conf::Store;
@@ -70,7 +70,7 @@ _system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
 
 sub getperms {
     my $repo = shift;
-    _die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
+    _die $generic_error if not owns($repo);
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     print slurp($pf) if -f $pf;
@@ -79,7 +79,7 @@ sub getperms {
 }
 
 sub setperms {
-    _die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
+    _die $generic_error if not owns($repo);
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     if ( not @_ ) {
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 1bcb123..c4051e4 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -538,20 +538,43 @@ sub list_repos {
 }
 
 =for list_memberships
-Usage:  gitolite list-memberships <name>
+Usage:  gitolite list-memberships -u|-r <name>
 
-  - list all groups a name is a member of
-  - takes one user/repo name
+List all groups a name is a member of.  One of the flags '-u' or '-r' is
+mandatory, to specify if the name is a user or a repo.
+
+For users, the output includes the result from GROUPLIST_PGM, if it is
+defined.  For repos, the output includes any repo patterns that the repo name
+matches, as well as any groups that contain those patterns.
 =cut
 
 sub list_memberships {
-    usage() if @_ and $_[0] eq '-h' or not @_;
+    require Getopt::Long;
 
-    my $name = shift;
+    my ( $user, $repo, $help );
+
+    Getopt::Long::GetOptionsFromArray(
+        \@_,
+        'user|u=s' => \$user,
+        'repo|r=s' => \$repo,
+        'help|h'   => \$help,
+    );
+    usage() if $help or ( not $user and not $repo );
 
     load_common();
-    my @m = memberships( '', $name );
-    return ( sort_u( \@m ) );
+    my @m;
+
+    if ($user and $repo) {
+        # unsupported/undocumented except via "in_role()" in Easy.pm
+        @m = memberships( 'user', $user, $repo );
+    } elsif ($user) {
+        @m = memberships( 'user', $user );
+    } elsif ($repo) {
+        @m = memberships( 'repo', $repo );
+    }
+
+    @m = grep { $_ ne '@all' and $_ ne ( $user || $repo ) } @m;
+    return ( sort_u(\@m) );
 }
 
 =for list_members
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index b634c04..35d01ba 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -35,6 +35,7 @@ package Gitolite::Easy;
   is_admin
   is_super_admin
   in_group
+  in_role
 
   owns
   can_read
@@ -101,12 +102,27 @@ sub in_group {
     my $g = shift;
     $g =~ s/^\@?/@/;
 
-    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships($user) };
+    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships('-u', $user) };
+}
+
+# in_role()
+
+# return true if $ENV{GL_USER} is set and has the given role for the given repo
+
+# shell equivalent
+#   if gitolite list-memberships -u $GL_USER -r $GL_REPO | grep -x $ROLENAME >/dev/null; then ...
+sub in_role {
+    valid_user();
+    my $r = shift;
+    $r =~ s/^\@?/@/;
+    my $repo = shift;
+
+    return grep { $_ eq $r } @{ Gitolite::Conf::Load::list_memberships("-u", $user, "-r", $repo) };
 }
 
 # owns()
 
-# return true if $ENV{GL_USER} is set and is the creator of the given repo
+# return true if $ENV{GL_USER} is set and is an OWNER of the given repo.
 
 # shell equivalent
 #   if gitolite creator $REPONAME $GL_USER; then ...
@@ -117,7 +133,7 @@ sub owns {
     # prevent unnecessary disclosure of repo existence info
     return 0 if repo_missing($r);
 
-    return ( creator($r) eq $user );
+    return ( creator($r) eq $user or $rc{OWNER_ROLENAME} and in_role( $rc{OWNER_ROLENAME}, $r ) );
 }
 
 # can_read()
diff --git a/t/listers.t b/t/listers.t
index 1f7c7eb..5fbf0ae 100755
--- a/t/listers.t
+++ b/t/listers.t
@@ -76,53 +76,37 @@ t3
 testing
 ';
 
-try "gitolite list-memberships alice"; cmp
-'@all
- at crypto
+try "gitolite list-memberships -u alice"; cmp
+'@crypto
 @dilbert
-alice
 ';
 
-try "gitolite list-memberships ashok"; cmp
-'@all
- at dilbert
-ashok
+try "gitolite list-memberships -u ashok"; cmp
+'@dilbert
 ';
 
-try "gitolite list-memberships carol"; cmp
-'@all
- at crypto
-carol
+try "gitolite list-memberships -u carol"; cmp
+'@crypto
 ';
 
-try "gitolite list-memberships git"; cmp
-'@all
- at oss
-git
+try "gitolite list-memberships -r git"; cmp
+'@oss
 ';
 
-try "gitolite list-memberships gitolite"; cmp
-'@all
- at oss
-gitolite
+try "gitolite list-memberships -r gitolite"; cmp
+'@oss
 ';
 
-try "gitolite list-memberships gitolite3"; cmp
-'@all
- at oss
-gitolite3
+try "gitolite list-memberships -r gitolite3"; cmp
+'@oss
 ';
 
-try "gitolite list-memberships cc"; cmp
-'@all
- at prop
-cc
+try "gitolite list-memberships -r cc"; cmp
+'@prop
 ';
 
-try "gitolite list-memberships p4"; cmp
-'@all
- at prop
-p4
+try "gitolite list-memberships -r p4"; cmp
+'@prop
 ';
 
 try "gitolite list-members \@crypto"; cmp

commit 5debb4da40b2fca58be3fea0825fc374ff179bc8
Author: Jokajak <jokajak at gmail.com>
Date:   Tue Mar 12 07:27:56 2013 -0400

    add a contributing document
    
    text is pulled out of the wiki.  This will enable github to show a link when
    a contributor creates an issue or opens a Pull Request

diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..11009ba
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,24 @@
+Go to http://gitolite.com/gitolite/index.html#contact for information on
+contacting me, the mailing list, and IRC channel.  *Unless you are reporting
+what you think is a security issue, I prefer you send to the mailing list,
+not to me directly.*
+
+Please DO NOT send messages via github's "issues" system, linkedin
+comments/discussion, stackoverflow questions, google+, and any other Web 3.0
+"coolness". (The issues system does have an email interface, but it is not a
+substitute for email. I can't cc anyone else when I want to, for instance.
+Well I can, but any response the original requester then makes using the
+website will not get cc-d to the person I cc-d).
+
+Please send patches *via email*, not as github pull requests. Again, if you
+think it's a security issue, send it directly to my gmail address, but
+otherwise please send it to the mailing list, so others can see it and comment
+on it.
+
+The preferred format is the files created by git-format-patch, as attachments.
+However, if your repo has a public clone URL, you can make a new branch just
+for this fix, and send the repo URL and branch name to the mailing list.
+
+(If you do send me a github pull request, I may take it if it's a trivial
+patch, but otherwise I'll ask you to close the pull request, then read
+this URL for how to send me the patch.)

commit 37833afdf1a4119e65da3e7a6ac72acebe68420c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 6 13:43:00 2013 +0530

    minor but ugly bug in refex expression sugar code

diff --git a/src/syntactic-sugar/refex-expr b/src/syntactic-sugar/refex-expr
index 0713579..3565c99 100644
--- a/src/syntactic-sugar/refex-expr
+++ b/src/syntactic-sugar/refex-expr
@@ -10,8 +10,6 @@ my $seq = 1;
 sub sugar_script {
     my $lines = shift;
 
-    Gitolite::Common::dd ['lines', $lines];
-
     # my @out  = ();
     for my $l (@$lines) {
         push @out, $l;
@@ -26,14 +24,11 @@ sub sugar_script {
         print STDERR ">>>> $l\n";
         pop @out;   # we need to replace that last line
 
-        my @words = grep { $_ !~ /^(and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne)$/ } split ' ', $refexes;
-        push @out, map { "RW+ $_ = $users" } @words;
         push @out, "option refex-expr.sugar$seq = $refexes";
         push @out, "$perm VREF/refex-expr/sugar$seq = $users";
 
         $seq++;
     }
 
-    Gitolite::Common::dd ['out', \@out];
     return \@out;
 }

commit f76afc640023dff39176e512023aa151c730bbdf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 5 11:53:07 2013 +0530

    v3.4

diff --git a/CHANGELOG b/CHANGELOG
index b7d4330..cff47dd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+2013-03-05  v3.4    new rc file format makes it much easier to enable specific
+                    features
+
 2012-12-29  v3.3    bug fix: gl-perms propagation to slaves broke sometime
                     after v3.2 (so if you're only picking up tagged releases
                     you're OK)

commit 79714c9c85d1321e6f4b0b3a3617bdb3503a98e1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 3 18:56:41 2013 +0530

    refex-expr

diff --git a/src/VREF/refex-expr b/src/VREF/refex-expr
index d0a51b7..8403469 100755
--- a/src/VREF/refex-expr
+++ b/src/VREF/refex-expr
@@ -2,6 +2,9 @@
 use strict;
 use warnings;
 
+# see bottom of this file for instructons and IMPORTANT WARNINGS!
+# ----------------------------------------------------------------------
+
 my $rule = $ARGV[7];
 die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
   unless exists $ENV{"GL_REFEX_EXPR_" . $rule};
@@ -12,64 +15,85 @@ exit 0;
 
 __END__
 
-Documentation for the refex-expression evaluation feature
+------------------------------------------------------------------------
+IMPORTANT WARNINGS:
+  * has not been tested heavily
+      * SO PLEASE TEST YOUR SPECIFIC USE CASE THOROUGHLY!
+      * read the NOTES section below
+  * syntax and semantics are to be considered beta and may change as I find
+    better use cases
+------------------------------------------------------------------------
 
-First, make sure you have both the VREF and the trigger scripts
-(src/VREF/refex-expr and src/lib/Gitolite/Triggers/RefexExpr.pm)
+Refex expressions, like VREFs, are best used as additional "deny" rules, to
+deny combinations that the normal ruleset cannot detect.
 
-Next, add this to the ACCESS_2 list in the rc file:
+To enable this, uncomment 'refex-expr' in the ENABLE list in the rc file.
 
-    'RefexExpr::access_2',
+It allows you to say things like "don't allow users u3 and u4 to change the
+Makefile in the master branch" (i.e., they can change any other file in
+master, or the Makefile in any other branch, but not that specific combo).
 
-For the rest, we'll use this example:
+    repo foo
+        RW+                                 =   u1 u2   # line 1
 
-  * user u1 can push foo to some other branch, and anything else to the master
-    branch, but not foo to the master branch
+        RW+ master                          =   u3 u4   # line 2
+        RW+                                 =   u3 u4   # line 3
+        RW+ VREF/NAME/Makefile              =   u3 u4   # line 4
+        -   master and VREF/NAME/Makefile   =   u3 u4   # line 5
 
-  * user u2 is allowed to push either 'doc/' or 'src/' but not both
+Line 5 is a "refex expression".  Here are the rules:
+
+  * for each refex in the expression ("master" and "VREF/NAME/Makefile" in
+    this example), a count is kept of the number of times the EXACT refex was
+    matched and allowed in the *normal* rules (here, lines 2 and 4) during
+    this push.
 
-Here's the conf file extract:
+  * the expression is evaluated based on these counts.  0 is false, and
+    any non-zero is true (see more examples later).  The truth value of the
+    expression determines whether the refex expression matched.
 
-    repo    testing
-        RW+     master              =   u1          # line 1
-        RW+                         =   @all        # line 2
+    You can use any logical or arithmetic expression using refexes as operands
+    and using these operators:
 
-        RW+     VREF/NAME/foo       =   u1
-        RW+     VREF/NAME/doc/      =   u2
-        RW+     VREF/NAME/src/      =   u2
+        not and or xor + - == -lt -gt -eq -le -ge -ne
 
-        # set up 2 refex expressions, named e1, e2
-        option  refex-expr.e1       =   master and VREF/NAME/foo
-        option  refex-expr.e2       =   VREF/NAME/doc/ and VREF/NAME/src/
+    Parens are not allowed.  Precedence is as you might expect for those
+    operators.  It's actually perl that is evaluating it (you can guess what
+    the '-lt' etc., get translated to) so if in doubt, check 'man perlop'.
 
-        # now deny users if the corresponding expression is true
-        -       VREF/refex-expr/e1  =   u1
-        -       VREF/refex-expr/e2  =   u2
+  * the refexes that form the terms of the expression (in this case, lines 2
+    and 4) MUST come before the expression itself (i.e., line 5).
 
-Here are some IMPORTANT notes:
+  * note the words "EXACT refex was matched" above.
 
-  * You MUST place VREF/refex-expr rules at the end.  (Only 'partial-copy', if
-    you use it, must come later).
+    Let's say you add "u3" to line 1.  Then the refex expression in line 5
+    would never match for u3.  This is because line 1 prevents line 2 from
+    matching (being more general *and* appearing earlier), so the count for
+    the "master" refex would be 0.  If "master" is 0 (false), then "master and
+    <anything>" is also false.
 
-  * You MUST explicitly permit the refexes used in your refex expressions.  If
-    you have more generic rules, the specific ones must come first.
+    (Same thing is you swap lines 2 and 3; i.e., put the "RW+ = ..." before
+    the "RW+ master = ...").
 
-    For example, without line 1, the refex recorded for user u1 will come from
-    line 2, (so it will be 'refs/.*'), and 'master' in the refex expressions
-    will never have a true value.
+    Put another way, the terms in the refex expression are refexes, not refs.
+    Merely pushing the master branch does not mean the count for "master"
+    increases; it has to *match* on a line that has "master" as the refex.
 
-  * (corollary) make sure you use the exact same refex in the expression as
-    you did on the original rule line.  E.g., a missing slash at the end will
-    mess things up.
+Here are some more examples:
+
+  * user u2 is allowed to push either 'doc/' or 'src/' but not both
 
-  * You can use any logical expression using refexes as operands and using
-    these operators:
+        repo    foo
+            RW+                         =   u1 u2 u3
 
-        and not xor or
+            RW+ VREF/NAME/doc/                      =   u2
+            RW+ VREF/NAME/src/                      =   u2
+            -   VREF/NAME/doc/ and VREF/NAME/src/   =   u2
 
-    Parens are not allowed.
+  * user u3 is allowed to push at most 2 files to conf/
 
-    If a refex has passed, it will have a 'true' value, else it will be false.
+        repo    foo
+            RW+                         =   u1 u2 u3
 
-    The result of the evaluation, after these substitutions, will be the
-    result of the refex-expr VREF.
+            RW+ VREF/NAME/conf/         =   u3
+            -   VREF/NAME/conf/ -gt 2   =   u3
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index a408698..7d1f53d 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -374,6 +374,7 @@ BEGIN { $non_core = "
     continuation-lines      SYNTACTIC_SUGAR .
     keysubdirs-as-groups    SYNTACTIC_SUGAR .
     macros                  SYNTACTIC_SUGAR .
+    refex-expr              SYNTACTIC_SUGAR .
 
     renice                  PRE_GIT         .
 
@@ -388,7 +389,7 @@ BEGIN { $non_core = "
     Mirroring               PRE_GIT         ::
     Mirroring               POST_GIT        ::
 
-    RefexExpr               ACCESS_2        ::
+    refex-expr              ACCESS_2        RefexExpr::access_2
 
     RepoUmask               PRE_GIT         ::
     RepoUmask               POST_CREATE     ::
diff --git a/src/lib/Gitolite/Triggers/RefexExpr.pm b/src/lib/Gitolite/Triggers/RefexExpr.pm
index 687cb9e..5e2114f 100644
--- a/src/lib/Gitolite/Triggers/RefexExpr.pm
+++ b/src/lib/Gitolite/Triggers/RefexExpr.pm
@@ -4,7 +4,7 @@ use warnings;
 
 # track refexes passed and evaluate expressions on them
 # ----------------------------------------------------------------------
-# see instructions for use at the bottom of src/VREF/refex-expr
+# see src/VREF/refex-expr for instructions and WARNINGS!
 
 use Gitolite::Easy;
 
diff --git a/src/syntactic-sugar/refex-expr b/src/syntactic-sugar/refex-expr
new file mode 100644
index 0000000..0713579
--- /dev/null
+++ b/src/syntactic-sugar/refex-expr
@@ -0,0 +1,39 @@
+# vim: syn=perl:
+
+# "sugar script" (syntactic sugar helper) for gitolite3
+# ----------------------------------------------------------------------
+# see src/VREF/refex-expr for instructions and WARNINGS!
+
+my $perm = qr(-|R|RW\+?C?D?M?);
+
+my $seq = 1;
+sub sugar_script {
+    my $lines = shift;
+
+    Gitolite::Common::dd ['lines', $lines];
+
+    # my @out  = ();
+    for my $l (@$lines) {
+        push @out, $l;
+
+        # quick check
+        next unless $l =~ /^($perm) /;
+        # more detailed check
+        next unless $l =~ /^($perm) (\S.*) = (\S.*)$/;
+        my ($perm, $refexes, $users) = ($1, $2, $3);
+        next unless $refexes =~ / (and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne) /;
+
+        print STDERR ">>>> $l\n";
+        pop @out;   # we need to replace that last line
+
+        my @words = grep { $_ !~ /^(and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne)$/ } split ' ', $refexes;
+        push @out, map { "RW+ $_ = $users" } @words;
+        push @out, "option refex-expr.sugar$seq = $refexes";
+        push @out, "$perm VREF/refex-expr/sugar$seq = $users";
+
+        $seq++;
+    }
+
+    Gitolite::Common::dd ['out', \@out];
+    return \@out;
+}
diff --git a/t/refex-expr-test-1 b/t/refex-expr-test-1
new file mode 100755
index 0000000..1372a1e
--- /dev/null
+++ b/t/refex-expr-test-1
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+# not part of the official test suite (yet); just some q&d testing
+
+# to be run from ~/gitolite as ./$0
+
+set -e
+exec 3>&2
+exec > /dev/null
+exec 2> /dev/null
+print2() { echo -n "$@" >&3; }
+say2() { echo "$@" >&3; }
+die() { echo FATAL: "$@" >&3; exit 1; }
+
+export od=$PWD
+export tmp=$(mktemp -d)
+echo $tmp >&3
+trap "rm -rf $tmp" 0
+cd $tmp
+
+print2 setting up...
+( cd $od; t/reset )
+echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
+cat <<EOF >> ~/.gitolite/conf/gitolite.conf
+
+repo r1
+    RW+                                 =   u1 u2   # line 1
+
+    RW+ master                          =   u3 u4   # line 2
+    RW+                                 =   u3 u4   # line 3
+    RW+ VREF/NAME/Makefile              =   u3 u4   # line 4
+    -   master and VREF/NAME/Makefile   =   u3 u4   # line 5
+
+EOF
+gitolite setup
+say2 done
+
+# ----------------------------------------------------------------------
+
+rm -rf u1
+git clone u1:r1 u1
+cd u1
+tsh 'tc f1'
+git push u1:r1 master
+tsh 'tc f2'
+git push u2:r1 master
+tsh 'tc f3'
+git push u3:r1 master
+tsh 'tc f4'
+git push u4:r1 master
+say2 everyone master no Makefile
+
+tsh 'tc f5 Makefile'
+git push u1:r1 master
+tsh 'tc f5 Makefile'
+git push u1:r1 master:m1
+say2 u1 Makefile master
+
+tsh 'tc f5 Makefile'
+git push u3:r1 master && die u3 r1 master should have failed
+git push u3:r1 master:m2
+say2 u3 Makefile master fail m2 pass
diff --git a/t/refex-expr-test-2 b/t/refex-expr-test-2
new file mode 100755
index 0000000..773e42c
--- /dev/null
+++ b/t/refex-expr-test-2
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+# not part of the official test suite (yet); just some q&d testing
+
+# to be run from ~/gitolite as ./$0
+
+set -e
+exec 3>&2
+exec > /dev/null
+exec 2> /dev/null
+print2() { echo -n "$@" >&3; }
+say2() { echo "$@" >&3; }
+die() { echo FATAL: "$@" >&3; exit 1; }
+
+export od=$PWD
+export tmp=$(mktemp -d)
+echo $tmp >&3
+trap "rm -rf $tmp" 0
+cd $tmp
+
+print2 setting up...
+( cd $od; t/reset )
+echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
+cat <<EOF >> ~/.gitolite/conf/gitolite.conf
+
+    repo    r2
+        RW+                         =   @all
+
+        RW+ VREF/NAME/doc/                      =   u2
+        RW+ VREF/NAME/src/                      =   u2
+        -   VREF/NAME/doc/ and VREF/NAME/src/   =   u2
+
+EOF
+gitolite setup
+say2 done
+
+# ----------------------------------------------------------------------
+
+git clone u2:r2
+cd r2
+
+tsh 'tc aa'
+git push origin master
+say2 aa pass
+
+mkdir doc src
+
+tsh 'tc doc/d1'
+git push origin master
+say2 doc pass
+
+tsh 'tc src/s1'
+tsh 'tc src/s2'
+git push origin master
+say2 src src pass
+
+tsh 'tc doc/d2 src/s3'
+git push origin master && die 1
+git push u1:r2 master
+say2 doc src u2 fail u1 pass
diff --git a/t/refex-expr-test-3 b/t/refex-expr-test-3
new file mode 100755
index 0000000..47599eb
--- /dev/null
+++ b/t/refex-expr-test-3
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+# not part of the official test suite (yet); just some q&d testing
+
+# to be run from ~/gitolite as ./$0
+
+set -e
+exec 3>&2
+exec > /dev/null
+exec 2> /dev/null
+print2() { echo -n "$@" >&3; }
+say2() { echo "$@" >&3; }
+die() { echo FATAL: "$@" >&3; exit 1; }
+
+export od=$PWD
+export tmp=$(mktemp -d)
+echo $tmp >&3
+trap "rm -rf $tmp" 0
+cd $tmp
+
+print2 setting up...
+( cd $od; t/reset )
+echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
+cat <<EOF >> ~/.gitolite/conf/gitolite.conf
+
+    repo    r3
+        RW+                         =   u1 u2 u3
+
+        RW+ VREF/NAME/conf/         =   u3
+        -   VREF/NAME/conf/ -gt 2   =   u3
+
+EOF
+gitolite setup
+say2 done
+
+# ----------------------------------------------------------------------
+
+git clone u3:r3
+cd r3
+
+tsh 'tc aa'
+git push origin master
+say2 aa pass
+
+mkdir doc conf
+
+tsh 'tc doc/d1 doc/d2 doc/d3 doc/d4 conf/c1'
+git push origin master
+say2 4 doc 1 conf pass
+
+tsh 'tc conf/c2 conf/c3 conf/c4'
+git push origin master && die 1
+
+git push u2:r3 master
+say2 3 conf u3 fail u2 pass
diff --git a/t/refex-expr-test-9 b/t/refex-expr-test-9
new file mode 100755
index 0000000..b3a9f09
--- /dev/null
+++ b/t/refex-expr-test-9
@@ -0,0 +1,107 @@
+#!/bin/bash
+
+# not part of the official test suite (yet); just some q&d testing
+
+# to be run from ~/gitolite as ./$0
+
+set -e
+exec 3>&2
+exec > /dev/null
+exec 2> /dev/null
+print2() { echo -n "$@" >&3; }
+say2() { echo "$@" >&3; }
+die() { echo FATAL: "$@" >&3; exit 1; }
+
+export od=$PWD
+export tmp=$(mktemp -d)
+echo $tmp >&3
+trap "rm -rf $tmp" 0
+cd $tmp
+
+print2 setting up...
+( cd $od; t/reset )
+echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
+cat <<EOF >> ~/.gitolite/conf/gitolite.conf
+
+repo r9
+
+    RW+                         =   u3 u4
+
+    # u1 and u2 have some restrictions
+
+    # cant push master
+    -   master                  =   u1 u2
+    # cant push versioned tags, but other tags are fine
+    -   refs/tags/v[0-9]        =   u1 u2
+    # everything else is fine, but we need to recognise when they're pushing
+    # tags, so that the refex expr will have the correct info
+    RW+ refs/tags/              =   u1 u2
+    RW+                         =   u1 u2
+
+    # can push files in "foo/" only to a tag
+    RW+ VREF/NAME/foo/          =   u1 u2
+
+    RW+ VREF/NAME/foo/ and refs/tags/  =   u1 u2
+    -   VREF/NAME/foo/ and not refs/tags/ = u1 u2
+
+EOF
+gitolite setup
+say2 done
+
+# ----------------------------------------------------------------------
+
+# make sure u3 is not constrained in any way
+
+git clone u3:r9 refex-test.repo
+cd refex-test.repo
+
+tsh 'tc u3-f1'
+git pom
+
+mkdir bar foo
+tsh 'tc bar/thr'
+git pom
+git tag v3
+git push origin v3
+tsh 'tc foo/rht'
+git pom
+git tag 3v
+git push origin 3v
+
+say2 u3 no limits
+
+# now test u1's constraints
+
+cd ..
+rm -rf refex-test.repo
+
+rm -rf ~/repositories/r9.git
+gitolite setup
+
+git clone u1:r9 refex-test.repo
+cd refex-test.repo
+
+tsh 'tc u1-f1'
+# cant push master
+git pom && die 1
+# can push other branches
+git push origin master:m1
+say2 master fail m1 pass
+
+mkdir bar foo
+tsh 'tc bar/one'
+git push origin master:m1
+git tag v1
+# cant push v-tag
+git push origin v1 && die 2
+say2 v-tag fail
+
+# cant push foo/ to a branch
+tsh 'tc foo/eno'
+git push origin master:m1 && die 3
+say2 foo/ m1 fail
+
+# but can push to a non-v-tag
+git tag 1v
+git push origin 1v
+say2 foo/ non-v-tag pass

commit c7705464e33fafee5d8d36787a4d9140b74188d9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 3 18:33:43 2013 +0530

    rc file format change: some inline doc/help text fixups

diff --git a/src/commands/htpasswd b/src/commands/htpasswd
index 3571cf1..fc468d0 100755
--- a/src/commands/htpasswd
+++ b/src/commands/htpasswd
@@ -14,7 +14,7 @@ Sets your htpasswd, assuming your admin has enabled it.
 
 (Admins: You need to add HTPASSWD_FILE to the rc file, pointing to an
 existing, writable, but possibly an initially empty, file, as well as adding
-an entry for 'htpasswd' to the COMMANDS hash).
+'htpasswd' to the ENABLE list).
 =cut
 
 # usage and sanity checks
diff --git a/src/commands/rsync b/src/commands/rsync
index 87fb046..46ec167 100755
--- a/src/commands/rsync
+++ b/src/commands/rsync
@@ -26,7 +26,7 @@ BUNDLE SUPPORT
         that was then either deleted or rewound in the repo.  This is checked
         on every invocation.
 
-    (2) Add 'rsync' to the COMMANDS list in the rc file
+    (2) Add 'rsync' to the ENABLE list in the rc file
 
 
 GENERIC RSYNC SUPPORT
diff --git a/src/lib/Gitolite/Triggers/AutoCreate.pm b/src/lib/Gitolite/Triggers/AutoCreate.pm
index 8fe46d7..e1d977a 100644
--- a/src/lib/Gitolite/Triggers/AutoCreate.pm
+++ b/src/lib/Gitolite/Triggers/AutoCreate.pm
@@ -6,16 +6,16 @@ use warnings;
 # perl trigger set for stuff to do with auto-creating repos
 # ----------------------------------------------------------------------
 
-# to deny auto-create on read access, add 'AutoCreate::deny_R' to the
-# PRE_CREATE trigger list
+# to deny auto-create on read access, uncomment 'no-create-on-read' in the
+# ENABLE list in the rc file
 sub deny_R {
     die "autocreate denied\n" if $_[3] and $_[3] eq 'R';
     return;
 }
 
-# to deny auto-create on read *and* write access, add 'AutoCreate::deny_RW' to
-# the PRE_CREATE trigger list.  This means you can only create repos using the
-# 'create' command, (which needs to be enabled in the COMMANDS list).
+# to deny auto-create on read *and* write, uncomment 'no-auto-create' in the
+# ENABLE list in the rc file.  This means you can only create wild repos using
+# the 'create' command, (which needs to be enabled in the ENABLE list).
 sub deny_RW {
     die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' );
     return;
diff --git a/src/lib/Gitolite/Triggers/Shell.pm b/src/lib/Gitolite/Triggers/Shell.pm
index b6e24c3..a2c5c0d 100644
--- a/src/lib/Gitolite/Triggers/Shell.pm
+++ b/src/lib/Gitolite/Triggers/Shell.pm
@@ -1,7 +1,6 @@
 package Gitolite::Triggers::Shell;
 
-# usage notes: this module must be loaded first in the INPUT trigger list.  Or
-# at least before Mirroring::input anyway.
+# usage notes: uncomment 'Shell' in the ENABLE list in the rc file.
 
 # documentation is in the ssh troubleshooting and tips document, under the
 # section "giving shell access to gitolite users"
diff --git a/src/syntactic-sugar/macros b/src/syntactic-sugar/macros
index 2dbb5fd..37a4b22 100644
--- a/src/syntactic-sugar/macros
+++ b/src/syntactic-sugar/macros
@@ -36,9 +36,9 @@ Documentation is mostly by example.
 
 Setup:
 
-  * the line
+  * uncomment the line
         'macros',
-    should be added to the SYNTACTIC_SUGAR list in ~/.gitolite.rc
+    in the ENABLE list in ~/.gitolite.rc
 
 Notes on macro definition:
 
diff --git a/src/triggers/post-compile/ssh-authkeys-split b/src/triggers/post-compile/ssh-authkeys-split
index b6b0e15..e538fff 100755
--- a/src/triggers/post-compile/ssh-authkeys-split
+++ b/src/triggers/post-compile/ssh-authkeys-split
@@ -27,8 +27,8 @@
 # USAGE
 # -----
 #
-# add it to the POST_COMPILE trigger list in the rc file, but *before* the
-# ssh-authkeys program entry.
+# to enable, uncomment the 'ssh-authkeys-split' line in the ENABLE list in the
+# rc file.
 
 cd $GL_ADMIN_BASE/keydir
 
diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
index f8c2bd6..06cbc58 100755
--- a/src/triggers/post-compile/update-description-file
+++ b/src/triggers/post-compile/update-description-file
@@ -5,9 +5,9 @@
 # just goes with the flow of setting config variables; nothing special needs
 # to be done for the description.
 
-# But this only works for gitweb, not for cgit.  Cgit users must therefore add
-# this line to the POST_COMPILE list in the rc file:
-#   'post-compile/update-description-file',
+# But this only works for gitweb, not for cgit.  Cgit users must uncomment the
+# 'cgit' line in the ENABLE list in the rc file (which has the effect of
+# adding this program to the POST_COMPILE trigger list).
 
 cd $GL_REPO_BASE
 gitolite list-phy-repos | gitolite git-config % gitweb.description |
diff --git a/src/triggers/upstream b/src/triggers/upstream
index c8e8c6d..c64e2f2 100755
--- a/src/triggers/upstream
+++ b/src/triggers/upstream
@@ -25,7 +25,7 @@ git fetch -q "$url" '+refs/*:refs/*'
 
 # INSTRUCTIONS:
 #
-# * add 'upstream' to the PRE_GIT trigger list in the rc file.
+# * uncomment 'upstream' in the ENABLE list in the rc file.
 # * add option lines to conf file.  For example:
 #
 #       repo git

commit d7024deffacf8f39afb104e834593cc78abcf102
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jan 10 18:49:33 2013 +0530

    rc file format change (please read below)
    
    First: if you have an old rc file it will still work.  In fact
    internally the new rc file data gets converted to the old one.  So you
    do not have to do anything normally.
    
    This changes the rc file in the following way, taking mirroring as an
    example.  Simple enough for most purposes but you really should read the
    documentation...
    
    old:
        INPUT                       =>
            [
                'Mirroring::input',
            ],
    
        PRE_GIT                     =>
            [
                'Mirroring::pre_git',
            ],
    
        POST_GIT                    =>
            [
                'Mirroring::post_git',
            ],
    
    new:
        ENABLE  =>
            [
            # COMMANDS
                ... several commands ...
            # Triggers
                ... several triggers ...
                'Mirroring',
            ],

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 056f2a0..a408698 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -27,6 +27,7 @@ use Gitolite::Common;
 # ----------------------------------------------------------------------
 
 our %rc;
+our $non_core;
 
 # ----------------------------------------------------------------------
 
@@ -75,6 +76,9 @@ if ( defined($GL_ADMINDIR) ) {
 # ----------------------------------------------------------------------
 @rc{ keys %RC } = values %RC;
 
+# expand the non_core list into INPUT, PRE_GIT, etc using 'ENABLE' settings
+non_core_expand() if $rc{ENABLE};
+
 # add internal triggers
 # ----------------------------------------------------------------------
 
@@ -120,6 +124,70 @@ my $glrc_default_text = '';
     $glrc_default_text = <DATA>;
 }
 
+# ----------------------------------------------------------------------
+
+sub non_core_expand {
+    my %enable;
+
+    for my $e ( @{ $rc{ENABLE} } ) {
+        my ($name, $arg) = split ' ', $e, 2;
+        # store args as the hash value for the name
+        $enable{$name} = $arg || '';
+
+        # for now, we pretend everything is a command, because commands
+        # are the only thing that the non_core list does not contain
+        $rc{COMMANDS}{$name} = $arg || 1;
+    }
+
+    # bring in additional non-core specs from the rc file, if given
+    if (my $nc2 = $rc{NON_CORE}) {
+        for ($non_core, $nc2) {
+            # beat 'em into shape :)
+            s/#.*//g;
+            s/[ \t]+/ /g; s/^ //mg; s/ $//mg;
+            s/\n+/\n/g;
+        }
+
+        for ( split "\n", $nc2 ) {
+            next unless /\S/;
+            my ($name, $where, $module, $before, $name2) = split ' ', $_;
+            if (not $before) {
+                $non_core .= "$name $where $module\n";
+                next;
+            }
+            die if $before ne 'before';
+            $non_core =~ s(^(?=$name2 $where( |$)))($name $where $module\n)m;
+        }
+    }
+
+    my @data = split "\n", $non_core || '';
+    for (@data) {
+        next if /^\s*(#|$)/;
+        my ($name, $where, $module) = split ' ', $_;
+
+        # if it appears here, it's not a command, so delete it.  At the end of
+        # this loop, what's left in $rc{COMMANDS} will be those names in the
+        # enable list that do not appear in the non_core list.
+        delete $rc{COMMANDS}{$name};
+
+        next unless exists $enable{$name};
+
+        # module to call is name if specified as "."
+        $module = $name if $module eq ".";
+
+        # module to call is "name::pre_git" or such if specified as "::"
+        ( $module = $name ) .= "::" . lc($where) if $module eq '::';
+
+        # append arguments, if supplied
+        $module .= " $enable{$name}" if $enable{$name};
+
+        push @{ $rc{$where} }, $module;
+    }
+}
+
+# exported functions
+# ----------------------------------------------------------------------
+
 sub glrc {
     my $cmd = shift;
     if ( $cmd eq 'default-filename' ) {
@@ -141,9 +209,6 @@ sub glrc {
     }
 }
 
-# exported functions
-# ----------------------------------------------------------------------
-
 my $all   = 0;
 my $nonl  = 0;
 my $quiet = 0;
@@ -300,6 +365,58 @@ sub args {
     return @ARGV;
 }
 
+# ----------------------------------------------------------------------
+
+BEGIN { $non_core = "
+    # No user-servicable parts inside.  Warranty void if seal broken.  Refer
+    # servicing to authorised service center only.
+
+    continuation-lines      SYNTACTIC_SUGAR .
+    keysubdirs-as-groups    SYNTACTIC_SUGAR .
+    macros                  SYNTACTIC_SUGAR .
+
+    renice                  PRE_GIT         .
+
+    CpuTime                 INPUT           ::
+    CpuTime                 POST_GIT        ::
+
+    Shell                   INPUT           ::
+
+    Alias                   INPUT           ::
+
+    Mirroring               INPUT           ::
+    Mirroring               PRE_GIT         ::
+    Mirroring               POST_GIT        ::
+
+    RefexExpr               ACCESS_2        ::
+
+    RepoUmask               PRE_GIT         ::
+    RepoUmask               POST_CREATE     ::
+
+    partial-copy            PRE_GIT         .
+
+    upstream                PRE_GIT         .
+
+    no-create-on-read       PRE_CREATE      AutoCreate::deny_R
+    no-auto-create          PRE_CREATE      AutoCreate::deny_RW
+
+    ssh-authkeys-split      POST_COMPILE    post-compile/ssh-authkeys-split
+    ssh-authkeys            POST_COMPILE    post-compile/ssh-authkeys
+    Shell                   POST_COMPILE    post-compile/ssh-authkeys-shell-users
+
+    git-config              POST_COMPILE    post-compile/update-git-configs
+    git-config              POST_CREATE     post-compile/update-git-configs
+
+    gitweb                  POST_CREATE     post-compile/update-gitweb-access-list
+    gitweb                  POST_COMPILE    post-compile/update-gitweb-access-list
+
+    cgit                    POST_COMPILE    post-compile/update-description-file
+
+    daemon                  POST_CREATE     post-compile/update-git-daemon-access-list
+    daemon                  POST_COMPILE    post-compile/update-git-daemon-access-list
+";
+}
+
 1;
 
 # ----------------------------------------------------------------------
@@ -313,135 +430,148 @@ __DATA__
 
 # (Tip: perl allows a comma after the last item in a list also!)
 
-# HELP for commands (see COMMANDS list below) can be had by running the
-# command with "-h" as the sole argument.
+# HELP for commands can be had by running the command with "-h".
 
-# HELP for all the other external programs (the syntactic sugar helpers and
-# the various programs/functions in the 8 trigger lists), can be found in
-# doc/non-core.mkd (http://sitaramc.github.com/gitolite/non-core.html) or in
-# the corresponding source file itself.
+# HELP for all the other FEATURES can be found in the documentation (look for
+# "list of non-core programs shipped with gitolite" in the master index) or
+# directly in the corresponding source file.
 
 %RC = (
-    # if you're using mirroring, you need a hostname.  This is *one* simple
-    # word, not a full domain name.  See documentation if in doubt
-    # HOSTNAME                  =>  'darkstar',
-    UMASK                       =>  0077,
 
-    # look in the "GIT-CONFIG" section in the README for what to do
-    GIT_CONFIG_KEYS             =>  '',
+    # ------------------------------------------------------------------
 
-    # comment out if you don't need all the extra detail in the logfile
-    LOG_EXTRA                   =>  1,
+    # default umask gives you perms of '0700'; see the rc file docs for
+    # how/why you might change this
+    UMASK                           =>  0077,
 
-    # settings used by external programs; uncomment and change as needed.  You
-    # can add your own variables for use in your own external programs; take a
-    # look at the info and desc commands for perl and shell samples.
+    # look for "git-config" in the documentation
+    GIT_CONFIG_KEYS                 =>  '',
 
-    # used by the CpuTime trigger
-    # DISPLAY_CPU_TIME          =>  1,
-    # CPU_TIME_WARN_LIMIT       =>  0.1,
-    # used by the desc command
-    # WRITER_CAN_UPDATE_DESC    =>  1,
-    # used by the info command
-    # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                       =>  1,
 
-    # add more roles (like MANAGER, TESTER, ...) here.
+    # roles.  add more roles (like MANAGER, TESTER, ...) here.
     #   WARNING: if you make changes to this hash, you MUST run 'gitolite
     #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
-    ROLES                       =>
-        {
-            READERS             =>  1,
-            WRITERS             =>  1,
-        },
+    ROLES => {
+        READERS                     =>  1,
+        WRITERS                     =>  1,
+    },
     # uncomment (and change) this if you wish
-    # DEFAULT_ROLE_PERMS          =>  'READERS @all',
-
-    # comment out or uncomment as needed
-    # these are available to remote users
-    COMMANDS                    =>
-        {
-            'help'              =>  1,
-            'desc'              =>  1,
-            # 'fork'            =>  1,
-            'info'              =>  1,
-            # 'mirror'          =>  1,
-            'perms'             =>  1,
-            # 'sskm'            =>  1,
-            'writable'          =>  1,
-            # 'D'               =>  1,
-        },
-
-    # comment out or uncomment as needed
-    # these will run in sequence during the conf file parse
-    SYNTACTIC_SUGAR             =>
-        [
+    # DEFAULT_ROLE_PERMS            =>  'READERS @all',
+
+    # ------------------------------------------------------------------
+
+    # rc variables used by various features
+
+    # the 'info' command prints this as additional info, if it is set
+        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
+    # the 'desc' command uses this
+        # WRITER_CAN_UPDATE_DESC    =>  1,
+
+    # the CpuTime feature uses these
+        # display user, system, and elapsed times to user after each git operation
+        # DISPLAY_CPU_TIME          =>  1,
+        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
+        # CPU_TIME_WARN_LIMIT       =>  0.1,
+
+    # the Mirroring feature needs this
+        # HOSTNAME                  =>  "foo",
+
+    # if you enabled 'Shell', you need this
+        # SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",
+
+    # ------------------------------------------------------------------
+
+    # List of commands and features to enable
+
+    ENABLE => [
+
+        # COMMANDS
+
+            # These are the commands enabled by default
+            'help',
+            'desc',
+            'info',
+            'perms',
+            'writable',
+
+            # Uncomment or add new commands here.
+            # 'create',
+            # 'fork',
+            # 'mirror',
+            # 'sskm',
+            # 'D',
+
+        # These FEATURES are enabled by default.
+
+            # essential (unless you're using smart-http mode)
+            'ssh-authkeys',
+
+            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
+            'git-config',
+
+            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
+            'daemon',
+
+            # creates projects.list file; if you don't use gitweb, comment this out
+            'gitweb',
+
+        # These FEATURES are disabled by default; uncomment to enable.  If you
+        # need to add new ones, ask on the mailing list :-)
+
+        # user-visible behaviour
+
+            # prevent wild repos auto-create on fetch/clone
+            # 'no-create-on-read',
+            # no auto-create at all (don't forget to enable the 'create' command!)
+            # 'no-auto-create',
+
+            # access a repo by another (possibly legacy) name
+            # 'Alias',
+
+            # give some users direct shell access
+            # 'Shell',
+
+        # system admin stuff
+
+            # enable mirroring (don't forget to set the HOSTNAME too!)
+            # 'Mirroring',
+
+            # allow people to submit pub files with more than one key in them
+            # 'ssh-authkeys-split',
+
+            # selective read control hack
+            # 'partial-copy',
+
+            # manage local, gitolite-controlled, copies of read-only upstream repos
+            # 'upstream',
+
+            # updates 'description' file instead of 'gitweb.description' config item
+            # 'cgit',
+
+        # performance, logging, monitoring...
+
+            # be nice
+            # 'renice 10',
+
+            # log CPU times (user, system, cumulative user, cumulative system)
+            # 'CpuTime',
+
+        # syntactic_sugar for gitolite.conf and included files
+
+            # allow backslash-escaped continuation lines in gitolite.conf
             # 'continuation-lines',
+
+            # create implicit user groups from directory names in keydir/
             # 'keysubdirs-as-groups',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence to modify the input (arguments and environment)
-    INPUT                       =>
-        [
-            # 'CpuTime::input',
-            # 'Shell::input',
-            # 'Alias::input',
-            # 'Mirroring::input',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just after the first access check is done
-    ACCESS_1                    =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just before the actual git command is invoked
-    PRE_GIT                     =>
-        [
-            # 'renice 10',
-            # 'Mirroring::pre_git',
-            # 'partial-copy',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just after the second access check is done
-    ACCESS_2                    =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after the git command returns
-    POST_GIT                    =>
-        [
-            # 'Mirroring::post_git',
-            # 'CpuTime::post_git',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence before a new wild repo is created
-    PRE_CREATE                  =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after a new repo is created
-    POST_CREATE                 =>
-        [
-            'post-compile/update-git-configs',
-            'post-compile/update-gitweb-access-list',
-            'post-compile/update-git-daemon-access-list',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after post-update
-    POST_COMPILE                =>
-        [
-            'post-compile/ssh-authkeys',
-            'post-compile/update-git-configs',
-            'post-compile/update-gitweb-access-list',
-            'post-compile/update-git-daemon-access-list',
-        ],
+
+            # allow simple line-oriented macros
+            # 'macros',
+
+    ],
+
 );
 
 # ------------------------------------------------------------------------------
diff --git a/t/mirror-test-rc b/t/mirror-test-rc
index 23cd108..d06fbc0 100644
--- a/t/mirror-test-rc
+++ b/t/mirror-test-rc
@@ -1,129 +1,153 @@
-# configuration variables for gitolite
-
 # This file is in perl syntax.  But you do NOT need to know perl to edit it --
 # just mind the commas, use single quotes unless you know what you're doing,
 # and make sure the brackets and braces stay matched up!
 
 # (Tip: perl allows a comma after the last item in a list also!)
 
+# HELP for commands (see COMMANDS list below) can be had by running the
+# command with "-h" as the sole argument.
+
+# HELP for all the other FEATURES can be found in the documentation (look for
+# "list of non-core programs shipped with gitolite" in the master index) or
+# directly in the corresponding source file.
+
 %RC = (
-    # if you're using mirroring, you need a hostname.  This is *one* simple
-    # word, not a full domain name.  See documentation if in doubt
-    HOSTNAME                    =>  '%HOSTNAME',
-    UMASK                       =>  0077,
-    GIT_CONFIG_KEYS             =>  '',
 
-    # comment out if you don't need all the extra detail in the logfile
-    LOG_EXTRA                   =>  1,
+    # ------------------------------------------------------------------
+
+    HOSTNAME                        =>  '%HOSTNAME',
+
+    # default umask gives you perms of '0700'; see the rc file docs for
+    # how/why you might change this
+    UMASK                           =>  0077,
 
-    # settings used by external programs; uncomment and change as needed.  You
-    # can add your own variables for use in your own external programs; take a
-    # look at the info and desc commands for perl and shell samples.
+    # look in the "GIT-CONFIG" section in the README for what to do
+    GIT_CONFIG_KEYS                 =>  '',
 
-    # used by the CpuTime trigger
-    # DISPLAY_CPU_TIME          =>  1,
-    # CPU_TIME_WARN_LIMIT       =>  0.1,
-    # used by the desc command
-    # WRITER_CAN_UPDATE_DESC    =>  1,
-    # used by the info command
-    # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                       =>  1,
 
-    # add more roles (like MANAGER, TESTER, ...) here.
+    # roles.  add more roles (like MANAGER, TESTER, ...) here.
     #   WARNING: if you make changes to this hash, you MUST run 'gitolite
     #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
-    ROLES                       =>
-        {
-            READERS             =>  1,
-            WRITERS             =>  1,
-        },
+    ROLES => {
+        READERS                     =>  1,
+        WRITERS                     =>  1,
+    },
     # uncomment (and change) this if you wish
-    # DEFAULT_ROLE_PERMS          =>  'READERS @all',
-
-    # comment out or uncomment as needed
-    # these are available to remote users
-    COMMANDS                    =>
-        {
-            'help'              =>  1,
-            'info'              =>  1,
-            'desc'              =>  1,
-            'perms'             =>  1,
-            'mirror'            =>  1,
-            'writable'          =>  1,
-        },
-
-    # comment out or uncomment as needed
-    # these will run in sequence during the conf file parse
-    SYNTACTIC_SUGAR             =>
-        [
-            # 'continuation-lines',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence to modify the input (arguments and environment)
-    INPUT                       =>
-        [
-            'Mirroring::input',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just after the first access check is done
-    ACCESS_1                    =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just before the actual git command is invoked
-    PRE_GIT                     =>
-        [
-            # if you use this, make this the first item in the list
-            # 'renice 10',
+    # DEFAULT_ROLE_PERMS            =>  'READERS @all',
+
+    # ------------------------------------------------------------------
+
+    # rc variables used by various features
+
+    # the 'info' command prints this as additional info, if it is set
+        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
+    # the 'desc' command uses this
+        # WRITER_CAN_UPDATE_DESC    =>  1,
+
+    # the CpuTime feature uses these
+        # display user, system, and elapsed times to user after each git operation
+        # DISPLAY_CPU_TIME          =>  1,
+        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
+        # CPU_TIME_WARN_LIMIT       =>  0.1,
+
+    # the Mirroring feature needs this
+        # HOSTNAME                  =>  "foo",
+
+    # if you enabled 'Shell', you need this
+        # SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",
+
+    # ------------------------------------------------------------------
+
+    # List of commands and features to enable
+
+    ENABLE => [
+
+        # COMMANDS
+
+            # These are the commands enabled by default
+            'help',
+            'desc',
+            'info',
+            'perms',
+            'writable',
+
+            'mirror',
+
+            # Uncomment or add new commands here.
+            # 'create',
+            # 'fork',
+            # 'mirror',
+            # 'sskm',
+            # 'D',
+
+        # These FEATURES are enabled by default.
 
-            'Mirroring::pre_git',
+            # essential (unless you're using smart-http mode)
+            'ssh-authkeys',
 
-            # see docs ("list of non-core programs shipped") for details
+            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
+            'git-config',
+
+            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
+            'daemon',
+
+            # creates projects.list file; if you don't use gitweb, comment this out
+            'gitweb',
+
+        # These FEATURES are disabled by default; uncomment to enable.  If you
+        # need to add new ones, ask on the mailing list :-)
+
+        # user-visible behaviour
+
+            # prevent wild repos auto-create on fetch/clone
+            # 'no-create-on-read',
+            # no auto-create at all (don't forget to enable the 'create' command!)
+            # 'no-auto-create',
+
+            # access a repo by another (possibly legacy) name
+            # 'Alias',
+
+            # give some users direct shell access
+            # 'Shell',
+
+        # system admin stuff
+
+            # enable mirroring (don't forget to set the HOSTNAME too!)
+            'Mirroring',
+
+            # allow people to submit pub files with more than one key in them
+            # 'ssh-authkeys-split',
+
+            # selective read control hack
             # 'partial-copy',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence just after the second access check is done
-    ACCESS_2                    =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after the git command returns
-    POST_GIT                    =>
-        [
-            'Mirroring::post_git',
-
-            # if you use this, make this the last item in the list
-            # 'CpuTime::post_git',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence before a new wild repo is created
-    PRE_CREATE                  =>
-        [
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after a new wild repo is created
-    POST_CREATE                 =>
-        [
-            'post-compile/update-git-configs',
-            'post-compile/update-gitweb-access-list',
-            'post-compile/update-git-daemon-access-list',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after post-update
-    POST_COMPILE                =>
-        [
-            'post-compile/ssh-authkeys',
-            'post-compile/update-git-configs',
-            'post-compile/update-gitweb-access-list',
-            'post-compile/update-git-daemon-access-list',
-        ],
+
+            # manage local, gitolite-controlled, copies of read-only upstream repos
+            # 'upstream',
+
+            # updates 'description' file instead of 'gitweb.description' config item
+            # 'cgit',
+
+        # performance, logging, monitoring...
+
+            # be nice
+            # 'renice 10',
+
+            # log CPU times (user, system, cumulative user, cumulative system)
+            # 'CpuTime',
+
+        # syntactic_sugar for gitolite.conf and included files
+
+            # allow backslash-escaped continuation lines in gitolite.conf
+            # 'continuation-lines',
+
+            # create implicit user groups from directory names in keydir/
+            # 'keysubdirs-as-groups',
+
+    ],
+
 );
 
 # ------------------------------------------------------------------------------

commit 1e3a4afadae14cadc2af4bf1771058bd6744f6fb
Author: Lars Djerf <lars.djerf at gmail.com>
Date:   Sat Mar 2 12:34:45 2013 +0100

    (minor) README docfix about empty config values
    
    (this should have been part of d8df4a9)

diff --git a/README.txt b/README.txt
index 8a8a15c..14e04b4 100644
--- a/README.txt
+++ b/README.txt
@@ -287,9 +287,9 @@ GIT-CONFIG
 
     **WARNING**
 
-        The last syntax shown above is the *only* way to *delete* a config
-        variable once you have added it.  Merely removing it from the conf
-        file will *not* delete it from the repo.git/config file.
+        The last two syntaxes shown above are the *only* way to *delete*
+        a config variable once you have added it.  Merely removing it from
+        the conf file will *not* delete it from the repo.git/config file.
 
     **SECURITY NOTE**
 

commit 744335d593ae1e2b6fef1ca4364029a4dc41d210
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Feb 28 04:46:52 2013 +0530

    remove restriction on @ in setup -pk

diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 69a1173..e59977f 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -85,14 +85,16 @@ sub args {
 
     if ($pubkey) {
         $pubkey =~ /\.pub$/ or _die "'$pubkey' name does not end in .pub";
-        $pubkey =~ /\@/ and _die "'$pubkey' name contains '\@'";
         tsh_try("cat $pubkey")              or _die "'$pubkey' not a readable file";
         tsh_lines() == 1                    or _die "'$pubkey' must have exactly one line";
         tsh_try("ssh-keygen -l -f $pubkey") or _die "'$pubkey' does not seem to be a valid ssh pubkey file";
 
         $admin = $pubkey;
-        $admin =~ s(.*/)();
-        $admin =~ s/\.pub$//;
+        # next 2 lines duplicated from args() in ssh-authkeys
+        $admin =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
+        $admin =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
+        $pubkey =~ /\@/ and print STDERR "NOTE: the admin username is '$admin'\n";
+
     }
 
     return ( $admin || '', $pubkey || '', $h_only || 0, $argv );

commit 0458bdf1e18f6112db6ad8b8ad49135c4fe2e8db
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Feb 28 04:33:24 2013 +0530

    (minor) change urls to fit new domain gitolite.com

diff --git a/README.txt b/README.txt
index 08fed1c..8a8a15c 100644
--- a/README.txt
+++ b/README.txt
@@ -15,7 +15,7 @@ This file contains BASIC DOCUMENTATION ONLY.
 
 The COMPLETE DOCUMENTATION is at:
 
-    http://sitaramc.github.com/gitolite/master-toc.html
+    http://gitolite.com/gitolite/master-toc.html
 
 Please go there for what/why/how, concepts, background, troubleshooting, more
 details on what is covered here, or advanced features not covered here.
diff --git a/check-g2-compat b/check-g2-compat
index 72c32fa..508c6fd 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -40,7 +40,7 @@ sub intro {
     msg( '', '' );
     msg( INFO => "'see docs' usually means the pre-migration checklist in" );
     msg( '',  => "'g2migr.html'; to get there, start from the main migration" );
-    msg( '',  => "page at http://sitaramc.github.com/gitolite/install.html#migr" );
+    msg( '',  => "page at http://gitolite.com/gitolite/migr.html" );
     msg( '', '' );
 }
 
diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 56ce700..3b2689d 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -60,7 +60,7 @@ if ($admin) {
     die "\t\t*** FATAL ***\n" .
         "$admin.pub maps to $fpu, not $admin.\n" .
         "You will not be able to access gitolite with this key.\n" .
-        "Look for the 'ssh troubleshooting' link in http://sitaramc.github.com/gitolite/.\n"
+        "Look for the 'ssh troubleshooting' link in http://gitolite.com/gitolite/ssh.html.\n"
     if $fpu ne "user $admin";
     #>>>
 }
diff --git a/src/commands/sskm b/src/commands/sskm
index d465816..fd60233 100755
--- a/src/commands/sskm
+++ b/src/commands/sskm
@@ -8,7 +8,7 @@ use Gitolite::Common;
 
 =for usage
 Usage for this command is not that simple.  Please read the full documentation
-in doc/sskm.mkd or online at http://sitaramc.github.com/gitolite/sskm.html.
+in doc/sskm.mkd or online at http://gitolite.com/gitolite/sskm.html.
 =cut
 
 usage() if @ARGV and $ARGV[0] eq '-h';
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 9c2ae5b..056f2a0 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -66,7 +66,7 @@ if (-r $rc and -s $rc) {
 }
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
-    say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g2migr.html)";
+    say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://gitolite.com/gitolite/g2migr.html)";
 
     exit 1;
 }
diff --git a/t/README b/t/README
index 6a98e27..5053260 100644
--- a/t/README
+++ b/t/README
@@ -9,5 +9,5 @@ On such a userid, clone gitolite then run this command in the clone:
 
     GITOLITE_TEST=y prove
 
-http://sitaramc.github.com/gitolite/testing.html has more details.  It will
+http://gitolite.com/gitolite/testing.html has more details.  It will
 also help you try out gitolite if you want to go beyond just the test suite.

commit bc041eac973134d4debc468fcc6e363cef59af99
Author: Jeffrey Adamson <jwadamson at gmail.com>
Date:   Sat Feb 23 23:09:48 2013 -0500

    Fix a warning about ambiguous shift usage
    
    [committer's note: newer perl's don't have this problem, but 5.8.8 at
    least appears to require the parens]

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index c6f4b67..e588c6a 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -121,7 +121,7 @@ sub fp_file {
 
 sub fp_line {
     my ( $fh, $fn ) = tempfile();
-    print $fh shift . "\n";
+    print $fh shift() . "\n";
     close $fh;
     my $fp = fp_file($fn);
     unlink $fn;

commit a1aba93b6080fa2406ab73a9d88c5f0925bfeeee
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Feb 18 19:21:09 2013 +0530

    allow non-gitolite keys to have options/command, etc
    
    Apparently, ssh-keygen can take fingerprints of entire authkeys files
    also.  This is totally undocumented.
    
    Since 'man ssh-keygen' only says: "Show fingerprint of specified public
    key file." and makes no mention of authorized_keys files, I had assumed
    that it treated a file containing this
    
        command="/usr/bin/backup" ssh-rsa .....
    
    (i.e., a non-gitolite key that nevertheless contains a command) as just
    a special type of pubkey file.  This meant, to me, that the presence or
    absence of a newline should not matter, because *without* the 'command='
    it certainly doesn't.
    
    But what's actually happening is that it is treating this as an
    authorized_keys file, and in *that* mode, it requires a newline.
    
    I still don't see why it should require a newline as a *terminator*;
    having it as a *separator* should be sufficient, but it's pointless to
    argue about that when the feature itself is undocumented.
    
    Wizmaster (code at wizmaster at fr) had to dig into the openssh source
    code to figure this out and explain it to me.

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 25d75f0..c6f4b67 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -121,7 +121,7 @@ sub fp_file {
 
 sub fp_line {
     my ( $fh, $fn ) = tempfile();
-    print $fh shift;
+    print $fh shift . "\n";
     close $fh;
     my $fp = fp_file($fn);
     unlink $fn;

commit 293df79e7ca4ce76d36c4c7b91b09d10c9c644c3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Feb 4 22:04:01 2013 +0530

    (minor) change ?? to syntax error

diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index e7ca028..a2823e7 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -73,7 +73,7 @@ sub parse {
             trace( 2, $line );
             set_subconf($1);
         } else {
-            _warn "?? $line";
+            _warn "syntax error, ignoring: '$line'";
         }
     }
     parse_done();

commit b8ea3827e0e3079bfb2c06fc69758bcbba48f1ea
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Feb 3 21:41:28 2013 +0530

    (minor) access: usage message was confusing

diff --git a/src/commands/access b/src/commands/access
index 636dc61..7254bc6 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -24,15 +24,10 @@ Notes:
     The 'any' ref is special -- it ignores deny rules (see docs for what this
     means and exceptions).
 
-For each case where access is not denied, one line is printed like this:
-
-    reponame<tab>username<tab>access rights
-
-This is orders of magnitude faster than running the command multiple times;
-you'll notice if you have more than a hundred or so repos.
-
-Advanced uses: see src/triggers/post-compile/update-git-daemon-access-list for
-a good example.
+Batch mode: see src/triggers/post-compile/update-git-daemon-access-list for a
+good example that shows how to test several repos in one invocation.  This is
+orders of magnitude faster than running the command multiple times; you'll
+notice if you have more than a hundred or so repos.
 =cut
 
 usage() if not @ARGV or $ARGV[0] eq '-h';

commit 61048a5f98f35500e9710d4956dd3fd6678c6c38
Author: Ralf Hemmecke <ralf at hemmecke.org>
Date:   Thu Jan 24 11:29:06 2013 +0100

    fix typo in ssh-authkeys

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index a18b833..25d75f0 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -78,7 +78,7 @@ sub sanity {
     _warn "$akdir missing; creating a new one"  if not -d $akdir;
     _warn "$akfile missing; creating a new one" if not -f $akfile;
 
-    _mkdir( $akdir, 0700 ) if not -d $akfile;
+    _mkdir( $akdir, 0700 ) if not -d $akdir;
     if ( not -f $akfile ) {
         _print( $akfile, "" );
         chmod 0700, $akfile;

commit 3e87a082c1411fa763fe5f61289f1b46772595b2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jan 24 14:14:25 2013 +0530

    (minor) perms would not print the correct message sometimes...
    
    i.e., if getperms had an error.  (The same error for setperms would
    print the correct message)

diff --git a/src/commands/perms b/src/commands/perms
index 88244a9..2314f00 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -36,6 +36,8 @@ usage() if not @ARGV or $ARGV[0] eq '-h';
 
 $ENV{GL_USER} or _die "GL_USER not set";
 
+my $generic_error = "repo does not exist, or you are not authorised";
+
 my $list = 0;
 if ( $ARGV[0] eq '-l' ) {
     $list++;
@@ -43,8 +45,6 @@ if ( $ARGV[0] eq '-l' ) {
     getperms(@ARGV);    # doesn't return
 }
 
-my $generic_error = "repo does not exist, or you are not authorised";
-
 # auto-create the repo if -c passed and repo doesn't exist
 if ( $ARGV[0] eq '-c' ) {
     shift;

commit 923cf4671916c5f28a639eabb5bf183addcaf918
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jan 23 18:53:37 2013 +0530

    sshkeys-lint: accept ecdsa keys also
    
    thanks to Richard Salts

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 5626a27..56ce700 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -142,10 +142,10 @@ sub ak_comment {
 sub fprint {
     local $_ = shift;
     my ( $fh, $tempfn, $in );
-    if (/ssh-(dss|rsa) /) {
+    if ( /ssh-(dss|rsa) / || /ecdsa-/ ) {
         # an actual key was passed.  Since ssh-keygen requires an actual file,
         # make a temp file to take the data and pass on to ssh-keygen
-        s/^.* (ssh-dss|ssh-rsa)/$1/;
+        s/^.* (ssh-dss|ssh-rsa|ecdsa-\S+)/$1/;
         use File::Temp qw(tempfile);
         ( $fh, $tempfn ) = tempfile();
         $in = $tempfn;

commit 404dafd79b27b836d9101e912ce0786bec2f09ec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jan 11 21:15:21 2013 +0530

    access(): the pattern for refs is too strict for filenames
    
    a filename also becomes a "ref" if you use VREF/NAME.
    
    For some reason[1], it seems some people use crazy filenames like foo(0)
    or bar%20baz, and these things blow up on that test.
    
    --
    
    [1] viz., the lack of someone with good taste, like me, leading their
    project ;-)

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 6c133e2..1bcb123 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -79,7 +79,11 @@ sub access {
     $deny_rules = option( $repo, 'deny-rules' );
 
     # sanity check the only piece the user can control
-    _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ $REF_OR_FILENAME_PATT;
+    _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ m(^VREF/NAME/) or $ref =~ $REF_OR_FILENAME_PATT;
+    # apparently we can't always force sanity; at least what we *return*
+    # should be sane/safe.  This pattern is based on REF_OR_FILENAME_PATT.
+    (my $safe_ref = $ref) =~ s([^-0-9a-zA-Z._\@/+ :,])(.)g;
+    trace( 2, "safe_ref $safe_ref created from $ref") if $ref ne $safe_ref;
 
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
     # check to give valid results.
@@ -91,7 +95,7 @@ sub access {
     # similarly, ^C must be denied if the repo exists
     if ( $aa eq '^C' and not repo_missing($repo) ) {
         trace( 2, "DENIED by existence" );
-        return "$aa $ref $repo $user DENIED by existence";
+        return "$aa $safe_ref $repo $user DENIED by existence";
     }
 
     trace( 2, scalar(@rules) . " rules found" );
@@ -107,7 +111,7 @@ sub access {
         next unless $ref =~ /^$refex/ or $ref eq 'any';
 
         trace( 2, "DENIED by $refex" ) if $perm eq '-';
-        return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-';
+        return "$aa $safe_ref $repo $user DENIED by $refex" if $perm eq '-';
 
         # $perm can be RW\+?(C|D|CD|DC)?M?.  $aa can be W, +, C or D, or
         # any of these followed by "M".
@@ -117,7 +121,7 @@ sub access {
         return $refex if ( $perm =~ /$aaq/ );
     }
     trace( 2, "DENIED by fallthru" );
-    return "$aa $ref $repo $user DENIED by fallthru";
+    return "$aa $safe_ref $repo $user DENIED by fallthru";
 }
 
 sub git_config {
diff --git a/t/invalid-refnames-filenames.t b/t/invalid-refnames-filenames.t
index bf4fabe..19267fe 100755
--- a/t/invalid-refnames-filenames.t
+++ b/t/invalid-refnames-filenames.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # invalid refnames
 # ----------------------------------------------------------------------
 
-try "plan 57";
+try "plan 56";
 try "DEF POK = !/DENIED/; !/failed to push/";
 
 confreset; confadd '
@@ -84,8 +84,7 @@ glt push u1 origin HEAD
 tc  aa=bb
 glt push u1 origin HEAD
         /To file:///aa/
-        /invalid characters in ref or filename: \\'VREF/NAME/aa=bb/
-        reject
+        POK; /HEAD -> master/
 
 # push to branch dd,ee ok
 git reset --hard HEAD^

commit d8fe7572ad78b6154a90b9856c52200dede07245
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jan 8 21:29:58 2013 +0530

    Easy.pm: should mention $HOME also
    
    ...thanks to Alan Ott

diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 9e0ae0b..b634c04 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -16,6 +16,7 @@ package Gitolite::Easy;
 # external program, using the paths you just found:
 #
 #   BEGIN {
+#       $ENV{HOME} = "/home/git";   # or whatever is the hosting user's $HOME
 #       $ENV{GL_BINDIR} = "/full/path/to/gitolite/src";
 #       $ENV{GL_LIBDIR} = "/full/path/to/gitolite/src/lib";
 #   }

commit 4cf55a52b40ace6d5e6347c6809988b03b0dce61
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Jan 7 21:54:12 2013 +0530

    update-git-configs: 'SAFE_CONFIG'...
    
    allow macros to ease the pain of UNSAFE_PATT
    
    See http://sitaramc.github.com/gitolite/git-config.html or equivalent.

diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 64b8ed9..e91df20 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -49,6 +49,9 @@ sub fixup_config {
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
+            while ( my ($mk, $mv) = each %{ $rc{SAFE_CONFIG} } ) {
+                $value =~ s/%$mk/$mv/g;
+            }
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {
             system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );

commit 089f0f9d9ed37d44ad2d8d0eb574e1496b113579
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Dec 31 06:23:00 2012 +0530

    on removing a repo...
    
    Not following through on instructions to remove a repo, per [1], is not
    sufficient.  Even if you did just the first step, the repo should  no
    longer be accessible.  See [2] for discussion.
    
    As a bonus, we get rid of one pesky warning that always confused people.
    (In hindsight -- this confusion itself should have been a warning that
    something is wrong and needed fixing!)
    
    [1]: http://sitaramc.github.com/gitolite/repos.html
    [2]: http://groups.google.com/group/gitolite/browse_thread/thread/a3d4c3e917056abb

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 441c644..6c133e2 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -246,7 +246,7 @@ sub load_1 {
     }
 
     if ( -f "gl-conf" ) {
-        _warn "split conf not set, gl-conf present for '$repo'" if not $split_conf{$repo};
+        return if not $split_conf{$repo};
 
         my $cc = "./gl-conf";
         _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;

commit 5aef1adc7b47e8c9145d912c76ad15737a5fd34b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Dec 31 05:48:18 2012 +0530

    list-dangling-repos: are we there yet?
    
    <sigh>First I forgot @groups that may contain repos and patterns, then I
    forgot patterns where the CREATOR token is used (this is the fix here).

diff --git a/src/commands/list-dangling-repos b/src/commands/list-dangling-repos
index ea36bab..6d86937 100755
--- a/src/commands/list-dangling-repos
+++ b/src/commands/list-dangling-repos
@@ -4,6 +4,7 @@ use warnings;
 
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Common;
+use Gitolite::Conf::Load;
 
 =for usage
 Usage:  gitolite list-dangling-repos
@@ -40,8 +41,9 @@ for my $pr (keys %phy_repos) {
 # Remove regex matches.
 for my $pr (keys %phy_repos) {
     my $matched = 0;
+    my $pr2 = Gitolite::Conf::Load::generic_name($pr);
     for my $r (keys %repos) {
-        if ($pr =~ /^$r$/) {
+        if ($pr =~ /^$r$/ or $pr2 =~ /^$r$/) {
             $matched = 1;
             next;
         }

commit 1fefb1c0d9acd1b7ebd74208c82cf15c7452e34b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Dec 29 13:58:12 2012 +0530

    v3.3

diff --git a/CHANGELOG b/CHANGELOG
index 91faaa5..b7d4330 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+2012-12-29  v3.3    bug fix: gl-perms propagation to slaves broke sometime
+                    after v3.2 (so if you're only picking up tagged releases
+                    you're OK)
+
+                    the "D" command now allows rm/unlock to be totally
+                    disabled
+
+                    new trigger: update-gitweb-daemon-from-options; another
+                    way to update gitweb and daemon access lists
+
+                    new 'create' command for explicit wild repo creation, and
+                    new AutoCreate trigger to control auto-creation
+
+                    allow simple macros in conf file
+
 2012-11-14  v3.2    major efficiency boost for large setups
 
                     optional support for multi-line pubkeys; see

commit ea3d04ea0a7283f641ef59390f64bf9b133291e6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Dec 29 07:31:27 2012 +0530

    perms batch mode confuses; print something to help
    
    What happens is that running
    
        ssh git at host perms reponame
    
    appears to hang, since it is waiting for STDIN.  I added a message to
    help, since we don't want users losing files accidentally!
    
    (The other alternative is to add a specific option for batch mode, but
    this is backward incompatible for people who have scripts that may be
    doing this).
    
    thanks to Caleb Cushing for catching this
    
    ----
    
    The "make sure Ctrl-C gets caught" thing needs some explanation.
    
    Without it, a user could inadvertently lose his gl-perms file if he ran
    the command in batch mode.  You'd think that the Ctrl-C would hit the
    
        for (<>) {
    
    line and bail, but it manages to reach the
    
        _print( $pf, @a );
    
    line somehow.  Even trapping SIG INT does not help.
    
    I suspect it is to do with how signals are propagated by ssh across a
    "no-pty" session, but am not sure.

diff --git a/src/commands/mirror b/src/commands/mirror
index d091979..d72f9ae 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -48,7 +48,7 @@ if ( $cmd eq 'push' ) {
     if (-f "gl-creator") {
         # try to propagate the wild repo, including creator name and gl-perms
         my $creator = `cat gl-creator`; chomp($creator);
-        trace(1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\'`);
+        trace(1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\' 2>/dev/null`);
     }
 
     my $errors = 0;
diff --git a/src/commands/perms b/src/commands/perms
index ffb4bd9..88244a9 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -84,12 +84,15 @@ sub setperms {
 
     if ( not @_ ) {
         # legacy mode; pipe data in
+        print STDERR "'batch' mode started, waiting for input (run with '-h' for details).\n";
+        print STDERR "Please hit Ctrl-C if you did not intend to do this.\n";
         @ARGV = ();
         my @a;
         for (<>) {
             _die "Invalid role '$1'; check the rc file" if /(\S+)/ and not $rc{ROLES}{$1};
             push @a, $_;
         }
+        print STDERR "\n";  # make sure Ctrl-C gets caught
         _print( $pf, @a );
         return;
     }

commit 84424e48b9a89fc9a3784dc4cd640c7420618318
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Dec 29 11:38:13 2012 +0530

    bug fix: perms propagation to slaves...
    
    Sometime after v3.2, I fixed what looked like an information disclosure
    issue, where a user could determine if an arbitrary repo existed or not,
    even if he had no rights to see the repo.  This was:
    
        96cc2ea "new features relating to creating wild repos:"
    
    Unfortunately, this appears to have broken gl-perms propagation to
    slaves, because now running "perm -c" on an existing repo dies!
    
    If you run
    
        git diff 96cc2ea^ <this commit> -- src/commands/perms
    
    you'll see how simple the fix *should* have been :-(

diff --git a/src/commands/perms b/src/commands/perms
index 6b61596..ffb4bd9 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -43,23 +43,23 @@ if ( $ARGV[0] eq '-l' ) {
     getperms(@ARGV);    # doesn't return
 }
 
+my $generic_error = "repo does not exist, or you are not authorised";
+
 # auto-create the repo if -c passed and repo doesn't exist
 if ( $ARGV[0] eq '-c' ) {
     shift;
     my $repo = $ARGV[0] or usage();
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
-    my $d = "$rc{GL_REPO_BASE}/$repo.git";
-    my $errmsg = "repo already exists or you are not authorised to create it";
-    # use the same message in both places to prevent leaking repo existence info
-    _die $errmsg if -d $d;
-    my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
-    _die $errmsg if $ret =~ /DENIED/;
-
-    require Gitolite::Conf::Store;
-    Gitolite::Conf::Store->import;
-    new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
-    gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
+    if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
+        my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
+        _die $generic_error if $ret =~ /DENIED/;
+
+        require Gitolite::Conf::Store;
+        Gitolite::Conf::Store->import;
+        new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
+        gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
+    }
 }
 
 my $repo = shift;
@@ -70,7 +70,7 @@ _system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
 
 sub getperms {
     my $repo = shift;
-    _die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
+    _die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     print slurp($pf) if -f $pf;
@@ -79,7 +79,7 @@ sub getperms {
 }
 
 sub setperms {
-    _die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
+    _die $generic_error if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     if ( not @_ ) {
diff --git a/t/sequence.t b/t/sequence.t
index a42b6b6..87f3731 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -100,7 +100,7 @@ try "
     # auto-create using perms fail
     echo READERS u5 | glt perms u4 -c foo/u4/baz
         !/Initialized empty Git repository in .*/foo/u4/baz.git/
-        /FATAL: repo already exists or you are not authorised to create it/
+        /FATAL: repo does not exist, or you are not authorised/
 
     # auto-create using perms
     echo READERS u2 | glt perms u1 -c foo/u1/baz

commit b3036948821003b4b0d4aad6927630deb6fdf266
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Dec 29 13:14:36 2012 +0530

    minor bugly...
    
    please remember we make up words here, like refex was a word we created
    to mean "a regex that matches a ref".
    
    A "bugly", then, is a bug that's merely ugly (and not a real problem!)

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 2611630..441c644 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -386,6 +386,8 @@ sub user_roles {
     for (@roles) {
         # READERS u3 u4 @g1
         s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
+        next if /^#/;
+        next unless /\S/;
         my ( $role, @members ) = split;
         # role = READERS, members = u3, u4, @g1
         if ( $role ne 'CREATOR' and not $rc{ROLES}{$role} ) {

commit b9bbb78278c12d9468d8c50b61ca27e6d6fa0c5b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Dec 19 07:17:09 2012 +0530

    D: allow rm and unlock to be disabled

diff --git a/src/commands/D b/src/commands/D
index a255073..1a8c2b5 100755
--- a/src/commands/D
+++ b/src/commands/D
@@ -12,6 +12,9 @@
 
 # - run a cron job to delete old repos based on age (the TRASH_SUFFIX has a
 #   timestamp); your choice how/how often you do that
+
+# - you can completely disable the 'rm' command by setting an rc variable
+#   called D_DISABLE_RM to "1".
 # ----------------------------------------------------------------------
 
 # ----------------------------------------------------------------------
@@ -67,6 +70,8 @@ owner_or_die() {
 if [ "$cmd" = "rm" ]
 then
 
+    gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
+
     owner_or_die
     [ -f $repo.git/gl-rm-ok ] || die "'$repo' is locked!"
     rm -rf $repo.git
@@ -82,6 +87,8 @@ then
 elif [ "$cmd" = "unlock" ]
 then
 
+    gitolite query-rc -q D_DISABLE_RM && die "sorry, 'unlock' and 'rm' are disabled"
+
     owner_or_die
     touch $repo.git/gl-rm-ok
     echo "'$repo' is now unlocked"

commit 3513f4a153a2ec9c5216ef11c84668107c4e409e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Dec 19 06:09:03 2012 +0530

    fix bug in list-dangling-repos
    
    Still, I would advise caution if you use this as a basis for deleting
    repos from the file system.  A bug in this program could cause you to
    lose important data!

diff --git a/src/commands/list-dangling-repos b/src/commands/list-dangling-repos
index 6889ed9..ea36bab 100755
--- a/src/commands/list-dangling-repos
+++ b/src/commands/list-dangling-repos
@@ -12,6 +12,9 @@ List all existing repos that no one can access remotely any more.  They could
 be normal repos that were taken out of "repo" statements in the conf file, or
 wildcard repos whose matching "wild" pattern was taken out or changed so it no
 longer matches.
+
+I would advise caution if you use this as a basis for deleting repos from the
+file system.  A bug in this program could cause you to lose important data!
 =cut
 
 usage() if @ARGV and $ARGV[0] eq '-h';
@@ -21,6 +24,9 @@ usage() if @ARGV and $ARGV[0] eq '-h';
 # is to cull %phy_repos of all keys that have a matching key in %repos, where
 # "matching" means "string equal" or "regex match".
 my %repos = map { chomp; $_ => 1 } `gitolite list-repos`;
+for my $r ( grep /^@/, keys %repos ) {
+    map { chomp; $repos{$_} = 1; } `gitolite list-members $r`;
+}
 my %phy_repos = map { chomp; $_ => 1 } `gitolite list-phy-repos`;
 
 # Remove exact matches.  But for repo names like "gtk+", you could have

commit 4f4658274dd84595d23b2d0fd16eba6927239a34
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Dec 14 07:29:20 2012 +0530

    CREATOR need only be a "word" in wild repo patterns
    
    this was a v2 compat breakage, caught by Dominik Schäfer
    (schaedpq at gmail)

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 00a6b8a..2611630 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -418,8 +418,7 @@ sub generic_name {
     $creator = creator($base);
 
     $base2 = $base;
-    $base2 =~ s(/$creator/)(/CREATOR/) if $creator;
-    $base2 =~ s(^$creator/)(CREATOR/)  if $creator;
+    $base2 =~ s(\b$creator\b)(CREATOR) if $creator;
     $base2 = '' if $base2 eq $base;    # if there was no change
 
     return $base2;

commit 20484845786fedc48ea3b78f0b9375a346485d94
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Dec 14 07:28:58 2012 +0530

    add more detail to error message
    
    this error normally happens due to some permission issue on the log
    file, but we weren't printing the actual cause, so it was confusing

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index c93e6ef..f7171ff 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -277,7 +277,7 @@ sub gl_log {
     my $fh;
     logger_plus_stderr( "errors found before logging could be setup", "$msg" ) if not $ENV{GL_LOGFILE};
     open my $lfh, ">>", $ENV{GL_LOGFILE}
-      or logger_plus_stderr( "errors found before logfile could be created", "$msg" );
+      or logger_plus_stderr( "errors found but logfile could not be created", "$ENV{GL_LOGFILE}: $!", "$msg" );
     print $lfh "$ts\t$tid\t$msg\n";
     close $lfh;
 }

commit 8e3ee2f9c11bcce2bdc85fd31453f4b5ab6a022b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Dec 13 20:15:17 2012 +0530

    (minor) macro buglets
    
      - allow parameter-less macros
      - allow macro body to start on next line

diff --git a/src/syntactic-sugar/macros b/src/syntactic-sugar/macros
index 1202dae..2dbb5fd 100644
--- a/src/syntactic-sugar/macros
+++ b/src/syntactic-sugar/macros
@@ -12,11 +12,11 @@ sub sugar_script {
     my @out  = ();
 
     my $l = join("\n", @$lines);
-    while ($l =~ s/^macro (\w+) (.*?)\nend//ms) {
+    while ($l =~ s/^macro (\w+)\b(.*?)\nend//ms) {
         $macro{$1} = $2;
     }
 
-    $l =~ s/^((\w+) .*)/$macro{$2} ? expand($1) : $1/gem;
+    $l =~ s/^((\w+)\b.*)/$macro{$2} ? expand($1) : $1/gem;
 
     $lines = [split "\n", $l];
     return $lines;

commit 3103d68a75473383d791e8c8d7ce446168ac4c7d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Dec 13 19:30:12 2012 +0530

    new trigger: update-gitweb-daemon-from-options
    
    another way to update gitweb and daemon access lists

diff --git a/src/triggers/post-compile/update-gitweb-daemon-from-options b/src/triggers/post-compile/update-gitweb-daemon-from-options
new file mode 100755
index 0000000..a3627c9
--- /dev/null
+++ b/src/triggers/post-compile/update-gitweb-daemon-from-options
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Update git-daemon and gitweb access using 'option' lines instead of special
+# usernames.
+
+# To use:
+
+# * enable this combined updater in the rc file by removing the other two
+#   update-*-access-list entries and inserting this one instead.  (This would
+#   be in the POST_CREATE and POST_COMPILE lists).
+
+# * the add option lines in the conf file, like this:
+#
+#       repo foo @bar
+#           option daemon = 1
+#           option gitweb = 1
+
+# Note: don't forget that gitweb can also be enabled by actual config
+# variables (gitweb.owner, gitweb.description, gitweb.category)
+
+# This is useful for people who don't like '@all' to be literally *all* users,
+# including gitweb and daemon, and can't/won't use deny-rules properly.
+
+# ----------------------------------------------------------------------
+# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
+# it's been triggered by a *normal* (not "wild") repo creation, which in turn
+# means a POST_COMPILE should be following so there's no need to waste time
+# running this once for each new repo
+[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+
+# first do the gitweb stuff
+
+plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
+[ -z "$plf" ] && plf=$HOME/projects.list
+
+(
+    gitolite list-phy-repos | gitolite git-config % gitolite-options.gitweb
+    gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
+) |
+    cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
+
+# now deal with git-daemon
+
+EO=git-daemon-export-ok
+RB=`gitolite query-rc GL_REPO_BASE`
+export EO RB
+
+export tmp=$(mktemp -d)
+trap "rm -rf $tmp" 0
+
+gitolite list-phy-repos | sort | tee $tmp/all | gitolite git-config % gitolite-options.daemon | cut -f1 > $tmp/daemon
+
+comm -23 $tmp/all $tmp/daemon | perl -lne 'unlink "$ENV{RB}/$_.git/$ENV{EO}"'
+cat               $tmp/daemon | while read repo
+do
+    > $RB/$repo.git/$EO
+done

commit f89408adb12a853f02e6f03787242e3d6cebd13c
Author: Sven Strickroth <email at cs-ware.de>
Date:   Sun Dec 9 03:31:45 2012 +0100

    Set Content-Type to text/plain for gitolite commands over http
    
    Explicitly set "Content-Type: text/plain" for gitolite commands when
    issued over http, so that it is possible to see the output with normal
    browsers.
    
    (At least) Apache httpd might set the Content-Type to something
    different and triggers a download instead of showing the text directly.
    
    Signed-off-by: Sven Strickroth <email at cs-ware.de>

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 4bbae48..a3ec321 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -234,5 +234,6 @@ sub http_print_headers {
     print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
     print "Pragma: no-cache\r\n";
     print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
+    print "Content-Type: text/plain\r\n";
     print "\r\n";
 }

commit fc7ddfc818498cf8c97fe1f12e61fb8209061427
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Dec 7 17:30:56 2012 +0530

    (minor) lint had syntax errors
    
    thanks to xcat on #gitolite for catching it
    (shows you how often it gets used I guess!)

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index a981a13..5626a27 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -177,9 +177,9 @@ Look for potential problems in ssh keys.
 
 sshkeys-lint expects:
   - the contents of an authorized_keys file via STDIN, otherwise it uses
-    $HOME/.ssh/authorized_keys
+    \$HOME/.ssh/authorized_keys
   - one or more pubkey filenames as arguments, otherwise it uses all the keys
-    found (recursively) in $HOME/.gitolite/keydir
+    found (recursively) in \$HOME/.gitolite/keydir
 
 The '-q' option will print only warnings instead of all mappings.
 

commit f1c69a3ec0bea287103232d2b2298c7ed51f336f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Dec 5 05:54:38 2012 +0530

    bugfix: don't delete description file when running perms
    
    thanks to drue on #gitolite for catching it

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index d986fb3..11e1aa6 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -6,8 +6,10 @@
 
 # ----------------------------------------------------------------------
 # delete the 'description' file that 'git init' created if this is run from
-# the post-create trigger
-[ "$1" = "POST_CREATE" ] && rm -f $GL_REPO_BASE/$2.git/description 2>/dev/null
+# the post-create trigger.  However, note that POST_CREATE is also called from
+# perms (since POST_CREATE doubles as eqvt of POST_COMPILE to propagate ad hoc
+# permissions changes for wild repos) and then you should not delete it.
+[ "$1" = "POST_CREATE" ] && [ "$4" != "perms" ] && rm -f $GL_REPO_BASE/$2.git/description 2>/dev/null
 
 # ----------------------------------------------------------------------
 # skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means

commit 2741fadc9d2538eeafa12cc8b91f2e19b9b05b1a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Nov 22 20:22:35 2012 +0530

    a few minor changes
    
      * minor typos
      * perltidy on Tsh
      * a minor optimisation to "do" in gl-conf
      * remove inapplicable caveat in fork command

diff --git a/README.txt b/README.txt
index 468062e..08fed1c 100644
--- a/README.txt
+++ b/README.txt
@@ -219,7 +219,7 @@ ACCESS RULES
 GROUPS
 ------
 
-    Gitolite allows you to groups users or repos for convenience.  Here's an
+    Gitolite allows you to group users or repos for convenience.  Here's an
     example that creates two groups of users:
 
         @staff      =   alice bob carol
diff --git a/src/commands/fork b/src/commands/fork
index 1d68d64..b381662 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -10,10 +10,6 @@
 # traffic but because it uses git clone's "-l" option to share the object
 # store also, so it is likely to be almost instantaneous, regardless of how
 # big the repo actually is.
-#
-# The only caveat is that the repo you cloned *from* must not later become
-# unavailable in any way.  If you cannot be sure of this, take the scenic
-# route (clone repo1, push to repo2).
 
 die() { echo "$@" >&2; exit 1; }
 usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 9a09793..00a6b8a 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -248,7 +248,7 @@ sub load_1 {
     if ( -f "gl-conf" ) {
         _warn "split conf not set, gl-conf present for '$repo'" if not $split_conf{$repo};
 
-        my $cc = "gl-conf";
+        my $cc = "./gl-conf";
         _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
 
         $last_repo = $repo;
diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index 2b7dcee..b3c43e0 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -261,7 +261,12 @@ sub rc_lines {
             $cmd = shift @cmds;
 
             # is the current command a "testing" command?
-            my $testing_cmd = ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) );
+            my $testing_cmd = (
+                   $cmd =~ m(^ok(?:\s+or\s+(.*))?$)
+                or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$)
+                or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$)
+                or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$)
+            );
 
             # warn if the previous command failed but rc is not being checked
             if ( $rc and not $testing_cmd ) {
@@ -474,7 +479,7 @@ sub fail {
 
 sub cmp {
     # compare input string with second input string or text()
-    my $in   = shift;
+    my $in = shift;
     my $text = ( @_ ? +shift : text() );
 
     if ( $text eq $in ) {
@@ -583,7 +588,7 @@ sub dummy_commits {
             test_tick();
             next;
         }
-        my $ts = ( $tick ? gmtime($tick+19800) : gmtime() );
+        my $ts = ( $tick ? gmtime( $tick + 19800 ) : gmtime() );
         _sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
     }
 }

commit b6d6260dbb8f7881f9d1a312fc26aa0e3f7d69de
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 28 06:22:55 2012 +0530

    prevent empty %groups being created in compiled conf
    
    this would happen if @all was used but no actual groups were defined,
    and would in turn cause a parse error on the compiled conf because it
    now ends with a 'false'.
    
    thanks to Jelle Raaijmakers

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 9090999..1d6d8e2 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -156,7 +156,7 @@ sub new_repos {
     # normal repos
     my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
     # add in members of repo groups
-    map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
+    map { push @repos, keys %{ $groups{$_} } } grep { /^@/ and $_ ne '@all' } keys %repos;
 
     for my $repo ( @{ sort_u( \@repos ) } ) {
         next unless $repo =~ $REPONAME_PATT;    # skip repo patterns

commit 72e36f32aa5c46c33e9205f47a54672fba672586
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 28 05:49:48 2012 +0530

    oops; hashes were getting printed twice in certain cases...
    
    harmless but wasteful

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 2a38a72..9090999 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -311,8 +311,7 @@ sub store_common {
         }
     }
 
-    $dumped_data = Data::Dumper->Dump( [ \%patterns ], [qw(*patterns)] ) if %patterns;
-    print $compiled_fh $dumped_data;
+    print $compiled_fh Data::Dumper->Dump( [ \%patterns ], [qw(*patterns)] ) if %patterns;
 
     print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
 

commit d2214b06b5f48af10dd2321e5e5b4bb926e43bfa
Author: Stephen Palmer <stephen at fun-machine.com>
Date:   Thu Nov 15 17:35:47 2012 +0000

    Fixed bug in lock script
    
    the unlock command was not checking the correct hash key to match
    the user name

diff --git a/src/commands/lock b/src/commands/lock
index f95af6c..a9e9073 100755
--- a/src/commands/lock
+++ b/src/commands/lock
@@ -71,7 +71,7 @@ sub f_unlock {
     my ( $repo, $file ) = @_;
 
     my %locks = get_locks();
-    _die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file} || '' ) ne $ENV{GL_USER};
+    _die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file}{USER} || '' ) ne $ENV{GL_USER};
     delete $locks{$file};
     put_locks(%locks);
 }

commit 96cc2eaf41aa9c313e56cfb3ddf25899cfa08f53
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Nov 22 19:53:15 2012 +0530

    new features relating to creating wild repos:
    
      - new 'create' command for explicit creation
      - new 'AutoCreate' trigger to prevent auto-creation on read operations
        or both read and write operations
      - a few related fixups to the perms command

diff --git a/src/commands/create b/src/commands/create
new file mode 100755
index 0000000..adac0e3
--- /dev/null
+++ b/src/commands/create
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Usage:    ssh git at host create <repo>
+#
+# Create wild repo.
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ -z "$2" ] || usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+exec $GL_BINDIR/commands/perms -c "$@" < /dev/null
diff --git a/src/commands/perms b/src/commands/perms
index 46c4e97..6b61596 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -46,18 +46,20 @@ if ( $ARGV[0] eq '-l' ) {
 # auto-create the repo if -c passed and repo doesn't exist
 if ( $ARGV[0] eq '-c' ) {
     shift;
-    my $repo = $ARGV[0];
+    my $repo = $ARGV[0] or usage();
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
-    if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
-        my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
-        _die $ret if $ret =~ /DENIED/;
-
-        require Gitolite::Conf::Store;
-        Gitolite::Conf::Store->import;
-        new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
-        gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
-    }
+    my $d = "$rc{GL_REPO_BASE}/$repo.git";
+    my $errmsg = "repo already exists or you are not authorised to create it";
+    # use the same message in both places to prevent leaking repo existence info
+    _die $errmsg if -d $d;
+    my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
+    _die $errmsg if $ret =~ /DENIED/;
+
+    require Gitolite::Conf::Store;
+    Gitolite::Conf::Store->import;
+    new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
+    gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
 }
 
 my $repo = shift;
diff --git a/src/lib/Gitolite/Triggers/AutoCreate.pm b/src/lib/Gitolite/Triggers/AutoCreate.pm
new file mode 100644
index 0000000..8fe46d7
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/AutoCreate.pm
@@ -0,0 +1,24 @@
+package Gitolite::Triggers::AutoCreate;
+
+use strict;
+use warnings;
+
+# perl trigger set for stuff to do with auto-creating repos
+# ----------------------------------------------------------------------
+
+# to deny auto-create on read access, add 'AutoCreate::deny_R' to the
+# PRE_CREATE trigger list
+sub deny_R {
+    die "autocreate denied\n" if $_[3] and $_[3] eq 'R';
+    return;
+}
+
+# to deny auto-create on read *and* write access, add 'AutoCreate::deny_RW' to
+# the PRE_CREATE trigger list.  This means you can only create repos using the
+# 'create' command, (which needs to be enabled in the COMMANDS list).
+sub deny_RW {
+    die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' );
+    return;
+}
+
+1;
diff --git a/t/sequence.t b/t/sequence.t
index e98690b..a42b6b6 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -100,7 +100,7 @@ try "
     # auto-create using perms fail
     echo READERS u5 | glt perms u4 -c foo/u4/baz
         !/Initialized empty Git repository in .*/foo/u4/baz.git/
-        /FATAL: .C any foo/u4/baz u4 DENIED by fallthru/
+        /FATAL: repo already exists or you are not authorised to create it/
 
     # auto-create using perms
     echo READERS u2 | glt perms u1 -c foo/u1/baz

commit 96be9503ef1a59dcb42fbb958e4c1d7e229c7c5e
Author: Sebastian Koslowski <koslowski at kit.edu>
Date:   Thu Nov 22 11:57:18 2012 +0100

    sudo command: CLI fix: 2 non-empty args required

diff --git a/src/commands/sudo b/src/commands/sudo
index ac2bb54..eeb0083 100755
--- a/src/commands/sudo
+++ b/src/commands/sudo
@@ -7,7 +7,7 @@
 
 die() { echo "$@" >&2; exit 1; }
 usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
-[ -z "$1" ] && usage
+[ -z "$2" ] && usage
 [ "$1" = "-h" ] && usage
 [ -z "$GL_USER" ] && die GL_USER not set
 

commit 7cec71b0eff40544882e56b6fbe080ce1b268e15
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Nov 22 15:59:13 2012 +0530

    minor fixups to some non-core programs
    
    (following a bit of a doc shakeup)

diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm
old mode 100755
new mode 100644
diff --git a/src/lib/Gitolite/Triggers/CpuTime.pm b/src/lib/Gitolite/Triggers/CpuTime.pm
old mode 100755
new mode 100644
index 552bf40..74b4217
--- a/src/lib/Gitolite/Triggers/CpuTime.pm
+++ b/src/lib/Gitolite/Triggers/CpuTime.pm
@@ -10,6 +10,7 @@ use warnings;
 
 # cpu and elapsed times for gitolite+git operations
 # ----------------------------------------------------------------------
+# uncomment the appropriate lines in the rc file to enable this
 
 # Ideally, you will (a) write your own code with a different filename so later
 # gitolite upgrades won't overwrite your copy, (b) add appropriate variables
@@ -18,8 +19,6 @@ use warnings;
 # ----------------------------------------------------------------------
 my $start_time;
 
-# this trigger is not yet documented; it gets called at the start and does not
-# receive any arguments.
 sub input {
     _warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$;
     $start_time = [ Time::HiRes::gettimeofday() ];
diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm
index b0a9ad1..ea675e2 100644
--- a/src/lib/Gitolite/Triggers/RepoUmask.pm
+++ b/src/lib/Gitolite/Triggers/RepoUmask.pm
@@ -9,6 +9,8 @@ use warnings;
 
 # setting a repo specific umask
 # ----------------------------------------------------------------------
+# this is for people who are too paranoid to trust e.g., gitweb's repo
+# exclusion logic, but not paranoid enough to put it on a different server
 
 =for usage
 
diff --git a/src/lib/Gitolite/Triggers/Shell.pm b/src/lib/Gitolite/Triggers/Shell.pm
index 0e6f0a1..b6e24c3 100644
--- a/src/lib/Gitolite/Triggers/Shell.pm
+++ b/src/lib/Gitolite/Triggers/Shell.pm
@@ -3,6 +3,9 @@ package Gitolite::Triggers::Shell;
 # usage notes: this module must be loaded first in the INPUT trigger list.  Or
 # at least before Mirroring::input anyway.
 
+# documentation is in the ssh troubleshooting and tips document, under the
+# section "giving shell access to gitolite users"
+
 use Gitolite::Rc;
 use Gitolite::Common;
 

commit cd838411faede4819f5c00c77ded802d059294f9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 21 20:58:07 2012 +0530

    'gitolite mirror' needs to set exit code on push failure

diff --git a/src/commands/mirror b/src/commands/mirror
index 16c14dd..d091979 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -53,10 +53,11 @@ if ( $cmd eq 'push' ) {
 
     my $errors = 0;
     for (`git push --mirror $host:$repo 2>&1`) {
+        $errors = 1 if $?;
         print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
         chomp;
         if (/FATAL/) {
-            $errors++;
+            $errors = 1;
             gl_log( 'mirror', $_ );
         } else {
             trace( 1, "mirror: $_" );

commit 2018267a4527429dce41a542a806cdb76225b86f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Nov 20 06:32:53 2012 +0530

    (minor) fixes to lint program, mainly usage message

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
index 2ff8e66..a981a13 100755
--- a/src/commands/sshkeys-lint
+++ b/src/commands/sshkeys-lint
@@ -73,7 +73,7 @@ for my $pkf (@pubkeyfiles) {
     my $fp = fprint($pkf);
     next unless $fp;
     msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
-    $pkf_by_fp{$fp} ||= $pkf;
+    $pkf_by_fp{$fp} ||= $pkfsn;
     my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
     msg 0, "$pkfsn maps to $fpu\n";
 }
@@ -170,17 +170,20 @@ sub fprint {
 sub usage {
     print <<EOF;
 
-sshkeys-lint expects
-  - the contents of an authorized_keys file via STDIN
-  - one or more pubkey filenames as arguments
+Usage:  gitolite sshkeys-lint [-q] [optional list of pubkey filenames]
+        (optionally, STDIN can be a pipe or redirected from a file; see below)
 
-sample use to check all keys on gitolite server:
-    cd ~/.gitolite/keydir
-    cat ~/.ssh/authorized_keys | sshkeys-lint `find . -name "*.pub"`
-    # or supply only one pubkey file to check only that:
-    cat ~/.ssh/authorized_keys | sshkeys-lint YourName.pub
+Look for potential problems in ssh keys.
 
-Note that it runs ssh-keygen -l for each line in the authkeys file and each
+sshkeys-lint expects:
+  - the contents of an authorized_keys file via STDIN, otherwise it uses
+    $HOME/.ssh/authorized_keys
+  - one or more pubkey filenames as arguments, otherwise it uses all the keys
+    found (recursively) in $HOME/.gitolite/keydir
+
+The '-q' option will print only warnings instead of all mappings.
+
+Note that this runs ssh-keygen -l for each line in the authkeys file and each
 pubkey in the argument list, so be wary of running it on something huge.  This
 is meant for troubleshooting.
 

commit a26532d6358cc8a617b36149600a2bf8d8717e8d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Nov 19 07:48:21 2012 +0530

    allow simple macros in conf file

diff --git a/src/syntactic-sugar/macros b/src/syntactic-sugar/macros
new file mode 100644
index 0000000..1202dae
--- /dev/null
+++ b/src/syntactic-sugar/macros
@@ -0,0 +1,74 @@
+# vim: syn=perl:
+
+# "sugar script" (syntactic sugar helper) for gitolite3
+
+# simple line-wise macro processor
+# ----------------------------------------------------------------------
+# see documentation at the end of this script
+
+my %macro;
+sub sugar_script {
+    my $lines = shift;
+    my @out  = ();
+
+    my $l = join("\n", @$lines);
+    while ($l =~ s/^macro (\w+) (.*?)\nend//ms) {
+        $macro{$1} = $2;
+    }
+
+    $l =~ s/^((\w+) .*)/$macro{$2} ? expand($1) : $1/gem;
+
+    $lines = [split "\n", $l];
+    return $lines;
+}
+
+sub expand {
+    my $l = shift;
+    my ($word, @arg) = split ' ', $l;
+    my $v = $macro{$word};
+    $v =~ s/%(\d+)/$arg[$1-1] or die "macro '$word' needs $1 arguments at '$l'\n"/gem;
+    return $v;
+}
+
+__END__
+
+Documentation is mostly by example.
+
+Setup:
+
+  * the line
+        'macros',
+    should be added to the SYNTACTIC_SUGAR list in ~/.gitolite.rc
+
+Notes on macro definition:
+
+  * the keywords 'macro' and 'end' should start on a new line
+  * the first word after 'macro' is the name of the macro, and the rest, until
+    the 'end', is the body
+
+Notes on macro use:
+
+  * the macro name should be the first word on a line
+  * the rest of the line is used as arguments to the macro
+
+Example:
+
+    if your conf contains:
+
+        macro foo repo aa-%1
+            RW  =   u1 %2
+            R   =   u2
+        end
+
+        foo 1 alice
+        foo 2 bob
+
+    this will effectively turn into
+
+        repo aa-1
+            RW  =   u1 alice
+            R   =   u2
+
+        repo aa-2
+            RW  =   u1 bob
+            R   =   u2

commit 5f9789ed8ee519a5987e8fede7f3e65001f4f9c0
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 14 15:36:04 2012 +0530

    v3.2

diff --git a/CHANGELOG b/CHANGELOG
index 5ec6cd9..91faaa5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+2012-11-14  v3.2    major efficiency boost for large setups
+
+                    optional support for multi-line pubkeys; see
+                    src/triggers/post-compile/ssh-authkeys-split
+
+                    bug fix for not creating gl-conf when repo para has only
+                    config lines and no access rules
+
+                    new 'bg' trigger command to put long jobs started from a
+                    trigger into background
+
+                    %GL_REPO and %GL_CREATOR now work for 'option's also
+
+                    test suite now much more BSD friendly
+
 2012-10-05  v3.1    (security) fix path traversal on wild repos
 
                     new %GL_CREATOR variable for git-config lines

commit d3d93961a082c286d3f205d310c78079ebc3256f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Nov 14 15:12:30 2012 +0530

    Uggh; horrible inner loop screwing up all performance :-(
    
    This might actually make the redis version unnecessary for most people!
    And if it does, well shame on me for not instrumenting things at a more
    granular level before going all "oh we need a cache!"
    
    [In my defense, I blame redis for being such a sweet little tool that I
    felt compelled to use it somehow!]
    
    ----
    
    t/sequence failed because the test itself was in error; fixed.

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index c696388..9a09793 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -32,6 +32,7 @@ our $data_version = '';
 our %repos;
 our %one_repo;
 our %groups;
+our %patterns;
 our %configs;
 our %one_config;
 our %split_conf;
@@ -326,8 +327,10 @@ sub memberships {
         }
     }
 
-    for my $i ( keys %groups ) {
-        if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
+    push @ret, @{ $groups{$base} } if exists $groups{$base};
+    push @ret, @{ $groups{$base2} } if $base2 and exists $groups{$base2};
+    for my $i ( keys %{ $patterns{groups} } ) {
+        if ( $base =~ /^$i$/ or $base2 and ( $base2 =~ /^$i$/ ) ) {
             push @ret, @{ $groups{$i} };
         }
     }
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 252bf14..2a38a72 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -288,6 +288,8 @@ sub store_common {
     my $cc = "conf/gitolite.conf-compiled.pm";
     my $compiled_fh = _open( ">", "$cc.new" );
 
+    my %patterns = ();
+
     my $data_version = glrc('current-data-version');
     trace( 3, "data_version = $data_version" );
     print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
@@ -301,7 +303,17 @@ sub store_common {
         my %groups = %{ inside_out( \%groups ) };
         $dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
         print $compiled_fh $dumped_data;
+
+        # save patterns in %groups for faster handling of multiple repos, such
+        # as happens in the various POST_COMPILE scripts
+        for my $k (keys %groups) {
+            $patterns{groups}{$k} = 1 unless $k =~ $REPONAME_PATT;
+        }
     }
+
+    $dumped_data = Data::Dumper->Dump( [ \%patterns ], [qw(*patterns)] ) if %patterns;
+    print $compiled_fh $dumped_data;
+
     print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
 
     close $compiled_fh or _die "close compiled-conf failed: $!\n";
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 58fc4db..9c2ae5b 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -58,7 +58,7 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 
 # find the rc file and 'do' it
 # ----------------------------------------------------------------------
-my $current_data_version = "3.0";
+my $current_data_version = "3.2";
 
 my $rc = glrc('filename');
 if (-r $rc and -s $rc) {
diff --git a/t/sequence.t b/t/sequence.t
index acccb0b..e98690b 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -55,7 +55,7 @@ try "
 
 confreset;confadd '
     @staff = u1 u2 u3
-    @gfoo = foo/CREATOR/.+
+    @gfoo = foo/CREATOR/..*
     repo  @gfoo
           C       = u1
           RW+     = CREATOR

commit 1f96180df0bd6c2b4be6b4f9a06c1051da61974f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Nov 13 08:45:45 2012 +0530

    allow multi-line pubkeys; see code for doc

diff --git a/src/triggers/post-compile/ssh-authkeys-split b/src/triggers/post-compile/ssh-authkeys-split
new file mode 100755
index 0000000..b6b0e15
--- /dev/null
+++ b/src/triggers/post-compile/ssh-authkeys-split
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+#   split multi-key files into separate keys like ssh-authkeys likes
+
+# WHY
+# ---
+#
+# Yeah I wonder that too, when it's so much more maintainable to keep the damn
+# keys as sitaram at home.pub and sitaram at work.pub or such.  But there's no
+# accounting for tastes, and some old fogies apparently want to put all of a
+# user's keys into a single ".pub" file.
+
+# WARNINGS AND CAVEATS
+# --------------------
+#
+# - assumes no "@" sign in basenames of any multi-key files (single line file
+#   may still have them)
+# - assumes you don't have a subdir in keydir called "__split_keys__"
+# - God help you if you try to throw in a putty key in there.
+
+# SUPPORT
+# -------
+#
+# NONE.  Mainly because I **know** someone will throw in a putty key.  I just
+# know it.
+
+# USAGE
+# -----
+#
+# add it to the POST_COMPILE trigger list in the rc file, but *before* the
+# ssh-authkeys program entry.
+
+cd $GL_ADMIN_BASE/keydir
+
+rm -rf __split_keys__
+mkdir __split_keys__
+export SKD=$PWD/__split_keys__
+
+find . -type f -name "*.pub" | while read k
+do
+    # do we need to split?
+    lines=`wc -l < $k`
+    [ "$lines" = "1" ] && continue
+
+    # is it sane to split?
+    base=`basename $k .pub`
+    echo $base | grep '@' >/dev/null && continue
+
+    # ok do it
+    seq=1
+    while read line
+    do
+        echo "$line" > $SKD/$base@$seq.pub
+        (( seq++ ))
+    done < $k
+
+    # now delete the original file
+    rm $k
+done

commit 57760d7e1bbff68629e2489f84fdeb0ffe83ef7d
Author: gitolite tester <tester at example.com>
Date:   Mon Nov 12 11:25:23 2012 +0530

    refex-expr: die when admin forgets to add the required line to the rc

diff --git a/src/VREF/refex-expr b/src/VREF/refex-expr
index 3ff4bf4..d0a51b7 100755
--- a/src/VREF/refex-expr
+++ b/src/VREF/refex-expr
@@ -3,6 +3,8 @@ use strict;
 use warnings;
 
 my $rule = $ARGV[7];
+die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
+  unless exists $ENV{"GL_REFEX_EXPR_" . $rule};
 my $res = $ENV{"GL_REFEX_EXPR_" . $rule} || 0;
 print "$ARGV[6] ($res)\n" if $res;
 

commit 16f2d9b879516e6268fe8f12f930318f6916a42c
Author: gitolite tester <tester at example.com>
Date:   Tue Nov 13 06:51:14 2012 +0530

    gl-conf must be created even if the repo para has only config lines
    
    (i.e., no access rules but only config lines)

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 8e2e6c8..252bf14 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -258,15 +258,18 @@ sub store_1 {
     # warning: writes and *deletes* it from %repos and %configs
     my ($repo) = shift;
     trace( 3, $repo );
-    return unless $repos{$repo} and -d "$repo.git";
+    return unless ( $repos{$repo} or $configs{$repo} ) and -d "$repo.git";
 
     my ( %one_repo, %one_config );
 
     open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
 
-    $one_repo{$repo} = $repos{$repo};
-    delete $repos{$repo};
-    my $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
+    my $dumped_data = '';
+    if ($repos{$repo}) {
+        $one_repo{$repo} = $repos{$repo};
+        delete $repos{$repo};
+        $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
+    }
 
     if ( $configs{$repo} ) {
         $one_config{$repo} = $configs{$repo};

commit c03d107bac810e663357e7d703a43f30ca0d376e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Nov 10 14:09:26 2012 +0530

    help run some trigger programs in the background

diff --git a/src/triggers/bg b/src/triggers/bg
new file mode 100755
index 0000000..3c66500
--- /dev/null
+++ b/src/triggers/bg
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# quick and dirty program to background any of the triggers programs that are
+# taking too long.  To use, just replace a line like
+#       'post-compile/update-gitweb-access-list',
+# with
+#       'bg post-compile/update-gitweb-access-list',
+
+# We dump output to a file in the log directory but please keep in mind this
+# is not a "log" so much as a redirection of the entire output.
+
+echo `date` $GL_TID "$0: $@" >> $GL_LOGFILE.bg
+
+path=${0%/*}
+script=$path/$1; shift
+
+( ( $script "$@" < /dev/null >> $GL_LOGFILE.bg 2>&1 & ) )

commit d491b5384f572d5a4bedb12aac430dc770ea475f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Nov 9 17:54:04 2012 +0530

    (minor) add quick and dirty timer code to Common.pm

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index b5c4b47..c93e6ef 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -14,6 +14,8 @@ package Gitolite::Common;
           gl_log
 
           dd
+          t_start
+          t_lap
 );
 #>>>
 use Exporter 'import';
@@ -70,6 +72,21 @@ sub dd {
     dbg(@_);
 }
 
+{
+    use Time::HiRes;
+    my %start_times;
+
+    sub t_start {
+        my $name = shift || 'default';
+        $start_times{$name} = [ Time::HiRes::gettimeofday() ];
+    }
+
+    sub t_lap {
+        my $name = shift || 'default';
+        return Time::HiRes::tv_interval( $start_times{$name} );
+    }
+}
+
 sub _warn {
     gl_log( 'warn', @_ );
     if ( $ENV{D} and $ENV{D} >= 3 ) {

commit 8a9564f171e99d0546d93d5610129893197c796f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Nov 8 19:12:20 2012 +0530

    some minor rearrangements of code...
    
    why?  now that would be telling!

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 27f3c95..c696388 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -70,8 +70,12 @@ sub access {
     _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
     sanity($repo);
 
-    my $deny_rules = option( $repo, 'deny-rules' );
+    my @rules;
+    my $deny_rules;
+
     load($repo);
+    @rules = rules( $repo, $user );
+    $deny_rules = option( $repo, 'deny-rules' );
 
     # sanity check the only piece the user can control
     _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ $REF_OR_FILENAME_PATT;
@@ -89,7 +93,6 @@ sub access {
         return "$aa $ref $repo $user DENIED by existence";
     }
 
-    my @rules = rules( $repo, $user );
     trace( 2, scalar(@rules) . " rules found" );
     for my $r (@rules) {
         my $perm = $r->[1];
@@ -304,9 +307,11 @@ sub load_1 {
 sub memberships {
     trace( 3, @_ );
     my ( $type, $base, $repo ) = @_;
+    $repo ||= '';
+    my @ret;
     my $base2 = '';
 
-    my @ret = ( $base, '@all' );
+    @ret = ( $base, '@all' );
 
     if ( $type eq 'repo' ) {
         # first, if a repo, say, pub/sitaram/project, has a gl-creator file

commit a509b208e340f11bb62a113dbdf089942b229db5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Oct 30 12:09:16 2012 +0530

    move %GL_REPO and %GL_CREATOR substitution into core
    
    see usage example at the end of src/triggers/upstream

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 0f76908..27f3c95 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -164,6 +164,14 @@ sub git_config {
         }
     }
 
+    my($k, $v);
+    my $creator = creator($repo);
+    while (($k, $v) = each %ret) {
+        $v =~ s/%GL_REPO/$repo/g;
+        $v =~ s/%GL_CREATOR/$creator/g if $creator;
+        $ret{$k} = $v;
+    }
+
     trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
     return \%ret;
 }
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 23458d2..64b8ed9 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -49,8 +49,6 @@ sub fixup_config {
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
-            $value =~ s/%GL_REPO/$pr/g;
-            $value =~ s/%GL_CREATOR/$creator/g if $creator;
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {
             system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );
diff --git a/src/triggers/upstream b/src/triggers/upstream
index 7c4981d..c8e8c6d 100755
--- a/src/triggers/upstream
+++ b/src/triggers/upstream
@@ -44,3 +44,29 @@ git fetch -q "$url" '+refs/*:refs/*'
 # * if the upstream URL changes, just change the conf and push admin repo
 # * the 'nice' setting is in minutes and is optional; it is the minimum
 #   elapsed time between 2 upstream fetches.
+
+# USAGE EXAMPLE:
+#
+# Let's say you want to keep a read-only local mirror of all your github repos
+# on your local gitolite installation.  Assuming your github usernames are the
+# same as your local usernames, and you have updated GIT_CONFIG_KEYS in the rc
+# file to allow 'config' lines, you can do this:
+#
+#   repo github/CREATOR/..*
+#       C   = @all
+#       R   = @all
+#       option upstream.url                     =   git://github.com/%GL_REPO.git
+#       option upstream.nice                    =   120
+#       config url.git://github.com/.insteadOf  =   git://github.com/github/
+#
+# Now you can make local, read-only, clones of all your github repos with
+#
+#   git ls-remote gitolite:github/sitaramc/gitolite
+#   git ls-remote gitolite:github/sitaramc/hap
+#   (etc)
+#
+# and if milki were also a user on this gitolite instance, then
+#
+#   git ls-remote gitolite:github/milki/xclip
+#   git ls-remote gitolite:github/milki/ircblogger
+#   (etc)

commit be61cd2d66da5729a7506e852262171ce1ea6ead
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Nov 6 06:19:13 2012 +0530

    make sure gl-perms exists, even if it is empty...
    
    I expect this to help if we optimise the rule generation by caching.

diff --git a/src/commands/fork b/src/commands/fork
index 6cd6eea..1d68d64 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -48,6 +48,7 @@ echo "$from forked to $to" >&2
 cd $GL_REPO_BASE/$to.git
 echo $GL_USER > gl-creator
 
+touch gl-perms
 if gitolite query-rc -q DEFAULT_ROLE_PERMS
 then
     gitolite query-rc DEFAULT_ROLE_PERMS > gl-perms
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 4d1c642..8e2e6c8 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -191,7 +191,7 @@ sub new_wild_repo {
     trigger( 'PRE_CREATE', $repo, $user, $aa );
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
-    _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
+    _print( "$repo.git/gl-perms", ( $rc{DEFAULT_ROLE_PERMS} ? "$rc{DEFAULT_ROLE_PERMS}\n" : "" ) );
     trigger( 'POST_CREATE', $repo, $user, $aa );
 
     _chdir( $rc{GL_ADMIN_BASE} );
diff --git a/t/fork.t b/t/fork.t
index d595847..99d4a41 100755
--- a/t/fork.t
+++ b/t/fork.t
@@ -61,7 +61,8 @@ try "
 
 my $t;
 try "cd $rb; find . -name gl-perms"; $t = md5sum(sort (lines())); cmp $t,
-'59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
+'d41d8cd98f00b204e9800998ecf8427e  ./foo/u1/u1a.git/gl-perms
+59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
 59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1e.git/gl-perms
 ';
 

commit 70ad045e08a663537b5652bc86ea779421cda62a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Oct 3 19:14:47 2012 +0530

    (minor fixups to some non-code parts)

diff --git a/README.txt b/README.txt
index e170df9..468062e 100644
--- a/README.txt
+++ b/README.txt
@@ -40,7 +40,7 @@ This file contains the following sections:
     GIT-DAEMON
     GITWEB
 
-    CONTACT
+    CONTACT AND SUPPORT
     LICENSE
 
 ------------------------------------------------------------------------
@@ -346,26 +346,22 @@ GITWEB
 ------------------------------------------------------------------------
 
 
-CONTACT
--------
-
-    NOTE: Unless you have very good reasons, please use the mailing list below
-    instead of mailing me personally.  If you have to mail me, use the gmail
-    address instead of my work address.
-
-    Author: sitaramc at gmail.com, sitaram at atc.tcs.com
+CONTACT AND SUPPORT
+-------------------
 
-    Mailing list for questions and general discussion:
+    Mailing list for support and general discussion:
         gitolite at googlegroups.com
         subscribe address: gitolite+subscribe at googlegroups.com
 
     Mailing list for announcements and notices:
-        gitolite-announce at googlegroups.com
         subscribe address: gitolite-announce+subscribe at googlegroups.com
 
     IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
     time zone).
 
+    Author: sitaramc at gmail.com, but please DO NOT use this for general support
+    questions.  Subscribe to the list and ask there instead.
+
 
 LICENSE
 -------
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 5d3b861..58fc4db 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -425,7 +425,7 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence after a new wild repo is created
+    # these will run in sequence after a new repo is created
     POST_CREATE                 =>
         [
             'post-compile/update-git-configs',

commit 2aa129bc7075254e24b5ced06a2bb11c137c0ba6
Author: Andrew Page <andrew at infosiftr.com>
Date:   Mon Oct 29 17:15:52 2012 -0600

    fix for keysubdirs-as-groups sugar script to support "old style multi-keys" for users

diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
index 69a69b1..0a3a9ae 100644
--- a/src/syntactic-sugar/keysubdirs-as-groups
+++ b/src/syntactic-sugar/keysubdirs-as-groups
@@ -20,7 +20,7 @@ sub groupnames {
     my @out     = ();
     my %members = ();
     for my $pk (`find ../keydir/ -name "*.pub"`) {
-        next unless $pk =~ m(.*/([^/]+)/([^/]+)\.pub$);
+        next unless $pk =~ m(.*/([^/]+)/([^/]+?)(?:@[^./]+)?\.pub$);
         next if $1 eq 'keydir';
         $members{$1} .= " $2";
     }

commit a802071a5e8880d47ced5b32231a28d3eb62a74f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Oct 27 13:20:55 2012 +0530

    (test suite) stop using 'ls' to test for presence/absence of files/directories
    
    another of those "duh!  what was I thinking" moments, this specific one
    being "why test that files/directories are created with the right user
    and group IDs?  Shouldn't that be out of your control, as well as
    totally unnecessary on a sane system?"

diff --git a/t/0-me-first.t b/t/0-me-first.t
index 22102ef..47784ae 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -11,7 +11,7 @@ my $rb = `gitolite query-rc -n GL_REPO_BASE`;
 # initial smoke tests
 # ----------------------------------------------------------------------
 
-try "plan 73";
+try "plan 71";
 
 # basic push admin repo
 confreset;confadd '
@@ -33,12 +33,11 @@ try "
     cd ..
     glt clone u1 file://aa u1aa;    ok;     /Cloning into 'u1aa'.../
                                             /warning: You appear to have cloned an empty repository/
-    ls -ald --time-style=long-iso u1aa;
-                                    ok;     /drwxr-xr-x 3 $ENV{USER} $ENV{USER} \\d+ 201.-..-.. ..:.. u1aa/
+    [ -d u1aa ];                    ok
 
     # basic clone deny
     glt clone u4 file://aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
-    ls -ald u4aa;                   !ok;    /ls: cannot access u4aa: No such file or directory/
+    [ -d u4aa ];                    !ok
 
     # basic push
     cd u1aa;                        ok
diff --git a/t/basic.t b/t/basic.t
index 3e8c3aa..4626f96 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -10,7 +10,7 @@ use Gitolite::Test;
 # ----------------------------------------------------------------------
 
 try "
-    plan 218
+    plan 217
     CHECK_SETUP
 
     # subtest 1
@@ -77,7 +77,7 @@ try "
                                         /fatal: The remote end hung up unexpectedly/
     CLONE u2 t1;                ok;     gsh
                                         /warning: You appear to have cloned an empty repository./
-    ls -al t1;                  ok;     /$ENV{USER}.*$ENV{USER}.*\.git/
+    [ -d t1/.git ];             ok
     cd t1;                      ok;
 
     # push
diff --git a/t/merge-check.t b/t/merge-check.t
index 443a848..fdea318 100755
--- a/t/merge-check.t
+++ b/t/merge-check.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # merge check -- the M flag
 # ----------------------------------------------------------------------
 
-try "plan 57";
+try "plan 55";
 
 confreset;confadd '
     repo  foo
@@ -25,7 +25,7 @@ try "ADMIN_PUSH set1; !/FATAL/" or die text();
 
 try "
     cd ..
-    ls -al foo;         !ok;    /cannot access foo: No such file or directory/
+    [ -d foo ];         !ok
     glt clone u1 file:///foo
                         ok;     /Cloning into/
                                 /You appear to have cloned an empty/
@@ -33,7 +33,7 @@ try "
 
 try "
     cd foo;             ok
-    ls -Al;             ok;     /\.git/
+    [ -d .git ];        ok
     test-commit aa;     ok;     /1 file changed, 1 insertion/
     tag start;          ok
     glt push u1 origin master
diff --git a/t/vrefs-1.t b/t/vrefs-1.t
index bd5086b..eea4b24 100755
--- a/t/vrefs-1.t
+++ b/t/vrefs-1.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # VREFs - part 1
 # ----------------------------------------------------------------------
 
-try "plan 90";
+try "plan 88";
 
 put "conf/gitolite.conf", "
     repo gitolite-admin
@@ -32,11 +32,11 @@ put "conf/gitolite.conf", "
 try "
     ADMIN_PUSH vr1a
     cd ..
-    ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
+    [ -d foo ];                 !ok
     CLONE u1 foo;               ok;     /Cloning into/
                                         /You appear to have cloned an empty/
     cd foo;                     ok
-    ls -Al;                     ok;     /\.git/
+    [ -d .git ];                ok
 
     # VREF not called for u1
     tc a1 a2 a3 a4 a5;          ok;     /aaf9e8e/
diff --git a/t/vrefs-2.t b/t/vrefs-2.t
index 6c53341..40db308 100755
--- a/t/vrefs-2.t
+++ b/t/vrefs-2.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # VREFs - part 2
 # ----------------------------------------------------------------------
 
-try "plan 74";
+try "plan 72";
 
 put "../gitolite-admin/conf/gitolite.conf", "
     \@gfoo = foo
@@ -32,11 +32,11 @@ try "
     ADMIN_PUSH vr2a
     cd ..
     # setup
-    ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
+    [ -d foo ];                 !ok
     CLONE u1 foo;               ok;     /Cloning into/
                                         /You appear to have cloned an empty/
     cd foo;                     ok
-    ls -Al;                     ok;     /\.git/
+    [ -d .git ];                ok
 
     # u1 push 15 new files
     tc a b c d e f g h i j k l m n o

commit 4eb8cd4ad17f961b35c2be4381c30dfde582bc84
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Oct 27 07:07:30 2012 +0530

    (minor) bash -> sh changes in some non-core code
    
    /bin/bash is muscle memory for me, although it appears that not too much
    of the actual code is bash-specific, so it's reasonably easy to fix.

diff --git a/src/VREF/COUNT b/src/VREF/COUNT
index 6b5cacf..f4c3eae 100755
--- a/src/VREF/COUNT
+++ b/src/VREF/COUNT
@@ -1,5 +1,4 @@
-#!/bin/bash
-# TODO: convert to perl!
+#!/bin/sh
 
 # gitolite VREF to count number of changed/new files in a push
 
@@ -34,7 +33,7 @@ nf=
 # $oldsha when you update an old feature branch from master and then push it
 count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`
 
-[[ $count -gt $max ]] && {
+[ $count -gt $max ] && {
     # count has been exceeded.  If $9 was NO_SIGNOFF there's still a chance
     # for redemption -- if the top commit has a proper signed-off by line
     [ "$9" = "NO_SIGNOFF" ] && {
diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE
index 2115a5c..3f1d5f9 100755
--- a/src/VREF/FILETYPE
+++ b/src/VREF/FILETYPE
@@ -1,5 +1,4 @@
-#!/bin/bash
-# TODO: convert to perl!
+#!/bin/sh
 
 # gitolite VREF to find autogenerated files
 
diff --git a/src/VREF/VOTES b/src/VREF/VOTES
index bb6bf22..8dc3563 100755
--- a/src/VREF/VOTES
+++ b/src/VREF/VOTES
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # gitolite VREF to count votes before allowing pushes to certain branches.
 
diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
index 7d00298..f8c2bd6 100755
--- a/src/triggers/post-compile/update-description-file
+++ b/src/triggers/post-compile/update-description-file
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # For normal (not "wild") repos, gitolite v3 sets 'gitweb.description' instead
 # of putting the text in the "description" file.  This is easier because it
diff --git a/src/triggers/upstream b/src/triggers/upstream
index 7834778..7c4981d 100755
--- a/src/triggers/upstream
+++ b/src/triggers/upstream
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # manage local, gitolite-controlled, copies of read-only upstream repos.
 

commit 3eefc06551149882d7676cfb606542256e9e4392
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Oct 10 13:43:17 2012 +0530

    (minor) clarify that D only works on wild repos

diff --git a/src/commands/D b/src/commands/D
index 2d96964..a255073 100755
--- a/src/commands/D
+++ b/src/commands/D
@@ -18,7 +18,8 @@
 # Usage:    ssh git at host D <subcommand> <argument>
 #
 # The whimsically named "D" command deletes repos ("D" is a counterpart to the
-# "C" permission which lets you create repos!)
+# "C" permission which lets you create repos.  Which also means that, just
+# like "C", it only works for wild repos).
 #
 # There are two kinds of deletions: 'rm' removes a repo completely, while
 # 'trash' moves it to a trashcan which can be recovered later (upto a time

commit 896ada58c052243d59ca3221d5bfd66473959c01
Author: Eugene E. Kashpureff Jr <eugene at khresear.ch>
Date:   Wed Oct 10 07:59:52 2012 +0000

    Fix spurious error in triggers/upstream
    
    The initial fetch of a new repo which has 'upstream' read-only mirroring
    configured will cause a spurious error concerning FETCH_HEAD not yet
    existing. This silences the error.

diff --git a/src/triggers/upstream b/src/triggers/upstream
index b66ae87..7834778 100755
--- a/src/triggers/upstream
+++ b/src/triggers/upstream
@@ -11,7 +11,7 @@ cd $GL_REPO_BASE/$repo.git || exit 1
 
 [ "$1" != "fetch" ] && {
     nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
-    [ -n "$nice" ] && find FETCH_HEAD -mmin -$nice | grep . >/dev/null && exit 0
+    [ -n "$nice" ] && find FETCH_HEAD -mmin -$nice 2>/dev/null | grep . >/dev/null && exit 0
 }
 
 git fetch -q "$url" '+refs/*:refs/*'

commit 51ab768e2a121eac48fa82bb41ef121f44082e64
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Oct 5 14:42:25 2012 +0530

    v3.1

diff --git a/CHANGELOG b/CHANGELOG
index f743d19..5ec6cd9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+2012-10-05  v3.1    (security) fix path traversal on wild repos
+
+                    new %GL_CREATOR variable for git-config lines
+
+                    rsync command to create and send bundles automagically
+
+                    migrated 'who-pushed'
+
+                    logical expressions on refexes!!!
+
 2012-06-27  v3.04   documentation graduated and moved out of parents house :)
 
                     new trigger for 'repo specific umask'

commit f636ce3ba3e340569b26d1e47b9d9b62dd8a3bf2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Oct 5 07:19:59 2012 +0530

    (security) fix bug in pattern to detect path traversal
    
    while we're about it, add the same check to some of the internal
    routines, so that commands can also be protected.
    
    finally, just to make sure we don't lose it again in some other fashion,
    add a few tests for path traversal...

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 5d48cc9..4bbae48 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -168,7 +168,7 @@ sub sanity {
     my $repo = shift;
     _die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT;
     _die "'$repo' ends with a '/'"         if $repo =~ m(/$);
-    _die "'$repo' contains '..'"           if $repo =~ m(\.\.$);
+    _die "'$repo' contains '..'"           if $repo =~ m(\.\.);
 }
 
 # ----------------------------------------------------------------------
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 4abfa90..0f76908 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -67,8 +67,9 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
-    _die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
     _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
+    sanity($repo);
+
     my $deny_rules = option( $repo, 'deny-rules' );
     load($repo);
 
@@ -175,8 +176,18 @@ sub option {
     return $ret->{$option};
 }
 
+sub sanity {
+    my $repo = shift;
+
+    _die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
+    _die "'$repo' ends with a '/'" if $repo =~ m(/$);
+    _die "'$repo' contains '..'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
+}
+
 sub repo_missing {
     my $repo = shift;
+    sanity($repo);
+
     return not -d "$rc{GL_REPO_BASE}/$repo.git";
 }
 
@@ -400,6 +411,8 @@ sub generic_name {
 
 sub creator {
     my $repo = shift;
+    sanity($repo);
+
     return ( $ENV{GL_USER} || '' ) if repo_missing($repo);
     my $f       = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
     my $creator = '';
diff --git a/t/0-me-first.t b/t/0-me-first.t
index dc8916b..22102ef 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -6,10 +6,12 @@ use warnings;
 use lib "src/lib";
 use Gitolite::Test;
 
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+
 # initial smoke tests
 # ----------------------------------------------------------------------
 
-try "plan 65";
+try "plan 73";
 
 # basic push admin repo
 confreset;confadd '
@@ -75,4 +77,19 @@ try "
     glt ls-remote u5 file:///cc/1;  ok;     perl s/TRACE.*//g; !/\\S/
     glt ls-remote u5 file:///cc/2;  !ok;    /DENIED by fallthru/
     glt ls-remote u6 file:///cc/2;  !ok;    /DENIED by fallthru/
+
+    # command
+    glt perms u4 -c cc/bar/baz/frob + READERS u2;
+                                    ok;     /Initialized empty .*cc/bar/baz/frob.git/
+
+    # path traversal
+    glt ls-remote u4 file:///cc/dd/../ee
+                                    !ok;    /FATAL: 'cc/dd/\\.\\./ee' contains '\\.\\.'/
+    glt ls-remote u5 file:///cc/../../../../../..$rb/gitolite-admin
+                                    !ok;    /FATAL: 'cc/../../../../../..$rb/gitolite-admin' contains '\\.\\.'/
+
+    glt perms u4 -c cc/bar/baz/../frob + READERS u2
+                                    !ok;    /FATAL: 'cc/bar/baz/\\.\\./frob' contains '\\.\\.'/
+
+
 ";

commit 0d371ac957840ba8152d5fd11357cd850ffbd76c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Oct 4 22:01:57 2012 +0530

    call GROUPLIST_PGM before determining user_roles()...
    
    thanks to Stephane Chazelas [1]
    
    [1]: https://groups.google.com/d/topic/gitolite/gy_ZkrxGSjg

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index dcc559c..4abfa90 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -308,6 +308,8 @@ sub memberships {
         }
     }
 
+    push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
+
     if ( $type eq 'user' and $repo and not repo_missing($repo) ) {
         # find the roles this user has when accessing this repo and add those
         # in as groupnames he is a member of.  You need the already existing
@@ -315,8 +317,6 @@ sub memberships {
         push @ret, user_roles( $base, $repo, @ret );
     }
 
-    push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
-
     @ret = @{ sort_u( \@ret ) };
     trace( 3, sort @ret );
     return @ret;

commit 2dbaa4d12ec66a9328ba30c1bdcbf2634b97ec1b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Sep 26 14:58:56 2012 +0530

    (minor) move a small chunk of code out of a loop

diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index c77cac3..4d1c642 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -63,12 +63,20 @@ sub add_to_group {
 }
 
 sub set_repolist {
-    @repolist = @_;
 
+    @repolist = ();
     # ...sanity checks
-    for (@repolist) {
+    for (@_) {
+        if ( check_subconf_repo_disallowed( $subconf, $_ ) ) {
+            (my $repo = $_) =~ s/^\@$subconf\./locally modified \@/;
+            $ignored{$subconf}{$repo} = 1;
+            next;
+        }
+
         _warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
         _die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
+
+        push @repolist, $_;
     }
 }
 
@@ -103,13 +111,6 @@ sub add_rule {
 
     $nextseq++;
     for my $repo (@repolist) {
-        if ( check_subconf_repo_disallowed( $subconf, $repo ) ) {
-            my $repo = $repo;
-            $repo =~ s/^\@$subconf\./locally modified \@/;
-            $ignored{$subconf}{$repo} = 1;
-            next;
-        }
-
         push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
     }
 }

commit 6328ec2cbe55929b53c729bbf32b35908759f7e9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Sep 25 05:07:05 2012 +0530

    dont auto-vivify empty entries in %repos...
    
    before this, trying to access a wild repo would create an empty hash in
    %repos.  This is pretty harmless, but at some later point, memberships()
    would try to use that in a pattern, attempting to match the real repo
    being access-checked.
    
    Which is still fine if your repo doesn't look like "libstdc++" AND
    you're using some recent perl.
    
    However, for perl 5.8.8, and if the repo has a ++ in it, perl barfs.
    
    Here's a test program to check your perl:
    
        #!/usr/bin/perl
    
        $base="foo/u1/libstdc++";
        $i="foo/u1/libstdc++";
    
        if ( $base =~ /^$i$/ ) {
            print 1;
        } else {
            print 2;
        }
    
    On 5.14.2 I get "2".  On 5.8.8 I get:
    
        Nested quantifiers in regex; marked by <-- HERE in m/^foo/u1/libstdc++ <-- HERE $/ at ./aa.pl line 6.

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index b6991f8..dcc559c 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -255,7 +255,7 @@ sub load_1 {
 
         for my $r (@repos) {
             for my $u (@users) {
-                push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u};
+                push @rules, @{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
             }
         }
 

commit 3fe8ecf974de37e1e4202bdc924740013e10b32f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Sep 25 19:05:12 2012 +0530

    (minor) avoid spurious 'repo missing' messages for repo patterns

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 70ec14a..b6991f8 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -211,7 +211,7 @@ sub load_1 {
     trace( 3, $repo );
 
     if ( repo_missing($repo) ) {
-        trace( 1, "repo '$repo' missing" );
+        trace( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
         return;
     }
     _chdir("$rc{GL_REPO_BASE}/$repo.git");

commit 9606e35528d0f83ae8f64f4fffd785f1e443f715
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Sep 19 06:24:07 2012 +0530

    help cgit folks out a bit :)

diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
new file mode 100755
index 0000000..7d00298
--- /dev/null
+++ b/src/triggers/post-compile/update-description-file
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# For normal (not "wild") repos, gitolite v3 sets 'gitweb.description' instead
+# of putting the text in the "description" file.  This is easier because it
+# just goes with the flow of setting config variables; nothing special needs
+# to be done for the description.
+
+# But this only works for gitweb, not for cgit.  Cgit users must therefore add
+# this line to the POST_COMPILE list in the rc file:
+#   'post-compile/update-description-file',
+
+cd $GL_REPO_BASE
+gitolite list-phy-repos | gitolite git-config % gitweb.description |
+    while read a b c
+    do
+        echo "$c" > $GL_REPO_BASE/$a.git/description
+    done

commit 724c741335acbcd6eb438ae5fad0bd1de56c9e3a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Sep 20 06:21:44 2012 +0530

    prevent barfage when presetting the rc file

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 346e049..5d3b861 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -61,7 +61,7 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
-if (-r $rc) {
+if (-r $rc and -s $rc) {
     do $rc or die $@;
 }
 if ( defined($GL_ADMINDIR) ) {

commit e59c3ba9f98abe27c6c34dbe6c31c87f54fabaef
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Sep 19 17:47:12 2012 +0530

    (minor docfix) add info on using Easy.pm from elsewhere

diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 9231d00..9e0ae0b 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -5,6 +5,25 @@ package Gitolite::Easy;
 # most/all functions in this module test $ENV{GL_USER}'s rights and
 # permissions so it needs to be set.
 
+# "use"-ing this module
+# ----------------------------------------------------------------------
+# Using this module from within a gitolite trigger or command is easy; you
+# just need 'use lib $ENV{GL_LIBDIR};' before the 'use Gitolite::Easy;'.
+#
+# Using it from something completely outside gitolite requires a bit more
+# work.  First, run 'gitolite query-rc -a' to find the correct values for
+# GL_BINDIR and GL_LIBDIR in your installation.  Then use this code in your
+# external program, using the paths you just found:
+#
+#   BEGIN {
+#       $ENV{GL_BINDIR} = "/full/path/to/gitolite/src";
+#       $ENV{GL_LIBDIR} = "/full/path/to/gitolite/src/lib";
+#   }
+#   use lib $ENV{GL_LIBDIR};
+#   use Gitolite::Easy;
+
+# API documentation
+# ----------------------------------------------------------------------
 # documentation for each function is at the top of the function.
 # Documentation is NOT in pod format; just read the source with a nice syntax
 # coloring text editor and you'll be happy enough.  (I do not like POD; please

commit aec8c718908b77e9b3d745f927689b391c9e833f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Aug 30 18:40:24 2012 +0530

    'help' command barfage fix
    
    should not barf if LOCAL_CODE is defined but it doesn't contain a
    "commands" subdirectory.

diff --git a/src/commands/help b/src/commands/help
index 2165d5f..23fa6ae 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -36,6 +36,7 @@ exit 0;
 sub list_x {
     my $d = shift;
     return unless $d;
+    return unless -d "$d/commands";
     _chdir "$d/commands";
     return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f -o -type l|sort`;
 }

commit ed4862ff968d9f33a1d423904d3419c072c598ea
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Aug 27 12:18:38 2012 +0530

    minor changes to README

diff --git a/README.txt b/README.txt
index e38b11a..e170df9 100644
--- a/README.txt
+++ b/README.txt
@@ -6,18 +6,26 @@ file will not suffice; you *must* check the online docs (see below for URL).
 ------------------------------------------------------------------------
 
 
-DOCUMENTATION FOR GITOLITE
-==========================
+This file contains BASIC DOCUMENTATION ONLY.
 
-This file contains basic documentation for a fresh, ssh-based, installation of
-gitolite and basic usage of its most important features.
+  * It is suitable for a fresh, ssh-based, installation of gitolite and basic
+    usage of its most important features.
 
-If you need more details on any of the topics covered here, or help with some
-troubleshooting, or just wish to read about the advanced features not covered
-here, please check the gitolite online documentation at:
+  * It is NOT meant to be exhaustive or detailed.
+
+The COMPLETE DOCUMENTATION is at:
 
     http://sitaramc.github.com/gitolite/master-toc.html
 
+Please go there for what/why/how, concepts, background, troubleshooting, more
+details on what is covered here, or advanced features not covered here.
+
+------------------------------------------------------------------------
+
+
+BASIC DOCUMENTATION FOR GITOLITE
+================================
+
 This file contains the following sections:
 
     INSTALLATION AND SETUP

commit cc9727c42bea266c19fca503f75b5b835382c676
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Aug 17 22:26:03 2012 +0530

    minor bug in include file handing...
    
    gitolite does indeed try to not load itself twice, but I forgot that by
    that time the pwd is ~/.gitolite/conf not ~/.gitolite so it always ended
    up reading itself twice in case of a wildcard include.

diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index 03f2cb2..ec78973 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -27,7 +27,7 @@ sub explode {
     my ( $file, $subconf, $out ) = @_;
 
     # seed the 'seen' list if it's empty
-    $included{ device_inode("conf/gitolite.conf") }++ unless %included;
+    $included{ device_inode("gitolite.conf") }++ unless %included;
 
     my $fh = _open( "<", $file );
     while (<$fh>) {

commit 740963582301665bcf9ece47a4fb438beed35589
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Aug 17 10:04:32 2012 +0530

    (minor) add a 'dd' function to quickly dump stuff to STDERR

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 03008b3..b5c4b47 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -12,6 +12,8 @@ package Gitolite::Common;
           usage                             tsh_run
           gen_lfn
           gl_log
+
+          dd
 );
 #>>>
 use Exporter 'import';
@@ -63,6 +65,11 @@ sub dbg {
     }
 }
 
+sub dd {
+    local $ENV{D} = 1;
+    dbg(@_);
+}
+
 sub _warn {
     gl_log( 'warn', @_ );
     if ( $ENV{D} and $ENV{D} >= 3 ) {

commit ba67f6f9ca2a9152b3687348d71feedcc58f3ce6
Author: Olof Johansson <olof at ethup.se>
Date:   Thu Aug 9 18:02:00 2012 +0200

    Bailout tests unless envvar $GITOLITE_TEST is 'y'
    
    [committer made some changes to t/README]

diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index e6e5a36..f42c463 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -37,6 +37,12 @@ use warnings;
 
 # ----------------------------------------------------------------------
 
+# make sure the user is ready for it
+if (not $ENV{GITOLITE_TEST} or $ENV{GITOLITE_TEST} ne 'y') {
+    print "Bail out! See t/README for information on how to run the tests.\n";
+    exit 255;
+}
+
 # required preamble for all tests
 try "
     DEF gsh = /TRACE: gsh.SOC=/
diff --git a/t/README b/t/README
index c863b41..6a98e27 100644
--- a/t/README
+++ b/t/README
@@ -1,8 +1,13 @@
+
+============================================
 WARNING: THE TEST SUITE DELETES STUFF FIRST!
+============================================
+
+Please run the tests ONLY on a userid where it's ok to LOSE DATA.
 
-Testing gitolite3 is now one command after the clone:
+On such a userid, clone gitolite then run this command in the clone:
 
-    prove
+    GITOLITE_TEST=y prove
 
-But because it starts by cleaning the slate, it's best to do it on a spare
-userid that you are ok to lose data on.
+http://sitaramc.github.com/gitolite/testing.html has more details.  It will
+also help you try out gitolite if you want to go beyond just the test suite.

commit 31166e1e1c3113012d022f4029e60f721e273074
Author: Nate Jones <nate at mediatemple.net>
Date:   Fri Aug 3 13:46:43 2012 -0700

    find symlinked commands when generating help list

diff --git a/src/commands/help b/src/commands/help
index d7fae78..2165d5f 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -37,5 +37,5 @@ sub list_x {
     my $d = shift;
     return unless $d;
     _chdir "$d/commands";
-    return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f|sort`;
+    return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f -o -type l|sort`;
 }

commit b2a3509e63a834f8bff3f45de74fcd58baf2c6a5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jul 27 21:19:07 2012 +0530

    point people to mailing list for general questions

diff --git a/README.txt b/README.txt
index b5d0a18..e38b11a 100644
--- a/README.txt
+++ b/README.txt
@@ -341,14 +341,19 @@ GITWEB
 CONTACT
 -------
 
+    NOTE: Unless you have very good reasons, please use the mailing list below
+    instead of mailing me personally.  If you have to mail me, use the gmail
+    address instead of my work address.
+
     Author: sitaramc at gmail.com, sitaram at atc.tcs.com
 
-    Mailing lists:
-        gitolite at googlegroups.com -- general discussion
-        gitolite-announce at googlegroups.com -- announcements and notices only
-    List subscribe addresses:
-        gitolite+subscribe at googlegroups.com
-        gitolite-announce+subscribe at googlegroups.com
+    Mailing list for questions and general discussion:
+        gitolite at googlegroups.com
+        subscribe address: gitolite+subscribe at googlegroups.com
+
+    Mailing list for announcements and notices:
+        gitolite-announce at googlegroups.com
+        subscribe address: gitolite-announce+subscribe at googlegroups.com
 
     IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
     time zone).

commit 57bea39a1e65d29ce43ee8d0c1fbcb9ff19c0a9b
Author: Patrick Westerhoff <PatrickWesterhoff at gmail.com>
Date:   Thu Jun 21 23:52:22 2012 +0200

    Add special %GL_CREATOR variable for git-config
    
    Add a special variable `%GL_CREATOR` to the the git-config trigger that
    is replaced by the name of the repository creator (if any).
    
    This can be useful to set up the default owner configuration for wild
    repositories.
    
    Example:
    
        repo assignments/CREATOR/a[0-9][0-9]
            C   = @students
            RW+ = CREATOR
            config gitweb.owner = %GL_CREATOR
    
    ----
    
    committer added an if condition to the s/// line.

diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 7069f6c..23458d2 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -43,12 +43,14 @@ for my $pr (@$lpr) {
 
 sub fixup_config {
     my $pr = shift;
+    my $creator = creator($pr);
 
     my $gc = git_config( $pr, '.', 1 );
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
             $value =~ s/%GL_REPO/$pr/g;
+            $value =~ s/%GL_CREATOR/$creator/g if $creator;
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {
             system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );

commit f4eb6dcb5326418366fadc8db1bf60bb46ebabe8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jul 18 15:34:28 2012 +0530

    'rsync' command to create and send bundles (manual smoke test only)
    
    run 'ssh git at host rsync -h' for usage, as usual

diff --git a/src/commands/rsync b/src/commands/rsync
new file mode 100755
index 0000000..87fb046
--- /dev/null
+++ b/src/commands/rsync
@@ -0,0 +1,149 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for admins
+
+BUNDLE SUPPORT
+
+    (1) For each repo in gitolite.conf for which you want bundle support (or
+        '@all', if you wish), add the following line:
+
+            option bundle       =   1
+
+        Or you can say:
+
+            option bundle.ttl   =   <number>
+
+        A bundle file that is more than <number> seconds old (default value
+        86400, i.e., 1 day) is recreated on the next bundle request.  Increase
+        this if your repo is not terribly active.
+
+        Note: a bundle file is also deleted and recreated if it contains a ref
+        that was then either deleted or rewound in the repo.  This is checked
+        on every invocation.
+
+    (2) Add 'rsync' to the COMMANDS list in the rc file
+
+
+GENERIC RSYNC SUPPORT
+
+    TBD
+
+=cut
+
+=for usage
+rsync helper for gitolite
+
+BUNDLE SUPPORT
+
+    Admins: see src/commands/rsync for setup instructions
+
+    Users:
+        rsync -P git at host:repo.bundle .
+            # downloads a file called "<basename of repo>.bundle"; repeat as
+            # needed till the whole thing is downloaded
+        git clone repo.bundle repo
+        cd repo
+        git remote set-url origin git at host:repo
+        git fetch origin    # and maybe git pull, etc. to freshen the clone
+
+GENERIC RSYNC SUPPORT
+
+    TBD
+
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+# rsync driver program.  Several things can be done later, but for now it
+# drives just the 'bundle' transfer.
+
+if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^rsync --server --sender (-[-\w=.]+ )+\. (\S+)\.bundle$/ ) {
+
+    my $repo = $2;
+    $repo =~ s/\.git$//;
+
+    # all errors have the same message to avoid leaking info
+    can_read($repo) or _die "you are not authorised";
+    my %config = config( $repo, "gitolite-options.bundle" ) or _die "you are not authorised";
+
+    my $ttl = $config{'gitolite-options.bundle.ttl'} || 86400;    # in seconds (default 1 day)
+
+    my $bundle = bundle_create( $repo, $ttl );
+
+    $ENV{SSH_ORIGINAL_COMMAND} =~ s( \S+\.bundle)( $bundle);
+    trace( 1, "rsync bundle", $ENV{SSH_ORIGINAL_COMMAND} );
+    Gitolite::Common::_system( split ' ', $ENV{SSH_ORIGINAL_COMMAND} );
+    exit 0;
+}
+
+_warn "invalid rsync command '$ENV{SSH_ORIGINAL_COMMAND}'";
+usage();
+
+# ----------------------------------------------------------------------
+# helpers
+# ----------------------------------------------------------------------
+
+sub bundle_create {
+    my ( $repo, $ttl ) = @_;
+    my $bundle   = "$repo.bundle";
+    $bundle =~ s(.*/)();
+    my $recreate = 0;
+
+    my ( %b, %r );
+    if ( -f $bundle ) {
+        %b = map { chomp; reverse split; } `git ls-remote --heads --tags $bundle`;
+        %r = map { chomp; reverse split; } `git ls-remote --heads --tags .`;
+
+        for my $ref ( sort keys %b ) {
+
+            my $mtime = ( stat $bundle )[9];
+            if ( time() - $mtime > $ttl ) {
+                trace( 1, "bundle too old" );
+                $recreate++;
+                last;
+            }
+
+            if ( not $r{$ref} ) {
+                trace( 1, "ref '$ref' deleted in repo" );
+                $recreate++;
+                last;
+            }
+
+            if ( $r{$ref} eq $b{$ref} ) {
+                # same on both sides; ignore
+                delete $r{$ref};
+                delete $b{$ref};
+                next;
+            }
+
+            `git rev-list --count --left-right $b{$ref}...$r{$ref}` =~ /^(\d+)\s+(\d+)$/ or _die "git too old";
+            if ($1) {
+                trace( 1, "ref '$ref' rewound in repo" );
+                $recreate++;
+                last;
+            }
+
+        }
+
+    } else {
+        trace( 1, "no bundle found" );
+        $recreate++;
+    }
+
+    return $bundle if not $recreate;
+
+    trace( 1, "creating bundle for '$repo'" );
+    -f $bundle and ( unlink $bundle or die "a horrible death" );
+    system("git bundle create $bundle --branches --tags >&2");
+
+    return $bundle;
+}
+
+sub trace {
+    Gitolite::Common::trace(@_);
+}

commit 8ad1eee2201c5337e1da3b79bc844dbb2817b7e1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jul 17 16:51:41 2012 +0530

    migrated 'who-pushed' command (manual smoke test only)

diff --git a/src/commands/who-pushed b/src/commands/who-pushed
new file mode 100755
index 0000000..5de7b9d
--- /dev/null
+++ b/src/commands/who-pushed
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Easy;
+
+=for usage
+Usage:    ssh git at host who-pushed <repo> <SHA>
+
+Determine who pushed the given commit.  The first few hex digits of the SHA
+should suffice.
+
+Each line of the output contains the following fields: timestamp, a
+transaction ID, username, refname, and the old and new SHAs for the ref.
+
+We assume the logfile names have been left as default, or if changed, in such
+a way that they come up oldest first when sorted.
+
+The program searches ALL the log files, in reverse sorted order (i.e., newest
+first).  This means it could take a long time if your log directory is large
+and contains lots of old log files.  Patches to limit the search to an
+optional date range are welcome.
+
+Note on the "transaction ID" field: if looking at the log file doesn't help
+you figure out what its purpose is, please just ignore it.
+=cut
+
+usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
+usage() if $ARGV[1] !~ /^[0-9a-f]+$/i;
+
+my $repo = shift;
+my $sha = shift; $sha =~ tr/A-F/a-f/;
+
+$ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" );
+
+# ----------------------------------------------------------------------
+
+my $repodir = "$ENV{GL_REPO_BASE}/$repo.git";
+chdir $repodir or die "repo '$repo' missing";
+(my $logdir = $ENV{GL_LOGFILE}) =~ s(/[^/]+$)();
+
+for my $logfile ( reverse glob("$logdir/*") ) {
+    @ARGV = ($logfile);
+    for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) {
+        chomp($line);
+        my @fields = split /\t/, $line;
+        my ($ts, $pid, $who, $ref, $d_old, $new) = @fields[ 0, 1, 4, 6, 7, 8];
+
+        # d_old is what you display
+        my $old = $d_old;
+        $old = "" if $d_old eq ("0" x 40);
+        $old = "$old.." if $old;
+
+        system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'");
+    }
+}

commit d3279e4ad03b928b5ee832447b46fa6851f2682e
Author: Dave Abrahams <dave.abrahams at gmail.com>
Date:   Wed Jul 11 21:29:05 2012 -0400

    Fix a typo

diff --git a/README.txt b/README.txt
index 09e6dd2..b5d0a18 100644
--- a/README.txt
+++ b/README.txt
@@ -184,7 +184,7 @@ ACCESS RULES
 
       * a permission of RW matches only a fast-forward push or create
       * a permission of RW+ matches any type of push
-      * a permission or '-' matches any type of push
+      * a permission of '-' matches any type of push
 
     Note 2: refex matching:
     (refex = optional regex to match the ref being pushed)

commit fd0778e6d61876ed5ef83dfc07f0d795d6d767bd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jul 10 21:04:52 2012 +0530

    (minor) don't keep adding the same thing to $PATH

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 876760a..346e049 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -91,7 +91,7 @@ do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
 unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
 
-$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
+$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}" unless $ENV{PATH} =~ /^$ENV{GL_BINDIR}:/;
 
 {
     $rc{GL_TID} = $ENV{GL_TID} ||= $$;

commit f35db87efc115a8e79ca7776fcc0c5898275b52c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jul 10 20:59:36 2012 +0530

    (minor) new mailing list

diff --git a/README.txt b/README.txt
index 61e4423..09e6dd2 100644
--- a/README.txt
+++ b/README.txt
@@ -343,8 +343,12 @@ CONTACT
 
     Author: sitaramc at gmail.com, sitaram at atc.tcs.com
 
-    Mailing list: gitolite at googlegroups.com
-    List subscribe address : gitolite+subscribe at googlegroups.com
+    Mailing lists:
+        gitolite at googlegroups.com -- general discussion
+        gitolite-announce at googlegroups.com -- announcements and notices only
+    List subscribe addresses:
+        gitolite+subscribe at googlegroups.com
+        gitolite-announce+subscribe at googlegroups.com
 
     IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
     time zone).

commit f545bc08f6e932fd15ca231696903237a736d878
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jul 1 16:00:25 2012 +0530

    minor fixups

diff --git a/check-g2-compat b/check-g2-compat
index 11fe7a7..72c32fa 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -38,8 +38,9 @@ sub intro {
     msg( ''   => "or that might end up giving *more* access to someone if migrated as-is." );
     msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
     msg( '', '' );
-    msg( INFO => "'see docs' usually means doc/g2migr.mkd" );
-    msg( '',  => "(online at http://sitaramc.github.com/gitolite/g2migr.html)" );
+    msg( INFO => "'see docs' usually means the pre-migration checklist in" );
+    msg( '',  => "'g2migr.html'; to get there, start from the main migration" );
+    msg( '',  => "page at http://sitaramc.github.com/gitolite/install.html#migr" );
     msg( '', '' );
 }
 
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 23ed421..876760a 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -326,6 +326,8 @@ __DATA__
     # word, not a full domain name.  See documentation if in doubt
     # HOSTNAME                  =>  'darkstar',
     UMASK                       =>  0077,
+
+    # look in the "GIT-CONFIG" section in the README for what to do
     GIT_CONFIG_KEYS             =>  '',
 
     # comment out if you don't need all the extra detail in the logfile

commit db2cf23379bf0d14cc2e1d0edd5289d38f7f5a90
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 29 22:19:06 2012 +0530

    logical expressions on refexes :-)

diff --git a/src/VREF/refex-expr b/src/VREF/refex-expr
new file mode 100755
index 0000000..3ff4bf4
--- /dev/null
+++ b/src/VREF/refex-expr
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+my $rule = $ARGV[7];
+my $res = $ENV{"GL_REFEX_EXPR_" . $rule} || 0;
+print "$ARGV[6] ($res)\n" if $res;
+
+exit 0;
+
+__END__
+
+Documentation for the refex-expression evaluation feature
+
+First, make sure you have both the VREF and the trigger scripts
+(src/VREF/refex-expr and src/lib/Gitolite/Triggers/RefexExpr.pm)
+
+Next, add this to the ACCESS_2 list in the rc file:
+
+    'RefexExpr::access_2',
+
+For the rest, we'll use this example:
+
+  * user u1 can push foo to some other branch, and anything else to the master
+    branch, but not foo to the master branch
+
+  * user u2 is allowed to push either 'doc/' or 'src/' but not both
+
+Here's the conf file extract:
+
+    repo    testing
+        RW+     master              =   u1          # line 1
+        RW+                         =   @all        # line 2
+
+        RW+     VREF/NAME/foo       =   u1
+        RW+     VREF/NAME/doc/      =   u2
+        RW+     VREF/NAME/src/      =   u2
+
+        # set up 2 refex expressions, named e1, e2
+        option  refex-expr.e1       =   master and VREF/NAME/foo
+        option  refex-expr.e2       =   VREF/NAME/doc/ and VREF/NAME/src/
+
+        # now deny users if the corresponding expression is true
+        -       VREF/refex-expr/e1  =   u1
+        -       VREF/refex-expr/e2  =   u2
+
+Here are some IMPORTANT notes:
+
+  * You MUST place VREF/refex-expr rules at the end.  (Only 'partial-copy', if
+    you use it, must come later).
+
+  * You MUST explicitly permit the refexes used in your refex expressions.  If
+    you have more generic rules, the specific ones must come first.
+
+    For example, without line 1, the refex recorded for user u1 will come from
+    line 2, (so it will be 'refs/.*'), and 'master' in the refex expressions
+    will never have a true value.
+
+  * (corollary) make sure you use the exact same refex in the expression as
+    you did on the original rule line.  E.g., a missing slash at the end will
+    mess things up.
+
+  * You can use any logical expression using refexes as operands and using
+    these operators:
+
+        and not xor or
+
+    Parens are not allowed.
+
+    If a refex has passed, it will have a 'true' value, else it will be false.
+
+    The result of the evaluation, after these substitutions, will be the
+    result of the refex-expr VREF.
diff --git a/src/lib/Gitolite/Triggers/RefexExpr.pm b/src/lib/Gitolite/Triggers/RefexExpr.pm
new file mode 100644
index 0000000..687cb9e
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/RefexExpr.pm
@@ -0,0 +1,80 @@
+package Gitolite::Triggers::RefexExpr;
+use strict;
+use warnings;
+
+# track refexes passed and evaluate expressions on them
+# ----------------------------------------------------------------------
+# see instructions for use at the bottom of src/VREF/refex-expr
+
+use Gitolite::Easy;
+
+my %passed;
+my %rules;
+my $init_done = 0;
+
+sub access_2 {
+    # get out quick for repos that don't have any rules
+    return if $init_done and not %rules;
+
+    # but we don't really know that the first time, heh!
+    if (not $init_done) {
+        my $repo = $_[1];
+        init($repo);
+        return unless %rules;
+    }
+
+    my $refex = $_[5];
+    return if $refex =~ /DENIED/;
+
+    $passed{$refex}++;
+
+    # evaluate the rules each time; it's not very expensive
+    for my $k (sort keys %rules) {
+        $ENV{"GL_REFEX_EXPR_" . $k} = eval_rule($rules{$k});
+    }
+}
+
+sub eval_rule {
+    my $rule = shift;
+
+    my $e;
+    $e = join " ", map { convert($_) } split ' ', $rule;
+
+    my $ret = eval $e;
+    _die "eval '$e' -> '$@'" if $@;
+    Gitolite::Common::trace(1, "RefexExpr", "'$rule' -> '$e' -> '$ret'");
+
+    return "'$rule' -> '$e'" if $ret;
+}
+
+my %constant;
+%constant = map { $_ => $_ } qw(1 not and or xor + - ==);
+$constant{'-lt'} = '<';
+$constant{'-gt'} = '>';
+$constant{'-eq'} = '==';
+$constant{'-le'} = '<=';
+$constant{'-ge'} = '>=';
+$constant{'-ne'} = '!=';
+
+sub convert {
+    my $i = shift;
+    return $i if $i =~ /^-?\d+$/;
+    return $constant{$i} || $passed{$i} || $passed{"refs/heads/$i"} || 0;
+}
+
+# called only once
+sub init {
+    $init_done = 1;
+    my $repo = shift;
+
+    # find all the rule expressions
+    my %t = config($repo, "^gitolite-options\\.refex-expr\\.");
+    my ($k, $v);
+    # get rid of the cruft and store just the rule name as the key
+    while ( ($k, $v) = each %t) {
+        $k =~ s/^gitolite-options\.refex-expr\.//;
+        $rules{$k} = $v;
+    }
+}
+
+1;

commit af437c3a7b9034c8bfda30d99daf3a8160ff904d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 27 07:10:09 2012 +0530

    v3.04

diff --git a/CHANGELOG b/CHANGELOG
index 2767b31..f743d19 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,25 @@
+2012-06-27  v3.04   documentation graduated and moved out of parents house :)
+
+                    new trigger for 'repo specific umask'
+
+                    new 'list-dangling-repos' command
+
+                    new LOCAL_CODE rc var; allow admin specified programs to
+                    override system-installed ones
+
+                    new 'upstream' trigger-cum-command to maintain local
+                    copies of external repos
+
+                    new 'sudo' command
+
+                    minor backward compat breakage in 'gitolite query-rc'
+
+                    'perms' command can now create repo if needed
+
+                    migrated 'symbolic-ref' command
+
+                    'gitolite setup --hooks-only'
+
 2012-05-23  v3.03   fix major bug that allowed an admin to get a shell
 
 2012-05-20  v3.02   packaging instructions fixed up and smoke tested

commit 49580fe4b30a9c5d9ac459f4b6bd66926e5da42c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Jun 25 12:01:04 2012 +0530

    doc split :(
    
    Background
    
      * I needed to have the documentation under CC-BY-NC-SA (something
        happened to force me to choose)
    
      * Distros don't like the NC part.  They'd rather drop the
        documentation entirely instead
    
      * I don't like that; it bothers me that even a clueful guy won't be
        able to do a basic install with what comes in the package.
    
      * Meanwhile, I have always had the occasional "TL;DR" complaint about
        my docs
    
    Taking all this into account, it seemed like the best way was:
    
      * Create a brand new README.txt that is crisp enough for someone to
        glance through and quickly get started.  At more then 300 lines, it
        covers enough ground that probably 60% of sites don't need more.
    
        Put this under the CC-BY-SA license, which is on the "good" list for
        Fedora (and also Debian, I am told).
    
      * Move the current documents to a new "gitolite-doc" repo that distros
        can simply ignore, but anyone who has trouble can go to.
    
        Make sure the online pages have the same content at the same URLs as
        they do now, getting it instead from this new repo.
    
        Link to the main URL in the new README.txt

diff --git a/doc/CHANGELOG b/CHANGELOG
similarity index 100%
rename from doc/CHANGELOG
rename to CHANGELOG
diff --git a/README.mkd b/README.mkd
deleted file mode 100644
index 6400dc4..0000000
--- a/README.mkd
+++ /dev/null
@@ -1,38 +0,0 @@
-# Gitolite README
-
-**Github users: please read the "wiki" link at the top of the page before
-submitting issues or pull requests**.
-
-----
-
-If you're reading this on the main gitolite page on github, several
-**IMPORTANT CHANGES** have happened to gitolite:
-
-1.  A competely re-written version of gitolite has been pushed to the "master"
-    branch, and is now the actively maintained and supported software.  Do NOT
-    try to merge this with your old "master" branch!
-
-    The [main page][h-mp] leads to several useful starting points.  The [table
-    of contents][h-mt] is a much more meaningfully ordered/structured list of
-    links (instead of putting them in alphabetical order of the filename, like
-    in g2!)
-
-    If you are an existing (g2) user and wish to migrate, you MUST read
-    [this](http://sitaramc.github.com/gitolite/install.html#migr).
-
-2.  Versions v2.x are on branch "g2".  It will be supported for security
-    issues and serious bugs in core functionality, but not for anything less
-    critical.  Versions v1.x are completely unsupported now.  (Documentation
-    links for this version are [here][o1] and [here][o2]).
-
-[h-mp]: http://sitaramc.github.com/gitolite/
-[h-mt]: http://sitaramc.github.com/gitolite/master-toc.html
-[o1]: http://sitaramc.github.com/gitolite/g2/
-[o2]: http://sitaramc.github.com/gitolite/g2/master-toc.html
-
-----
-
-License information for code and documentation is at the end of doc/index.mkd
-(or you can read it online
-[here](http://sitaramc.github.com/gitolite/index.html#license)).
-
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..61e4423
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,360 @@
+Github-users: click the 'wiki' link before sending me anything via github.
+
+Existing users: this is gitolite v3.x.  If you are upgrading from v2.x this
+file will not suffice; you *must* check the online docs (see below for URL).
+
+------------------------------------------------------------------------
+
+
+DOCUMENTATION FOR GITOLITE
+==========================
+
+This file contains basic documentation for a fresh, ssh-based, installation of
+gitolite and basic usage of its most important features.
+
+If you need more details on any of the topics covered here, or help with some
+troubleshooting, or just wish to read about the advanced features not covered
+here, please check the gitolite online documentation at:
+
+    http://sitaramc.github.com/gitolite/master-toc.html
+
+This file contains the following sections:
+
+    INSTALLATION AND SETUP
+    ADDING USERS AND REPOS
+    HELP FOR YOUR USERS
+    BASIC SYNTAX
+    ACCESS RULES
+    GROUPS
+    COMMANDS
+    THE 'rc' FILE
+    GIT-CONFIG
+    GIT-DAEMON
+    GITWEB
+
+    CONTACT
+    LICENSE
+
+------------------------------------------------------------------------
+
+
+INSTALLATION AND SETUP
+----------------------
+
+    Server requirements:
+
+      * any unix system
+      * sh
+      * git 1.6.6+
+      * perl 5.8.8+
+      * openssh 5.0+
+      * a dedicated userid to host the repos (in this document, we assume it
+        is 'git'), with shell access ONLY by 'su - git' from some other userid
+        on the same server.
+
+    Steps to install:
+
+      * login as 'git' as described above
+
+      * make sure ~/.ssh/authorized_keys is empty or non-existent
+
+      * make sure your ssh public key from your workstation is available at
+        $HOME/YourName.pub
+
+      * run the following commands:
+
+            git clone git://github.com/sitaramc/gitolite
+            mkdir -p $HOME/bin
+            gitolite/install -to $HOME/bin
+            gitolite setup -pk YourName.pub
+
+        If the last command doesn't run perhaps 'bin' in not in your 'PATH'.
+        You can either add it, or just run:
+
+            $HOME/bin/gitolite setup -pk YourName.pub
+
+
+ADDING USERS AND REPOS
+----------------------
+
+    Do NOT add new repos or users manually on the server.  Gitolite users,
+    repos, and access rules are maintained by making changes to a special repo
+    called 'gitolite-admin' and pushing those changes to the server.
+
+    ----
+
+    To administer your gitolite installation, start by doing this on your
+    workstation (if you have not already done so):
+
+        git clone git at host:gitolite-admin
+
+    **NOTE**: if you are asked for a password, something has gone wrong.
+
+    Now if you 'cd gitolite-admin', you will see two subdirectories in it:
+    'conf' and 'keydir'.
+
+    To add new users alice, bob, and carol, obtain their public keys and add
+    them to 'keydir' as alice.pub, bob.pub, and carol.pub respectively.
+
+    To add a new repo 'foo' and give different levels of access to these
+    users, edit the file 'conf/gitolite.conf' and add lines like this:
+
+        repo foo
+            RW+         =   alice
+            RW          =   bob
+            R           =   carol
+
+    See the 'ACCESS RULES' section later for more details.
+
+    Once you have made these changes, do something like this:
+
+        git add conf
+        git add keydir
+        git commit -m 'added foo, gave access to alice, bob, carol'
+        git push
+
+    When the push completes, gitolite will add the new users to
+    ~/.ssh/authorized_keys on the server, as well as create a new, empty, repo
+    called 'foo'.
+
+
+HELP FOR YOUR USERS
+-------------------
+
+    Once a user has sent you their public key and you have added them as
+    specified above and given them access, you have to tell them what URL to
+    access their repos at.  This is usually 'git clone git at host:reponame'; see
+    man git-clone for other forms.
+
+    **NOTE**: again, if they are asked for a password, something is wrong.
+
+    If they need to know what repos they have access to, they just have to run
+    'ssh git at host info'; see 'COMMANDS' section later for more on this.
+
+
+BASIC SYNTAX
+------------
+
+    The basic syntax of the conf file is very simple.
+
+      * Everything is space separated; there are no commas, semicolons, etc.,
+        in the syntax.
+
+      * Comments are in the usual perl/shell style.
+
+      * User and repo names are as simple as possible; they must start with an
+        alphanumeric, but after that they can also contain '.', '_', or '-'.
+
+        Usernames can optionally be followed by an '@' and a domainname
+        containing at least one '.'; this allows you to use an email address
+        as someone's username.
+
+        Reponames can contain '/' characters; this allows you to put your
+        repos in a tree-structure for convenience.
+
+      * There are no continuation lines.
+
+
+ACCESS RULES
+------------
+
+    This section is mostly 'by example'.
+
+    Gitolite's access rules are very powerful.  The simplest use was already
+    shown above.  Here is a slightly more detailed example:
+
+        repo foo
+            RW+                     =   alice
+            -   master              =   bob
+            -   refs/tags/v[0-9]    =   bob
+            RW                      =   bob
+            RW  refs/tags/v[0-9]    =   carol
+            R                       =   dave
+
+    For clones and fetches, as long as the user is listed with an R, RW
+    or RW+ in at least one rule, he is allowed to read the repo.
+
+    For pushes, rules are processed in sequence until a rule is found
+    where the user, the permission (see note 1), and the refex (note 2)
+    *all* match.  At that point, if the permission on the matched rule
+    was '-', the push is denied, otherwise it is allowed.  If no rule
+    matches, the push is denied.
+
+    Note 1: permission matching:
+
+      * a permission of RW matches only a fast-forward push or create
+      * a permission of RW+ matches any type of push
+      * a permission or '-' matches any type of push
+
+    Note 2: refex matching:
+    (refex = optional regex to match the ref being pushed)
+
+      * an empty refex is treated as 'refs/.*'
+      * a refex that does not start with 'refs/' is prefixed with 'refs/heads/'
+      * finally, a '^' is prefixed
+      * the ref being pushed is matched against this resulting refex
+
+    With all that background, here's what the example rules say:
+
+      * alice can do anything to any branch or tag -- create, push,
+        delete, rewind/overwrite etc.
+
+      * bob can create or fast-forward push any branch whose name does
+        not start with 'master' and create any tag whose name does not
+        start with 'v'+digit.
+
+      * carol can create tags whose names start with 'v'+digit.
+
+      * dave can clone/fetch.
+
+
+GROUPS
+------
+
+    Gitolite allows you to groups users or repos for convenience.  Here's an
+    example that creates two groups of users:
+
+        @staff      =   alice bob carol
+        @interns    =   ashok
+
+        repo secret
+            RW      =   @staff
+
+        repo foss
+            RW+     =   @staff
+            RW      =   @interns
+
+    Group lists accumulate.  The following two lines have the same effect as
+    the earlier definition of @staff above:
+
+        @staff      =   alice bob
+        @staff      =   carol
+
+    You can also use group names in other group names:
+
+        @all-devs   =   @staff @interns
+
+    Finally, @all is a special group name that is often convenient to use if
+    you really mean 'all repos' or 'all users'.
+
+
+COMMANDS
+--------
+
+    Users can run certain commands remotely, using ssh.  For example:
+
+        ssh git at host help
+
+    prints a list of available commands.
+
+    The most commonly used command is 'info'.  All commands respond to a
+    single argument of '-h' with suitable information.
+
+    If you have shell on the server, you have a lot more commands available to
+    you; try running 'gitolite help'.
+
+
+THE 'rc' FILE
+--------------
+
+    Some of the instructions below may require you to edit the rc file
+    (~/.gitolite.rc on the server).
+
+    The rc file is perl code, but you do NOT need to know perl to edit it.
+    Just mind the commas, use single quotes unless you know what you're doing,
+    and make sure the brackets and braces stay matched up.
+
+
+GIT-CONFIG
+----------
+
+    Gitolite lets you set git-config values for individual repos without
+    having to log on to the server and run 'git config' commands:
+
+        repo foo
+            config hooks.mailinglist = foo-commits at example.tld
+            config hooks.emailprefix = '[foo] '
+            config foo.bar = ''
+            config foo.baz =
+
+    **WARNING**
+
+        The last syntax shown above is the *only* way to *delete* a config
+        variable once you have added it.  Merely removing it from the conf
+        file will *not* delete it from the repo.git/config file.
+
+    **SECURITY NOTE**
+
+        Some git-config keys allow arbitrary code to be run on the server.
+
+        If all of your gitolite admins already have shell access to the server
+        account hosting it, you can edit the rc file (~/.gitolite.rc) on the
+        server, and change the GIT_CONFIG_KEYS line to look like this:
+
+            GIT_CONFIG_KEYS     =>  '.*',
+
+        Otherwise, give it a space-separated list of regular expressions that
+        define what git-config keys are allowed.  For example, this one allows
+        only variables whose names start with 'gitweb' or with 'gc' to be
+        defined:
+
+            GIT_CONFIG_KEYS     =>  'gitweb\..* gc\..*',
+
+
+GIT-DAEMON
+----------
+
+    Gitolite creates the 'git-daemon-export-ok' file for any repo that is
+    readable by a special user called 'daemon', like so:
+
+        repo foo
+            R   =   daemon
+
+
+GITWEB
+------
+
+    Any repo that is readable by a special user called 'gitweb' will be added
+    to the projects.list file.
+
+        repo foo
+            R   =   gitweb
+
+    Or you can set one or more of the following config variables instead:
+
+        repo foo
+            config gitweb.owner         =   some person's name
+            config gitweb.description   =   some description
+            config gitweb.category      =   some category
+
+    **NOTE**
+
+        You will probably need to change the UMASK in the rc file from the
+        default (0077) to 0027 and add whatever user your gitweb is running as
+        to the 'git' group.  After that, you need to run a one-time 'chmod -R'
+        on the already created files and directories.
+
+
+------------------------------------------------------------------------
+
+
+CONTACT
+-------
+
+    Author: sitaramc at gmail.com, sitaram at atc.tcs.com
+
+    Mailing list: gitolite at googlegroups.com
+    List subscribe address : gitolite+subscribe at googlegroups.com
+
+    IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
+    time zone).
+
+
+LICENSE
+-------
+
+    The gitolite *code* is released under GPL v2.  See COPYING for details.
+
+    This documentation, which is part of the source code repository, is
+    provided under a Creative Commons Attribution-ShareAlike 3.0 Unported
+    License -- see http://creativecommons.org/licenses/by-sa/3.0/
diff --git a/doc/Markdown.pl b/doc/Markdown.pl
deleted file mode 100755
index 47d82e8..0000000
--- a/doc/Markdown.pl
+++ /dev/null
@@ -1,1450 +0,0 @@
-#!/usr/bin/perl
-
-#
-# Markdown -- A text-to-HTML conversion tool for web writers
-#
-# Copyright (c) 2004 John Gruber
-# <http://daringfireball.net/projects/markdown/>
-#
-
-
-package Markdown;
-require 5.006_000;
-use strict;
-use warnings;
-
-use Digest::MD5 qw(md5_hex);
-use vars qw($VERSION);
-$VERSION = '1.0.1';
-# Tue 14 Dec 2004
-
-## Disabled; causes problems under Perl 5.6.1:
-# use utf8;
-# binmode( STDOUT, ":utf8" );  # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html
-
-
-#
-# Global default settings:
-#
-my $g_empty_element_suffix = " />";     # Change to ">" for HTML output
-my $g_tab_width = 4;
-
-
-#
-# Globals:
-#
-
-# Regex to match balanced [brackets]. See Friedl's
-# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
-my $g_nested_brackets;
-$g_nested_brackets = qr{
-	(?> 								# Atomic matching
-	   [^\[\]]+							# Anything other than brackets
-	 | 
-	   \[
-		 (??{ $g_nested_brackets })		# Recursive set of nested brackets
-	   \]
-	)*
-}x;
-
-
-# Table of hash values for escaped characters:
-my %g_escape_table;
-foreach my $char (split //, '\\`*_{}[]()>#+-.!') {
-	$g_escape_table{$char} = md5_hex($char);
-}
-
-
-# Global hashes, used by various utility routines
-my %g_urls;
-my %g_titles;
-my %g_html_blocks;
-
-# Used to track when we're inside an ordered or unordered list
-# (see _ProcessListItems() for details):
-my $g_list_level = 0;
-
-
-#### Blosxom plug-in interface ##########################################
-
-# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine
-# which posts Markdown should process, using a "meta-markup: markdown"
-# header. If it's set to 0 (the default), Markdown will process all
-# entries.
-my $g_blosxom_use_meta = 0;
-
-sub start { 1; }
-sub story {
-	my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
-
-	if ( (! $g_blosxom_use_meta) or
-	     (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i))
-	     ){
-			$$body_ref  = Markdown($$body_ref);
-     }
-     1;
-}
-
-
-#### Movable Type plug-in interface #####################################
-eval {require MT};  # Test to see if we're running in MT.
-unless ($@) {
-    require MT;
-    import  MT;
-    require MT::Template::Context;
-    import  MT::Template::Context;
-
-	eval {require MT::Plugin};  # Test to see if we're running >= MT 3.0.
-	unless ($@) {
-		require MT::Plugin;
-		import  MT::Plugin;
-		my $plugin = new MT::Plugin({
-			name => "Markdown",
-			description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)",
-			doc_link => 'http://daringfireball.net/projects/markdown/'
-		});
-		MT->add_plugin( $plugin );
-	}
-
-	MT::Template::Context->add_container_tag(MarkdownOptions => sub {
-		my $ctx	 = shift;
-		my $args = shift;
-		my $builder = $ctx->stash('builder');
-		my $tokens = $ctx->stash('tokens');
-
-		if (defined ($args->{'output'}) ) {
-			$ctx->stash('markdown_output', lc $args->{'output'});
-		}
-
-		defined (my $str = $builder->build($ctx, $tokens) )
-			or return $ctx->error($builder->errstr);
-		$str;		# return value
-	});
-
-	MT->add_text_filter('markdown' => {
-		label     => 'Markdown',
-		docs      => 'http://daringfireball.net/projects/markdown/',
-		on_format => sub {
-			my $text = shift;
-			my $ctx  = shift;
-			my $raw  = 0;
-		    if (defined $ctx) {
-		    	my $output = $ctx->stash('markdown_output'); 
-				if (defined $output  &&  $output =~ m/^html/i) {
-					$g_empty_element_suffix = ">";
-					$ctx->stash('markdown_output', '');
-				}
-				elsif (defined $output  &&  $output eq 'raw') {
-					$raw = 1;
-					$ctx->stash('markdown_output', '');
-				}
-				else {
-					$raw = 0;
-					$g_empty_element_suffix = " />";
-				}
-			}
-			$text = $raw ? $text : Markdown($text);
-			$text;
-		},
-	});
-
-	# If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter:
-	my $smartypants;
-
-	{
-		no warnings "once";
-		$smartypants = $MT::Template::Context::Global_filters{'smarty_pants'};
-	}
-
-	if ($smartypants) {
-		MT->add_text_filter('markdown_with_smartypants' => {
-			label     => 'Markdown With SmartyPants',
-			docs      => 'http://daringfireball.net/projects/markdown/',
-			on_format => sub {
-				my $text = shift;
-				my $ctx  = shift;
-				if (defined $ctx) {
-					my $output = $ctx->stash('markdown_output'); 
-					if (defined $output  &&  $output eq 'html') {
-						$g_empty_element_suffix = ">";
-					}
-					else {
-						$g_empty_element_suffix = " />";
-					}
-				}
-				$text = Markdown($text);
-				$text = $smartypants->($text, '1');
-			},
-		});
-	}
-}
-else {
-#### BBEdit/command-line text filter interface ##########################
-# Needs to be hidden from MT (and Blosxom when running in static mode).
-
-    # We're only using $blosxom::version once; tell Perl not to warn us:
-	no warnings 'once';
-    unless ( defined($blosxom::version) ) {
-		use warnings;
-
-		#### Check for command-line switches: #################
-		my %cli_opts;
-		use Getopt::Long;
-		Getopt::Long::Configure('pass_through');
-		GetOptions(\%cli_opts,
-			'version',
-			'shortversion',
-			'html4tags',
-		);
-		if ($cli_opts{'version'}) {		# Version info
-			print "\nThis is Markdown, version $VERSION.\n";
-			print "Copyright 2004 John Gruber\n";
-			print "http://daringfireball.net/projects/markdown/\n\n";
-			exit 0;
-		}
-		if ($cli_opts{'shortversion'}) {		# Just the version number string.
-			print $VERSION;
-			exit 0;
-		}
-		if ($cli_opts{'html4tags'}) {			# Use HTML tag style instead of XHTML
-			$g_empty_element_suffix = ">";
-		}
-
-
-		#### Process incoming text: ###########################
-		my $text;
-		{
-			local $/;               # Slurp the whole file
-			$text = <>;
-		}
-        print Markdown($text);
-    }
-}
-
-
-
-sub Markdown {
-#
-# Main function. The order in which other subs are called here is
-# essential. Link and image substitutions need to happen before
-# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
-# and <img> tags get encoded.
-#
-	my $text = shift;
-
-	# Clear the global hashes. If we don't clear these, you get conflicts
-	# from other articles when generating a page which contains more than
-	# one article (e.g. an index page that shows the N most recent
-	# articles):
-	%g_urls = ();
-	%g_titles = ();
-	%g_html_blocks = ();
-
-
-	# Standardize line endings:
-	$text =~ s{\r\n}{\n}g; 	# DOS to Unix
-	$text =~ s{\r}{\n}g; 	# Mac to Unix
-
-	# Make sure $text ends with a couple of newlines:
-	$text .= "\n\n";
-
-	# Convert all tabs to spaces.
-	$text = _Detab($text);
-
-	# Strip any lines consisting only of spaces and tabs.
-	# This makes subsequent regexen easier to write, because we can
-	# match consecutive blank lines with /\n+/ instead of something
-	# contorted like /[ \t]*\n+/ .
-	$text =~ s/^[ \t]+$//mg;
-
-	# Turn block-level HTML blocks into hash entries
-	$text = _HashHTMLBlocks($text);
-
-	# Strip link definitions, store in hashes.
-	$text = _StripLinkDefinitions($text);
-
-	$text = _RunBlockGamut($text);
-
-	$text = _UnescapeSpecialChars($text);
-
-	return $text . "\n";
-}
-
-
-sub _StripLinkDefinitions {
-#
-# Strips link definitions from text, stores the URLs and titles in
-# hash references.
-#
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Link defs are in the form: ^[id]: url "optional title"
-	while ($text =~ s{
-						^[ ]{0,$less_than_tab}\[(.+)\]:	# id = $1
-						  [ \t]*
-						  \n?				# maybe *one* newline
-						  [ \t]*
-						<?(\S+?)>?			# url = $2
-						  [ \t]*
-						  \n?				# maybe one newline
-						  [ \t]*
-						(?:
-							(?<=\s)			# lookbehind for whitespace
-							["(]
-							(.+?)			# title = $3
-							[")]
-							[ \t]*
-						)?	# title is optional
-						(?:\n+|\Z)
-					}
-					{}mx) {
-		$g_urls{lc $1} = _EncodeAmpsAndAngles( $2 );	# Link IDs are case-insensitive
-		if ($3) {
-			$g_titles{lc $1} = $3;
-			$g_titles{lc $1} =~ s/"/"/g;
-		}
-	}
-
-	return $text;
-}
-
-
-sub _HashHTMLBlocks {
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Hashify HTML blocks:
-	# We only want to do this for block-level HTML tags, such as headers,
-	# lists, and tables. That's because we still want to wrap <p>s around
-	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
-	# phrase emphasis, and spans. The list of tags we're looking for is
-	# hard-coded:
-	my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/;
-	my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/;
-
-	# First, look for nested blocks, e.g.:
-	# 	<div>
-	# 		<div>
-	# 		tags for inner block must be indented.
-	# 		</div>
-	# 	</div>
-	#
-	# The outermost tags must start at the left margin for this to match, and
-	# the inner nested divs must be indented.
-	# We need to do this before the next, more liberal match, because the next
-	# match will start at the first `<div>` and stop at the first `</div>`.
-	$text =~ s{
-				(						# save in $1
-					^					# start of line  (with /m)
-					<($block_tags_a)	# start tag = $2
-					\b					# word break
-					(.*\n)*?			# any number of lines, minimally matching
-					</\2>				# the matching end tag
-					[ \t]*				# trailing spaces/tabs
-					(?=\n+|\Z)	# followed by a newline or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egmx;
-
-
-	#
-	# Now match more liberally, simply from `\n<tag>` to `</tag>\n`
-	#
-	$text =~ s{
-				(						# save in $1
-					^					# start of line  (with /m)
-					<($block_tags_b)	# start tag = $2
-					\b					# word break
-					(.*\n)*?			# any number of lines, minimally matching
-					.*</\2>				# the matching end tag
-					[ \t]*				# trailing spaces/tabs
-					(?=\n+|\Z)	# followed by a newline or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egmx;
-	# Special case just for <hr />. It was easier to make a special case than
-	# to make the other regex more complicated.	
-	$text =~ s{
-				(?:
-					(?<=\n\n)		# Starting after a blank line
-					|				# or
-					\A\n?			# the beginning of the doc
-				)
-				(						# save in $1
-					[ ]{0,$less_than_tab}
-					<(hr)				# start tag = $2
-					\b					# word break
-					([^<>])*?			# 
-					/?>					# the matching end tag/
-					[ \t]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egx;
-
-	# Special case for standalone HTML comments:
-	$text =~ s{
-				(?:
-					(?<=\n\n)		# Starting after a blank line
-					|				# or
-					\A\n?			# the beginning of the doc
-				)
-				(						# save in $1
-					[ ]{0,$less_than_tab}
-					(?s:
-						<!
-						(--.*?--\s*)+
-						>
-					)
-					[ \t]*
-					(?=\n{2,}|\Z)		# followed by a blank line or end of document
-				)
-			}{
-				my $key = md5_hex($1);
-				$g_html_blocks{$key} = $1;
-				"\n\n" . $key . "\n\n";
-			}egx;
-
-
-	return $text;
-}
-
-
-sub _RunBlockGamut {
-#
-# These are all the transformations that form block-level
-# tags like paragraphs, headers, and list items.
-#
-	my $text = shift;
-
-	$text = _DoHeaders($text);
-
-	# Do Horizontal Rules:
-	$text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-	$text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-	$text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
-
-	$text = _DoLists($text);
-
-	$text = _DoCodeBlocks($text);
-
-	$text = _DoBlockQuotes($text);
-
-	# We already ran _HashHTMLBlocks() before, in Markdown(), but that
-	# was to escape raw HTML in the original Markdown source. This time,
-	# we're escaping the markup we've just created, so that we don't wrap
-	# <p> tags around block-level tags.
-	$text = _HashHTMLBlocks($text);
-
-	$text = _FormParagraphs($text);
-
-	return $text;
-}
-
-
-sub _RunSpanGamut {
-#
-# These are all the transformations that occur *within* block-level
-# tags like paragraphs, headers, and list items.
-#
-	my $text = shift;
-
-	$text = _DoCodeSpans($text);
-
-	$text = _EscapeSpecialChars($text);
-
-	# Process anchor and image tags. Images must come first,
-	# because ![foo][f] looks like an anchor.
-	$text = _DoImages($text);
-	$text = _DoAnchors($text);
-
-	# Make links out of things like `<http://example.com/>`
-	# Must come after _DoAnchors(), because you can use < and >
-	# delimiters in inline links like [this](<url>).
-	$text = _DoAutoLinks($text);
-
-	$text = _EncodeAmpsAndAngles($text);
-
-	$text = _DoItalicsAndBold($text);
-
-	# Do hard breaks:
-	$text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g;
-
-	return $text;
-}
-
-
-sub _EscapeSpecialChars {
-	my $text = shift;
-	my $tokens ||= _TokenizeHTML($text);
-
-	$text = '';   # rebuild $text from the tokens
-# 	my $in_pre = 0;	 # Keep track of when we're inside <pre> or <code> tags.
-# 	my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!;
-
-	foreach my $cur_token (@$tokens) {
-		if ($cur_token->[0] eq "tag") {
-			# Within tags, encode * and _ so they don't conflict
-			# with their use in Markdown for italics and strong.
-			# We're replacing each such character with its
-			# corresponding MD5 checksum value; this is likely
-			# overkill, but it should prevent us from colliding
-			# with the escape values by accident.
-			$cur_token->[1] =~  s! \* !$g_escape_table{'*'}!gx;
-			$cur_token->[1] =~  s! _  !$g_escape_table{'_'}!gx;
-			$text .= $cur_token->[1];
-		} else {
-			my $t = $cur_token->[1];
-			$t = _EncodeBackslashEscapes($t);
-			$text .= $t;
-		}
-	}
-	return $text;
-}
-
-
-sub _DoAnchors {
-#
-# Turn Markdown link shortcuts into XHTML <a> tags.
-#
-	my $text = shift;
-
-	#
-	# First, handle reference-style links: [link text] [id]
-	#
-	$text =~ s{
-		(					# wrap whole match in $1
-		  \[
-		    ($g_nested_brackets)	# link text = $2
-		  \]
-
-		  [ ]?				# one optional space
-		  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-		  \[
-		    (.*?)		# id = $3
-		  \]
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $link_text   = $2;
-		my $link_id     = lc $3;
-
-		if ($link_id eq "") {
-			$link_id = lc $link_text;     # for shortcut links like [this][].
-		}
-
-		if (defined $g_urls{$link_id}) {
-			my $url = $g_urls{$link_id};
-			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-			$result = "<a href=\"$url\"";
-			if ( defined $g_titles{$link_id} ) {
-				my $title = $g_titles{$link_id};
-				$title =~ s! \* !$g_escape_table{'*'}!gx;
-				$title =~ s!  _ !$g_escape_table{'_'}!gx;
-				$result .=  " title=\"$title\"";
-			}
-			$result .= ">$link_text</a>";
-		}
-		else {
-			$result = $whole_match;
-		}
-		$result;
-	}xsge;
-
-	#
-	# Next, inline-style links: [link text](url "optional title")
-	#
-	$text =~ s{
-		(				# wrap whole match in $1
-		  \[
-		    ($g_nested_brackets)	# link text = $2
-		  \]
-		  \(			# literal paren
-		  	[ \t]*
-			<?(.*?)>?	# href = $3
-		  	[ \t]*
-			(			# $4
-			  (['"])	# quote char = $5
-			  (.*?)		# Title = $6
-			  \5		# matching quote
-			)?			# title is optional
-		  \)
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $link_text   = $2;
-		my $url	  		= $3;
-		my $title		= $6;
-
-		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-		$result = "<a href=\"$url\"";
-
-		if (defined $title) {
-			$title =~ s/"/"/g;
-			$title =~ s! \* !$g_escape_table{'*'}!gx;
-			$title =~ s!  _ !$g_escape_table{'_'}!gx;
-			$result .=  " title=\"$title\"";
-		}
-
-		$result .= ">$link_text</a>";
-
-		$result;
-	}xsge;
-
-	return $text;
-}
-
-
-sub _DoImages {
-#
-# Turn Markdown image shortcuts into <img> tags.
-#
-	my $text = shift;
-
-	#
-	# First, handle reference-style labeled images: ![alt text][id]
-	#
-	$text =~ s{
-		(				# wrap whole match in $1
-		  !\[
-		    (.*?)		# alt text = $2
-		  \]
-
-		  [ ]?				# one optional space
-		  (?:\n[ ]*)?		# one optional newline followed by spaces
-
-		  \[
-		    (.*?)		# id = $3
-		  \]
-
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $alt_text    = $2;
-		my $link_id     = lc $3;
-
-		if ($link_id eq "") {
-			$link_id = lc $alt_text;     # for shortcut links like ![this][].
-		}
-
-		$alt_text =~ s/"/"/g;
-		if (defined $g_urls{$link_id}) {
-			my $url = $g_urls{$link_id};
-			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-			$result = "<img src=\"$url\" alt=\"$alt_text\"";
-			if (defined $g_titles{$link_id}) {
-				my $title = $g_titles{$link_id};
-				$title =~ s! \* !$g_escape_table{'*'}!gx;
-				$title =~ s!  _ !$g_escape_table{'_'}!gx;
-				$result .=  " title=\"$title\"";
-			}
-			$result .= $g_empty_element_suffix;
-		}
-		else {
-			# If there's no such link ID, leave intact:
-			$result = $whole_match;
-		}
-
-		$result;
-	}xsge;
-
-	#
-	# Next, handle inline images:  ![alt text](url "optional title")
-	# Don't forget: encode * and _
-
-	$text =~ s{
-		(				# wrap whole match in $1
-		  !\[
-		    (.*?)		# alt text = $2
-		  \]
-		  \(			# literal paren
-		  	[ \t]*
-			<?(\S+?)>?	# src url = $3
-		  	[ \t]*
-			(			# $4
-			  (['"])	# quote char = $5
-			  (.*?)		# title = $6
-			  \5		# matching quote
-			  [ \t]*
-			)?			# title is optional
-		  \)
-		)
-	}{
-		my $result;
-		my $whole_match = $1;
-		my $alt_text    = $2;
-		my $url	  		= $3;
-		my $title		= '';
-		if (defined($6)) {
-			$title		= $6;
-		}
-
-		$alt_text =~ s/"/"/g;
-		$title    =~ s/"/"/g;
-		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
-		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
-		$result = "<img src=\"$url\" alt=\"$alt_text\"";
-		if (defined $title) {
-			$title =~ s! \* !$g_escape_table{'*'}!gx;
-			$title =~ s!  _ !$g_escape_table{'_'}!gx;
-			$result .=  " title=\"$title\"";
-		}
-		$result .= $g_empty_element_suffix;
-
-		$result;
-	}xsge;
-
-	return $text;
-}
-
-
-sub _DoHeaders {
-	my $text = shift;
-
-	# Setext-style headers:
-	#	  Header 1
-	#	  ========
-	#  
-	#	  Header 2
-	#	  --------
-	#
-	$text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{
-		"<h1>"  .  _RunSpanGamut($1)  .  "</h1>\n\n";
-	}egmx;
-
-	$text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{
-		"<h2>"  .  _RunSpanGamut($1)  .  "</h2>\n\n";
-	}egmx;
-
-
-	# atx-style headers:
-	#	# Header 1
-	#	## Header 2
-	#	## Header 2 with closing hashes ##
-	#	...
-	#	###### Header 6
-	#
-	$text =~ s{
-			^(\#{1,6})	# $1 = string of #'s
-			[ \t]*
-			(.+?)		# $2 = Header text
-			[ \t]*
-			\#*			# optional closing #'s (not counted)
-			\n+
-		}{
-			my $h_level = length($1);
-			"<h$h_level>"  .  _RunSpanGamut($2)  .  "</h$h_level>\n\n";
-		}egmx;
-
-	return $text;
-}
-
-
-sub _DoLists {
-#
-# Form HTML ordered (numbered) and unordered (bulleted) lists.
-#
-	my $text = shift;
-	my $less_than_tab = $g_tab_width - 1;
-
-	# Re-usable patterns to match list item bullets and number markers:
-	my $marker_ul  = qr/[*+-]/;
-	my $marker_ol  = qr/\d+[.]/;
-	my $marker_any = qr/(?:$marker_ul|$marker_ol)/;
-
-	# Re-usable pattern to match any entirel ul or ol list:
-	my $whole_list = qr{
-		(								# $1 = whole list
-		  (								# $2
-			[ ]{0,$less_than_tab}
-			(${marker_any})				# $3 = first list item marker
-			[ \t]+
-		  )
-		  (?s:.+?)
-		  (								# $4
-			  \z
-			|
-			  \n{2,}
-			  (?=\S)
-			  (?!						# Negative lookahead for another list item marker
-				[ \t]*
-				${marker_any}[ \t]+
-			  )
-		  )
-		)
-	}mx;
-
-	# We use a different prefix before nested lists than top-level lists.
-	# See extended comment in _ProcessListItems().
-	#
-	# Note: There's a bit of duplication here. My original implementation
-	# created a scalar regex pattern as the conditional result of the test on
-	# $g_list_level, and then only ran the $text =~ s{...}{...}egmx
-	# substitution once, using the scalar as the pattern. This worked,
-	# everywhere except when running under MT on my hosting account at Pair
-	# Networks. There, this caused all rebuilds to be killed by the reaper (or
-	# perhaps they crashed, but that seems incredibly unlikely given that the
-	# same script on the same server ran fine *except* under MT. I've spent
-	# more time trying to figure out why this is happening than I'd like to
-	# admit. My only guess, backed up by the fact that this workaround works,
-	# is that Perl optimizes the substition when it can figure out that the
-	# pattern will never change, and when this optimization isn't on, we run
-	# afoul of the reaper. Thus, the slightly redundant code to that uses two
-	# static s/// patterns rather than one conditional pattern.
-
-	if ($g_list_level) {
-		$text =~ s{
-				^
-				$whole_list
-			}{
-				my $list = $1;
-				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
-				# Turn double returns into triple returns, so that we can make a
-				# paragraph for the last item in a list, if necessary:
-				$list =~ s/\n{2,}(?! {8,})/\n\n\n/g;
-				my $result = _ProcessListItems($list, $marker_any);
-				$result = "<$list_type>\n" . $result . "</$list_type>\n";
-				$result;
-			}egmx;
-	}
-	else {
-		$text =~ s{
-				(?:(?<=\n\n)|\A\n?)
-				$whole_list
-			}{
-				my $list = $1;
-				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
-				# Turn double returns into triple returns, so that we can make a
-				# paragraph for the last item in a list, if necessary:
-				$list =~ s/\n{2,}(?! {8,})/\n\n\n/g;
-				my $result = _ProcessListItems($list, $marker_any);
-				$result = "<$list_type>\n" . $result . "</$list_type>\n";
-				$result;
-			}egmx;
-	}
-
-
-	return $text;
-}
-
-
-sub _ProcessListItems {
-#
-#	Process the contents of a single ordered or unordered list, splitting it
-#	into individual list items.
-#
-
-	my $list_str = shift;
-	my $marker_any = shift;
-
-
-	# The $g_list_level global keeps track of when we're inside a list.
-	# Each time we enter a list, we increment it; when we leave a list,
-	# we decrement. If it's zero, we're not in a list anymore.
-	#
-	# We do this because when we're not inside a list, we want to treat
-	# something like this:
-	#
-	#		I recommend upgrading to version
-	#		8. Oops, now this line is treated
-	#		as a sub-list.
-	#
-	# As a single paragraph, despite the fact that the second line starts
-	# with a digit-period-space sequence.
-	#
-	# Whereas when we're inside a list (or sub-list), that line will be
-	# treated as the start of a sub-list. What a kludge, huh? This is
-	# an aspect of Markdown's syntax that's hard to parse perfectly
-	# without resorting to mind-reading. Perhaps the solution is to
-	# change the syntax rules such that sub-lists must start with a
-	# starting cardinal number; e.g. "1." or "a.".
-
-	$g_list_level++;
-
-	# trim trailing blank lines:
-	$list_str =~ s/\n{2,}\z/\n/;
-
-
-	$list_str =~ s{
-		(\n)?							# leading line = $1
-		(^[ \t]*)						# leading whitespace = $2
-		($marker_any) [ \t]+			# list marker = $3
-		((?s:.+?)						# list item text   = $4
-		(\n{1,2}))
-		(?= \n* (\z | \2 ($marker_any) [ \t]+))
-	}{
-		my $item = $4;
-		my $leading_line = $1;
-		my $leading_space = $2;
-
-		if ($leading_line or ($item =~ m/\n{2,}/)) {
-			$item = _RunBlockGamut(_Outdent($item));
-		}
-		else {
-			# Recursion for sub-lists:
-			$item = _DoLists(_Outdent($item));
-			chomp $item;
-			$item = _RunSpanGamut($item);
-		}
-
-		"<li>" . $item . "</li>\n";
-	}egmx;
-
-	$g_list_level--;
-	return $list_str;
-}
-
-
-
-sub _DoCodeBlocks {
-#
-#	Process Markdown `<pre><code>` blocks.
-#	
-
-	my $text = shift;
-
-	$text =~ s{
-			(?:\n\n|\A)
-			(	            # $1 = the code block -- one or more lines, starting with a space/tab
-			  (?:
-			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
-			    .*\n+
-			  )+
-			)
-			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
-		}{
-			my $codeblock = $1;
-			my $result; # return value
-
-			$codeblock = _EncodeCode(_Outdent($codeblock));
-			$codeblock = _Detab($codeblock);
-			$codeblock =~ s/\A\n+//; # trim leading newlines
-			$codeblock =~ s/\s+\z//; # trim trailing whitespace
-
-			$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
-
-			$result;
-		}egmx;
-
-	return $text;
-}
-
-
-sub _DoCodeSpans {
-#
-# 	*	Backtick quotes are used for <code></code> spans.
-# 
-# 	*	You can use multiple backticks as the delimiters if you want to
-# 		include literal backticks in the code span. So, this input:
-#     
-#         Just type ``foo `bar` baz`` at the prompt.
-#     
-#     	Will translate to:
-#     
-#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
-#     
-#		There's no arbitrary limit to the number of backticks you
-#		can use as delimters. If you need three consecutive backticks
-#		in your code, use four for delimiters, etc.
-#
-#	*	You can use spaces to get literal backticks at the edges:
-#     
-#         ... type `` `bar` `` ...
-#     
-#     	Turns to:
-#     
-#         ... type <code>`bar`</code> ...
-#
-
-	my $text = shift;
-
-	$text =~ s@
-			(`+)		# $1 = Opening run of `
-			(.+?)		# $2 = The code block
-			(?<!`)
-			\1			# Matching closer
-			(?!`)
-		@
- 			my $c = "$2";
- 			$c =~ s/^[ \t]*//g; # leading whitespace
- 			$c =~ s/[ \t]*$//g; # trailing whitespace
- 			$c = _EncodeCode($c);
-			"<code>$c</code>";
-		@egsx;
-
-	return $text;
-}
-
-
-sub _EncodeCode {
-#
-# Encode/escape certain characters inside Markdown code runs.
-# The point is that in code, these characters are literals,
-# and lose their special Markdown meanings.
-#
-    local $_ = shift;
-
-	# Encode all ampersands; HTML entities are not
-	# entities within a Markdown code span.
-	s/&/&/g;
-
-	# Encode $'s, but only if we're running under Blosxom.
-	# (Blosxom interpolates Perl variables in article bodies.)
-	{
-		no warnings 'once';
-    	if (defined($blosxom::version)) {
-    		s/\$/$/g;	
-    	}
-    }
-
-
-	# Do the angle bracket song and dance:
-	s! <  !<!gx;
-	s! >  !>!gx;
-
-	# Now, escape characters that are magic in Markdown:
-	s! \* !$g_escape_table{'*'}!gx;
-	s! _  !$g_escape_table{'_'}!gx;
-	s! {  !$g_escape_table{'{'}!gx;
-	s! }  !$g_escape_table{'}'}!gx;
-	s! \[ !$g_escape_table{'['}!gx;
-	s! \] !$g_escape_table{']'}!gx;
-	s! \\ !$g_escape_table{'\\'}!gx;
-
-	return $_;
-}
-
-
-sub _DoItalicsAndBold {
-	my $text = shift;
-
-	# <strong> must go first:
-	$text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 }
-		{<strong>$2</strong>}gsx;
-
-	$text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }
-		{<em>$2</em>}gsx;
-
-	return $text;
-}
-
-
-sub _DoBlockQuotes {
-	my $text = shift;
-
-	$text =~ s{
-		  (								# Wrap whole match in $1
-			(
-			  ^[ \t]*>[ \t]?			# '>' at the start of a line
-			    .+\n					# rest of the first line
-			  (.+\n)*					# subsequent consecutive lines
-			  \n*						# blanks
-			)+
-		  )
-		}{
-			my $bq = $1;
-			$bq =~ s/^[ \t]*>[ \t]?//gm;	# trim one level of quoting
-			$bq =~ s/^[ \t]+$//mg;			# trim whitespace-only lines
-			$bq = _RunBlockGamut($bq);		# recurse
-
-			$bq =~ s/^/  /g;
-			# These leading spaces screw with <pre> content, so we need to fix that:
-			$bq =~ s{
-					(\s*<pre>.+?</pre>)
-				}{
-					my $pre = $1;
-					$pre =~ s/^  //mg;
-					$pre;
-				}egsx;
-
-			"<blockquote>\n$bq\n</blockquote>\n\n";
-		}egmx;
-
-
-	return $text;
-}
-
-
-sub _FormParagraphs {
-#
-#	Params:
-#		$text - string to process with html <p> tags
-#
-	my $text = shift;
-
-	# Strip leading and trailing lines:
-	$text =~ s/\A\n+//;
-	$text =~ s/\n+\z//;
-
-	my @grafs = split(/\n{2,}/, $text);
-
-	#
-	# Wrap <p> tags.
-	#
-	foreach (@grafs) {
-		unless (defined( $g_html_blocks{$_} )) {
-			$_ = _RunSpanGamut($_);
-			s/^([ \t]*)/<p>/;
-			$_ .= "</p>";
-		}
-	}
-
-	#
-	# Unhashify HTML blocks
-	#
-	foreach (@grafs) {
-		if (defined( $g_html_blocks{$_} )) {
-			$_ = $g_html_blocks{$_};
-		}
-	}
-
-	return join "\n\n", @grafs;
-}
-
-
-sub _EncodeAmpsAndAngles {
-# Smart processing for ampersands and angle brackets that need to be encoded.
-
-	my $text = shift;
-
-	# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
-	#   http://bumppo.net/projects/amputator/
- 	$text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g;
-
-	# Encode naked <'s
- 	$text =~ s{<(?![a-z/?\$!])}{<}gi;
-
-	return $text;
-}
-
-
-sub _EncodeBackslashEscapes {
-#
-#   Parameter:  String.
-#   Returns:    The string, with after processing the following backslash
-#               escape sequences.
-#
-    local $_ = shift;
-
-    s! \\\\  !$g_escape_table{'\\'}!gx;		# Must process escaped backslashes first.
-    s! \\`   !$g_escape_table{'`'}!gx;
-    s! \\\*  !$g_escape_table{'*'}!gx;
-    s! \\_   !$g_escape_table{'_'}!gx;
-    s! \\\{  !$g_escape_table{'{'}!gx;
-    s! \\\}  !$g_escape_table{'}'}!gx;
-    s! \\\[  !$g_escape_table{'['}!gx;
-    s! \\\]  !$g_escape_table{']'}!gx;
-    s! \\\(  !$g_escape_table{'('}!gx;
-    s! \\\)  !$g_escape_table{')'}!gx;
-    s! \\>   !$g_escape_table{'>'}!gx;
-    s! \\\#  !$g_escape_table{'#'}!gx;
-    s! \\\+  !$g_escape_table{'+'}!gx;
-    s! \\\-  !$g_escape_table{'-'}!gx;
-    s! \\\.  !$g_escape_table{'.'}!gx;
-    s{ \\!  }{$g_escape_table{'!'}}gx;
-
-    return $_;
-}
-
-
-sub _DoAutoLinks {
-	my $text = shift;
-
-	$text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi;
-
-	# Email addresses: <address at domain.foo>
-	$text =~ s{
-		<
-        (?:mailto:)?
-		(
-			[-.\w]+
-			\@
-			[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
-		)
-		>
-	}{
-		_EncodeEmailAddress( _UnescapeSpecialChars($1) );
-	}egix;
-
-	return $text;
-}
-
-
-sub _EncodeEmailAddress {
-#
-#	Input: an email address, e.g. "foo at example.com"
-#
-#	Output: the email address as a mailto link, with each character
-#		of the address encoded as either a decimal or hex entity, in
-#		the hopes of foiling most address harvesting spam bots. E.g.:
-#
-#	  <a href="&#x6D;ail&#x74;o:foo@e
-#       x&#x61;m&#x70;l&#x65;&#x2E;com">foo
-#       @ex&#x61;m&#x70;l&#x65;&#x2E;com</a>
-#
-#	Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
-#	mailing list: <http://tinyurl.com/yu7ue>
-#
-
-	my $addr = shift;
-
-	srand;
-	my @encode = (
-		sub { '&#' .                 ord(shift)   . ';' },
-		sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' },
-		sub {                            shift          },
-	);
-
-	$addr = "mailto:" . $addr;
-
-	$addr =~ s{(.)}{
-		my $char = $1;
-		if ( $char eq '@' ) {
-			# this *must* be encoded. I insist.
-			$char = $encode[int rand 1]->($char);
-		} elsif ( $char ne ':' ) {
-			# leave ':' alone (to spot mailto: later)
-			my $r = rand;
-			# roughly 10% raw, 45% hex, 45% dec
-			$char = (
-				$r > .9   ?  $encode[2]->($char)  :
-				$r < .45  ?  $encode[1]->($char)  :
-							 $encode[0]->($char)
-			);
-		}
-		$char;
-	}gex;
-
-	$addr = qq{<a href="$addr">$addr</a>};
-	$addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part
-
-	return $addr;
-}
-
-
-sub _UnescapeSpecialChars {
-#
-# Swap back in all the special characters we've hidden.
-#
-	my $text = shift;
-
-	while( my($char, $hash) = each(%g_escape_table) ) {
-		$text =~ s/$hash/$char/g;
-	}
-    return $text;
-}
-
-
-sub _TokenizeHTML {
-#
-#   Parameter:  String containing HTML markup.
-#   Returns:    Reference to an array of the tokens comprising the input
-#               string. Each token is either a tag (possibly with nested,
-#               tags contained therein, such as <a href="<MTFoo>">, or a
-#               run of text between tags. Each element of the array is a
-#               two-element array; the first is either 'tag' or 'text';
-#               the second is the actual value.
-#
-#
-#   Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin.
-#       <http://www.bradchoate.com/past/mtregex.php>
-#
-
-    my $str = shift;
-    my $pos = 0;
-    my $len = length $str;
-    my @tokens;
-
-    my $depth = 6;
-    my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x  $depth);
-    my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) |  # comment
-                   (?s: <\? .*? \?> ) |              # processing instruction
-                   $nested_tags/ix;                   # nested tags
-
-    while ($str =~ m/($match)/g) {
-        my $whole_tag = $1;
-        my $sec_start = pos $str;
-        my $tag_start = $sec_start - length $whole_tag;
-        if ($pos < $tag_start) {
-            push @tokens, ['text', substr($str, $pos, $tag_start - $pos)];
-        }
-        push @tokens, ['tag', $whole_tag];
-        $pos = pos $str;
-    }
-    push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len;
-    \@tokens;
-}
-
-
-sub _Outdent {
-#
-# Remove one level of line-leading tabs or spaces
-#
-	my $text = shift;
-
-	$text =~ s/^(\t|[ ]{1,$g_tab_width})//gm;
-	return $text;
-}
-
-
-sub _Detab {
-#
-# Cribbed from a post by Bart Lateur:
-# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
-#
-	my $text = shift;
-
-	$text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge;
-	return $text;
-}
-
-
-1;
-
-__END__
-
-
-=pod
-
-=head1 NAME
-
-B<Markdown>
-
-
-=head1 SYNOPSIS
-
-B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ]
-    [ I<file> ... ]
-
-
-=head1 DESCRIPTION
-
-Markdown is a text-to-HTML filter; it translates an easy-to-read /
-easy-to-write structured text format into HTML. Markdown's text format
-is most similar to that of plain text email, and supports features such
-as headers, *emphasis*, code blocks, blockquotes, and links.
-
-Markdown's syntax is designed not as a generic markup language, but
-specifically to serve as a front-end to (X)HTML. You can  use span-level
-HTML tags anywhere in a Markdown document, and you can use block level
-HTML tags (like <div> and <table> as well).
-
-For more information about Markdown's syntax, see:
-
-    http://daringfireball.net/projects/markdown/
-
-
-=head1 OPTIONS
-
-Use "--" to end switch parsing. For example, to open a file named "-z", use:
-
-	Markdown.pl -- -z
-
-=over 4
-
-
-=item B<--html4tags>
-
-Use HTML 4 style for empty element tags, e.g.:
-
-    <br>
-
-instead of Markdown's default XHTML style tags, e.g.:
-
-    <br />
-
-
-=item B<-v>, B<--version>
-
-Display Markdown's version number and copyright information.
-
-
-=item B<-s>, B<--shortversion>
-
-Display the short-form version number.
-
-
-=back
-
-
-
-=head1 BUGS
-
-To file bug reports or feature requests (other than topics listed in the
-Caveats section above) please send email to:
-
-    support at daringfireball.net
-
-Please include with your report: (1) the example input; (2) the output
-you expected; (3) the output Markdown actually produced.
-
-
-=head1 VERSION HISTORY
-
-See the readme file for detailed release notes for this version.
-
-1.0.1 - 14 Dec 2004
-
-1.0 - 28 Aug 2004
-
-
-=head1 AUTHOR
-
-    John Gruber
-    http://daringfireball.net
-
-    PHP port and other contributions by Michel Fortin
-    http://michelf.com
-
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (c) 2003-2004 John Gruber   
-<http://daringfireball.net/>   
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright notice,
-  this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-* Neither the name "Markdown" nor the names of its contributors may
-  be used to endorse or promote products derived from this software
-  without specific prior written permission.
-
-This software is provided by the copyright holders and contributors "as
-is" and any express or implied warranties, including, but not limited
-to, the implied warranties of merchantability and fitness for a
-particular purpose are disclaimed. In no event shall the copyright owner
-or contributors be liable for any direct, indirect, incidental, special,
-exemplary, or consequential damages (including, but not limited to,
-procurement of substitute goods or services; loss of use, data, or
-profits; or business interruption) however caused and on any theory of
-liability, whether in contract, strict liability, or tort (including
-negligence or otherwise) arising in any way out of the use of this
-software, even if advised of the possibility of such damage.
-
-=cut
diff --git a/doc/WARNINGS.mkd b/doc/WARNINGS.mkd
deleted file mode 100644
index dc364de..0000000
--- a/doc/WARNINGS.mkd
+++ /dev/null
@@ -1,72 +0,0 @@
-# WARNINGS
-
-Gitolite does NOT like it if you fiddle with files, directories, permissions,
-etc., on the server except as directed in the documentation.  Gitolite also
-expects all the directories and files it manages/uses to be owned by the
-hosting user and not have strange permissions and ownerships.
-
-Very few people have fallen foul of this, but the ones who *did* became very
-obnoxious about it, hence this warning.
-
-Gitolite depends on several system-installed packages: openssh, git, perl, sh
-being the main ones.  They should all be configured sensibly and with most of
-the normal defaults.  (For example, if your sshd config says the authorized
-keys file should be placed in some directory other than the default, expect
-trouble).
-
-----
-
-For the entertainment of the sensible majority, and as a way of thanking all
-of you, here are some examples of requests (demands in some cases) I have
-received over the last couple of years.
-
-  * deleting environment variables copied from client session
-
-    demand: add code to delete certain environment variables at startup
-    because "the openssh servers in the linux distribution that [he] use[s],
-    are configured to copy `GIT_*` variables to the remote session".
-
-    This is wrong on so many levels it's almost plonk-able!
-
-  * using `cp` instead of `ln`
-
-    Guy has an NTFS file system mounted on Linux.  So... no symlinks (an NTFS
-    file system on Windows works fine because msysgit/cygwin manage to
-    *simulate* them.  NTFS mounted on Linux won't do that!)
-
-    He wanted all the symlink stuff to be replaced by copies.
-
-    No. Way.
-
-  * non-bare repos on the server
-
-    Some guy gave me a complicated spiel about git-svn not liking bare repos
-    or whatever.  I tuned off at the first mention of those 3 letters so I
-    don't really know what the actual problem was.
-
-    But it doesn't matter.  Even if someone (Ralf H) had not chipped in with a
-    workable solution, I still would not do it.  A server repo should be bare.
-    Period.
-
-  * incomplete ownership of `GL_REPO_BASE`
-
-    This guy had a repo-base directory where not all of the files were owned
-    by the git user.  As a result, some of the hooks did not get created.  He
-    claimed my code should detect OS-permissions issues while it's doing its
-    stuff.
-
-    No.  I refuse to have the code constantly look over its shoulder making
-    sure fundamental assumptions are being met.
-
-  * empty template directory
-
-    (See man git-init for what a template directory is).
-
-    The same guy with the environment variables had an empty template
-    directory because he "does not like to have sample hooks in every
-    repository".  So naturally, the hooks directory does not get created when
-    you run a `git init`.  He expects gitolite to compensate for it.
-
-    Granted, it's only a 1-line change.  But again, this falls under
-    "constantly looking over your shoulder to double check fundamental
-    assumptions".  Where does it end?
diff --git a/doc/admin.mkd b/doc/admin.mkd
deleted file mode 100644
index 47c3aeb..0000000
--- a/doc/admin.mkd
+++ /dev/null
@@ -1,50 +0,0 @@
-# gitolite administration
-
-## #server server-side administration
-
-The following activities require command line access to the server.  They are
-usually one-time or rarely done activities.
-
-  * Changing anything in the [rc][] file.
-  * Installing custom [hooks][], whether to all repos or just some repos.
-  * Moving [existing][] repos into gitolite control.
-
-Please read the [WARNINGS][] page first.
-
-## #adminrepo access control via the gitolite-admin repo
-
-Most day-to-day administration of a gitolite site happens like this:
-
-  * [clone][] the gitolite-admin repo to your workstation
-  * make appropriate changes
-  * add, commit, and push
-
-### #conf the conf/gitolite.conf file
-
-Most of gitolite's power is in the conf/gitolite.conf file, which specifies
-detailed access control for repos.  Everything except [adding users][users]
-happens from this file.
-
-Here is an example of a simple conf/gitolite.conf file.
-
-        @staff              =   dilbert alice           # line 1
-        @projects           =   foo bar                 # line 2
-
-        repo @projects baz                              # line 3
-            RW+             =   @staff                  # line 4
-            -       master  =   ashok                   # line 5
-            RW              =   ashok                   # line 6
-            R               =   wally                   # line 7
-
-            config hooks.emailprefix = '[%GL_REPO] '    # line 8
-
-Use the following links to learn more:
-
-  * The basic [syntax][] -- comments, whitespace, include files, etc.
-  * Defining [groups][], as in lines 1 and 2.
-  * Adding and removing [users][].
-  * Adding and removing [repos][], as in line 3.
-  * Defining access [rules][], as in lines 4, 5, 6, and 7.
-  * Gitolite [options][].
-  * [Git config][git-config] keys and values, as in line 8.
-  * ["Wild"][wild] repos -- ad hoc, user-created, repos.
diff --git a/doc/clone.mkd b/doc/clone.mkd
deleted file mode 100644
index f51c2d0..0000000
--- a/doc/clone.mkd
+++ /dev/null
@@ -1,19 +0,0 @@
-# cloning the admin repo
-
-This is the third step in using gitolite, after [install][] and [setup][].
-
-To clone the admin repo, go to the workstation where the public key used in
-'setup' came from, and run this:
-
-    git clone git at host:gitolite-admin
-
-NOTE that (1) you must not include the `repositories/` part (gitolite handles
-that internally), and (2) you may include the ".git" at the end but it is
-optional.
-
-If this step succeeds, you can add [users][], [repos][], or anything else
-described [here][adminrepo].
-
-If this step fails, be sure to look at the [ssh][] documentation before asking
-for help.  (A very basic first step is to run `ssh git at host info`;
-[this][info] page tells you what to expect).
diff --git a/doc/cust.mkd b/doc/cust.mkd
deleted file mode 100644
index 8c7607b..0000000
--- a/doc/cust.mkd
+++ /dev/null
@@ -1,154 +0,0 @@
-# customising gitolite
-
-Much of gitolite (g3)'s functionality comes from programs and scripts that are
-not considered "core".  This keeps the core simpler, and allows you to enhance
-gitolite for your own purposes without too much fuss.  (As an extreme example,
-even mirroring is not in core now!)
-
-This document will tell you about the types of non-core programs, and
-how/where to install your own.  (Actually *writing* the code is described in
-the [developer notes][dev-notes] page).
-
-----
-
-[[TOC]]
-
-----
-
-## introduction
-
-There are 5 basic types of non-core programs.
-
-  * *Commands* can be run from the shell command line.  Among those, the ones
-    listed in the COMMANDS hash of the rc file can also be run remotely.
-  * *Hooks* are standard git hooks.
-  * *Sugar scripts* change the conf language for your convenience.  The word
-    sugar comes from "syntactic sugar".
-  * *Triggers* are to gitolite what hooks are to git.  I just chose a
-    different name to avoid confusion and constant disambiguation in the docs.
-  * **VREFs** are extensions to the access control check part of gitolite.
-
-[Here][non-core] is a list of non-core programs shipped with gitolite, with
-some description of each.
-
-## locations
-
-### default/primary location of non-core programs
-
-Regardless of how you installed gitolite, `gitolite query-rc GL_BINDIR` will
-tell you where the programs reside.  Within that directory, the locations of
-non-core programs are:
-
-  * `commands` for commands.
-  * `syntactic-sugar` for sugar scripts.
-  * `triggers` and `lib/Gitolite/Triggers` for triggers ([this][triggers] will
-    explain the difference).
-  * `VREF` for [VREFs][vref].
-
-### #localcode alternate location -- the `LOCAL_CODE` rc variable
-
-If you want to add new non-core programs to your installation, or override the
-shipped non-core programs with your own versions, it's easy enough to simply
-copy your programs to the appropriate directory above, but then they'd get
-wiped out on the next upgrade.
-
-A simple, "git-ish", method is to maintain a "local" branch in your clone of
-the gitolite source repo and make your changes there.  Maintain them using
-rebase or merge when you 'git pull' gitolite itself, then use the rebased or
-merged "local" as the source for your gitolite upgrades.  Works very nicely,
-and uses nothing but your git knowledge.
-
-Sadly, it doesn't work for people installing from RPMs/DEBs; their "primary
-location" has already been setup, so any site-local customisations have to be
-done elsewhere.
-
-This is where `LOCAL_CODE` comes in.  If you define the `LOCAL_CODE` rc
-variable, then its value (**please use a FULL path**) describes a location
-where you can have any or all of these subdirectories:
-
-  * `commands`
-  * `hooks/common`
-  * `syntactic-sugar`
-  * `triggers` and `lib/Gitolite/Triggers`
-  * `VREF`
-
-You might have noticed there's a new `hooks/common` directory here so you can
-add hooks also using this mechanism.  Unlike the rest of the directories,
-adding new hooks to `hooks/common` requires that you follow up with `gitolite
-setup`, or at least `gitolite setup --hooks-only`.
-
-### #pushcode managing custom code via the gitolite-admin repo
-
-The location given in `LOCAL_CODE` could be anywhere on disk.
-
-However, if you point it to someplace inside `$GL_ADMIN_BASE` (i.e.,
-`$HOME/.gitolite`), then you can version those programs using the
-gitolite-admin repo.
-
-I suggest using a directory called "local-code" within the gitolite-admin repo
-that contains as much of the above directory structure you need.  If you do
-that, then this is what you'd have in the rc file:
-
-    LOCAL_CODE                  =>  "$ENV{HOME}/.gitolite/local-code",
-
-When you do this, gitolite takes care of everything automatically, including
-running `gitolite setup --hooks-only` when you change any hooks and push.
-**However, if you do this, anyone who can push changes to the admin repo will
-effectively be able to run any arbitrary command on the server.**
-
-## types of non-core programs
-
-### #commands gitolite "commands"
-
-Gitolite comes with several commands that users can run.  Remote users run
-commands by saying:
-
-    ssh git at host command-name [args...]
-
-while on the server you can run
-
-    gitolite command [args...]
-
-Very few commands are designed to be run both ways, but it can be done, by
-checking for the presence of env var `GL_USER`.
-
-You can get a **list of available commands** by using the `help` command.
-Naturally, a remote user will see a much smaller list than the server user.
-
-You allow a command to be run from remote clients by adding its name to (or
-uncommenting it if it's already added but commented out) the COMMANDS hash in
-the [rc][] file.
-
-### #hooks hooks and gitolite
-
-You can install any hooks except these:
-
-  * (all repos) gitolite reserves the `update` hook.  See the "update hook"
-    section in [dev-notes][] if you want additional update hook functionality.
-
-  * (gitolite-admin repo only) gitolite reserves the `post-update` hook.
-
-How/where to install them is described in detail in the "locations" section
-above, especially [this][localcode] and [this][pushcode].  The summary is that
-you put them in the "hooks/common" sub-directory within the directory whose
-name is given in the `LOCAL_CODE` rc variable.
-
-### #sugar syntactic sugar
-
-Sugar scripts help you change the perceived syntax of the conf language.  The
-base syntax of the language is very simple, so sugar scripts take something
-*else* and convert it into that.
-
-That way, the admin sees additional features (like allowing continuation
-lines), while the parser in the core gitolite engine does not change.
-
-If you want to write your own sugar scripts, please read the "your own sugar"
-section in [dev-notes][] first then email me.
-
-### triggers
-
-Triggers have their own [document][triggers].
-
-### VREFs
-
-VREFs also have their own [document][vref].
diff --git a/doc/deleg.mkd b/doc/deleg.mkd
deleted file mode 100644
index f693432..0000000
--- a/doc/deleg.mkd
+++ /dev/null
@@ -1,118 +0,0 @@
-# delegating access control responsibilities
-
-Delegation allows you to divide up a large conf file into smaller groups of
-repos (called **subconf**s) and hand over responsibility to manage them to
-**sub-admin**s.  Gitolite can prevent one sub-admin from being able to set
-access rules for any other sub-admin's repos.
-
-Delegation is achieved by combining two gitolite features: [subconf][] and the
-[NAME VREF][NAME].
-
-To understand delegation, read both those links then come back to this
-example.
-
-## example
-
-    @webbrowsers        = firefox lynx browsers/..*
-    @webservers         = apache nginx servers/..*
-    @malwares           = conficker storm ms/..*
-        # side note: if anyone objects, we claim ms stands for "metasploit" ;-)
-
-    # the admin repo access was probably like this to start with:
-    repo gitolite-admin
-        RW+                                     = sitaram
-    # now add these lines to the config for the admin repo
-        RW                                      = alice bob mallory
-        RW  VREF/NAME/conf/subs/webbrowsers     = alice
-        RW  VREF/NAME/conf/subs/webservers      = bob
-        RW  VREF/NAME/conf/subs/malwares        = mallory
-        -   VREF/NAME/                          = alice bob mallory
-
-Finally, you tell gitolite to pull in these files using the "subconf" command
-
-    subconf "subs/*.conf"
-
-And that's it.
-
-## #subconf the subconf command
-
-Subconf is exactly like the include command in syntax:
-
-    subconf "foo.conf"
-
-but while reading the included file (as well as anything included from it),
-gitolite sets the "subconf name" to "foo".
-
-A "subconf" imposes some restrictions on what repos can be managed.
-
-For example, while the subconf name is "foo", as in the above example,
-gitolite will only process "repo" lines for:
-
-  * A repo called "foo".
-  * A group called "@foo", as long as the group is defined in the main conf
-    file (i.e., *outside* "foo.conf").
-  * A member of a group called "@foo" (again, defined outside).
-  * A repo that matches a member of a group called "@foo" if that member is a
-    regular expression pattern.
-
-Here's an example.  If the main conf file contains
-
-    @foo    =   aa bb cc/..*
-
-then the subconf can only accept repo statements that refer to 'foo', '@foo',
-'aa', 'bb', or any repo whose name starts with 'cc/'.
-
-**Note**: the subconf name "master" is special; it is the default subconf in
-effect for the main conf file and has no restrictions.
-
-### how the "subconf name" is derived
-
-For subconf lines that look just like include statements, i.e.,
-
-    subconf "foo/bar.conf"
-    subconf "frob/*.conf"
-        # assume frob has files aa.conf, bb.conf
-
-the subconf name as each file is being processed is the base name of the file.
-This means it would be "bar" for the first line, "aa" when processing
-"frob/aa.conf", and "bb" when processing "frob/bb.conf".
-
-A variation of subconf exists that can explicitly state the subconf name:
-
-    subconf foo "frob/*.conf"
-
-In this variation, regardless of what file in "frob/" is being read, the
-subconf name in effect is "foo".
-
-## security notes
-
-### group names
-
-You can use "@group"s defined in the main config file but do not attempt to
-redefine or extend them in your own subconf file.  If you must extend a group
-(say `@foo`) defined in the main config file, do this:
-
-    @myfoo  =   @foo
-    # now do whatever you want with @myfoo
-
-Group names you define in your subconf will not clash even if the exact same
-name is used in another subconf file, so you need not worry about that.
-
-### delegating pubkeys
-
-Short answer: not gonna happen.
-
-The delegation feature is meant only for access control rules, not pubkeys.
-Adding/removing pubkeys is a much more significant event than changing branch
-level permissions for people already on staff, and only the main admin should
-be allowed to do it.
-
-Gitolite's "userids" all live in the same namespace.  This is unlikely to
-change, so please don't ask -- it gets real complicated to do otherwise.
-Allowing sub-admins to add users means username collisions, which also means
-security problems (admin-A creates a pubkey for Admin-B, thus gaining access
-to all of Admin-B's stuff).
-
-If you feel the need to delegate even that, please just go the whole hog and
-give them separate gitolite instances (i.e., running under different gitolite
-hosting users)!
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
deleted file mode 100644
index a493ee7..0000000
--- a/doc/dev-notes.mkd
+++ /dev/null
@@ -1,165 +0,0 @@
-# notes for developers
-
-[[TOC]]
-
-----
-
-Gitolite has a huge bunch of existing features that gradually need to moved
-over.  Plus you may want to write your own programs to interact with it.
-
-**This document is about *writing* hooks, commands, triggers, VREFS, and sugar
-scripts.  *Installing* them, including "where and how", is described
-[here][localcode]**.
-
-## environment variables and other inputs
-
-In general, the following environment variables should always be available:
-
-    GL_BINDIR
-    GL_REPO_BASE
-    GL_ADMIN_BASE
-
-Commands invoked by a remote client will also have `GL_USER` set.  Hooks will
-have `GL_REPO` also set.
-
-Finally, note that triggers get a lot of relevant information from gitolite as
-arguments; see [here][triggers] for details.
-
-## APIs
-
-### the shell API
-
-The following commands exist to help you write shell scripts that interact
-easily with gitolite.  Each of them responds to `-h` so please run that for
-more info.
-
-  * `gitolite access` to check access rights given repo, user, type of access
-    (R, W, ...) and refname (optional).  Example use: src/commands/desc.
-
-  * `gitolite creator` to get/check the creator of a repo.  Example use:
-    src/commands/desc.
-
-  * `gitolite git-config` to check gitolite options or git config variables
-    directly from gitolite's "compiled" output, (i.e., without looking at the
-    actual `repo.git/config` file or using the `git config` command).  Example
-    use: src/triggers/post-compile/update-gitweb-access-list.
-
-  * `gitolite query-rc` to check the value of an RC variable.  Example use:
-    src/commands/desc.
-
-In addition, you can also look at the comments in src/lib/Gitolite/Easy.pm
-(the perl API module) for ideas.
-
-### the perl API
-
-...is implemented by Gitolite::Easy; the comments in src/lib/Gitolite/Easy.pm
-serve as documentation.
-
-Note that some of the perl functions called by Easy.pm will change the current
-directory to something else, without saving and restoring the directory.
-Patches (to Easy.pm *only*) welcome.
-
-## writing your own...
-
-### ...commands
-
-Commands are standalone programs, in any language you like.  They simply
-receive the arguments you append.  In addition, the env var `GL_USER` is
-available if it is being run remotely.  src/commands/desc is the best example
-at present.
-
-### ...hooks
-
-#### anything but the update hook
-
-If you want to add any hook other than the update hook, 'man githooks' is all
-you need.
-
-#### update hook
-
-If you want to add additional `update` hook functionality, do this:
-
-  * Write and test your update hook separately from gitolite.
-
-  * Now add the code as a VREF (see [here][localcode] for details).  Let's say
-    you called it "foo".
-
-  * To call your new update hook to all accesses for all repos, add this to
-    the end of your conf file:
-
-        repo @all
-            -       VREF/foo        =   @all
-
-As you probably guessed, you can make your additional update hooks more
-selective, applying them only to some repos / users / combinations.
-
-Note: a normal update hook expects 3 arguments (ref, old SHA, new SHA).  A
-VREF will get those three, followed by at least 4 more.  Your VREF should just
-ignore the extra args.
-
-### ...trigger programs
-
-Trigger programs run at specific points in gitolite's execution, with specific
-arguments being passed to them.  See the [triggers][] page for details.
-
-You can write programs that are both manually runnable as well as callable by
-trigger events, especially if they don't *need* any arguments.
-
-### ..."sugar"
-
-Syntactic sugar helpers are NOT complete, standalone, programs.  They must
-include a perl sub called `sugar_script` that takes in a listref, and returns
-a listref.  The listrefs point to a list that contains the entire conf file
-(with all [include][] processing already done).  You create a new list with
-contents modified as you like and return a ref to it.
-
-There are a couple of examples in src/syntactic-sugar.
-
-## appendix 1: notes on the INPUT trigger
-
-Note: some of this won't make sense if you haven't read about [triggers][].
-
-The INPUT trigger sequence is designed to set or change environment variables
-or the argument list.  (Side note: this means INPUT triggers have to be
-written as perl modules; they cannot be standalone scripts).  This is a very
-powerful idea so an extended description may be useful.
-
-Sshd invokes gitolite-shell with the SSH\_ORIGINAL\_COMMAND env var containing
-the git/gitolite command and one argument: the gitolite username.
-
-  * see [this][glssh] for details on the latter
-  * the *first* thing gitolite does in smart http mode is to use the
-    REMOTE\_USER and the CGI variables that apache provides to *construct*
-    a fake argument list and a fake SSH\_ORIGINAL\_COMMAND env var, so the
-    rest of the code can stay the same
-
-The INPUT trigger is then run.  The purpose of the input trigger is to ensure
-that the first argument *is* the gitolite username, and that the
-SSH\_ORIGINAL\_COMMAND env var contains the actual command to execute.  It can
-also be used to set up any other environment variables that you may decide you
-need.
-
-Wait... didn't we say that's what gitolite-shell gets anyway, just now?
-
-Well, we lied a bit there; it's not always true!
-
-For example, if [this][giving-shell] feature is used, the first argument *may*
-be "-s", with the username in the *second* argument.  Shell.pm deals with
-that.  <font color="gray">(Order matters.  If you use this feature, put the
-`'Shell::input',` line ahead of the others, since it is the only one prepared
-to deal with username not being the first argument).</font>
-
-If you look at CpuTime.pm, you'll see that it's `input()` function doesn't set
-or change anything, but does set a package variable to record the start time.
-Later, when the same module's `post_git()` function is invoked, it uses this
-variable to determine elapsed time.
-
-*(This is a very nice and simple example of how you can implement features by
-latching onto multiple events and sharing data to do something)*.
-
-You can even change the reponame the user sees, behind his back.  Alias.pm
-handles that.
-
-Finally, as an exercise for the reader, consider how you would create a brand
-new env var that contains the *comment* field of the ssh pubkey that was used
-to gain access, using the information [here][kfn].
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
deleted file mode 100644
index 6d40949..0000000
--- a/doc/dev-status.mkd
+++ /dev/null
@@ -1,36 +0,0 @@
-## #dev-status g3 development status
-
-Not yet done (will be tackled in this order unless someone asks):
-
-  * mechanism for ADCs using unchecked arguments -- this is not just a matter
-    of writing it; I have to think about *how* it will be done.  (AFAIK the
-    only tool affected is git-annexe; if there are more let me know)
-
-Help needed:
-
-  * I'd like distro packagers to play with it and help with migration advice
-    for distro-upgrades
-  * rsync
-  * git-annexe support (but this has a pre-requisite in the previous list)
-
-Won't be done unless someone asks (saw no evidence that anyone used them in g2
-anyway!):
-
-  * mob branches
-  * password access
-  * some of the more arcane rc variables!
-  * specific ADCs -- there are too many for me to bother without applying
-    Pareto somewhere, so I choose to not do any and wait for people to ask :-)
-
-Done:
-
-  * core code
-  * test suite
-  * mirroring
-  * documentation
-  * migration documentation
-  * distro packaging instructions
-  * migration advice for common cases
-  * smart http
-  * svnserve
-  * htpasswd
diff --git a/doc/emergencies.mkd b/doc/emergencies.mkd
deleted file mode 100644
index 1d0e616..0000000
--- a/doc/emergencies.mkd
+++ /dev/null
@@ -1,192 +0,0 @@
-# help for emergencies
-
-----
-
-"Don't Panic!"
-
-----
-
-## #lost-key lost admin key/access
-
-If you lost your gitolite **admin** key or access, here's what you do.  We'll
-assume your username is 'alice'.
-
-  * Make yourself a new keypair and copy the public key to the server as
-    'alice.pub'.
-
-  * Log on to the server, and run `gitolite setup -pk alice.pub`.
-
-That's it; the new alice.pub file replaces whatever existed in the repo
-before.
-
-## #bypass bypassing gitolite
-
-You may have lost access because of a conf file error, in which case the above
-trick won't help.  What you want is to make changes to the repo (or perhaps
-just rewind) and push that.  Here's how to do that:
-
-  * Log on to the server.
-
-  * Clone the admin repo using the full path: `git clone
-    $HOME/repositories/gitolite-admin.git temp`.
-
-  * Make whatever changes you want -- add/replace a key, 'git revert' or 'git
-    reset --hard' to an older commit, etc.  Anything you need to fix the
-    problem, really.
-
-  * Run `gitolite push` (or possibly `gitolite push -f`).  Note that's
-    'gitolite push', not 'git push'.
-
-<font color="red">
-**NOTE**: gitolite does **no access checking** when you do this!
-</font>
-
-## #clean cleaning out a botched install
-
-If you've read the [files involved in gitolite][files] page, you probably know
-the answer, but here's a list of files you should blow away.
-
-  * **Gitolite sources** -- can be found by running `which gitolite`.  If it's
-    a symlink, go to its target directory.
-
-  * **Gitolite admin directory** -- `$HOME/.gitolite`.  Save the 'logs'
-    directory if you want to preserve them for any reason.
-
-  * **The rc file** -- `$HOME/.gitolite.rc`.  If you made any changes to it
-    you can save it as some other name instead of deleting it.
-
-  * **The gitolite-admin repo** -- `$HOME/repositories/gitolite-admin.git`.
-    You can clone it somewhere to save it before blowing it away if you wish.
-
-  * **Git repositories** -- `$HOME/repositories`.  The install process will
-    not touch any existing repos except 'gitolite-admin.git', so you do not
-    have to blow away (or move) your work repos to fix a botched install.
-
-    Only when you update the conf to include those repos and push the changes
-    will those repos be touched.  And even then all that happens is that the
-    update hook, if any, is replaced with gitolite's own hook.
-
-  * **Ssh stuff** -- exercise caution when doing this, but in general it
-    should be safe to delete all lines between the "gitolite start" and
-    "gitolite end" markers in `$HOME/.ssh/authorized_keys`.
-
-    Gitolite does not touch any other files in the ssh directory.
-
-## #ce common errors
-
-  * `WARNING: keydir/<yourname>.pub duplicates a non-gitolite key, sshd will ignore it`
-
-    You used a key that is already set to give you shell access.  You cannot
-    use the same key to get shell access as well as access gitolite repos.
-
-    Solution: use a different keypair for gitolite.  There's a slightly longer
-    discussion in the [setup][] page.  Also see [why bypassing causes a
-    problem][ybpfail] and both the documents in [ssh][] for background.
-
-  * `Empty compile time value given to use lib at hooks/update line 6`
-
-    (followed by `Can't locate Gitolite/Hooks/Update.pm in @INC` a couple of
-    lines later).
-
-    You're bypassing gitolite.  You cloned the repo using the full path (i.e.,
-    including the `repositories/` prefix), either directly on the server, or
-    via ssh but with a key that gives you **shell** access.
-
-    Solution: same as for the previous bullet.
-
-    NOTE: If you really *must* do it, and this is a one-time thing, you can
-    try `gitolite push` instead of `git push`.  **BUT**... this defeats all
-    gitolite access control, so if you're going to do this often, maybe you
-    don't need gitolite!
-
-## #ue uncommon errors
-
-  * `WARNING: split conf not set, gl-conf present for <repo>`
-
-    (Case 1) you copied a bare repo ("repo.git") from another g3 site (or g2
-    with `GL_BIG_CONFIG` on).  Then you pushed a change to "gitolite.conf" or
-    ran certain server-side commands without adding the repo to the conf.
-
-    Conversely, you removed "repo" from "gitolite.conf" but did not remove the
-    actual "repo.git" on disk.
-
-    (Case 2) This can also happen if you changed something like this
-
-        repo foo
-            ...<some rules>...
-
-    to this
-
-        @grp = foo
-        repo @grp
-            ...<some rules>...
-
-    Also, even running `gitolite setup` will not fix this.
-
-    The root cause is an internal consistency check that I do not wish to
-    disable or subvert.  It is there for a reason, and I would prefer a
-    warning that a human can investigate.
-
-    If you're sure the reasons are one of the two above, you can either add
-    the repo to the conf file in case 1, or manually remove the gl-conf file
-    from the repo.git directory in case 2.
-
-    Either way, run `gitolite setup` afterwards to make sure things are in
-    good shape.
-
-    If you think neither of those is the cause, email me.
-
-## #ngp things that are not gitolite problems
-
-There are several things that appear to be gitolite problems but are not.  I
-cannot help with most of these (although the good folks on irc or the mailing
-list -- see [contact][] -- might be able to; they certainly appear to have a
-lot more patience than I do, bless 'em!)
-
-  * **Client side software**
-
-      * putty/plink
-      * jgit/Eclipse
-      * Mac OS client **or** server
-      * putty/plink
-      * windows as a server
-      * ...probably some more I forgot; will update this list as I remember...
-      * did I mention putty/plink?
-
-  * **Ssh**
-
-    The *superstar* of the "not a gitolite problem" category is actually ssh.
-
-    Surprised?  It is so common that it has [its own document][auth] to tell
-    you why it is *not* a gitolite problem, while [another one][ssh] tries to
-    help you anyway!
-
-    Everything I know is in that latter link.  Please email me about ssh ONLY
-    if you find something wrong or missing in those documents.
-
-  * **Git**
-
-    I wish I had a dollar for each time someone did a *first push* on a new
-    repo, got an error because there were "no refs in common (etc.)", and
-    asked me why gitolite was not allowing the push.
-
-    Gitolite is designed to look like just another bare repo server to a
-    client (except requiring public keys -- no passwords allowed).  It is
-    *completely transparent* when there is no authorisation failure (i.e.,
-    when the access is allowed, the remote client has no way of knowing
-    gitolite was even installed!)
-
-    Even "on disk", apart from reserving the `update` hook for itself,
-    gitolite does nothing to your bare repos unless you tell it to (for
-    example, adding 'gitweb.owner' and such to the config file).
-
-    BEFORE you think gitolite is the problem, try the same thing with a normal
-    bare repo.  In most cases you can play with it just by doing something
-    like this:
-
-        mkdir /tmp/throwaway
-        cd    /tmp/throwaway
-        git clone --mirror <some repo you have a URL for> bare.git
-        git clone bare.git worktree
-        cd worktree
-        <...try stuff>
diff --git a/doc/external.mkd b/doc/external.mkd
deleted file mode 100644
index 45d19a7..0000000
--- a/doc/external.mkd
+++ /dev/null
@@ -1,71 +0,0 @@
-# interfacing with external tools
-
->   ----
-
->   **Note**: The old gitolite (v1.x, v2.x) used to tie itself into knots
->   dealing with gitweb and daemon.  One of the goals of g3 was to get out of
->   that game, which your author does not play anyway.  This means statements
->   like "...special user called 'gitweb'..." really apply to the [non-core][]
->   programs that gitolite ships with, not to "core" gitolite, and any or all
->   of this functionality can be disabled by commenting out certain lines in
->   the [rc][] file.
-
->   ----
-
->   Also, **note** that gitolite does **not** install or configure
->   gitweb/git-daemon -- that is a one-time setup you must do separately.
-
->   ----
-
-## gitweb
-
-The following repos are deemed to be readable by gitweb:
-
-  * any repos readable by the special user `gitweb`
-  * any repos containing one or more of the following types of lines:
-
-        config gitweb.owner         =   owner name
-        config gitweb.description   =   some description
-        config gitweb.category      =   some category
-
-    Side note: the following shorter forms are available as [syntactic
-    sugar][sugar] for the above longer forms:
-
-        owner       =   owner name
-        desc        =   some description
-        category    =   some category
-
-The list of gitweb-readable repos is written to a file whose name is given by
-the [rc][] file variable `GITWEB_PROJECTS_LIST`.  The default value of this
-variable, if it is not specified or empty, is `$HOME/projects.list`.
-
-In addition, each of the config variables described above is written to the
-repo to which it pertains, so that gitweb can use them.
-
-### #umask changing the UMASK
-
-Gitweb typically runs under a different userid, and the default permissions
-that gitolite sets make them unreadable.
-
-See the section on the `UMASK` variable in the documentation for the [rc
-file][rc].
-
-## git-daemon
-
-Any repo readable by the special user `daemon` is deemed to be readable by
-git-daemon.  For each of these repos, an empty file called
-`git-daemon-export-ok` is created in the repository (i.e., the `repo.git`
-directory inside `$HOME/repositories`).
-
-## tips
-
-Setting descriptions en-masse usually does not make sense, but you can
-certainly do things like
-
-    repo @all
-        R       =   gitweb daemon
-
-assuming you have other means of setting 'gitweb.description' and
-'gitweb.owner'.
-
-Also see [this][deny-rules] for a twist on that.
diff --git a/doc/extras/auth.mkd b/doc/extras/auth.mkd
deleted file mode 100644
index 1b88a6a..0000000
--- a/doc/extras/auth.mkd
+++ /dev/null
@@ -1,106 +0,0 @@
-# authentication versus authorisation
-
-This document will explain why an "ssh issue" is almost never a "gitolite
-issue", and, indirectly, why I dont get too excited about the former.
-
-Note: for actual ssh troubleshooting see [this][sts].
-
-Here is a fundamental point: <font color="red">**Gitolite does not do
-authentication.  It only does authorisation**.</font>
-
-So first, let's loosely define these words:
-
->   **Authentication** is the process of verifying that you are who you claim
->   to be.  An authentication system will establish that I am the user
->   "sitaram" on my work system.  The one behind gmail will similarly
->   establish that I am "sitaramc".  And so on...
-
->   **Authorisation** is the process of asking what you want to do and
->   deciding if you're allowed to do it or not.
-
-Now, if you managed to read about [gitolite and ssh][glssh], you
-know that gitolite is meant to be invoked as:
-
-    /full/path/to/gitolite-shell some-authenticated-gitolite-username
-
-(where the "gitolite username" is a "virtual" username; it does not have to
-be, and usually *isn't*, an actual *unix* username).
-
-As you can see, authentication happens before gitolite is called.
-
-## but... but... you have all that ssh stuff in gitolite!
-
-No I don't.  Not in "core" gitolite from g3 onwards :-)
-
-The default setup does use ssh keys, but it's only helping you **setup**
-ssh-based authentication **as a convenience to you**.  But in fact it is a
-*completely* separate program that you can disable (in the rc file) or replace
-with something else of your choice.
-
-For example, in both [smart http][http] and ldap-backed sshd, gitolite has no
-role to play in creating users, setting up their passwords/keys, etc.
-
-## so you're basically saying you won't support "X"
-
-(where "X" is some ssh related behaviour change or feature)
-
-Well, if it's not a security issue I won't.  But since it's no longer part of
-"core" gitolite, I can be much more relaxed about taking patches, or even
-alternative implementations.
-
-While we're on the subject, locking someone out is *not* a security issue.
-Even if you [lost the admin key][lost-key], the docs tell you how to recover
-from such errors.  You do need some password based method to get a shell
-command line on the server, of course.
-
-## #otherauth how to use other authentication systems with gitolite
-
-The bottom line in terms of how to invoke gitolite has been described above,
-and as long as you manage to do that gitolite won't even know how the
-authentication was done.  Which in turn means you can use whatever
-authentication scheme you want.
-
-It also expects the `SSH_ORIGINAL_COMMAND` environment variable to contain the
-full command (typically starting with git-receive-pack or git-upload-pack)
-that the client sent.  Also, when using [smart http][http], things are somewhat
-different: gitolite uses certain environment variables that it expects httpd
-to have set up.  Even the user name comes from the `REMOTE_USER` environment
-variable instead of as a command line argument in this case.
-
-However, it has to be an authentication system that is compatible with sshd or
-httpd in some form.  Why?  Because the git *client* accessing the server only
-knows those 2 protocols to "speak git".  (Well, the `git://` protocol is
-unauthenticated, and `file://` doesn't really apply to this discussion, so
-we're ignoring those).
-
-For example, let's say you have an LDAP-based authentication system somewhere.
-It is possible to make apache use that to authenticate users, so when a user
-accesses a git url using `http://sitaram:password@git.example.com/repo`, it is
-LDAP that does the actual authentication.  [I wouldn't know how to do it but I
-know it is possible.  Patches to this doc explaining how are welcome!]
-
-There are also ssh daemons that use LDAP to store the authorised keys (instead
-of putting them all in `~/.ssh/authorized_keys`).  The clients will still need
-to generate keypairs and send them to the admin, but they can be more
-centrally stored and perhaps used by other programs or tools simultaneously,
-which can be useful.
-
-## #ldap getting user group info from LDAP
-
-Gitolite's [groups][] are pretty convenient, but some organisations already
-have similar (or sufficient) information in their LDAP store.
-
-Gitolite can tap into that information, with a little help.  Write a program
-which, given a username, queries your LDAP store and returns a space-separated
-list of groups that the user is a member of.  Then put the full path to this
-program in an [rc][] variable called `GROUPLIST_PGM`, like so:
-
-    GROUPLIST_PGM           =>  '/home/git/bin/ldap-query-groups',
-
-Now you can use those groupnames in access rules in gitolite, just as if you
-had declared their memberships in the conf file.
-
-Caution: your program must do its own logging if you want the audit trail of
-"why/how did this user get access to this repo at this time?" to resolve
-properly.  Gitolite does not do any logging of the results of the queries
-because for people who don't need it that would be a huge waste.
diff --git a/doc/extras/glssh.mkd b/doc/extras/glssh.mkd
deleted file mode 100644
index f25bc13..0000000
--- a/doc/extras/glssh.mkd
+++ /dev/null
@@ -1,149 +0,0 @@
-# #glssh how gitolite uses ssh
-
-[[TOC]]
-
-----
-
-Although other forms of authentications exist (see the document on
-[authentication versus authorisation][auth]), ssh is the one that most git
-users use.
-
-***Therefore, gitolite is (usually) heavily dependent on ssh***.
-
-Most people didn't realise this, and even if they did they don't know ssh
-well enough to help themselves.  If you don't understand how ssh public key
-authentication works, or how the `~/.ssh/authorized_keys` file can be used to
-restrict users, etc., you will have endless amounts of trouble getting
-gitolite to work, because you'll be attacking the wrong problem.
-
-So please please please understand this before tearing your hair out and
-blaming ***git/gitolite*** for whatever is going wrong with your setup :-)
-
-## ssh basics
-
-Let's start with some basics, focusing *only* on the pieces relevant to
-`gitolite`.  If this is not detailed enough, please use google and learn more
-from somewhere, or maybe buy the OReilly ssh book.
-
-  * You can login to an ssh server by typing a password, but ssh can also use
-    ***public-private keys*** (also called "key pairs") for authentication.
-    `gitolite` *requires* you to use this mechanism for your users -- they
-    cannot log in using passwords.  Hopefully by the time you finish reading
-    this document you will understand why :-)
-
-    The way you set this up is you generate a key pair on your workstation,
-    and give the server the public key.  (I need not add that the "private"
-    key must be, well, kept *private*!)
-
-  * **Generating a key pair on your workstation** is done by running the
-    command `ssh-keygen -t rsa`.  This produces two files in `~/.ssh`.  One is
-    `id_rsa`; this is the **private** key -- ***never*** let it out of your
-    machine.  The other is `id_rsa.pub`, which is the corresponding public
-    key.  This public key is usually just one long line of text.
-
-    * On Windows machines with msysgit installed, you should do this from
-      within a "git bash" window.  The command will report the full path where
-      the files have been written; make a note of this, and use those files in
-      any of the description that follows.
-
-  * **Adding your public key to the server**'s `~/.ssh/authorized_keys`
-    file is how ssh uses pubkeys to authenticate users.  Let's say
-    sita at work.station is trying to log in as git at serv.er.  What you have to do
-    is take the `~/.ssh/id_rsa.pub` file for user sita on work.station and
-    append its contents (remember it's only one line) to
-    `~/.ssh/authorized_keys` for user git on serv.er.
-
-    The `authorized_keys` file can have multiple public keys (from many
-    different people) added to it so any of them can log in to git at serv.er.
-
-    In the normal case (not gitolite, but your normal everyday shell access),
-    there's a command that does this, `ssh-copy-id`, which also fixes up
-    permissions etc., as needed, since sshd is a little picky about allowing
-    pubkey access if permissions on the server are loose.  Or you can do it
-    manually, as long as you know what you're doing and you're careful not to
-    erase or overwrite the existing contents of `~/.ssh/authorized_keys` on
-    the server!
-
-    But in the gitolite case, it's different; we'll get to that in a minute.
-
-    * **Troubleshooting pubkey authentication failures**: if you are unable to
-      get ssh access to the server after doing all this, you'll have to look
-      in `/var/log/secure` or `/var/log/auth.log` or some such file on the
-      server to see what specific error `sshd` is complaining about.
-
-  * **Restricting users to specific commands** is very important for gitolite.
-    If you read `man sshd` and look for `authorized_keys file format`, you'll
-    see a lot of options you can add to the public key line, which restrict
-    the incoming user in various ways.  In particular, note the `command=`
-    option, which means "regardless of what the incoming user is asking to do,
-    forcibly run this command instead".
-
-    Also note that when there are many public keys (i.e., lines) in the
-    `authorized_keys` file, each line can have a *different* set of options
-    and `command=` values.
-
-    Without this `command=` option, the ssh daemon will simply give you a
-    shell, which is not what we want for our gitolite keys (although we may
-    well have other keys which we use to get a shell).
-
-    **This is the backbone of what makes gitolite work; please make sure you
-    understand this**.
-
-## how does gitolite use all this ssh magic?
-
-These are two different questions you ought to be having by now: 
-
-  * How does it distinguish between me and someone else, since we're all
-    logging in as the same remote user "git".
-  * How does it restrict what I can do within a repository.
-
-### restricting shell access/distinguishing one user from another
-
-The answer to the first question is the `command=` we talked about before.  If
-you look in the `authorized_keys` file, you'll see entries like this (I chopped
-off the ends of course; they're pretty long lines):
-
-    command="[path]/gitolite-shell sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
-    command="[path]/gitolite-shell usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
-
-First, it finds out which of the public keys in this file match the incoming
-login.  That's crypto stuff, and I won't go into it.  Once the match has been
-found, it will run the command given on that line; e.g., if I logged in, it
-would run `[path]/gitolite-shell sitaram`.  So the first thing to note is
-that such users do not get "shell access", which is good!
-
-Before running the command, however, sshd sets up an environment variable
-called `SSH_ORIGINAL_COMMAND` which contains the actual git command that your
-workstation sent out.  This is the command that *would have run* if you did
-not have the `command=` part in the authorised keys file.
-
-When `gitolite-shell` gets control, it looks at the first argument
-("sitaram", "usertwo", etc) to determine who you are.  It then looks at the
-`SSH_ORIGINAL_COMMAND` variable to find out which repository you want to
-access, and whether you're reading or writing.
-
-Now that it has a user, repository, and access requested (read/write), gitolite looks
-at its config file, and either allows or rejects the request.
-
-But this cannot differentiate between different branches within a repo; that
-has to be done separately.
-
-### restricting branch level actions
-
-[If you look inside the git source tree, there's a file among the "howto"s in
-there called `update-hook-example.txt`, which was the inspiration for this
-part of gitolite.]
-
-Git allows you to specify many "hooks", which get control as various events
-happen -- see `git help hooks` for details.  One of those hooks is the
-`update` hook, which, if it is present, is invoked just before a branch or a
-tag is about to be updated.  The hook is passed the name of the branch or tag,
-the old SHA1 value, and the new SHA1 value, as arguments.  Hooks that are
-called *before* an action happens are allowed to prevent that action from
-happening by returning an error code.
-
-When gitolite is told to create a new repository (by the admin), it installs
-a special update hook.  This hook takes all the information presented, looks
-at the config file, and decides to allow or reject the update.
-
-And that's basically it.
diff --git a/doc/extras/putty.mkd b/doc/extras/putty.mkd
deleted file mode 100644
index 5a4ff56..0000000
--- a/doc/extras/putty.mkd
+++ /dev/null
@@ -1,203 +0,0 @@
-# putty and msysgit
-
-This document is intended for those who wish to use Putty/Plink with msysgit.
-
-If you need more help with putty or component programs I suggest looking at [the official putty documentation](http://the.earth.li/~sgtatham/putty/latest/htmldoc/).
-
-**If you are not already using Putty for SSH it is recommended you do _NOT_ use it with msysgit.**
-
-**Please note that this only covers the client side of things, and does not involve server side components to troubleshooting. For that, please see the [ssh-troubleshooting document][sts].**
-
-<a name="msysgit_setup"/>
-
-## msysgit setup
-
-Provided you have putty sessions msysgit should give you the option of specifying a location to plink. If it did not then you will need to add an environment variable named "GIT\_SSH" to point at plink.exe, wherever you have that sitting.
-
-How to do that on your version of windows will likely vary, and is not covered here. For purposes of example, on a 64 bit Windows Vista machine the GIT\_SSH value could be:
-
-    C:\Program Files (x86)\PuTTY\plink.exe
-
-Note the lack of quotes.
-
-Testing that msysgit is properly configured can be done from the git bash shell. Simply type (case sensitive, include the quotes):
-
-    "$GIT_SSH" -V
-
-You should get a response similar to this:
-
-    plink: Release 0.60
-
-If instead you get a "command not found" type error you likely have a typo in your environment variable.
-
-<a name="Going_back_to_OpenSSH"/>
-
-## Going back to OpenSSH
-
-If you wish to go back to OpenSSH all you need to do is delete the GIT\_SSH environment variable. This will vary by your version of windows and thus is not covered here.
-
-<a name="Putty_keys"/>
-
-## Putty keys
-
-If you do not already have putty private key files (.ppk) you will need to make at least one. You can either make a new one or convert an existing key to putty private key format.
-
-Either way, you will want to use puttygen. Note that you can go the other way if you want to stop using putty but keep the key by exporting the key to OpenSSH format.
-
-<a name="Creating_a_new_key"/>
-
-### Creating a new key
-
-To make it simple, I suggest SSH-2 RSA and a bit size of at least 1024. Larger keys will take longer to generate and will take longer to authenticate you on most systems. Making the key is as simple at hitting "Generate".
-
-It is recommended to give the key a meaningful comment.
-
-<a name="Importing_an_existing_key"/>
-
-### Importing an existing key
-
-If you already have an OpenSSH or ssh.com key you can import it using the "Import" option on the "Conversions" menu.
-
-If the key does not have a meaningful comment I would suggest adding one at this point.
-
-<a name="Loading_an_existing_key"/>
-
-### Loading an existing key
-
-If you need to load an existing key to edit or view it you can do so from the File menu.
-
-<a name="Public_key"/>
-
-### Public key
-
-To get your public key for use with gitolite, load (or generate, or import) your key into puttygen. There is a box labeled "Public key for pasting into OpenSSH `authorized_keys` file" there. Copy the text into your preferred text editor and save.
-
-<a name="Putty_ageant"/>
-
-### Putty ageant
-
-Though not required in all cases you may wish to use the putty ageant, pageant, to load your key(s). This will allow for your key(s) to be passphrase protected but not have to enter the passphrase when you go to use them, provided you have already loaded the key into the ageant.
-
-<a name="Sessionless_or_raw_hostname_usage"/>
-
-## Sessionless or raw hostname usage
-
-When using plink without a putty session you pretty much have to load your keys with putty ageant, if only so that plink can find them.
-
-<a name="Putty_sessions"/>
-
-## Putty sessions
-
-In addition to hostnames msysgit can, when using putty, use putty sessions. This works in a manner similar to definitions in OpenSSH's `ssh_config` file. All settings in the session that apply to plink usage will be loaded, including the key file to use and even the username to connect to. Thus, instead of:
-
-    ssh://user@host.example.ext:port/repo
-
-You can use:
-
-    ssh://session_name/repo
-
-<a name="Host_key_authentication"/>
-
-## Host key authentication
-
-Whether you are using hostnames or sessions you still run into one potential problem. Plink currently wants to validate the server's SSH host key before allowing you to connect, and when git calls plink there is no way to tell it yes. Thus, you may get something like this:
-
-    The server's host key is not cached in the registry. You
-    have no guarantee that the server is the computer you
-    think it is.
-    The server's rsa2 key fingerprint is:
-    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
-    Connection abandoned.
-    fatal: The remote end hung up unexpectedly
-
-Or, in the case of the host key changing, something like this:
-
-    WARNING - POTENTIAL SECURITY BREACH!
-    The server's host key does not match the one PuTTY has
-    cached in the registry. This means that either the
-    server administrator has changed the host key, or you
-    have actually connected to another computer pretending
-    to be the server.
-    The new rsa2 key fingerprint is:
-    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
-    Connection abandoned.
-    fatal: The remote end hung up unexpectedly
-
-The solution is to call plink directly, or start putty and connect with it first. To use plink, open the Git Bash shell and enter:
-
-    "$GIT_SSH" hostname_or_session_name
-
-When you do you will see something like this:
-
-    The server's host key is not cached in the registry. You
-    have no guarantee that the server is the computer you
-    think it is.
-    The server's rsa2 key fingerprint is:
-    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
-    If you trust this host, enter "y" to add the key to
-    PuTTY's cache and carry on connecting.
-    If you want to carry on connecting just once, without
-    adding the key to the cache, enter "n".
-    If you do not trust this host, press Return to abandon the
-    connection.
-    Store key in cache? (y/n)
-
-Or, in the case of a changed key, a response like this:
-
-    WARNING - POTENTIAL SECURITY BREACH!
-    The server's host key does not match the one PuTTY has
-    cached in the registry. This means that either the
-    server administrator has changed the host key, or you
-    have actually connected to another computer pretending
-    to be the server.
-    The new rsa2 key fingerprint is:
-    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
-    If you were expecting this change and trust the new key,
-    enter "y" to update PuTTY's cache and continue connecting.
-    If you want to carry on connecting but without updating
-    the cache, enter "n".
-    If you want to abandon the connection completely, press
-    Return to cancel. Pressing Return is the ONLY guaranteed
-    safe choice.
-    Update cached key? (y/n, Return cancels connection)
-
-In either case hit y and the key will be stored.
-
-<a name="Debugging_multiple_putty_ageant_keys"/>
-
-## Debugging multiple putty ageant keys
-
-In the event you are using putty ageant with multiple keys loaded you may see the wrong key being used. In general, pageant keys are tried in the order they were loaded into the ageant. If you have descriptive comment on each of your keys you can try connecting with plink in verbose mode to see what keys are being tried. Simply open the Git bash shell and run:
-
-    "$GIT_SSH" -v user at hostname
-
-Or, if using sessions with a pre-entered username:
-
-    "$GIT_SSH" -v session_name
-
-In either case, you should look for lines like:
-
-    Trying Pageant key #0
-	Authenticating with public key "My Key" from agent
-
-The first says which (numerical) key the ageant is trying. The second tells you the key comment for the authenticating key. To my knowledge the second line should only show up once, for the valid key.
-
-<a name="Setperms_and_other_commands"/>
-
-## Setperms and other commands
-
-When using wildcard repos the setperms command is very important, and other commands can come in handy as well. See their documentation for how to use them, but where they use:
-
-    ssh user at host command etc etc
-
-You will want to use:
-
-    "$GIT_SSH" user at host command etc etc
-
-Otherwise everything should be identical.
-
-<a name="About_this_document"/>
-
-## About this document
-
-This document was written by Thomas Berezansky (tsbere (at) mvlc (dot) org) in the hopes that it would be useful to those using putty on windows and wishing to use git/gitolite with their putty keys and sessions.
diff --git a/doc/extras/regex.mkd b/doc/extras/regex.mkd
deleted file mode 100644
index c334c2c..0000000
--- a/doc/extras/regex.mkd
+++ /dev/null
@@ -1,34 +0,0 @@
-# extremely brief regex overview
-
-Regexes are powerful.  Gitolite uses that power as much as it can.  If you
-can't handle that power, hire someone who can and become a manager.
-
-That said, here's a very quick overview of the highlights.
-
-`^` and `$` are called "anchors".  They anchor the match to the beginning and
-end of the string respectively.
-
-    ^foo    matches any string starting with 'foo'
-    foo$    matches any string ending with 'foo'
-    ^foo$   matches exact string 'foo'.
-
-To be precise, the last one is "any string starting and ending with *the same*
-'foo'".  "foofoo" does not match.
-
-`[0-9]` is an example of a character class; it matches any single digit.
-`[a-z]` matches any lower case alpha, and `[0-9a-f]` is the range of hex
-characters.  You should now guess what `[a-zA-Z0-9_]` does.
-
-`.` (the period) is special -- it matches any character.  If you want to match
-an actual period, you need to say `\.`.
-
-`*`, `?`, and `+` are quantifiers.  They apply to the previous token.  `a*`
-means "zero or more 'a' characters".  Similarly `a+` means "one or more", and
-`a?` means "zero or one".
-
-As a result, `.*` means "any number (including zero) of any character".
-
-The previous token need not be a single character; you can use parens to make
-it longer.  `(foo)+` matches one or more "foo", (like "foo", "foofoo",
-"foofoofoo", etc.)
-
diff --git a/doc/extras/ssh.mkd b/doc/extras/ssh.mkd
deleted file mode 100644
index fb51655..0000000
--- a/doc/extras/ssh.mkd
+++ /dev/null
@@ -1,16 +0,0 @@
-# ssh
-
-If you're installing gitolite, you're a "system admin", like it or not.  If
-you're using the default ssh mode (i.e., not [http][] mode), ssh is a
-necessary skill.  Please take the time to learn at least enough to get
-passwordless access working.
-
-There are two documents you need to read, in order:
-
-  * [Gitolite and ssh][glssh] explains how gitolite uses openssh features to
-    create any number of virtual users over just one actual (unix) user, and
-    distinguish between them by their public keys.
-
-  * [Ssh troubleshooting][sts] is a rather long document that, as far as I
-    know, covers almost every known ssh related issue.  If you find something
-    missing, send me an email with details so I can update it.
diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
deleted file mode 100644
index 1c68f42..0000000
--- a/doc/extras/sts.mkd
+++ /dev/null
@@ -1,472 +0,0 @@
-# #sts ssh troubleshooting and tips
-
-**This document must be read in full the first time.  If you start from some
-nice looking section in the middle it may not help you unless you're already
-an expert at ssh**.
-
-This document should help you troubleshoot ssh-related problems in installing
-and accessing gitolite.  It also has a section of random ssh-related tips and
-tricks that gitolite can do.
-
-----
-
-[[TOC]]
-
-----
-
-## IMPORTANT -- READ THIS FIRST
-
-### caveats
-
-  * Before reading this document, it is **mandatory** to read and **completely
-    understand** [this][ssh], which is a very detailed look at how gitolite
-    uses ssh's features on the server side.  Don't assume you know all that;
-    if you did, you wouldn't be needing *this* document either!
-
-  * This document, and others linked from this, together comprise all the help
-    I can give you in terms of the ssh aspect of using gitolite.  If you're
-    installing gitolite, you're a "system admin", like it or not.  Ssh is
-    therefore a necessary skill.  Please take the time to learn at least
-    enough to get passwordless access working.
-
-  * Please note that authentication is not really gitolite's job at all.  I'd
-    rather spend time on actual gitolite features, code, and documentation
-    than authentication (i.e., ssh, in the common case).
-
-    Surprised?  [This][auth] might help explain better.
-
-### naming conventions used
-
-  * Your workstation is the **client**.  Your userid on the client does not
-    matter, and it has no relation to your gitolite username.
-
-  * The server is called **server** and the "hosting user" is **git**.  If
-    this is an RPM/DEB install, the hosting user is probably called
-    "gitolite", however we will use "git" in this document.
-
-### taking stock -- relevant files and directories
-
-  * The client has a `~/.ssh` containing a few keypairs.  It may also have a
-    `config` file.
-
-  * The client also has a clone of the "gitolite-admin" repo, which contains a
-    bunch of `*.pub` files in `keydir`.  We assume this clone is in `$HOME`;
-    if it is not, adjust instructions accordingly when needed.
-
-  * The git user on the server has a `~/.ssh/authorized_keys` file that the
-    ssh daemon uses to authenticate incoming users.  We often call this file
-    **authkeys** to save typing, and it always means the one on the server
-    (we're not interested in this file on the client side).
-
-  * The server also has a `~/.gitolite/keydir` which contains a bunch of
-    `*.pub` files.
-
-### normal gitolite key handling
-
-Here's how normal gitolite key handling works:
-
-  * (On client) pub key changes like adding new ones, deleting old ones, etc.,
-    are done in the `keydir` directory in the gitolite-admin repo clone.  Then
-    the admin `git add`s and `git commit`s those changes, then `git push`es
-    them to the server.
-
-  * (On server) a successful push from the client makes git invoke the
-    post-update hook in the gitolite-admin repo.  This hook is installed by
-    gitolite, and it does a bunch of things which are quite transparent to
-    the admin, but we'll describe briefly here:
-
-      * The pubkey files from this push are checked-out into
-        `~/.gitolite/keydir` (and similarly the config files into
-        `~/.gitolite/conf`).
-
-      * The "compile" script then runs, which uses these files to populate
-        `~/.ssh/authorized_keys` on the server.
-
-        The authkeys file may have other, (non-gitolite) keys also.  Those
-        lines are preserved.  Gitolite only touches lines that are found
-        between gitolite's "marker" lines (`# gitolite start` and `# gitolite
-        end`).
-
-## common ssh problems
-
-Since I'm pretty sure at least some of you didn't bother to read the
-"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
-you there again.  Especially the first bullet.
-
-Done?  OK, read on...
-
-The following problem(s) indicate that pubkey access is not working at all, so
-you should start with [appendix 1][stsapp1].  If that doesn't fix the problem, continue
-with the other appendices in sequence.
-
-  * Running any git clone/fetch/ls-remote or just `ssh git at server info` asks
-    you for a password.
-
-The following problem(s) indicate that your pubkey is bypassing gitolite and
-going straight to a shell.  You should start with [appendix 2][sshkeys-lint]
-and continue with the rest in sequence.  [Appendix 5][ybpfail] has some
-background info.
-
-  * Running `ssh git at server info` gets you the output of the GNU 'info'
-    command instead of gitolite's version and access info.
-
-  * Running `git clone git at server:repositories/reponame` (note presence of
-    `repositories/` in URL) works.
-
-    [A proper gitolite key will only let you `git clone git at server:reponame`
-    (note absence of `repositories/`)]
-
-  * You are able to clone repositories but are unable to push changes back
-    (the error complains about the `GL_BINDIR` environment variable not being
-    set, and the `hooks/update` failing in some way).
-
-    [If you run `git remote -v` you will find that your clone URL included the
-    `repositories/` described above!]
-
-  * Conversely, using the correct syntax, `git clone git at server:reponame`
-    (note absence of `repositories/` in the URL), gets you `fatal: 'reponame'
-    does not appear to be a git repository`, and yet you are sure 'reponame'
-    exists, you haven't mis-spelled it, etc.
-
-## step by step
-
-Since I'm pretty sure at least some of you didn't bother to read the
-"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
-you there again.  Especially the first bullet.
-
-Done?  OK, now the general outline for ssh troubleshooting is this:
-
-  * Make sure the server's overall setup even *allows* pubkey based login.
-    I.e., check that git fetch/clone/ls-remote commands or a plain `ssh
-    git at server info` do NOT ask for a password.  If you do get asked for a
-    password, see [appendix 1][stsapp1].
-
-  * Match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
-    file.  To do this, run `sshkeys-lint`, which tells you in detail what key
-    has what access.  See [appendix 2][sshkeys-lint].
-
-  * At this point, we know that we have the right key, and that if sshd
-    receives that key, things will work.  But we're not done yet.  We still
-    need to make sure that this specific key is being offered/sent by the
-    client, instead of the default key.  See [appendix 3][stsapp3] and
-    [appendix 4][ssh-ha].
-
-## random tips, tricks, and notes
-
-### #giving-shell giving shell access to gitolite users
-
-Thanks to an idea from Jesse Keating, a single key can allow both gitolite
-access *and* shell access.
-
-To do this:
-
-  * add the list of users who will have shell access -- one username per line,
-    no extra whitespace -- to a plain text file of your choice.
-
-  * put the name of this file in a new rc variable `SHELL_USERS_LIST`.  For
-    example it could be
-
-        SHELL_USERS_LIST        =>  "$ENV{HOME}/.gitolite.shell-users",
-
-  * add the line `'Shell::input',` to the `INPUT` list in the rc file.  This
-    must be the first item on the list (possibly preceded by CpuTime, if
-    you're using that).
-
-  * add the line `'post-compile/ssh-authkeys-shell-users',` to the
-    `POST_COMPILE` list, *after* the `'post-compile/ssh-authkeys',` line.
-
-Then run `gitolite compile; gitolite trigger POST_COMPILE` or push a dummy
-change to the admin repo.
-
-### #kfn distinguishing one key from another
-
-Since a user can have [more than one key][multi-key], it is sometimes useful
-to distinguish one key from another.  Sshd does not tell you even the
-fingerprint of the key that finally matched, so normally all you have is the
-`GL_USER` env var.
-
-However, if you replace
-
-    'post-compile/ssh-authkeys',
-
-in the `POST_COMPILE` trigger list in the rc file with
-
-    'post-compile/ssh-authkeys --key-file-name',
-
-then an extra argument is added after the username in the "command" variable
-of the authkeys file.  That is, instead of this:
-
-    command="/home/g3/gitolite/src/gitolite-shell u3",no-port-forwarding,...
-
-you get this:
-
-    command="/home/g3/gitolite/src/gitolite-shell u3 keydir/u3.pub",no-port-forwarding,...
-
-You can then write an INPUT trigger to do whatever you need with the file
-name, which is in `$ARGV[1]` (the second argument).  The actual file is
-available at `$ENV{GL_ADMIN_BASE}/$ARGV[1]` if you need its contents.
-
-### simulating ssh-copy-id
-
-don't have `ssh-copy-id`?  This is broadly what that command does, if you want
-to replicate it manually.  The input is your pubkey, typically
-`~/.ssh/id_rsa.pub` from your client/workstation.
-
-  * It copies it to the server as some file.
-
-  * It appends that file to `~/.ssh/authorized_keys` on the server
-    (creating it if it doesn't already exist).
-
-  * It then makes sure that all these files/directories have go-w perms
-    set (assuming user is "git"):
-
-        /home/git/.ssh/authorized_keys
-        /home/git/.ssh
-        /home/git
-
-[Actually, `sshd` requires that even directories *above* `~` (`/`, `/home`,
-typically) also must be `go-w`, but that needs root.  And typically
-they're already set that way anyway.  (Or if they're not, you've got
-bigger problems than gitolite install not working!)]
-
-### problems with using non-openssh public keys
-
-Gitolite accepts public keys only in openssh format.  Trying to use an "ssh2"
-key (used by proprietary SSH software) will not be a happy experience.
-src/triggers/post-compile/ssh-authkeys can be made to detect non-openssh
-formats and automatically convert them; patches welcome!
-
-The actual conversion command, if you want to just do it manually for now and
-be done with it, is:
-
-    ssh-keygen -i -f /tmp/ssh2/YourName.pub > /tmp/openssh/YourName.pub
-
-then use the resulting pubkey as you normally would in gitolite.
-
-### windows issues
-
-On windows, I have only used msysgit, and the openssh that comes with it.
-Over time, I have grown to distrust putty/plink due to the number of people
-who seem to have trouble when those beasts are involved (I myself have never
-used them for any kind of git access).  If you have unusual ssh problems that
-just don't seem to have any explanation, try removing all traces of
-putty/plink, including environment variables, etc., and then try again.
-
-Thankfully, someone contributed [this][putty].
-
-## #stsapp1 appendix 1: ssh daemon asks for a password
-
->   **NOTE**: This section should be useful to anyone trying to get
->   password-less access working.  It is not necessarily specific to gitolite,
->   so keep that in mind if the wording feels a little more general than you
->   were expecting.
-
-You have generated a keypair on your workstation (`ssh-keygen`) and copied the
-public part of it (`~/.ssh/id_rsa.pub`, by default) to the server.
-
-On the server you have appended this file to `~/.ssh/authorized_keys`.  Or you
-ran something, like the `gitolite setup` step during a gitolite install, which
-should have done that for you.
-
-You now expect to log in without having to type in a password, but when you
-try, you are being asked for a password.
-
-This is a quick checklist:
-
-  * Make sure you're being asked for a password and not a pass*phrase*.  Do
-    not confuse or mistake a prompt saying `Enter passphrase for key
-    '/home/sitaram/.ssh/id_rsa':` for a password prompt from the remote
-    server!
-
-    When you create an ssh keypair using `ssh-keygen`, you have the option of
-    protecting it with a passphrase.  When you subsequently use that keypair
-    to access a remote host, your *local* ssh client needs to unlock the
-    corresponding private key, and ssh will probably ask for the passphrase
-    you set when you created the keypair.
-
-    You have two choices to avoid this prompt every time you try to use the
-    private key.  The first is to create keypairs *without* a passphrase (just
-    hit enter when prompted for one).  **Be sure to add a passphrase later,
-    once everything is working, using `ssh-keygen -p`**.
-
-    The second is to use `ssh-agent` (or `keychain`, which in turn uses
-    `ssh-agent`) or something like that to manage your keys.  Other than
-    discussing one more potential trouble-spot with ssh-agent (see below),
-    further discussion of ssh-agent/keychain is out of scope of this document.
-
-  * Ssh is very sensitive to permissions.  An extremely conservative setup is
-    given below, but be sure to do this on **both the client and the server**:
-
-        cd $HOME
-        chmod go-rwx .
-        chmod -R go-rwx .ssh
-
-  * Actually, every component of the path to `~/.ssh/authorized_keys` all the
-    way upto the root directory must be at least `chmod go-w`.  So be sure to
-    check `/` and `/home` also.
-
-  * While you're doing this, make sure the owner and group info for each of
-    these components are correct.  `ls -ald ~ ~/.ssh ~/.ssh/authorized_keys`
-    will tell you what they are.
-
-  * You may also want to check `/etc/ssh/sshd_config` to see if the "git" user
-    is allowed to login at all.  For example, if that file contains an
-    `AllowUsers` config entry, then only users mentioned in that line are
-    allowed to log in!
-
-  * While you're in there, check that file does NOT have a setting for
-    `AuthorizedKeysFile`.  See `man sshd_config` for details.  This setting is
-    a show stopper for gitolite to use ssh.
-
-  * Some OSs/distributions require that the "git" user should have a password
-    and/or not be a locked account.  You may want to check that as well.
-
-  * If your server is running SELinux, and you install gitolite to
-    `/var/gitolite` or another location unsupported by default SELinux
-    policies, then SELinux will prevent sshd from reading
-    `.ssh/authorized_keys`. Consider installing gitolite to
-    `/var/lib/gitolite`, which is a supported location by default SELinux
-    policies.
-
-  * If all that fails, log onto the server as root, `cd /var/log`, and look
-    for a file called `auth.log` or `secure` or some such name.  Look inside
-    this file for messages matching the approximate time of your last attempt
-    to login, to see if they tell you what is the problem.
-
-## #sshkeys-lint appendix 2: which key is which -- running sshkeys-lint
-
-The sshkeys-lint program can be run on the server or the client.  Run it with
-'-h' to get a help message.
-
-On the server you can run `gitolite sshkeys-lint` and it will tell you, for
-each key in the admin directory's keydir, what access is available.  This is
-especially good at finding duplicate keys and such.
-
-To run it on the client you have to copy the file src/commands/sshkeys-lint
-from some gitolite clone, then follow these steps:
-
-  * Get a copy of `~/.ssh/authorized_keys` from the server and put it in
-    `/tmp/foo` or something.
-
-  * cd to `~/.ssh`.
-
-  * Run `/path/to/sshkeys-lint *.pub < /tmp/foo`.
-
-Note that it is not trying to log in or anything -- it's just comparing
-fingerprints as computed by `ssh-keygen -l`.
-
-If the pubkey file you're interested in appears to have the correct access to
-the server, you're done with this step.
-
-Otherwise you have to rename some keypairs and try again to get the effect you
-need.  Be careful:
-
-  * Do not just rename the ".pub" file; you will have to rename the
-    corresponding private key also (the one with the same basename but without
-    an extension).
-
-  * If you're running ssh-agent, you may have to delete (using `ssh-add -D`)
-    and re-add identities for it to pick up the renamed ones correctly.
-
-### typical cause(s)
-
-The admin often has passwordless shell access to `git at server` already, and
-then used that same key to get access to gitolite (i.e., copied that same
-pubkey as YourName.pub and ran `gitolite setup` on it).
-
-As a result, the same key appears twice in the authkeys file now, and since
-the ssh server will always use the first match, the second occurrence (which
-invokes gitolite) is ignored.
-
-To fix this, you have to use a different keypair for gitolite access.  The
-best way to do this is to create a new keypair, copy the pubkey to the server
-as YourName.pub, then run `gitolite setup -pk YourName.pub` on the server.
-Remember to adjust your agent identities using ssh-add -D and ssh-add if
-you're using ssh-agent, otherwise these new keys may not work.
-
-## #stsapp3 appendix 3: ssh client may not be offering the right key
-
-  * Make sure the right private key is being offered.  Run ssh in very
-    verbose mode and look for the word "Offering", like so:
-
-        ssh -vvv user at host pwd 2> >(grep -i offer)
-
-    If some keys *are* being offered, but not the key that was supposed to be
-    used, you may be using ssh-agent (next bullet).  You may also need to
-    create some host aliases in `~/.ssh/config` ([appendix 4][ssh-ha]).
-
-  * (ssh-agent issues) If `ssh-add -l` responds with either "The agent has no
-    identities." or "Could not open a connection to your authentication
-    agent.", then you can skip this bullet.
-
-    However, if `ssh-add -l` lists *any* keys at all, then something weird
-    happens.  Due to a quirk in ssh-agent, ssh will now *only* use one of
-    those keys, *even if you explicitly ask* for some other key to be used.
-
-    In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try
-    the access again.
-
-## #ssh-ha appendix 4: ssh host aliases
-
-(or "making git use the right options for ssh")
-
-The ssh command has several options for non-default items to be specified.
-Two common examples are `-p` for the port number if it is not 22, and `-i` for
-the public key file if you do not want to use just `~/.ssh/id_rsa` or such.
-
-Git has two ssh-based URL syntaxes, but neither allows specifying a
-non-default public key file.  And a port number is only allowed in one of
-them.  (See `man git-clone` for details).  Finally, hosts often have to be
-referred with IP addresses (such is life), or the name is very long, or hard
-to remember.
-
-Using a "host" para in `~/.ssh/config` lets you nicely encapsulate all this
-within ssh and give it a short, easy-to-remember, name.  Example:
-
-    host gitolite
-        user git
-        hostname a.long.server.name.or.annoying.IP.address
-        port 22
-        identityfile ~/.ssh/id_rsa
-
-Now you can simply use the one word `gitolite` (which is the host alias we
-defined here) and ssh will infer all those details defined under it -- just
-say `ssh gitolite` and `git clone gitolite:reponame` and things will work.
-
-(By the way, the 'port' and 'identityfile' lines are needed only if you have
-non-default values, although I put them in anyway just to be complete).
-
-If you have *more than one* pubkey with access to the *same* server, you
-**must** use this method to make git pick up the right key.  There is no other
-way to do this, as far as I know.
-
-[tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial
-
-## #ybpfail appendix 5: why bypassing gitolite causes a problem
-
-When you bypass gitolite, you end up running your normal shell instead of the
-special gitolite entry point script `gitolite-shell`.
-
-This means commands (like 'info') are interpreted by the shell instead of
-gitolite.
-
-It also means git operations look for repos in `$HOME`.
-
-However, gitolite places all your repos in a subdirectory pointed to by
-`$REPO_BASE` in the rc file (default: `repositories`), and internally prefixes
-this before calling the actual git command you invoked.  Thus, the pathname of
-the repo that you use on the client is almost never the correct pathname on
-the server.  (This is by design.  Don't argue...)
-
-This means that, you get 2 kinds of errors if you bypass gitolite
-
-  * When you use `git at server:reponame` with a key that bypasses gitolite
-    (i.e., gets you a shell), this prefixing does not happen, and so the repo
-    is not found.  Neither a clone/fetch nor a push will work.
-
-  * Conversely, consider `git at server:repositories/reponame.git`.  The clone
-    operation will work -- you're using the full Unix path, (assuming default
-    `$REPO_BASE` setting), and so the shell finds the repo where you said it
-    would be.  However, when you push, gitolite's **update hook** kicks in,
-    and fails to run because some of the environment variables it is expecting
-    are not present.
diff --git a/doc/files.mkd b/doc/files.mkd
deleted file mode 100644
index 1cb7b2d..0000000
--- a/doc/files.mkd
+++ /dev/null
@@ -1,86 +0,0 @@
-# files involved in gitolite
-
-## install
-
-Let's say you start from a totally clean slate:
-
-    $ pwd
-    /home/gl-test
-
-    $ ls -a
-    .  ..  .bash_logout  .bash_profile  .bashrc
-
-You run `git clone git://github.com/sitaramc/gitolite`.
-
-Now you have
-
-    $ ls -aF
-    ./  ../  .bash_logout  .bash_profile  .bashrc  gitolite/
-
-    $ ls -aF gitolite
-    ./  ../  check-g2-compat*  doc/  dot.pl  .git/  install*  src/  t/
-
-If we were an existing user, we'd run the migration checker 'check-g2-compat'
-(see [here][g2migr]).  For now we're only interested in "gitolite/src", so:
-
-    $ ls -aF gitolite/src
-    ./  ../  commands/  gitolite*  Gitolite/  gitolite-shell*  syntactic-sugar/  triggers/  VREF/
-
-We check our PATH to make sure `$HOME/bin` is in it:
-
-    $ echo $PATH
-    /usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/gl-test/.local/bin:/home/gl-test/bin
-
-Since it is, we run `mkdir bin; gitolite/install -ln`, and get:
-
-    $ mkdir bin
-    $ gitolite/install -ln
-
-    $ ls -aF
-    ./  ../  .bash_logout  .bash_profile  .bashrc  bin/  gitolite/
-
-    $ ls -aF bin
-    ./  ../  gitolite@
-
-    $ file bin/gitolite
-    bin/gitolite: symbolic link to `/home/gl-test/gitolite/src/gitolite'
-
-That's the install done.  At the end of it, we have just *one* symlink in
-`$HOME/bin`; everything else is in a different directory.
-
-## setup
-
-Now you copy your pubkey (typically `id_rsa.pub`) from your workstation to the
-server, then run `gitolite setup -pk "yourname.pub"`.  Which gives you this:
-
-    $ ls -aF
-    ./   .bash_logout   .bashrc  gitolite/   .gitolite.rc   repositories/  .ssh/
-    ../  .bash_profile  bin/     .gitolite/  projects.list  sitaram.pub
-
-    $ ls -aF bin
-    ./  ../  gitolite@
-
-    $ ls -aF .gitolite
-    ./  ../  conf/  hooks/  keydir/  logs/
-
-    $ ls -aF repositories
-    ./  ../  gitolite-admin.git/  testing.git/
-
-    $ ls -aF .ssh
-    ./  ../  authorized_keys*
-
-And that's the setup done.  At the end of this step, you have
-
-  * `~/bin/gitolite` -- a symlink to `~/gitolite/src/gitolite`.  The target of
-    this symlink tells gitolite where the rest of the code is.
-  * `~/gitolite/src` -- the rest of the code.
-  * `~/.gitolite` -- the gitolite "admin" directory.  The only thing you
-    should directly touch here are the [log][] files and [hooks][].  (I.e., do
-    NOT change the conf or keydir contents here; see adding [users][] and
-    [repos][] for how to do that.
-  * `~/.gitolite.rc` -- the [rc][] file.
-  * `~/repositories` -- which contains all the repositories that gitolite will
-    be managing.
-  * `~/.ssh` -- which contains (at least) the `authorized_keys` file that
-    provides access to users.  You can look inside that file and correlate it
-    with what the [ssh][] docs tell you, if you wish.
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
deleted file mode 100644
index ca54ebc..0000000
--- a/doc/g2incompat.mkd
+++ /dev/null
@@ -1,103 +0,0 @@
-# #g2incompat incompatibility with g2
-
-This page expands on some incompatibilities that were only briefly mentioned
-in the [migration][g2migr] page.
-
-## #g2i-name NAME rules
-
-1.  NAME/ rules must be changed to VREF/NAME/
-
-2.  Fallthru on all VREFs is "success" now, so any NAME/ rules you have
-    **MUST** change the ruleset in some way to maintain the same restrictions.
-    The simplest is to add the following line to the end of each repo's rule
-    list:
-
-        -   VREF/NAME/       =   @all
-
-## #g2i-subconf subconf command in admin repo
-
-(This is also affected by the previous issue, 'NAME rules'; please read that
-as well).
-
-If you're using delegation in your admin conf setup, please add the following
-lines to the end of the gitolite-admin rules in your conf/gitolite.conf file:
-
-    repo gitolite-admin
-        -   VREF/NAME/       =   @all
-
-    subconf "fragments/*.conf"
-
-The first part compensates for fallthru now being a success when processing
-[VREF][vref] rules (NAME rules are just one specific VREF).  Although,
-**ideally**, you should change your ruleset so that you no longer require that
-line.  As the [vref documentation][vref] says:
-
->   **Virtual refs are best used as additional "deny" rules**, performing
->   extra checks that core gitolite cannot.
-
-The second part explicitly says when and where to include the subconf files.
-(Before subconf was invented, this used to happen implicitly at the end of the
-main conf file, and was hardcoded to that specific glob.)
-
-## #g2i-gl-time gl-time for performance measurement
-
-If you've been using gl-time for performance measurement, there's a much
-better system available now.
-
-gl-time used to only log elapsed time.  The new 'CpuTime' trigger module
-shipped with gitolite, if enabled in the rc file, can also report CPU times
-using perl's 'times()' function.  See comments within that file and in the
-default rc file that contain the word "cpu", for more details.
-
-Further, you can copy that module with a different name, add your own
-functionality, and invoke *that* from the rc file instead.
-
-## #g2i-mirroring changes in mirroring setup
-
-There are several changes with regard to mirroring:
-
-  * There is no 'post-receive' hook to be installed.  Mirroring is handled by
-    g3's [triggers][] mechanism.  Gitolite triggers are enabled by adding (or
-    uncommenting, in this case) appropriate lines in the rc file.
-
-  * The `GL_HOSTNAME` variable is now `HOSTNAME`.  (Note that the rc file
-    syntax itself has changed quite a bit; to be accurate, HOSTNAME is not a
-    variable but a hash key with an associated value).
-
-  * The `GL_GITCONFIG_KEYS` variable is now `GIT_CONFIG_KEYS`, **but** you no
-    longer need to set it to anything for mirroring to work.
-
-  * The `gl-tool` program does not exist anymore.  Adding keys for peer
-    servers is done just like adding user keys, except that the pubkey file
-    name must start with `server-`.  For example, to add a peer host called
-    frodo, you will acquire its pubkey and add it as `server-frodo.pub`.
-
-  * The config variables are quite different now.  The main ones now look like
-    this:
-
-        option mirror.master        =   sam
-        option mirror.slaves        =   frodo gollum
-
-    The redirectOK looks like this:
-
-        option mirror.redirectOK    =   frodo
-
-    The special value "true" to say that all slaves are trusted is now "all":
-
-        option mirror.redirectOK    =   all
-
-  * There are no more mirroring "keys", (lists of servers named in config keys
-    like 'gitolite.mirror.nightly', etc).  You can certainly add lines like
-
-        option mirror.nightly       =   merry pippin
-
-    but they will not be processed by gitolite.  Your cron jobs should use
-    `gitolite git-config` to query this variable, grab the list of peers, and
-    run `gitolite mirror` on each of them.
-
-  * The external command to resync mirrors is 'mirror', run just like any
-    other [command][commands].  In particular, you can run `gitolite mirror
-    -h` to get help.  It cannot be run from a slave to ask a master to push
-    (unlike in the old system) but what's more convenient is that any user who
-    has any access to the master can run it remotely (if you allow it) to
-    invoke a push.
diff --git a/doc/g2migr-example.mkd b/doc/g2migr-example.mkd
deleted file mode 100644
index 28ec8b2..0000000
--- a/doc/g2migr-example.mkd
+++ /dev/null
@@ -1,269 +0,0 @@
-# migration example
-
-This shows what a typical migration would look like, using a test setup.
-
-[[TOC]]
-
-## existing setup
-
-The existing gitolite is the latest in the "g2" (v2.x) branch.
-
-First, the rc file has the following lines different from the default:
-
-    -$GL_WILDREPOS = 0;
-    +$GL_WILDREPOS = 1;
-
-    -$GL_GITCONFIG_KEYS = "";
-    +$GL_GITCONFIG_KEYS = ".*";
-
-Next, the conf/gitolite.conf file in `~/.gitolite`:
-
-    repo    gitolite-admin
-            RW+             =   tester u1
-
-    repo    testing
-            RW+     =   @all
-
-    repo foo
-            RW+             =   u1 u2
-            RW+ NAME/       =   u1
-            RW+ NAME/u2     =   u2
-
-    repo bar
-            RW  =   u2
-
-    repo baz/..*
-            C   =   u3 u4
-            RW+         =   CREATOR
-            config foo.bar = baz
-
-(Note that this conf file has NAME/ rules, which **have changed**
-significantly in g3; see [here][g2i-name] for details).
-
-These are the repos already existing
-
-    $ find repositories -name "*.git" | sort
-    repositories/bar.git
-    repositories/baz/u3.git
-    repositories/baz/u4.git
-    repositories/baz/uthree.git
-    repositories/foo.git
-    repositories/gitolite-admin.git
-    repositories/testing.git
-
-The config entries exist for all the baz/ repos:
-
-    $ grep -2 foo `find repositories -name "config" `
-    repositories/baz/uthree.git/config-[gitweb]
-    repositories/baz/uthree.git/config- owner = u3
-    repositories/baz/uthree.git/config:[foo]
-    repositories/baz/uthree.git/config- bar = baz
-    --
-    repositories/baz/u4.git/config-[gitweb]
-    repositories/baz/u4.git/config-     owner = u4
-    repositories/baz/u4.git/config:[foo]
-    repositories/baz/u4.git/config-     bar = baz
-    --
-    repositories/baz/u3.git/config-[gitweb]
-    repositories/baz/u3.git/config-     owner = u3
-    repositories/baz/u3.git/config:[foo]
-    repositories/baz/u3.git/config-     bar = baz
-
-## preparing for the migration
-
-### getting g3
-
-Fortunately this is easy here; I just happened to have the repo already
-fetched so I just had to switch branches.  You may have to 'git clone ...'
-from github.
-
-    $ cd gitolite
-    $ git checkout master
-    Branch master set up to track remote branch master from origin.
-    Switched to a new branch 'master'
-
-### run check-g2-compat
-
-This is a quick and dirty program to catch some of the big issues.
-
-    $ cd
-    $ gitolite/check-g2-compat
-    INFO        This program only checks for uses that make the new g3 completely unusable
-                or that might end up giving *more* access to someone if migrated as-is.
-                It does NOT attempt to catch all the differences described in the docs.
-
-    INFO        'see docs' usually means doc/g2migr.mkd
-                (online at http://sitaramc.github.com/gitolite/g2migr.html)
-
-    checking rc file...
-    NOTE        GL_ADMINDIR is in the right place; assuming you did not mess with
-                GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED
-
-    checking conf file(s)...
-    SEVERE      NAME rules; see docs
-
-    checking repos...
-    WARNING     found 3 gl-creater files; see docs
-
-    ...all done...
-
-## the actual migration
-
-Here's the actual migration, step by step
-
-### step 1
-
-    $ ls -a bin
-    .                gl-admin-push    gl-install       gl-setup-authkeys  gl-VREF-DUPKEYS
-    ..               gl-auth-command  gl-mirror-push   gl-system-install  gl-VREF-EMAIL_CHECK
-    gitolite_env.pm  gl-compile-conf  gl-mirror-shell  gl-time            gl-VREF-FILETYPE
-    gitolite.pm      gl-conf-convert  gl-query-rc      gl-tool            gl-VREF-MERGE_CHECK
-    gitolite_rc.pm   gl-dryrun        gl-setup         gl-VREF-COUNT      sshkeys-lint
-    $ rm -rf bin;mkdir bin
-
-    $ grep GL_PACKAGE .gitolite.rc
-    $GL_PACKAGE_CONF = "/home/g3/share/gitolite/conf";
-    $GL_PACKAGE_HOOKS = "/home/g3/share/gitolite/hooks";
-    $ rm -rf share
-
-    $GL_PACKAGE_HOOKS = "/home/g3/share/gitolite/hooks";
-    $ rm -rf share
-
-    $ mv .gitolite.rc old.grc
-
-(still on step 1, this is substep 3) notice we are cloning **on the server**,
-using a **full path** to the repo.
-
-    $ git clone repositories/gitolite-admin.git old.ga
-    Cloning into 'old.ga'...
-    done.
-    $ rm -rf repositories/gitolite-admin.git/
-
-Since I'm not interested in preserving the logs and don't have any custom
-hooks:
-
-    $ rm -rf .gitolite
-
-### step 2
-
-I have no variables that *must* be preset, since the report by
-`check-g2-compat` is clear.
-
-### step 3
-
-Here we install the new gitolite.  Remember we already got the new software
-(in order to run 'check-g2-compat').
-
-Just check that bin is empty, then run 'install -ln' from the gitolite source
-tree:
-
-    $ ls -al bin
-    total 8
-    drwxrwxr-x 2 g3 g3 4096 Apr 24 10:57 .
-    drwx------ 8 g3 g3 4096 Apr 24 10:59 ..
-    $ gitolite/install -ln
-    $ ls -al bin
-    total 8
-    drwxrwxr-x 2 g3 g3 4096 Apr 24 11:01 .
-    drwx------ 8 g3 g3 4096 Apr 24 10:59 ..
-    lrwxrwxrwx 1 g3 g3   30 Apr 24 11:01 gitolite -> /home/g3/gitolite/src/gitolite
-
-OK that went well.  Now setup gitolite.  You don't need a key here; just use a
-random name:
-
-    $ gitolite setup -a admin
-    Initialized empty Git repository in /home/g3/repositories/gitolite-admin.git/
-
-### step 4
-
-Now go to your old clone, and push it:
-
-    $ cd old.ga
-    $ gitolite push -f
-        ...usual git progress output deleted...
-    remote: FATAL: git config foo.bar not allowed
-    remote: check GIT_CONFIG_KEYS in the rc file
-    To /home/g3/repositories/gitolite-admin.git
-     + 7eb8163...1474770 master -> master (forced update)
-
-Aaha!  I forgot to set `CONFIG_KEYS` (new name for `GL_GIT_CONFIG_KEYS`) in
-the new rc file so fix that:
-
-    $ vim ~/.gitolite.rc
-    (edit and set it to `.*` for now)
-
-and push again:
-
-    $ gitolite push -f
-    Everything up-to-date
-
-Damn.  We have to make a dummy commit to allow the push to do something.
-
-But wait!  We forgot fix the [NAME/][g2i-name] rules, so may as well fix
-those, add, and push:
-
-    $ vim conf/gitolite.conf
-    # change all NAME/ to VREF/NAME/
-    # append a '- VREF/NAME/ = @all' at the end
-    # save
-    git add conf
-
-    $ git commit -m name-rules
-        ... some output for add...
-
-    $ gitolite push -f
-    Counting objects: 1, done.
-    Writing objects: 100% (1/1), 181 bytes, done.
-    Total 1 (delta 0), reused 0 (delta 0)
-    Unpacking objects: 100% (1/1), done.
-    To /home/g3/repositories/gitolite-admin.git
-       1474770..4c2b41d  master -> master
-
-### step 5
-
-The only thing left is to fix up the gl-creater files:
-
-    $ cd $HOME/repositories
-    $ find . -type d -name "*.git" -prune | while read r
-    > do
-    >     mv $r/gl-creater $r/gl-creator
-    > done 2>/dev/null
-
-And we're done!
-
-## checking things out
-
-Let's see what repos u3 has:
-
-    ssh u3 info
-    hello u3, this is g3 at sita-lt running gitolite3 v3.0-11-g090b0f5 on git 1.7.7.6
-
-         C      baz/..*
-     R W        baz/u3
-     R W        baz/uthree
-     R W        gitolite-admin
-     R W        testing
-
-That's a combination of 'info' and 'expand', by the way.  There is no expand
-command any more.
-
-How about adding a new repo and checking if the config entries made it?
-
-    $ git ls-remote u4:baz/ufour
-    Initialized empty Git repository in /home/g3/repositories/baz/ufour.git/
-    $ grep -A1 foo `find repositories -name "config" `
-    repositories/baz/u3.git/config:[foo]
-    repositories/baz/u3.git/config- bar = baz
-    --
-    repositories/baz/u4.git/config:[foo]
-    repositories/baz/u4.git/config- bar = baz
-    --
-    repositories/baz/ufour.git/config:[foo]
-    repositories/baz/ufour.git/config-      bar = baz
-    --
-    repositories/baz/uthree.git/config:[foo]
-    repositories/baz/uthree.git/config-     bar = baz
-
-And there it is, in the second block of lines...
-
-And now we're really done.
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
deleted file mode 100644
index 2783cf8..0000000
--- a/doc/g2migr.mkd
+++ /dev/null
@@ -1,279 +0,0 @@
-# #g2migr migrating from g2
-
-<font color="red"> **This document is a *MUST* read if you are currently using
-g2 and want to move to g3.** </font>
-
-----
-
-[[TOC]]
-
-----
-
-First things first: g2 will be supported for a good long time for critical
-bugs, although enhancements and new features won't happen.
-
-Migration should be straightforward, but it is not automatic.  The biggest
-differences are in the rc file, mirroring, "NAME/" rules, and delegation.
-
->   ----
-
->   **Presetting the rc file**
-
->   Some rc settings in the older gitolite are such that you cannot directly
->   run `gitolite setup` when you're ready to migrate.  **Doing that will
->   clobber something important**.  See [presetting the rc file][rc-preset]
->   for details.
-
->   ----
-
-The `check-g2-compat` program attempts to identify any *big* issues you will
-be facing; run that first.  See [later][cg2c] in this page for what its
-messages mean.  If it does not report any issues, your migrate will probably
-go quickly.  I still suggest you go through the links below in case that
-program missed something.
-
-## incompatible features
-
-Here's a list of incompatible features and what you need to do to migrate.
-Some of them have links where there is more detail than I want to put here.
-
-### high impact
-
-(serious loss of functionality and/or access control compromised)
-
-  * [`NAME/`][g2i-name] rules: thes need to change to `VREF/NAME/`, and you
-    need to add a deny rule at the end because fallthru is "success" for all
-    [VREFs][vref] now, including the "NAME" VREF.
-
-  * [subconf][g2i-subconf]: if you're using [delegation][deleg], there is no
-    implicit "subconf" at the end; you'll have to add it in.
-
-  * There are several important differences in mirroring.  You can start from
-    scratch by reading the new [mirroring][mirroring] doc or
-    [migrate][g2i-mirroring] (carefully!).
-
-  * `ADMIN_POST_UPDATE_CHAINS_TO` -- **dropped**.  Add your script to the
-    `POST_COMPILE` trigger chain.  Your script won't be getting the arguments
-    that *git* sends to the post-update hook, but for the admin repo the only
-    argument that even comes in (or is significant) is "refs/heads/master"
-    anyway.
-
-  * `GL_ALL_INCLUDES_SPECIAL` -- **dropped**, **requires presetting**.
-
-    @all always includes gitweb and daemon now.  Use [deny-rules][] if you
-    want to say `R = @all` but not have the repo(s) be visible to gitweb or
-    daemon.
-
-  * `GL_NO_CREATE_REPOS` -- **dropped**.  If you think you need this, email
-    me.  I know one group who does need this so I will be putting it in
-    eventually but not right away.  It's almost certain to be renamed anyway.
-
-  * `GL_NO_DAEMON_NO_GITWEB` **dropped**, **requires presetting**.  Default
-    will clobber your projects.list file and git-daemon-export-ok files.
-
-    Comment out the appropriate lines in the rc file, in the `POST_COMPILE`
-    *and* the `POST_CREATE` trigger sections.  As a bonus, gitweb and daemon
-    can now be separately disabled, instead of both being tied to the same
-    setting.
-
-  * `GL_NO_SETUP_AUTHKEYS` **dropped**, **requires presetting**.  Default will
-    clobber your authkeys file.
-
-    Comment out all the line(s) that call ssh-authkeys in the rc file.
-
-  * `UPDATE_CHAINS_TO` **dropped**, **requires presetting**.  Default will
-    fail to run this extra check when users push.
-
-    Use a [vref][] instead.  You can directly use any existing chained-to
-    script as a VREF; they'll work.  Don't forget to add a rule that
-    references the new VREF!
-
-  * `GIT_PATH` **dropped**, **requires presetting**.
-
-    If you need its functionality, add these lines to the end of the rc file:
-
-        $ENV{PATH}="...whatever you want...";
-        1;
-
-### medium impact
-
-(important functionality lost, but access control not compromised)
-
-  * `GL_ADMINDIR` -- **dropped**, is now at a fixed location: `~/.gitolite`.
-    If you want it somewhere else go ahead and move it, then place a symlink
-    from the assumed location to the real one.
-
-  * `GL_GET_MEMBERSHIPS_PGM` -- is now `GROUPLIST_PGM`, see
-    [here][ldap].
-
-  * `GL_WILDREPOS_DEFPERMS` -- is now `DEFAULT_ROLE_PERMS`.
-
-  * `REPO_BASE` -- **dropped**, is now at a fixed location: `~/repositories`.
-    If you want it somewhere else go ahead and move it, then place a symlink
-    from the assumed location to the real one.
-
-### low impact
-
-(ancillary, non-core, or minor functionality lost)
-
-  * Built-in command `expand` -- **dropped**.  The 'info' command shows you
-    both normal and wild repos now.  The output format is also much simpler.
-
-  * Built-in commands 'getperms', 'setperms' -- **merged** into external
-    command 'perms'.  Run `ssh git at host perms -h` for details.
-
-    Similarly, 'getdesc' and 'setdesc' have been merged into 'desc'.
-
-  * Several 'ADC's -- see the [dev-status][] page for more on this.
-
-  * [gl-time][g2i-gl-time]: the CpuTime module replaces gl-time.
-
-  * `BIG_INFO_CAP` -- **dropped**.  If you think you must have this, try it
-    without and see if there's a difference.  If you *know* you need this,
-    convince me.
-
-  * `GL_ADC_PATH` -- **dropped**.  It is obsolete; use [commands][] or add
-    [your own][dev-notes].
-
-  * `GL_ALL_READ_ALL` -- **dropped**.  If you think you must have this, try it
-    without and see if there's a difference.  If you *know* you need this,
-    convince me.
-
-  * `GL_BIG_CONFIG` -- **dropped**.  This feature is default now.
-
-  * `GL_CONF`, `GL_CONF_COMPILED`, and `GL_KEYDIR` -- **dropped**.  You had no
-    business touching these anyway; if you did, move them into the expected
-    default locations before attempting to run `gitolite setup`
-
-  * `GL_GITCONFIG_KEYS` -- is now `GIT_CONFIG_KEYS`.
-
-  * `GL_LOGT` -- now has a fixed value; email me if this is a problem.
-
-  * `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` -- **dropped**.  They are not
-    needed anymore, but check if you had any custom hooks set in the latter
-    location and copy them across.
-
-  * `GL_PERFLOGT` -- **dropped**.  See [gl-time][g2i-gl-time].
-
-  * `GL_SITE_INFO` -- is now `SITE_INFO`.
-
-  * `GL_WILDREPOS` -- **dropped**.  This feature is default now.
-
-  * `GL_WILDREPOS_PERM_CATS` -- is now the ROLES hash in the rc file.
-
-  * `RSYNC_BASE` -- needs work.  Email me if you are using this.
-
-  * `NICE_VALUE` -- **dropped**.  Uncomment the 'renice 10' line in the rc
-    file.  You can also change the 10 to something else if you wish.
-
-  * `PROJECTS_LIST` -- is now `GITWEB_PROJECTS_LIST`.  More importantly, it is
-    only used by update-gitweb-access-list in src/commands/post-compile.  This
-    variable now has nothing to do with gitolite core, and the rc is just
-    helping to store settings for external programs like that one.
-
-  * `REPO_UMASK` -- is now `UMASK`.
-
-  * `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` -- **dropped**.  Patches to the
-    update program to directly do those things are welcome.  Personally, I
-    think people who use spaces and other funky characters in dir/file names
-    should be shot but no one listens to me anyway.
-
-## #cg2c using the "check-g2-compat" program
-
-This program checks a few things only, not everything.  In particular, it
-looks for settings and status that might:
-
-  * make g3 unusable for lots of users
-  * make g3 give *more* access than g2 under some conditions
-
-It does NOT look for or warn about anything else; you're expected to read (and
-act upon, if needed) the rest of the migration guide links given a few paras
-above to cover everything else.
-
-Here's an explanation of those messages that the check-g2-compat program may
-put that contain the words "see docs":
-
-  * `GL_ADMINDIR in the wrong place -- aborting`
-
-    It expects to find `GL_ADMINDIR` and `REPO_BASE` pointing to the right
-    places.  It aborts if these conditions are not met and does not scan
-    further since that sort of guesswork is not good.  If you are in that
-    position, make a symlink from the real location to the expected location,
-    change the RC accordingly, and re-try.
-
-  * `REPO_BASE in the wrong place -- aborting`
-
-    same as above
-
-  * `NAME rules`
-
-    **This is a significant difference and affects access badly (gives access
-    that would otherwise not be given)**.  Please see the [list of non-RC
-    incompatibilities][g2incompat].
-
-  * `subconf command in admin repo`
-
-    This is not so bad security wise but it might *reduce* access by not
-    processing files you intended to.  Again, see the same link as in the
-    previous bullet.
-
-  * `mirroring used`
-
-    There have been quite a few changes to mirroring.  You can start from
-    scratch by reading the new [mirroring][mirroring] doc or
-    [migrate][g2i-mirroring] (carefully!).
-
-  * `found N gl-creater files`
-
-    These need to be renamed to `gl-creator` (the correct spelling at last,
-    hooray!).  Suggested command sequence:
-
-        cd $HOME/repositories
-        find . -type d -name "*.git" -prune | while read r
-        do
-            mv $r/gl-creater $r/gl-creator
-        done 2>/dev/null
-
-    Once you do this, the g2 will not work completely unless you change them
-    back.
-
-  * `found N gl-perms files with R or RW`
-
-    Setting perms of R and RW will no longer work; you have to say READERS and
-    WRITERS now.  Suggested command:
-
-        find `gitolite query-rc GL_REPO_BASE` -name gl-perms |
-            xargs perl -pi -e 's/\bR\b/READERS/;s/\bRW\b/WRITERS/'
-
-## #rc-preset presetting the rc file
-
-Some rc settings in the older gitolite are such that you cannot directly run
-`gitolite setup` when you're ready to migrate.  **Doing that will clobber
-something important**.  You have to create a default rc file, edit it
-appropriately, and *then* run `gitolite setup`.
-
-The most serious example of this is `GL_NO_SETUP_AUTHKEYS`, which tells the
-(old) gitolite that you want to manage `~/.ssh/authorized_keys` yourself and
-it should not fiddle with it.
-
-If you don't preset the rc (in this case, by commenting out the 'ssh-authkeys'
-line) **before** running `gitolite setup`, **your `~/.ssh/authorized_keys`
-file will get clobbered**.
-
-The actual rc settings that require presetting are listed in the "high
-impact" section above.  This section tells you how to do the presetting.
-
-  * save your old (g2) rc file somewhere, just in case
-
-  * run
-
-        gitolite print-default-rc > $HOME/.gitolite.rc
-
-  * edit the file
-
-        ${EDITOR:-vim} $HOME/.gitolite.rc
-
-    make appropriate changes as described elsewhere in this migration guide,
-    and save it.
-
-  * *then* you can run [gitolite setup][setup].
diff --git a/doc/g3why.mkd b/doc/g3why.mkd
deleted file mode 100644
index ae6cad0..0000000
--- a/doc/g3why.mkd
+++ /dev/null
@@ -1,97 +0,0 @@
-# why a completely new version?
-
-Gitolite started life as 400 lines of perl written on a weekend because I was
-quickly losing control of my projects at work, exacerbated by git newbies
-doing all the wrong things.  I really needed it!
-
-That little 400 line thing is now a huge bunch of programs that do all sorts
-of things (I mean, rsync access control in a git related program?  WTF!),
-because it kinda just *grew* while I wasn't looking.
-
-So, briefly, here are the advantages of g3:
-
-  * compile versus everything else
-
-    g2's "compile" script was doing way, way too much.  For example, dealing
-    with gitweb and git-daemon was a good chunk of code in g2.  In contrast,
-    here's how g3 generates gitweb's projects.list file:
-
-        (
-            gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
-            gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
-        ) |
-            cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
-
-  * core versus non-core
-
-    That's just the tip of the iceberg.  The commands above run from a script
-    that is itself outside gitolite, and can be enabled and disabled from the
-    rc file.  There are six different "events" within gitolite that can
-    trigger external programs, with specific arguments passed to them, much
-    like git's own hooks.  The example you saw is called from the
-    "POST_COMPILE" trigger.
-
-    And as you can see, these programs be in any language.
-
-  * get/set perms/desc, and ADCs
-
-    I've always wanted to kick setperms out of core and make it an ADC.
-    Sadly, it couldn't be done because when you update your repo's permissions
-    using setperms, that can affect gitweb/daemon access, which -- you guessed
-    right -- feeds back into the main code in complex ways.  It *had* to be an
-    "inside job".
-
-    But now, the new 'perms' program is quite external to gitolite.  And how
-    does it fix up gitweb/daemon permissions after it is done updating the
-    "gl-perms" file?
-
-        system("gitolite", "trigger", "POST_CREATE");
-
-  * syntax versus semantics
-
-    I got tired of people asking things like "why can't I have
-    backslash-escaped continuation lines?"  I designed it differently because
-    I don't like them but perhaps it's reasonable for some people.
-
-    Someone else wanted to use subdirectories of 'keydir' as group names.  Why
-    not?
-
-    G3 comes with a stackable set of "syntactic sugar" helpers.  And you can
-    write your own, though they do have to be in perl (because they're not
-    standalone programs).
-
-    Once the code is written and placed in the right place, all a site has to
-    do to enable it is to uncomment some lines in the rc file:
-
-        # these will run in sequence during the conf file parse
-        SYNTACTIC_SUGAR             =>
-            [
-                # 'continuation-lines',
-                # 'keysubdirs-as-groups',
-            <etc>
-
-  * roll your own
-
-    Having a decent shell API helps enormously.  You saw an example above but
-    how about if your boss asks you "I need a list of everyone who *currently*
-    has read access to the 'foo' repo"?
-
-    Sure you could look in conf/gitolite.conf, all its include files (if you
-    have any), and if the repo is user-created, then in its gl-perms.
-
-    Or you could do something like this:
-
-        gitolite list-users | gitolite access foo % R any | cut -f1
-
-  * over-engineered
-
-    g2 was, to some extent, over-engineered.  One of the best examples is the
-    documentation on hook-propagation in g2, which required even a *picture*
-    to make clear (always a bad sign).  In g3, the [hooks][] section is 4
-    sentences.
-
-Anyway you get the idea.
-
-The point is not that you can do all these cool tricks.  The point is they are
-possible because of the redesign.  There is no way on God's green earth I
-could have done this with the old code.
diff --git a/doc/git-config.mkd b/doc/git-config.mkd
deleted file mode 100644
index 94487e1..0000000
--- a/doc/git-config.mkd
+++ /dev/null
@@ -1,66 +0,0 @@
-# "git-config" keys and values
-
-Here's all you want to know about setting repo-specific git-config values.
-
-(Original version thanks to teemu dot matilainen at iki dot fi)
-
->   ----
-
->   **Note**: this won't work unless the rc file has the right settings;
->   please see `$GIT_CONFIG_KEYS` in the [rc file doc][rc].
-
->   ----
-
-The syntax is simple:
-
-    config sectionname.keyname = [optional value_string]
-
-For example:
-
-    repo gitolite
-        config hooks.mailinglist = gitolite-commits at example.tld
-        config hooks.emailprefix = "[gitolite] "
-        config foo.bar = ""
-        config foo.baz =
-
-This does either a plain "git config section.key value" (for the first 2
-examples above) or "git config --unset-all section.key" (for the last 2
-examples).  Other forms of the `git config` command (`--add`, the
-`value_regex`, etc) are not supported.
-
->   ----
-
->   **WARNING**: simply deleting the config line from the `conf/gitolite.conf`
->   file will *not* delete the variable from `repo.git/config`.  The syntax in
->   the last example is the *only* way to make gitolite execute a
->   `--unset-all` operation on the given key.
-
->   ----
-
-You can also use the special value `%GL_REPO` in the string to save typing:
-
-    repo foo bar baz
-        config hooks.mailinglist = %GL_REPO-commits at example.tld
-        config hooks.emailprefix = "[%GL_REPO] "
-
-You can repeat the 'config' line as many times as you like, and the last
-occurrence will be the one in effect.  This allows you to override settings
-just for one project, as in this example:
-
-    repo @all
-        config hooks.mailinglist = %GL_REPO-commits at example.tld
-        config hooks.emailprefix = "[%GL_REPO] "
-
-    repo customer-project
-        # different mailing list
-        config hooks.mailinglist = announce at customer.tld
-
-The "delete config variable" syntax can also be used, if you wish:
-
-    repo secret     # no emails for this one please
-        config hooks.mailinglist =
-        config hooks.emailprefix =
-
-As you can see, the general idea is to place the most generic ones (`repo
- at all`, or repo patterns like `repo foo.*`) first, and place more specific ones
-later to override the generic settings.
diff --git a/doc/groups.mkd b/doc/groups.mkd
deleted file mode 100644
index 970ea1d..0000000
--- a/doc/groups.mkd
+++ /dev/null
@@ -1,30 +0,0 @@
-# group definitions
-
-You can group repos or users for convenience.  The syntax is the same for both
-and does not distinguish; until you *use* the group name it could really be
-either.
-
-Here's an example:
-
-    @developers     =   dilbert alice wally
-
-Group definitions accumulate; this is the same as the above:
-
-    @developers     =   dilbert
-    @developers     =   alice
-    @developers     =   wally
-
-You can use one group in another group definition; the values will be expanded
-right there (meaning later additions will not appear in the second group).
-
-    @developers     =   dilbert alice
-    @interns        =   ashok
-    @staff          =   @interns @developers
-    @developers     =   wally
-
-    # wally is NOT part of @staff
-
-## special group `@all`
-
-`@all` is a special group name that is often convenient to use if you really
-mean "all repos" or "all users".
diff --git a/doc/gsmigr.mkd b/doc/gsmigr.mkd
deleted file mode 100644
index 0f54bbb..0000000
--- a/doc/gsmigr.mkd
+++ /dev/null
@@ -1,125 +0,0 @@
-# migrating from gitosis to gitolite
-
-[2012-04-09] Modified for gitolite g3.  These instructions have not really
-been tested with g3, but are expected to work.
-
-Migrating from gitosis to gitolite is fairly easy, because the basic design is
-the same.
-
-There's only one thing that might trip up people: the userid.  Gitosis uses
-`gitosis`.  Gitolite can use any userid you want; most of the documentation
-uses `git`, while DEB/RPM packages use `gitolite`.
-
-Here are the steps on the server:
-
-  * (as 'gitosis' on the server) **Rename** `~/.ssh/authorized_keys` to
-    something else so that no one can accidentally push while you're doing
-    this.
-
-  * (as 'gitosis' on the server) For added safety, **delete** the post-update
-    hook that gitosis-admin installed
-
-        rm ~/repositories/gitosis-admin.git/hooks/post-update
-
-    or at least rename it to `.sample` like all the other hooks hanging
-    around, or edit it and comment out the line that calls `gitosis-run-hook
-    post-update`.
-
-  * (as 'gitosis' on the server) If you already use the `update` hook for some
-    reason, you'll have to make that a [VREF][vref].  This is because gitolite
-    uses the update hook for checking write access.
-
-  * (as 'root' on the server) copy all of `~/repositories` to the gitolite
-    hosting user's home directory.  Something like
-
-        cp -a /home/gitosis/repositories /home/git
-        chown -R git.git /home/git/repositories
-
-  * (as 'root' and/or 'git' on the server) Follow instructions to install
-    gitolite; see the [install document][install].
-
-    This will give you a gitolite config that has the required entries for the
-    "gitolite-admin" repo.
-
-Now, log off the server and get back to the client.  All subsequent
-instructions are to be read as "on gitolite admin's workstation".
-
-  * **Clone** the new gitolite-admin repo to your workstation.  (You already
-    have a clone of the gitosis-admin repo so now you have both).
-
-  * **Convert** your gitosis config file and append it to your gitolite config
-    file.  Substitute the path for your gitosis-admin clone in `$GSAC` below,
-    and similarly the path for your gito**lite**-admin clone in `$GLAC`.  (The
-    convert-gitosis-conf program is a standalone program that you can bring
-    over from any gitolite clone; you don't have to install all of gitolite on
-    your workstation to use this):
-
-        ./convert-gitosis-conf < $GSAC/gitosis.conf >> $GLAC/conf/gitolite.conf
-
-    **Be sure to check the file to make sure it converted correctly** -- due
-    it's "I only need it once" nature, this program has not received too much
-    attention from anyone!
-
-  * Remove the entry for the 'gitosis-admin' repo.  You do not need it here
-    and it may cause confusion.
-
-  * **Copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC)
-
-        cp $GSAC/keydir/* $GLAC/keydir
-
-    If your gitosis-admin key was `you at machine.pub`, and you supplied the same
-    one to gitolite's gl-setup program as `you.pub` when you installed
-    gitolite, then you should remove `you at machine.pub` from the new keydir
-    now.  Otherwise you will have 2 pubkey files (`you.pub` and
-    `you at machine.pub`) which are identical, which is *not* a good idea.
-
-    Similarly, you should replace all occurrences of `you at machine.pub` with
-    `you` in the `conf/gitolite.conf` file.
-
-  * **IMPORTANT**: if you have any users with names like `user at foo`, where the
-    part after the `@` does *not* have a `.` in it (i.e., does not look like
-    an email address), you need to change them, because gitolite uses that
-    syntax for [enabling multi keys][multi-key].
-
-    You have two choices in how to fix this.  You can change the gitolite
-    config so that all mention of `user at foo` is changed to just `user`.
-
-    Or you can change each occurrence of `user at foo` to, say, `user_foo` *and*
-    change the pubkey filename in keydir/ also the same way (`user_foo.pub`).
-
-    Just to repeat, you do NOT need to do this if the username was like
-    `user at foo.bar`, i.e., the part after the `@` had a `.` in it, because then
-    it looks like an email address.
-
-  * **IMPORTANT: expand any multi-key files you may have**.  Gitosis is happy
-    to accept files containing more than one public key (one per line) and
-    assign all the keys to the same user.  Gitolite does not allow that; see
-    [here][multi-key]'s for how gitolite handles multi-keys.
-
-    So if you had any multi-keys in gitosis, they need to be carefully split
-    into individual keys.
-
-    You can split the keys manually, or use the following code (just
-    copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo
-    clone):
-
-        wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b
-        do
-            i=1
-            cat $b|while read l
-            do
-                echo "$l" > ${b%.pub}@$i.pub
-                (( i++ ))
-            done
-            mv $b $b.done
-        done
-
-    This will split each multi-key file (say "sitaram.pub") into individual
-    files called "sitaram at 1.pub", "sitaram at 2.pub", etc., and rename the
-    original to "sitaram.pub.done" so gitolite won't pick it up.
-
-    At this point you can rename the split parts more appropriately, like
-    "sitaram at laptop.pub" and "sitaram at desktop.pub" or whatever.  *Please check
-    the files to make sure this worked properly*
-
-  * Check all your changes to your gitolite-admin clone, commit, and push.
diff --git a/doc/http.mkd b/doc/http.mkd
deleted file mode 100644
index 3b9809e..0000000
--- a/doc/http.mkd
+++ /dev/null
@@ -1,157 +0,0 @@
-# how to setup gitolite to use smart http mode
-
-**Note**: "smart http" refers to the feature that came with git 1.6.6, late
-2009 or so.  The base documentation for this is `man git-http-backend`.  Do
-**NOT** read `Documentation/howto/setup-git-server-over-http.txt` and think
-that is the same or even relevant -- that is from 2006 and is quite different
-(and arguably obsolete).
-
-## WARNINGS and important notes
-
-  * Please read [authentication versus authorisation][auth] first, and make
-    sure you understand what is gitolite's responsibility and what isn't.
-
-  * I have tested this only on stock Fedora 16; YDMV.
-
-## assumptions:
-
-  * Apache 2.x and git installed.
-  * Httpd runs under the "apache" userid; adjust instructions below if not.
-  * Similarly for "/var/www" and other file names/locations.
-
-## instructions
-
-The detailed instructions I used to have in g2 have now been replaced by a
-script called `t/smart-http.root-setup`.  **Do NOT run this script as is -- it
-is actually meant for my testing setup and deletes stuff**.  However, it does
-provide an excellent (and working!) narration of what you need to do to
-install gitolite in smart http mode.
-
-Make a copy of the script, go through it carefully, (possibly removing lines
-that delete files etc.), change values per your system, and only then run it.
-
-<font color="gray">Note that the `GIT_PROJECT_ROOT` variable (see "man
-git-http-backend") is no longer optional.  Make sure you set it to some place
-outside apache's `DOCUMENT_ROOT`.</font>
-
-## Making repositories available to both ssh and http mode clients
-
-This section has been contributed by Thomas Hager (duke at sigsegv dot at).
-
-Assumptions:
-
-  * Apache 2.x with CGI and Suexec support installed.
-  * Git and Gitolite installed with user "git" and group "git", and pubkey SSH
-    access configured and working.
-  * Git plumbing installed to /usr/libexec/git-core
-  * Gitolite base located at /opt/git
-  * Apache `DOCUMENT_ROOT` set to /var/www
-  * Apache runs with user www and group www
-
-Please adjust the instructions below to reflect your setup (users and paths).
-
-Edit your .gitolite.rc and add
-
-    $ENV{PATH} .= ":/opt/git/bin";
-
-at the very top (as described in `t/smart-http.root-setup`).
-
-Next, check which document root your Apache's suexec accepts:
-
-    # suexec -V
-     -D AP_DOC_ROOT="/var/www"
-     -D AP_GID_MIN=100
-     -D AP_HTTPD_USER="www"
-     -D AP_LOG_EXEC="/var/log/apache/suexec.log"
-     -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
-     -D AP_UID_MIN=100
-     -D AP_USERDIR_SUFFIX="public_html"
-
-We're interested in `AP_DOC_ROOT`, which is set to `/var/www` in our case.
-
-Create a `bin` and a `git` directory in `AP_DOC_ROOT`:
-
-    install -d -m 0755 -o git -g git /var/www/bin
-    install -d -m 0755 -o www -g www /var/www/git
-
-`/var/www/git` is just a dummy directory used as Apache's document root (see below).
-
-Next, create a shell script inside `/var/www/bin` named `gitolite-suexec-wrapper.sh`,
-with mode **0700** and owned by user and group **git**. Add the following content:
-
-    #!/bin/bash
-    #
-    # Suexec wrapper for gitolite-shell
-    #
-
-    export GIT_PROJECT_ROOT="/opt/git/repositories"
-    export GITOLITE_HTTP_HOME="/opt/git"
-
-    exec ${GITOLITE_HTTP_HOME}/gitolite-source/src/gitolite-shell
-
-Edit your Apache's config to add http pull/push support, preferably in
-a dedicated `VirtualHost` section:
-
-    <VirtualHost *:80>
-        ServerName        git.example.com
-        ServerAlias       git
-        ServerAdmin       you at example.com
-
-        DocumentRoot /var/www/git
-        <Directory /var/www/git>
-            Options       None
-            AllowOverride none
-            Order         allow,deny
-            Allow         from all
-        </Directory>
-
-        SuexecUserGroup git git
-        ScriptAlias /git/ /var/www/bin/gitolite-suexec-wrapper.sh/
-        ScriptAlias /gitmob/ /var/www/bin/gitolite-suexec-wrapper.sh/
-
-        <Location /git>
-            AuthType Basic
-            AuthName "Git Access"
-            Require valid-user
-            AuthUserFile /etc/apache/git.passwd
-        </Location>
-    </VirtualHost>
-
-This Apache config is just an example, you probably should adapt the authentication
-section and use https instead of http!
-
-Finally, add an `R = daemon` access rule to all repositories you want to
-make available via http.
-
-## usage
-
-### client side
-
-Git URLs look like `http://user:password@server/git/reponame.git`.
-
-The custom commands, like "info", "expand" should be handled as follows.  The
-command name will come just after the `/git/`, followed by a `?`, followed by
-the arguments, with `+` representing a space.  Here are some examples:
-
-    # ssh git at server info
-    curl http://user:password@server/git/info
-    # ssh git at server info repopatt
-    curl http://user:password@server/git/info?repopatt
-    # ssh git at server info repopatt user1 user2
-    curl http://user:password@server/git/info?repopatt+user1+user2
-
-With a few nice shell aliases, you won't even notice the horrible convolutions
-here ;-)  See t/smart-http for a couple of useful ones.
-
-### server side
-
-The 'gitolite' command (for example, 'gitolite compile', 'gitolite query-rc',
-and so on) *can* be run on the server, but it's not straightforward.  Assuming
-you installed exactly as given in this document, you should
-
-  * get a shell by using, say, `su -s /bin/bash - apache`
-  * run `export HOME=$HOME/gitolite-home`
-  * run `export PATH=$PATH:$HOME/bin`
-
-and *then* you can run `gitolite <subcommand>`
-
diff --git a/doc/index.mkd b/doc/index.mkd
deleted file mode 100644
index 9552975..0000000
--- a/doc/index.mkd
+++ /dev/null
@@ -1,66 +0,0 @@
-# Hosting git repositories
-
-Gitolite allows you to setup git hosting on a central server, with
-fine-grained access control and many more powerful features.
-
-Here's more on [what][] it is and [why][] you might need it.
-
-<font color="gray">
-
-## #g2 (for older gitolite (v1.x and v2.x) users)
-
-For users of gitolite v2.x (call it "g2" for convenience),
-
-  * [Why][g3why] I rewrote gitolite.
-  * Development [status][dev-status].
-  * Information on [migrating][migr] from g2 to g3.
-
-</font>
-
-## #ql quick links
-
-  * [Trying][trying] out gitolite.
-  * Minimum [requirements][req].
-  * Here's how to do a [quick install, setup, and clone][qi].
-  * Don't know ssh well enough?  [Learn][ssh].  It's **IMPORTANT**.
-  * Add [users][] and [repos][].
-  * Learn about fine-grained access control with the [conf][] file.
-  * Explain gitolite to your [users][user].
-
-The rest of the documentation is in the "master index" link at the top of each
-page on this website.  This is the first place you should search when looking
-for specific information.
-
-## #what What is gitolite?
-
-Gitolite is an access control layer on top of git.  Here are the features that
-most people see:
-
-  * Use a single unix user ("real" user) on the server.
-  * Provide access to many gitolite users:
-      * they are not "real" users,
-      * they do not get shell access.
-  * Control access to many git repositories:
-      * read access controlled at the repo level,
-      * write access controlled at the branch/tag/file/directory level,
-        including who can rewind, create, and delete branches/tags.
-  * Can be installed without root access, assuming git and perl are already
-    installed.
-  * Authentication is most commonly done using sshd, but you can also use
-    [http][] if you prefer (this may require root access).
-
-## #contact contact
-
-  * author: sitaramc at gmail.com, sitaram at atc.tcs.com
-  * mailing list: gitolite at googlegroups.com
-  * list subscribe address : gitolite+subscribe at googlegroups.com
-  * IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
-    time zone).
-
-## #license license
-
-The gitolite *code* is released under GPL v2.  See COPYING for details.
-
-The gitolite documentation is provided under a [Creative Commons
-Attribution-NonCommercial-ShareAlike 3.0 Unported
-License](http://creativecommons.org/licenses/by-nc-sa/3.0/).
diff --git a/doc/install.mkd b/doc/install.mkd
deleted file mode 100644
index 519ab52..0000000
--- a/doc/install.mkd
+++ /dev/null
@@ -1,265 +0,0 @@
-# installing gitolite
-
-<font color="red">**NOTE**: if you're migrating from g2, there are some
-settings that MUST be dealt with **before** running `gitolite setup`; please
-start [here][migr].  RTFM is *mandatory* for migrations.</font>
-
-----
-
-This is the first step in using gitolite, and happens on the server.  It is
-followed by [setup][], then [clone][].
-
-----
-
-[[TOC]]
-
-----
-
-## notes and naming conventions
-
-Gitolite uses a single "real" (i.e., unix) user to provide secure access to
-git repos to any number of "virtual" users, without giving them a shell.
-
-The real user used is called the **hosting user**.  Typically this user is
-*git*, and that is what we will use throughout the documentation.  However
-RPMs and DEBs create a user called *gitolite* for this, so adjust instructions
-and examples accordingly.
-
-**Unless otherwise stated, everything in this page is to be done by logging in
-as this "hosting user"**.
-
-Notes:
-
-  * Any unix user can be a hosting user.
-  * Which also means you can have several hosting users on the same machine.
-  * The URLs used will be of the form `git at host:reponame` (or its longer
-    equivalent starting with `ssh://`).  The `.git` at the end is optional.  I
-    recommend you leave it out, so your reponames are consistent with what the
-    conf file uses.
-
-## #req requirements
-
-### your skills
-
-  * If you're installing gitolite, you're a "system admin", like it or not.
-    Ssh is therefore a necessary skill.  Please take the time to learn at
-    least enough to get passwordless access working.
-
-  * You also need to be somewhat familiar with git itself.  You cannot
-    administer a whole bunch of git repositories if you don't know the basics
-    of git.
-
-  * Some familiarity with Unix and shells is probably required.
-
-  * Regular expressions are a big part of gitolite in many places but
-    familiarity is not necessary to do *basic* access control.
-
-### server
-
-  * Any Unix system with a posix compatible "sh".
-  * Git version 1.6.6 or later.
-  * Perl 5.8.8 or later.
-  * Openssh (almost any version).  Optional if you're using [smart
-    http][http].
-  * A dedicated Unix userid to be the hosting user, usually "git" but it can
-    be any user, even your own normal one.  (If you're using an RPM/DEB the
-    install probably created one called "gitolite").
-
-Also see the [WARNINGS][] page for more on what gitolite expects on the server
-side.
-
-### client
-
-  * Openssh client.
-  * Git 1.6.6 or later.  Almost any git client will work, as long as it knows
-    how to use ssh keys and send the right one along.
-
-## getting the software
-
-    git clone git://github.com/sitaramc/gitolite
-
-## the actual install
-
-**Note**: This section describes installing an ssh-based setup.  For smart
-http setup click [here][http].
-
-Gitolite has only one server side "command" now, much like git itself.  This
-command is `gitolite`.  You don't need to place it anywhere special; worst
-case you run it with the full path.
-
-"Installation" consists of the following options:
-
-1.  Keep the sources anywhere and use the full path to run the `gitolite`
-    command.
-2.  Keep the sources anywhere and symlink *just* the `gitolite` program to
-    some directory on your `$PATH`.
-3.  Copy the sources somewhere and use that path to run the `gitolite`
-    command.
-
-Option 2 is the best for general use.
-
-There is a program called 'install' that helps you do these easily.  Assuming
-your cloned the repo like this:
-
-    git clone git://github.com/sitaramc/gitolite
-
-you can run the 'install' command in 3 different ways:
-
-    # option 1
-    gitolite/install
-
-    # option 2
-    gitolite/install -ln
-    # defaults to $HOME/bin (which is assumed to exist)
-    #   ** or **
-    # or use a specific directory (please supply full path):
-    gitolite/install -ln /usr/local/bin
-
-    # option 3
-    # (again, please supply a full path)
-    gitolite/install -to /usr/local/gitolite/bin
-
-Creating a symlink doesn't need a separate program but 'install' also runs
-`git describe` to create a VERSION file, which, trust me, is important!
-
-**Next step**: run [**setup**][setup].
-
-## upgrading
-
-  * Update your clone of the gitolite source.
-  * Repeat the install command you used earlier (make sure you use the same
-    arguments as before).
-  * Run `gitolite setup`.
-
-## #package packaging gitolite
-
-Gitolite has broad similarities to git in terms of packaging requirements.
-
-  * Git has 150 executables to marshal and put somewhere.  Gitolite has the
-    directories `commands`, `lib`, `syntactic-sugar`, `triggers`, and `VREF`.
-
-    It doesn't matter what this directory is.  As an example, Fedora keeps
-    git's 150 executables in /usr/libexec/git-core, so /usr/libexec/gitolite
-    may be a good choice; it's upto you.
-
-    *The rest of this section will assume you chose /usr/libexec/gitolite as
-    the location, and that this location contains the 5 directories named
-    above*.
-
-  * Git has the `GIT_EXEC_PATH` env var to point to this directory.  Gitolite
-    has `GL_BINDIR`.  However, in git, the "make" process embeds a suitable
-    default into the binary, making the env var optional.
-
-With that said, here's one way to package gitolite:
-
-  * Put the executable `gitolite` somewhere in PATH.  Put the executable
-    `gitolite-shell` in /usr/libexec/gitolite (along with those 5 directories).
-
-    Change the 2 assignments to `$ENV{GL_BINDIR}`, one in 'gitolite', one in
-    'gitolite-shell', to "/usr/libexec/gitolite" from `$FindBin::RealBin`.
-    This is equivalent to "make" embedding the exec-path into the executable.
-
-    **OR**
-
-    Put both executables `gitolite` and `gitolite-shell` also into
-    /usr/libexec/gitolite (i.e., as siblings to the 5 directories mentioned
-    above).  Then *symlink* `/usr/libexec/gitolite/gitolite` to some directory
-    in the PATH.  Do not *copy* it; it must be a symlink.
-
-    Gitolite will find the exec-path by following the symlink.
-
-  * The `Gitolite` subdirectory in `/usr/libexec/gitolite/lib` can stay right
-    there, **OR**, if your distro policies don't allow that, can be put in any
-    directory in perl's `@INC` path (such as `/usr/share/perl5/vendor_perl`).
-
-  * Finally, a file called `/usr/libexec/gitolite/VERSION` must contain a
-    suitable version string.
-
-## #migr migrating
-
-<font color="gray">This section is about migrating from older gitolite to
-"g3".  If you're migrating from gitosis, see [here][gsmigr].</font>
-
-First things first: g2 will be supported for a good long time for critical
-bugs, although enhancements and new features won't happen.
-
-If you're an existing (gitolite v1.x or v2.x) user, and wish to migrate , here
-are the steps:
-
-### pre-migration checks
-
-1.  Check the [dev-status][] page to make sure all the features you want have
-    been implemented in g3.
-
-2.  Read the [g2 migration][g2migr] page to see what changes affect you and
-    your users, and how much time it might take you to migrate.  (The closer
-    you were to a default install of the old gitolite, the less time a
-    migration will take.)
-
-    This includes at least running `check-g2-compat` to see what are the big
-    issues you might need to address during the migration.
-
-### the actual migration
-
-(Note: You may also like the [example migration][g2migr-example] page).
-
-**Note**: nothing in any of the gitolite install/setup/etc will ever touch the
-*data* in any repository except the gitolite-admin repo.  The only thing it
-will normally touch in normal repos is the `update` hook.
-
-**Note: all migration happens on the server; you do not need your
-workstation**.
-
-1.  Carefully wipe out the old gitolite:
-
-      * The **code**
-
-          * Delete or move away all the old gitolite scripts.  Check the path
-            to the gl-auth-command in `~/.ssh/authorized_keys` if you forgot
-            where you put them.
-
-          * Delete or move away the two directories named in the two variables
-            `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` in `~/.gitolite.rc`.
-
-      * The **rc file**
-
-          * Rename `~/.gitolite.rc` to something else.
-
-      * The **admin repo**
-
-          * clone `~/repositories/gitolite-admin.git` to someplace safe
-          * then delete `~/repositories/gitolite-admin.git`
-
-        (Make sure you do not delete any other repos!)
-
-      * The **admin directory**.
-
-          * If you need to preserve logs, move the ~/.gitolite/logs` directory
-            somewhere else.
-
-          * If you added any custom hooks and wish to preserve them, move the
-            ~/.gitolite/hooks` directory somewhere else.
-
-          * Delete `~/.gitolite`.
-
-2.  Read about [presetting][rc-preset] the rc file; if you're using any
-    variables listed as requiring preset, follow those instructions to create
-    your new rc file.
-
-3.  Install gitolite g3; see [quick install and setup][qi] or [install][]
-    followed by [setup][].  However, the 'setup' step need not supply a
-    private key.  You can run it as `gitolite setup -a admin`.
-
-    NOTE: ignore any 'split conf not set, gl-conf present...' errors at this
-    time.  You may see none, some, or many.  It does not matter right now.
-
-4.  Make sure your gitolite-admin clone has the correct pubkey for the
-    administrator in its `keydir` directory, then run [`gitolite push
-    -f`][bypass] to overwrite the "default" admin repo created by the install.
-
-5.  Handle any errors, look for migration issues, etc., as described in the
-    links at the top of this page.
-
-    This also includes building up your new `~/.gitolite.rc` file.
-
-You're done.
diff --git a/doc/ips.mkd b/doc/ips.mkd
deleted file mode 100644
index dcbff02..0000000
--- a/doc/ips.mkd
+++ /dev/null
@@ -1,104 +0,0 @@
-## idiot-proof setup for gitolite
-
-WARNING 1: this document uses my new, Linus-inspired, motto: people who get
-offended, *should* be offended.
-
-WARNING 2: contains more words in ALL CAPS than all my other documents put
-together.
-
-WARNING 3: this document will work for any Linux on which git has already been
-installed.  BSDs, or legacy systems like Solaris, also should work but I can't
-guarantee it or test it.
-
-----
-
-Some people seem to get terribly, **terribly** confused by ssh.  If you've
-read through all the [documentation on ssh][ssh] that came with gitolite, yet
-you *still* cannot get things to work, you have two choices:
-
-1.  exchange your "developer" badge for a "manager" badge and then hire
-    someone to do this.
-
-2.  start over with the instructions below.  They include some DRASTIC
-    measures, requirements, and restrictions, almost NONE of which are
-    normally necessary, but it's either that or a suit and tie from tomorrow
-    so play along.
-
-**IMPORTANT**: Do NOT ask for help on these instructions unless you have kept
-a detailed log of every command you typed, and the complete response you got.
-I do not ask for this information to help you -- that's only a front.  I
-*know* these instructions work (at least on any Linux that already has git
-installed), so the real reason is to find where you mistyped something and
-mock you for that,
-
-### Assumptions
-
-  * your name is Ron.  Substitute accordingly in the instructions below.
-
-  * you have a workstation.
-
-  * you have a server called `server`.
-
-  * you have root access on this server.
-
-### Tasks
-
-1.  Create a new userid on the server, say `git`.  This will be the **hosting
-    user**.  ("hosting user" means when you're done installing, your users
-    will use URLs like `git at server:reponame` or `ssh://git@server/reponame`).
-
-    **Make sure this is a NEW userid**.
-
-    If the name you want already exists, then:
-
-      * log in as root
-      * if you have any data on that user's HOME directory save it somewhere
-        else
-      * delete the userid
-      * obliterate the home directory of the user (since on most systems
-        merely deleting the user does not remove the home directory).
-      * re-create the userid again
-
-2.  If you don't already have one, make yourself an ssh keypair **on your
-    workstation**.
-
-    Do NOT, in a fit of inspiration and energy, add this public key to the
-    authorised keys file on the newly created hosting user!
-
-    Your ONLY access to the new (`git`) userid should be by logging onto the
-    server as root, then running `su - git`.
-
-3.  Now copy the pubkey from your workstation (`~/.ssh/id_rsa.pub`) to the
-    server as `/tmp/ron.pub`.  (Your name is Ron, remember?)
-
-4.  Log on to the server as root.
-
-5.  Switch to the `git` user:
-
-        su - git
-
-6.  Clone the gitolite source code
-
-        git clone git://github.com/sitaramc/gitolite
-
-7.  Install it
-
-        cd $HOME
-        mkdir -p bin
-        gitolite/install -to $HOME/bin
-
-8.  Set it up
-
-        cd $HOME
-        $HOME/bin/gitolite setup -pk /tmp/ron.pub
-
-9.  Now go to your workstation and type in
-
-        git ls-remote git at server:gitolite-admin
-
-    This should return something like
-
-        9dd8aab60bac5e54ccf887a87b4f3d35c96b05e4    HEAD
-        9dd8aab60bac5e54ccf887a87b4f3d35c96b05e4    refs/heads/master
-
-    (do I have to mention that your SHAs will be different?)
diff --git a/doc/locking.mkd b/doc/locking.mkd
deleted file mode 100644
index 2799003..0000000
--- a/doc/locking.mkd
+++ /dev/null
@@ -1,160 +0,0 @@
-# locking binary files
-
-Locking is useful to make sure that binary files (office docs, images, ...)
-don't get into a merge state.  (<font color="gray">If you think it's not a big
-deal, you have never manually merged independent changes to an ODT or
-something!</font>)
-
-When git is used in a truly distributed fashion, locking is impossible.
-However, in most corporate setups, there is a single central server acting as
-the canonical source of truth and collaboration point for all developers.  In
-this situation it should be possible to at least prevent commits from being
-pushed that contains changes to files locked by someone else.
-
-The two "lock" programs (one a command that a user uses, and one a VREF that
-the admin adds to a repo's access rules) together attempt to achieve this.
-
-Of course, locking by itself is not quite enough.  You may still get into
-merge situations if you make changes in branches.  For best results you should
-actually keep all the binary files in their own branch, separate from the ones
-containing source code.
-
-----
-
-[[TOC]]
-
-## problem description
-
-Our users are alice, bob, and carol.  Our repo is foo.  It has some "odt"
-files in the "doc/" directory.  We want to make sure these odt files never get
-into a "merge" situation.
-
-## admin/setup
-
-First, someone with shell access to the server must add 'lock' to the
-"COMMANDS" list in the rc file.
-
-Next, the gitolite.conf file should have something like this:
-
-    repo foo
-        <...other rules...>
-        -   VREF/lock      =   @all
-
-However, see below for the difference between "RW" and "RW+" from the point of
-view of this feature and adjust permissions accordingly.
-
-## user view
-
-Here's a summary:
-
-  * Any user with "W" permissions to any branch in the repo can "lock" any
-    file.  Once locked, no other user can push changes to that file, *in any
-    branch*, until it is unlocked.
-  * Any user with "+" permissions to any branch in the repo can "break" a lock
-    held by someone else if needed.
-
-For best results, everyone on the team should:
-
-  * Switch to the branch containing the binary files when wanting to make a
-    change.
-  * Run 'git pull' or eqvt, then lock the binary file(s) before editing them.
-  * Finish the editing task as quickly as possible, then commit, push, and
-    unlock the file(s) so others are not needlessly blocked.
-  * Understand that breaking a lock require additional, (out of band)
-    communication.  It is upto the team's policies what that entails.
-
-## detailed example
-
-Alice declares her intent to work on "d1.odt":
-
-    $ git pull
-    $ ssh git at host lock -l foo doc/d1.odt
-
-Similarly Bob starts on "d2.odt"
-
-    $ git pull
-    $ ssh git at host lock -l foo doc/d2.odt
-
-Carol makes some changes to d2.odt (**without attempting to lock the file or
-checking to see if it is already locked**) and pushes:
-
-    $ ooffice doc/d2.odt
-    $ git add doc/d2.odt
-    $ git commit -m 'added footnotes to d2 in klingon'
-    $ git push
-    <...normal push progress output...>
-    remote: FATAL: W VREF/lock testing carol DENIED by VREF/lock
-    remote: 'doc/d2.odt' locked by 'bob'
-    remote: error: hook declined to update refs/heads/master
-    To u2:testing
-     ! [remote rejected] master -> master (hook declined)
-    error: failed to push some refs to 'carol:foo'
-
-Carol backs out her changes, but saves them away for a "manual merge" later.
-
-    git reset HEAD^
-    git stash save 'klingon changes to d2.odt saved for possible manual merge later'
-
-Note that this still represents wasted work in some sense, because Carol would
-have to somehow re-apply the same changes to the new version of d2.odt after
-pulling it down.  **This is because she did not lock the file before making
-changes on her local repo.  Educating users in doing this is important if this
-scheme is to help you.**
-
-She now decides to work on "d1.odt".  However, she has learned her lesson and
-decides to follow the protocol described above:
-
-    $ git pull
-    $ ssh git at host lock -l foo doc/d1.odt
-    FATAL: 'doc/d1.odt' locked by 'alice' since Sun May 27 17:59:59 2012
-
-Oh damn; can't work on that either.
-
-Carol now decides to see what else there may be.  Instead of checking each
-file to see if she can lock it, she starts with a list of what is already
-locked:
-
-    $ ssh git at host lock -ls foo
-
-    # locks held:
-
-    alice   doc/d1.odt      (Sun May 27 17:59:59 2012)
-    bob     doc/d2.odt      (Sun May 27 18:00:06 2012)
-
-    # locks broken:
-
-Aha, looks like only d1 and d2 are locked.  She picks d3.odt to work on.  This
-time, she starts by locking it:
-
-    $ ssh git at host lock -l foo doc/d3.odt
-    $ ooffice doc/d3.odt
-    <...etc...>
-
-Meanwhile, in a parallel universe where d3.odt doesn't exist, and Alice has
-gone on vacation while keeping d1.odt locked, Carol breaks the lock.  Carol
-can do this because she has RW+ permissions for the repository itself.
-
-However, protocol in this team requires that she get email approval from the
-team lead before doing this and that Alice be in CC in those emails, so she
-does that first, and *then* she breaks the lock:
-
-    $ git pull
-    $ ssh git at host lock --break foo doc/d1.odt
-
-She then locks d1.odt for herself:
-
-    $ ssh git at host lock -l foo doc/d1.odt
-
-When Alice comes back, she can tell who broke her lock and when:
-
-    $ ssh git at host lock -ls foo
-
-    # locks held:
-
-    carol   doc/d1.odt      (Sun May 27 18:17:29 2012)
-    bob     doc/d2.odt      (Sun May 27 18:00:06 2012)
-
-    # locks broken:
-
-    carol   doc/d1.odt      (Sun May 27 18:17:03 2012)      (locked by alice at Sun May 27 17:59:59 2012)
-
diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
deleted file mode 100644
index 77cd40f..0000000
--- a/doc/master-toc.mkd
+++ /dev/null
@@ -1,170 +0,0 @@
-# gitolite documentation
-
-## [Introduction][index]
-
-  * (for [older][g2] gitolite (v1 or v2) users)
-  * [quick links][ql]
-  * [what][] is gitolite
-  * [why][] might you need it
-  * [contact][] info, mailing list, IRC channel
-  * [license][] info
-
-## help for [emergencies][]
-
-  * [lost][lost-key] admin key/access
-  * [bypass][]ing gitolite
-  * [clean][]ing out a botched install
-  * [common][ce] errors
-  * [uncommon][ue] errors
-  * things that are [not gitolite problems][ngp]
-
-## [WARNINGS][]
-
-
-## [testing][] gitolite
-  * [trying][] out gitolite
-
-
-## [quick][qi] install, setup, and clone
-
-
-## [install][]
-
-  * notes and naming conventions
-  * requirements
-      * your skills
-      * server
-      * client
-  * getting the software
-  * the actual install
-  * upgrading
-  * packaging gitolite
-  * [migr][]ating
-
-## [setup][]
-
-
-## [clone][]
-
-
-## gitolite [admin][]istration
-
-  * ([server-side][server]) settings, hooks, etc.
-      * ([link][WARNINGS]: important cautions on server side activity)
-      * changing settings in the [rc][] file
-      * installing custom [hooks][]
-      * ([link][existing]: moving existing repos into gitolite)
-  * ([client-side][adminrepo]) access control via the gitolite-admin repo
-      * basic [syntax][] of the [conf][] file
-          * include files
-          * ([link][sugar]: syntactic sugar)
-      * [groups][] (of users and repos)
-          * special: '@all'
-          * (link: storing user group info in LDAP)
-      * adding and removing [users][]
-          * multiple keys per user
-      * adding and removing [repos][]
-          * renaming repos
-      * defining access [rules][]
-          * what does a rule look like?
-          * when are the rules checked?
-          * how are the rules matched?
-          * summary of [permissions][permsum]
-          * additional topics
-              * [rule accumulation][rule-accum]
-              * applying [deny rules][deny-rules] during the pre-git check
-          * ([link][refex]: refexes)
-          * ([link][write-types]: different types of write operations)
-          * ([link][vref]: virtual refs)
-      * gitolite [options][]
-      * "[git config][git-config]" keys and values
-      * ["wild"][wild] repos (user created repos)
-          * quick introduction
-          * (admin) declaring wild repos in the conf file
-          * (user) [creating][create] a specific repo
-          * repo patterns
-          * roles
-          * adding other roles
-              * [IMPORTANT WARNING ABOUT THIS FEATURE][rolenamewarn]
-          * listing wild repos
-          * deleting wild repos
-
-## what your [user][]s need to know
-
-
-## [special][] features/setups
-
-  * putting 'repositories' and '.gitolite' [somewhere else][elsewhere]
-  * [disabling pushes][writable] to take backups
-  * [personal][pers] branches
-  * ([link][votes]: voting on commits)
-  * [delegating][deleg] access control responsibilities
-      * ([link][NAME]: the NAME VREF)
-      * the [subconf][] command
-  * ([link][partial-copy]: faking selective READ control)
-  * using pubkeys obtained [from elsewhere][keysonly]
-  * ([link][pushcode]: updating code via the admin repo)
-
-## interfacing with [external][] tools
-
-  * gitweb
-      * changing the [UMASK][umask]
-  * git-daemon
-
-## [mirroring][]
-
-
-## [rare][]/one-time activities
-
-  * moving [existing][] repos into gitolite
-  * [moving][] servers
-
-## [customisation][cust]
-
-  * where do you put custom code?
-  * types of non-core programs
-      * [commands][]
-      * [hooks][]
-      * syntactic [sugar][]
-      * ([link][triggers]: triggers)
-      * ([link][vref]: VREFs)
-  * ([link][non-core]: non-core programs shipped with gitolite)
-  * [developer notes][dev-notes] -- writing custom code
-      * environment variables and other inputs
-      * APIs
-          * the shell API
-          * the perl API
-      * writing your own...
-          * hooks
-          * commands
-          * trigger programs
-          * sugar
-
-## [non-core][] programs shipped with gitolite
-
-  * commands
-  * syntactic sugar
-  * triggers
-  * ([link][vref]: VREFs)
-  * special cases
-      * [partial-copy][]: selective read control for branches
-
-## background info
-
-  * [files and directories][files] involved in install+setup
-  * [auth][]entication versus authorisation
-      * interfacing with [other authentication][otherauth] systems
-      * getting user group info from [LDAP][ldap]
-  * [ssh][]
-  * [regular expressions][regex]
-
-## contributed software, tools, and documentation
-
-  * TBD
-
-## TBD
-
-  * log file format, LOG_EXTRA
-  * hub
-  * mob branches
-  * password access
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
deleted file mode 100644
index 7b07882..0000000
--- a/doc/mirroring.mkd
+++ /dev/null
@@ -1,235 +0,0 @@
-# mirroring using gitolite
-
-<font color="red">**WARNING** existing gitolite mirroring users please note:
-**there are [significant changes][g2i-mirroring]** in syntax and usage
-compared to g2.  If you're not the kind who reads documentation before doing
-serious system admin things, well... good luck!</font>
-
-----
-
-[[TOC]]
-
-----
-
-Mirroring is simple: you have one "master" server and one or more "slave"
-servers.  The slaves get updates only from the master; to the rest of the
-world they are at best read-only.
-
-Gitolite extends this simple notion in the following ways:
-
-  * Different masters and sets of slaves for different repos
-
-    This lets you do things like:
-
-      * Use the server closest to *most* of its developers as the master for
-        that repo.
-      * Not mirror a repo at all to some servers.
-      * Have repos that are purely local to a server (not mirrored at all).
-      * Negotiate mirroring with servers that are not even under your control.
-      * Push to a slave on demand or via cron (helps deal with bandwidth or
-        connectivity constraints).
-
-  * Pushes to a slave can be transparently forwarded to the real master.
-
-    Your developers need not worry about where a repo's master is -- they just
-    write to their local mirror for *all* repos, even if their local mirror is
-    only a slave for some.
-
-## caveats
-
-  * Mirroring will never *create* a repo on a slave; it has to exist and be
-    prepared to receive updates from the master.
-
-    However, there is limited support for auto-creating wild card repos and
-    sending 'perms' info across, with the following caveats at present.  (Some
-    of this text won't make sense unless you know what those features are).
-
-      * *WARNING: it does NOT make sense to mirror wild repos in setups where
-        the authentication data is not the same (i.e., where "alice" on the
-        master and "alice" on a slave maybe totally different people)*.
-
-      * This has only been minimally tested.  For example, complex setups or
-        asymmetric configs on master and slave, etc. have NOT been tested.
-
-      * Permission changes will only propagate on the next 'git push'.  Of
-        course, if you know the name of the slave server, you can run
-
-            ssh git at host mirror push slave-server-name repo-name
-
-      * Using 'perms' on a slave is allowed but will neither propagate nor
-        persist.  They will be overwritten by whatever perms the master has
-        (even if it is an empty set) on the next 'git push'.
-
-      * As with lots of extra features in gitolite, smart http support is not
-        on my radar.  Don't ask.
-
-    Please test it out and let me know if something surprising happens.  Be
-    aware that I have been known to claim bugs are features if I don't have
-    time to fix them immediately :-)
-
-  * Mirroring is only for git repos.  Ancillary files like gl-creator and
-    gl-perms in the repo directory are not mirrored; you must do that
-    separately.  Files in the admin directory (like log files) are also not
-    mirrored.
-
-  * If you ever do a [bypass push][bypass], mirroring will not work.
-    Mirroring checks also will not work -- for example, you can push to a
-    slave, which is not usually a good idea.  So don't bypass gitolite if the
-    repo is mirrored!
-
-## setting up mirroring
-
-This is in two parts: the initial setup and the rc file, followed by the conf
-file settings and syntax.
-
-### the initial setup and the rc file
-
-On **each** server:
-
-  * Install gitolite normally.  Make clones of the server's 'gitolite-admin'
-    repo on your workstation so you can admin them all from one place.
-
-  * Give the server a short, simple, "hostname" and set the HOSTNAME in the
-    rc file to this name, for example 'mars'.
-
-  * Run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
-    public key to a common area and name it after the host, but with 'server-'
-    prefixed.  For example, the pubkey for server 'mars' must be stored as
-    'server-mars.pub'.
-
-  * Copy all keys to all the admin repo clones on your workstation and and add
-    them as usual.  This is an `O(N^2)` operation ;-)
-
-    You may have guessed that the prefix 'server-' is special, and
-    distinguishes a human user from a mirroring peer.
-
-  * Create "host" aliases to refer to all other machines.  See [here][ssh-ha]
-    for what/how.
-
-    The host alias for a host (in all other machines' `~/.ssh/config` files)
-    MUST be the same as the `HOSTNAME` in the referred host's
-    `~/.gitolite.rc`.  Gitolite mirroring **requires** this consistency in
-    naming; things will NOT work otherwise.
-
-    Normally you should be able to build one common file and append it to all
-    the servers' `~/.ssh/config` files.
-
-  * The following **MUST** work for **each pair** of servers that must talk to
-    each other:
-
-        # on server mars
-        ssh phobos info
-        # the response MUST start with "hello, server-mars..."
-
-    Note the exact syntax used; variations like "ssh git at phobos.example.com
-    info" are NOT sufficient.  That is why you need the ssh host aliases.
-
-    Check this command from *everywhere to everywhere else*, and make sure you
-    get expected results.  **Do NOT proceed otherwise.**
-
-  * Setup the gitolite.conf file on all the servers.  If the slaves are to be
-    exact copies of the master, you need to do the complete configuration only
-    on the master; the slaves can have just this:
-
-        repo gitolite-admin
-            RW+     =   <some local admin>
-
-            option mirror.master    =   mars
-            option mirror.slaves    =   phobos
-
-    because on the first push to the master it will update all the slaves
-    anyway.
-
-  * When that is all done and tested, **enable mirroring** by going through
-    the rc file and uncommenting all the lines mentioning `Mirroring`.
-
-### conf file settings and syntax
-
-Mirroring is defined by the following [options][].  You can have different
-settings for different repos, and of course some repos may not have any mirror
-options at all -- they are then purely local.
-
-    repo foo
-        ...access rules...
-
-        option mirror.master        =   mars
-        option mirror.slaves        =   phobos deimos
-        option mirror.redirectOK    =   all
-
-The first line is easy, since a repo can have only one master.
-
-The second is a space separated list of hosts that are all slaves.  You can
-have several slave lists, as long as the config key starts with
-'mirror.slaves' and is unique.  For example.
-
-        option mirror.slaves-1   =   phobos deimos
-        option mirror.slaves-2   =   io europa
-        option mirror.slaves-3   =   ganymede callisto
-
-Do not repeat a key; then only the last line for that key will be effective.
-
-## redirected pushes
-
-**Please read carefully; there are security implications if you enable this
-for mirrors NOT under your control**.
-
-Normally, a master, (and *only* a master), pushes to a slave, and the slaves
-are "read-only" to the users.  Gitolite allows a *slave* to receive pushes
-from a user and transparently redirect them to the *master*.
-
-This simplifies things for users in complex setups, letting them use their
-local mirror for both fetch and push access to all repos.
-
-Just remember that if you do this, **authentication** happens on the slave,
-but **authorisation** is on the master.  The master is trusting the slave to
-authenticate the user correctly, *and* use the same authentication data (i.e.,
-user alice on the slave should be guaranteed to be the same as user alice on
-the master).
-
-The syntax for enabling this is one of these:
-
-    option mirror.redirectOK    =   all
-    option mirror.redirectOK    =   phobos deimos
-
-The first syntax trusts all valid slaves to redirect user pushes, while the
-second one trusts only some slaves.
-
-Note that you cannot redirect gitolite commands (like perms, etc).
-
-## #sync manually synchronising a slave repo
-
-You can use the `gitolite mirror push` command on a master to manually
-synchronise any of its slaves.  Try it with `-h` to get usage info.
-
-Tip: if you want to do this to all the slaves, try this:
-
-    for s in `gitolite git-config -r reponame mirror.slave | cut -f3`
-    do
-        gitolite mirror push $s reponame
-    done
-
-This command can also be run remotely; run `ssh git at host mirror -h` for
-details.
-
-## #HOSTNAME appendix A: HOSTNAME substitution
-
-Wherever gitolite sees the word `%HOSTNAME`, it will replace it with the
-HOSTNAME supplied in the rc file, if one was supplied.  This lets you maintain
-configurations for all servers in one repo, yet havethem act differently on
-different servers, by saying something like:
-
-    subconf "%HOSTNAME/*.conf"
-
-You can use it in other places also, for example:
-
-    RW+     VREF/NAME/subs/%HOSTNAME/       =   @%HOSTNAME-admins
-
-(you still have to define @mars-admins, @phobos-admins, etc., but the actual
-VREF is now one line instead of one for each server!)
-
-## appendix B: efficiency versus paranoia
-
-If you're paranoid enough to use mirrors, you should be paranoid enough to
-set this on each server, despite the possible CPU overhead:
-
-    git config --global receive.fsckObjects true
diff --git a/doc/mkdoc b/doc/mkdoc
deleted file mode 100755
index 72470d3..0000000
--- a/doc/mkdoc
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/perl
-
-#   cd <g3 clone>/doc; ./mkdoc     # --> creates ../html/*.html
-
-my $MKD = "./Markdown.pl";
-
-use 5.10.0;
-use strict;
-use warnings;
-use lib '../src/lib/Gitolite/Test';
-use Tsh;
-
-$ENV{TSH_ERREXIT} = 1;
-
-try "
-    mkdir ../html;                      ok
-    git status -s -uno;                 !/./
-    git log --oneline -1
-" or die 1;
-
-my $head = (lines())[0];
-
-main();
-
-try "
-    git checkout gh-pages;              ok
-    git reset --hard github/gh-pages;   ok
-    cd ..;                              ok
-    git rm *.html;                      ok
-    mv html/*.html .;                   ok
-    git add *.html;                     ok
-    git commit -m '$head';              ok
-    git checkout master;                ok
-    rmdir html;                         ok
-" or die 2;
-
-sub main {
-    chomp(@ARGV = `find . -name "*.mkd" | cut -c3-`) if not @ARGV;
-    @ARGV = grep { /./ } @ARGV;
-    my @save = @ARGV;
-    my $css_block = join("", <DATA>);
-
-    my %ct; # chapter tocs
-    my %title;
-    my $mf = '';
-    my $fh;
-
-    while (<>) {
-        $ARGV =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
-        my $b = $1;
-
-        if (/^(#+) (?:#(\S+) )?(.*)/) {
-            $title{$b} ||= $3;
-            if ( length($1) == 1 ) {
-                $ct{$b} .= "\n";
-                $ct{$b} .= "  * [$3][$b]\n";
-                $mf .= "[$b]: $b.html\n";
-            } else {
-                $ct{$b} .= " " x ( 4 * ( length($1) - 1 ) );
-                $ct{$b} .= "  * ";
-                $ct{$b} .= (
-                    $2
-                    ? "[$3][$2]"
-                    : "$3"
-                );
-                $ct{$b} .= "\n";
-                $mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
-            }
-        }
-    }
-
-    # open($fh, ">", "master-toc.mk2")
-    #   and print $fh $mt
-    #   and close $fh;
-
-    # after this, do this for every mkd (including the master-toc.mkd)
-
-    #       cat $css_block > $base.html
-    #       cat $base.mkd $mf | $MKD >> $base.html
-
-    for my $mkd (@save) {
-        $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
-        my $b = $1;
-
-        my $css = $css_block;
-        $css =~ s/%TITLE/$title{$b} || $b/e;
-
-        open($fh, ">", "../html/$b.html")
-          and print $fh $css
-          and close $fh;
-
-        my $mkt = `cat $mkd`;
-        $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
-        $mkt =~ s/^\[\[TOC\]\]/$ct{$b}/mg;
-        open($fh, "|-", "$MKD >> ../html/$b.html")
-          and print $fh $mkt, "\n\n", $mf
-          and close $fh;
-    }
-}
-
-__DATA__
-
-<head>
-    <title>%TITLE</title>
-<style>
-    body        { background: #fff; text-color: #000; margin-left:  40px;   font-size:  0.9em;  font-family: sans-serif; max-width: 800px; }
-    h1          { background: #ffb; text-color: #000; margin-left: -30px;   border-top:    5px  solid #ccc; }
-    h2          { background: #ffb; text-color: #000; margin-left: -20px;   border-top:    3px  solid #ddd; }
-    h3          { background: #ffb; text-color: #000; margin-left: -10px; }
-    h4          { background: #ffb; text-color: #000; }
-    code        { font-size:    1.1em;  background:  #ddf; text-color: #000; }
-    pre         { margin-left:  2em;    background:  #ddf; text-color: #000; }
-    pre code    { font-size:    1.1em;  background:  #ddf; text-color: #000; }
-</style>
-</head>
-
-<p style="text-align:center">
-    <a href="master-toc.html">master TOC</a>
-|
-    <a href="index.html">main page</a>
-|
-    <a href="index.html#license">license</a>
-</p>
-<p style="text-align:center">
-<font color="gray">This is for gitolite "g3"; for older (v2.x) documentation click <a href="http://sitaramc.github.com/gitolite/g2/master-toc.html">here</a></font>
-</p>
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
deleted file mode 100644
index dc0e188..0000000
--- a/doc/non-core.mkd
+++ /dev/null
@@ -1,131 +0,0 @@
-# non-core programs shipped with gitolite
-
-----
-
-[[TOC]]
-
-----
-
-## commands
-
-A list of these commands can be obtained by running `gitolite help` on the
-server.  A different (and probably much smaller) list can be obtained by a
-remote user running `ssh git at host help`.
-
-All the commands that ship with gitolite will respond to `-h`; please report a
-bug to me if they don't.
-
-A particularly interesting command is the 'sudo' command, which allows an
-admin to run any remote command as some other user (though not the other way
-round!)
-
-Here's a list of some commands where additional information is available
-elsewhere:
-
-  * 'info' -- documented [here][info]
-  * 'mirror' -- documented [here][sync]
-  * 'perms' -- get/set the gl-perms file; see [perms][] for more
-  * 'sskm' -- self-service key management, see [sskm][] for more
-
-## syntactic sugar
-
-The following "sugar" programs are available:
-
-  * Continuation-lines -- allow the use of C-style backslash escaped
-    continuation lines in the conf file.  I don't like it but some people do,
-    and now I can support them without bulking up the "core" conf parser!
-
-  * Keysubdirs-as-groups -- someone wanted the sub-directory name (of
-    "keydir/") in which the pubkey was placed to be a group to which the user
-    automatically belonged.  A very unusual requirement, and one which would
-    *never* have seen the light of day in g2, but in g3 it's easy, and doesn't
-    affect anyone else!
-
-    (Note: the last component of the directory path is used if there are more
-    than one level between "keydir/" and the actual file).
-
-## triggers
-
-Most triggers need to be enabled by adding or uncommenting an appropriate line
-in the [rc][] file.  There are enough examples in there for the syntax to not
-need explanation.
-
-Some features need to be enabled in more than one trigger.  Mirroring is
-probably the best example -- it needs to hook into the `INPUT`, `PRE_GIT`, and
-the `POST_GIT` triggers to work.
-
-In general, the source code for each trigger will tell you what it is doing
-and which trigger list you should add it to.  Please note that there are two
-types of [triggers][]; the perl triggers usually have subroutine names that
-reflect what trigger sections they should go into.  (Using mirroring as an
-example again, the Mirroring.pm perl module has sub's named 'input',
-'pre\_git', and 'post\_git').
-
-Please report a bug to me if you could not find the information you wanted on
-any specific trigger.
-
-Here's a list of some triggers where additional information is available
-elsewhere:
-
-  * Shell -- see "giving shell access to gitolite users" in [this][sts] page.
-  * Mirroring -- see [doc/mirroring.mkd][mirroring]
-  * partial-copy -- this has its own section later in this page.
-
-## VREFs
-
-VREFs have their [own page][vref].
-
-## special cases
-
-### #partial-copy partial-copy: selective read control for branches
-
-Git (and therefore gitolite) cannot do selective read control -- allowing
-someone to read branch A but not branch B.  It's the entire repo or nothing.
-
-<font color="gray"> [Side note: Gerrit Code Review can do that, but that is
-because they have their own git stack (and their own sshd, and so on) all in
-one big Java program.  Gerrit is *really* useful if you want code review to be
-part of the access control decision] </font>
-
-Gitolite can now help you do this.  Note that this is only for branches; you
-can't do this for files and directories.
-
-Here's how:
-
-1.  enable 'partial-copy' in the `PRE_GIT` section in the rc file.
-
-2.  for each repo "foo" which has secret branches that a certain set of
-    developers (we'll use a group called `@temp-emp` as an example) are not
-    supposed to see, do this:
-
-        repo foo
-            # rules should allow @temp-emp NO ACCESS
-
-        repo foo-partialcopy-1
-            -   secret-branch               =   @temp-emp
-
-            # other rules; see notes below
-
-            -   VREF/partial-copy           =   @all
-            config gitolite.partialCopyOf   =   foo
-
-    **IMPORTANT NOTES**:
-
-      * if you're using other VREFs, **make sure** this one is placed at the
-        end, after all the others.
-
-      * remember that any change allowed to be made to the partial-copy repo
-        will propagate to the main repo so make sure you use other rules to
-        restrict pushes to other branches and tags as needed.
-
-And that should be it.  **Please test it and let me know if it doesn't work!**
-
-WARNINGS:
-
-  * If you change the config to disallow something that used to be allowed,
-    you should delete the partial repo on the server and then run `gitolite
-    compile; gitolite trigger POST_COMPILE` to let it build again.
-
-  * Not tested with smart http; probably won't work.
-
-  * Also not tested with mirroring, or with wild card repos.
diff --git a/doc/options.mkd b/doc/options.mkd
deleted file mode 100644
index 22d12af..0000000
--- a/doc/options.mkd
+++ /dev/null
@@ -1,20 +0,0 @@
-# gitolite options
-
-Some gitolite features are enabled, or gitolite's behaviour changed, by
-setting "options".
-
-Options are set by repo.  The syntax is very simple:
-
-    option  foo.bar     =   baz
-
-Of course this is useless if some other part of gitolite, or some external
-command, is not querying for the option key 'foo.bar'!
-
-Options are therefore documented in the section/page they belong in, not here.
-Here are the currently recognised options:
-
-  * [deny-rules][] -- ask gitolite to honor deny rules during the pre-git
-    check also.
-
-  * [mirroring][] related options -- tell gitolite who is the master server,
-    and who are the slaves, for each repo.
diff --git a/doc/progit.mkd b/doc/progit.mkd
deleted file mode 100644
index 5e293c1..0000000
--- a/doc/progit.mkd
+++ /dev/null
@@ -1,163 +0,0 @@
-# (expanded version of the gitolite chapter in the progit book)
-
-## Gitolite ##
-
-[Update 2012-04-10]: This page has been completely rewritten for gitolite version 3, informally called "g3".  G3 is a *total* rewrite of gitolite to push a lot more features away from "core", give the core a bunch of extension mechanisms, and finally have much better shell and perl APIs to bring all this together.
-
-Git has become very popular in corporate environments, which tend to have some additional requirements in terms of access control.  Gitolite was originally created to help with those requirements, but it turns out that it's equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too.  KDE, and kernel.org, are other very high-profile users of gitolite.
-
-Gitolite allows you to specify permissions not just by repository, but also by branch or tag names within each repository.  That is, you can specify that certain people (or groups of people) can only push certain "refs" (branches or tags) but not others.
-
-### Installing ###
-
-Installing Gitolite is very easy, even if you don't read the extensive documentation that comes with it.  You need an account on a Unix server of some kind.  You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed.  In the examples below, we will use the `git` account on a host called `gitserver`.
-
-Gitolite is somewhat unusual as far as "server" software goes -- access is via ssh, and so every userid on the server is a potential "gitolite host".  Gitolite is at present best installed manually, as the "g3" version does not yet have RPM/DEB support from distros.  We will describe the simplest install method in this article; for the other methods please see the documentation.
-
-To begin, create a user called `git` on your server and login to this user.  Copy your ssh pubkey (a file called `~/.ssh/id_rsa.pub` if you did a plain `ssh-keygen` with all the defaults) from your workstation, renaming it to `YourName.pub`.  Then run these commands:
-
-    git clone git://github.com/sitaramc/gitolite
-    gitolite/install -ln
-        # assumes $HOME/bin exists and is in your $PATH
-    gitolite setup -pk $HOME/YourName.pub
-        # for example, I would run 'gitolite setup -pk $HOME/sitaram.pub'
-
-Finally, back on your workstation, run `git clone git at server:gitolite-admin`.
-
-And you're done!  Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in your workstation.  You administer your gitolite setup by making changes to this repository and pushing.  See adding [users][] and [repos][] to start with.
-
-### Customising the Install ###
-
-While the default, quick, install works for most people, there are some ways to customise the install if you need to.  Some changes can be made simply by editing the [rc file][rc], but if that is not sufficient, there's documentation on [customising gitolite][cust] by using non-core programs.
-
-### Config File and Access Control Rules ###
-
-Once the install is done, you switch to the `gitolite-admin` repository (placed in your HOME directory) and poke around to see what you got:
-
-	$ cd ~/gitolite-admin/
-	$ ls
-	conf/  keydir/
-	$ find conf keydir -type f
-	conf/gitolite.conf
-	keydir/sitaram.pub
-	$ cat conf/gitolite.conf
-
-	repo gitolite-admin
-	    RW+                 = sitaram
-
-	repo testing
-	    RW+                 = @all
-
-Notice that "sitaram" (the name of the pubkey in the gl-setup command you used earlier) has read-write permissions on the `gitolite-admin` repository as well as a public key file of the same name.
-
-The config file syntax for gitolite is [well documented][conf], so we'll only mention some highlights here.
-
-You can group users or repos for convenience.  The group names are just like macros; when defining them, it doesn't even matter whether they are projects or users; that distinction is only made when you *use* the "macro".
-
-	@oss_repos      = linux perl rakudo git gitolite
-	@secret_repos   = fenestra pear
-
-	@admins         = scott     # Adams, not Chacon, sorry :)
-	@interns        = ashok     # get the spelling right, Scott!
-	@engineers      = sitaram dilbert wally alice
-	@staff          = @admins @engineers @interns
-
-You can control permissions at the "ref" level.  In the following example, interns can only push the "int" branch.  Engineers can push any branch whose name starts with "eng-", and tags that start with "rc" followed by a digit.  And the admins can do anything (including rewind) to any ref.
-
-	repo @oss_repos
-	    RW  int$                = @interns
-	    RW  eng-                = @engineers
-	    RW  refs/tags/rc[0-9]   = @engineers
-	    RW+                     = @admins
-
-The expression after the `RW` or `RW+` is a regular expression (regex) that the refname (ref) being pushed is matched against.  So we call it a "refex"!  Of course, a refex can be far more powerful than shown here, so don't overdo it if you're not comfortable with perl regexes.
-
-Also, as you probably guessed, Gitolite prefixes `refs/heads/` as a syntactic convenience if the refex does not begin with `refs/`.
-
-An important feature of the config file's syntax is that all the rules for a repository need not be in one place.  You can keep all the common stuff together, like the rules for all `oss_repos` shown above, then add specific rules for specific cases later on, like so:
-
-	repo gitolite
-	    RW+                     = sitaram
-
-That rule will just get added to the ruleset for the `gitolite` repository.
-
-At this point you might be wondering how the access control rules are actually applied, so let's go over that briefly.
-
-There are two levels of access control in gitolite.  The first is at the repository level; if you have read (or write) access to *any* ref in the repository, then you have read (or write) access to the repository.
-
-The second level, applicable only to "write" access, is by branch or tag within a repository.  The username, the access being attempted (`W` or `+`), and the refname being updated are known.  The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched).  If a match is found, the push succeeds.  A fallthrough results in access being denied.
-
-### Advanced Access Control with "deny" rules ###
-
-So far, we've only seen permissions to be one or `R`, `RW`, or `RW+`.  However, gitolite allows another permission: `-`, standing for "deny".  This gives you a lot more power, at the expense of some complexity, because now fallthrough is not the *only* way for access to be denied, so the *order of the rules now matters*!
-
-Let us say, in the situation above, we want engineers to be able to rewind any branch *except* master and integ.  Here's how to do that:
-
-	    RW  master integ    = @engineers
-	    -   master integ    = @engineers
-	    RW+                 = @engineers
-
-Again, you simply follow the rules top down until you hit a match for your access mode, or a deny.  Non-rewind push to master or integ is allowed by the first rule.  A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied.  Any push (rewind or non-rewind) to refs other than master or integ won't match the first two rules anyway, and the third rule allows it.
-
-You can also use deny rules to hide specific repos from people (or gitweb, or git-daemon, etc.), when you have otherwise allowed them access to *all* repos.  For example, a server containing open source repos may nevertheless wish to hide the special 'gitolite-admin' repo from gitweb, even though all the other repos can be made visible:
-
-	    repo gitolite-admin
-		-   =   gitweb daemon
-		[... other access rules ...]
-		option deny-rules = 1
-                # remember this is for gitolite "g3"; the older gitolite had a
-                # different syntax
-
-	    repo @all
-		R   =   gitweb daemon
-
-[This][deny-rules] page has more details.
-
-### Restricting pushes by files changed ###
-
-In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch.  For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done *just right*.  You can tell gitolite:
-
-    repo foo
-        RW                      =   @junior_devs @senior_devs
-
-        -   VREF/NAME/Makefile  =   @junior_devs
-
-User who are migrating from the older gitolite should note that there is a significant change in behaviour with regard to this feature; please see the [migration guide][migr] for details.
-
-### Personal Branches ###
-
-Gitolite also has a feature called "personal branches" (or rather, "personal branch namespace") that can be very useful in a corporate environment.
-
-A lot of code exchange in the git world happens by "please pull" requests.  In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there.
-
-This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin.
-
-Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer (for example, `refs/personal/<devname>/*`); the details are [here][pers].
-
-### "Wildcard" repositories ###
-
-Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for example `assignments/s[0-9][0-9]/a[0-9][0-9]`, to pick a random example.  It allows you to assign a new permission mode ("C") which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc.  This feature is documented [here][wild].
-
-### Other Features ###
-
-We'll round off this discussion with a sampling of other features, all of which, and many more, are described in great detail in the documentation.
-
-**Logging**: Gitolite logs all successful accesses.  If you were somewhat relaxed about giving people rewind permissions (`RW+`) and some kid blew away "master", the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed.
-
-**Access rights reporting**: Another convenient feature is what happens when you try and just ssh to the server.  Gitolite shows you what repos you have access to, and what that access may be.  Here's an example:
-
-        hello sitaram, this is git at git running gitolite3 v0.02-15-g1db50f4 on git 1.7.4.4
-
-             R     anu-wsd
-             R     entrans
-             R  W  git-notes
-             R  W  gitolite
-             R  W  gitolite-admin
-             R     indic_web_input
-             R     shreelipi_converter
-
-**Delegation**: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently.  This reduces the load on the main admin, and makes him less of a bottleneck.  See [here][deleg] for more on this.
-
-**Mirroring**: Gitolite can help you maintain multiple [mirrors][mirroring], and switch between them easily if the primary server goes down.
-
-[progit]: http://sitaramc.github.com/gitolite/progit.html
diff --git a/doc/qi.mkd b/doc/qi.mkd
deleted file mode 100644
index 90a83c2..0000000
--- a/doc/qi.mkd
+++ /dev/null
@@ -1,48 +0,0 @@
-# quick install, setup, and clone
-
-## ASSUMPTIONS
-
-  * This is an ssh-based setup.  For smart http setup click [here][http].
-
-  * This is a fresh install, not a migration from the old gitolite (v1.x,
-    v2.x).
-
-  * On the server, `$HOME/bin` exists and is in your `$PATH`.  If you don't
-    like that, there are [other install methods][install].
-
-  * "your-name.pub" is your public key from your workstation.
-      * Also, this key does not already have shell access to this gitolite
-        hosting user.
-
-  * The setup command does not generate any warnings.
-      * If it does, please see [common errors][ce] and fix things before
-        continuing, or read the more complete [setup][] page.
-
-## instructions
-
-On the server, as the hosting user (e.g., 'git'):
-
-    # get the software
-    git clone git://github.com/sitaramc/gitolite
-
-    # install it
-    gitolite/install -ln
-
-    # setup the initial repos with your key
-    gitolite setup -pk your-name.pub
-
-On your workstation:
-
-    # clone the admin repo so you can start adding stuff
-    git clone git at host:gitolite-admin.git
-    # Note 1: clone path must not include "repositories/"
-    # Note 2: it may include the ".git" at the end but it is optional
-
-## next steps
-
-If this step succeeds, you can add [users][], [repos][], or anything else
-described [here][adminrepo].
-
-If this step fails, be sure to look at the [ssh][] documentation before asking
-for help.  (A very basic first step is to run `ssh git at host info`;
-[this][info] page tells you what to expect).
diff --git a/doc/rare.mkd b/doc/rare.mkd
deleted file mode 100644
index bcb1c6a..0000000
--- a/doc/rare.mkd
+++ /dev/null
@@ -1,52 +0,0 @@
-# rare or one-time activities
-
-## #existing moving existing repos into gitolite
-
-On the server:
-
-  * Move the repos to `$HOME/repositories`.
-
-  * Make sure that:
-
-      * They are all *bare* repos.
-      * All the repo names end in ".git".
-      * All the files and directories are owned and writable by the gitolite
-        hosting user (especially true if you copied them as root).
-
-  * Run `gitolite setup`.  **If you forget this step, you can also forget
-    about write access control!**
-
-Back on your workstation:
-
-  * [Add them][repos] to conf/gitolite.conf in your clone of the admin repo,
-    then commit and push the change.
-
-    If the repos are already covered by some [wild][] pattern, this is
-    optional.
-
-## #moving moving servers
-
-This is adapted from the "migrating" section of the [install][] page; if
-you're actually *migrating* from g2 please go there!
-
-Nothing in any of the gitolite install/setup/etc will ever touch the *data* in
-any repository except the gitolite-admin repo.  The only thing it will
-normally touch is the `update` hook.  So one fool-proof way of "moving"
-servers is this (untested but should work; feedback appreciated):
-
-1.  Install gitolite on the new server, using the same key for the admin as
-    for the old server.
-
-2.  Copy the [rc][] file from the old server, overwriting this one.
-
-3.  [Disable][writable] the old server so people won't push to it.
-
-4.  Copy all the repos over from the old server, including gitolite-admin.
-    Make sure the files end up with the right ownership and permissions; if
-    not, chown/chmod them.
-
-5.  Run `gitolite setup`.
-
-6.  On a clone of the old gitolite-admin, add a new remote (or change an
-    existing one) to point to the new server.  Then `git push -f` to this
-    remote.
diff --git a/doc/rc.mkd b/doc/rc.mkd
deleted file mode 100644
index 98ad1d7..0000000
--- a/doc/rc.mkd
+++ /dev/null
@@ -1,104 +0,0 @@
-# the "rc" file (`$HOME/.gitolite.rc`)
-
-**NOTE**: if you're migrating from g2, there are some settings that MUST be
-dealt with **before** running `gitolite setup`; please read the [g2
-migration][g2migr] page and linked pages, and especially the one on
-[presetting the rc file][rc-preset].
-
-----
-
-The rc file for g3 is *quite* different from that of g2.
-
-As before, it is designed to be the only thing unique to your site for most
-setups.  What is new is that it is easy to extend it when new needs come up,
-without having to touch core gitolite.
-
-The rc file is perl code, but you do NOT need to know perl to edit it.  Just
-mind the commas, use single quotes unless you know what you're doing, and make
-sure the brackets and braces stay matched up!
-
-Please look at the `~/.gitolite.rc` file that gets installed when you setup
-gitolite.  As you can see there are 3 types of variables in it:
-
-  * simple variables (like `UMASK`)
-  * lists (like `POST_COMPILE`, `POST_CREATE`)
-  * hashes (like `ROLES`, `COMMANDS`)
-
-While some of the variables are documented in this file, many of them are not.
-Their purposes are to be found in each of their individual documentation files
-around; start with [customising gitolite][cust].  If a setting is used by an
-external command then running that command with '-h' may give you additional
-information.
-
-## specific variables
-
-  * `$UMASK`, octal, default `0077`
-
-    The default UMASK that gitolite uses makes all the repos and their
-    contents have `rwx------` permissions.  People who want to run gitweb
-    realise that this will not do.
-
-    The correct way to deal with this is to give this variable a value like
-    `0027` (note the syntax: the leading 0 is required), and then make the
-    user running the webserver (apache, www-data, whatever) a member of the
-    'git' group.
-
-    If you've already installed gitolite then existing files will have to be
-    fixed up manually (for a umask or 0027, that would be `chmod -R g+rX`).
-    This is because umask only affects permissions on newly created files, not
-    existing ones.
-
-  * `$GIT_CONFIG_KEYS`, string, default empty
-
-    This setting allows the repo admin to define acceptable gitconfig keys.
-
-    Gitolite allows you to set git config values using the "config" keyword;
-    see [here][git-config] for details and syntax.
-
-    However, if you are in an installation where the repo admin does not (and
-    should not) have shell access to the server, then allowing him to set
-    arbitrary repo config options *may* be a security risk -- some config
-    settings allow executing arbitrary commands!
-
-    You have 3 choices.  By default `$GIT_CONFIG_KEYS` is left empty, which
-    completely disables this feature (meaning you cannot set git configs via
-    the repo config).
-
-    The second choice is to give it a space separated list of settings you
-    consider safe.  (These are actually treated as a set of [regular
-    expression][regex] patterns, and any one of them must match).
-
-    For example:
-
-        $GIT_CONFIG_KEYS = 'core\.logAllRefUpdates core\..*compression';
-
-    Each pattern should match the *whole* key (in other words, there
-    is an implicit `^` at the start of each pattern, and a `$` at the
-    end).
-
-    The third choice (which you may have guessed already if you're familiar
-    with regular expressions) is to allow anything and everything:
-    `$GIT_CONFIG_KEYS = '.*';`
-
-  * `DEFAULT_ROLE_PERMS`, string, default undef
-
-    This sets default wildcard permissions for newly created wildcard repos.
-
-    If set, this value will be used as the default role permissions for new
-    wildcard repositories. The user can change this value with the perms
-    command as desired after repository creation; it is only a default.
-
-    Please be aware this is potentially a multi-line variable.  In most
-    setups, it will be left undefined.  Some installations may benefit from
-    setting it to `READERS @all`.
-
-    If you want multiple roles to be assigned by default, here is how.  Note
-    double quotes this time, due to the embedded newline, which in turn
-    require the '@' to be escaped:
-
-        DEFAULT_ROLE_PERMS  =>  "READERS \@all\nWRITERS \@senior_devs",
-
-  * `LOCAL_CODE`, string
-
-    This is described in more detail [here][localcode].  Please be aware
-    **this must be a FULL path**, not a relative path.
diff --git a/doc/refex.mkd b/doc/refex.mkd
deleted file mode 100644
index c5359c2..0000000
--- a/doc/refex.mkd
+++ /dev/null
@@ -1,34 +0,0 @@
-## #refex matching a ref and a refex
-
-A refex is a word I made up to mean "a regex that matches a ref".  If you know
-[regular expressions][regex] you're halfway there.
-
-In addition:
-
-  * If no refex is supplied, it defaults to `refs/.*`, for example in a rule
-    like this:
-
-        RW              =   alice
-
-  * A refex not starting with `refs/` is assumed to start with `refs/heads/`.
-    This means normal branches can be conveniently written like this:
-
-        RW  master      =   alice
-        # becomes 'refs/heads/master' internally
-
-    while tags will need to be fully qualified
-
-        RW  refs/tags/v[0-9]    =   bob
-
-  * A refex is implicitly anchored at the start, but not at the end.  In
-    regular expression lingo, a `^` is assumed at the start (but no `$` at the
-    end is assumed).  So a refex of `master` will match all these:
-
-        refs/heads/master
-        refs/heads/master1
-        refs/heads/master2
-        refs/heads/master/full
-
-    If you want to restrict the match to just the one specific ref, use
-
-        RW  master$     =   alice
diff --git a/doc/repos.mkd b/doc/repos.mkd
deleted file mode 100644
index fc73d36..0000000
--- a/doc/repos.mkd
+++ /dev/null
@@ -1,56 +0,0 @@
-# adding and removing repos
-
-**NOTE**: this page describes how to add new repos.  To bring already existing
-repos into gitolite control, click [here][existing].
-
->   ----
-
->   *WARNING: Do NOT add new repos or users manually on the server.  Gitolite
->   users, repos, and access rules are maintained by making changes to a
->   special repo called 'gitolite-admin' and pushing those changes to the
->   server.*
-
->   ----
-
-Just as for [users][], all operations are in a clone of the gitolite-admin
-repo.
-
-To **add** a new repo, edit `conf/gitolite.conf` and add it, along with at
-least one user with some permissions.  Or add it to an existing repo line:
-
-    repo gitolite tsh gitpod
-        RW+     =   sitaram
-        RW  dev =   alice bob
-        R       =   @all
-
-The "repo" line can have any number of repo names or repo group names in it.
-However, it can only be one line; this will not work
-
-    repo foo
-    repo bar    # WRONG; 'foo' is now forgotten
-        RW      =   alice
-
-If you have too many, use a group name:
-
-    @myrepos    =   foo
-    @myrepos    =   bar
-
-    repo @myrepos
-        RW      =   alice
-
-Finally, you add, commit, and push this change.  Gitolite will create a bare,
-empty, repo on the server that is ready to be cloned.
-
-**Removing** a repo is not so straightforward.  You certainly must remove the
-appropriate lines from the `conf/gitolite.conf` file, but gitolite will not
-automatically delete the repo from the server.  You have to log on to the
-server and do the dirty deed yourself :-)
-
-It is best to make the change in the conf file, push it, and *then* go to the
-server and do what you need to.
-
-**Renaming** a repo is also not automatic.  Here's what you do (and the order
-is important):
-
-  * Go to the server and rename the repo at the Unix command line.
-  * Change the name in the conf/gitolite.conf file and add/commit/push.
diff --git a/doc/rules.mkd b/doc/rules.mkd
deleted file mode 100644
index 39cabe9..0000000
--- a/doc/rules.mkd
+++ /dev/null
@@ -1,151 +0,0 @@
-## #rules access rules
-
-This is arguably the most complex part of day-to-day gitolite.  There are
-several interconnected ideas that make this hard to lay out easily if you're
-totally new to this, so read carefully.
-
-We will use this as a running example:
-
-    @staff          =   dilbert alice wally bob
-
-    repo foo
-        RW+         =   dilbert     # line 1
-        RW+ dev     =   alice       # line 2
-        -           =   wally       # line 3
-        RW  temp/   =   @staff      # line 4
-        R           =   ashok       # line 5
-
-### what does a rule look like?
-
-A rule line has the structure
-
-    <permission> <zero or more refexes> = <one or more users/user groups>
-
-The most common permissions used are:
-
-  * R, for read only
-  * RW, for push existing ref or create new ref
-  * RW+, for  "push -f" or ref deletion allowed (i.e., destroy
-    information)
-  * `-` (the minus sign), to **deny** access.
-
-There are also other, less commonly used, [types of permissions][write-types].
-
-A refex is an expression that matches the ref (i.e., branch or tag) being
-pushed.  See [this][refex] for more info.
-
-### when are the rules checked?
-
-There are 2 places where access rules are checked.
-
-The "pre-git" check is before git is invoked.  Gitolite knows the repo name,
-user name, and attempted access (R or W), but no ref name.
-
-The "update" check is only for write operations, and it is just before git
-updates a ref.  This time gitolite knows the refname also.
-
-### how are the rules matched?
-
-For the **pre-git check**, any permission that contains "R" matches a read
-operation, and any permission that contains "W" matches a write operation.
-This is because we simply don't know enough to make finer distinctions at this
-point.
-
-In addition, *gitolite ignores deny rules during the pre-git check*.  <font
-color="gray">(You can [change this][deny-rules] if you wish, though it's
-rarely needed)</font>.  This means line 3 is ignored, and so Wally in our
-example will pass the pre-git check.
-
-For the **update check**, git gives us all the information we need.  Then:
-
-  * All the rules for a repo are [accumulated][rule-accum].
-
-  * The rules pertaining to this repo *and* this user (or to a group to which
-    they belong, respectively) are kept; the rest are ignored.
-
-  * These rules are examined *in the sequence they appeared in the conf file*.
-    For each rule:
-
-      * If the ref does not match the [refex][], the rule is skipped.
-      * If it's a deny rule (the permissions field is a `-`), access is
-        **rejected** and the matching stops.
-      * If the permission field matches the specific [type of
-        write][write-types] operation, access is **allowed** and the matching
-        stops.
-
-  * If no rule ends with a decision, ("fallthru"), access is **rejected**.
-
-Now you need to understand how [refex][] matching happens and how the
-permissions match the various [types of write operations][write-types].
-
-Using these, you can see, in our example, that:
-
-  * Everyone, even wally, can read the repo.
-  * Dilbert can push, rewind, or delete any ref.
-  * Alice can push, rewind, or delete any ref whose name starts with 'dev';
-    see [refex][] for details.
-  * Alice can also push (but not rewind or delete) any ref whose name starts
-    with 'temp/'.  This applies to bob also.
-  * If it weren't for line 3, the previous statement would apply to wally
-    also.
-
-Interestingly, wally can get past the pre-git check because gitolite ignores
-deny rules for pre-git, but having got past it, he can't actually do anything.
-That's by design, and as I said if you don't like it you can ask gitolite to
-[deny at pre-git][deny-rules].
-
-### #permsum summary of permissions
-
-The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`.  This expands
-to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or
-`RW+CD`, all but the first two optionally followed by an `M`.  And by now you
-know what they all mean.
-
-## additional topics
-
-### #rule-accum rule accumulation
-
-Gitolite was meant to collect rules from multiple places and apply them all.
-For example, this:
-
-    repo foo
-        RW  =   u1
-
-    @gr1 = foo bar
-
-    repo @gr1
-        RW  =   u2
-        R   =   u3
-
-    repo @all
-        R   =   gitweb
-
-is effectively the same as this, for repo foo:
-
-    repo foo
-        RW  =   u1
-        RW  =   u2
-        R   =   u3
-        R   =   gitweb
-
-This extends to patterns also, but I'll leave an example for later.
-
-### #deny-rules applying deny rules during the pre-git check
-
-The access rules section above describes the problem in one scenario.  Here's
-another.  Let's say you have this at the end of your gitolite.conf file:
-
-    repo @all
-        R   =   gitweb daemon
-
-but you don't want the gitolite-admin repo showing up on gitweb.  How do you
-do that?  Here's how:
-
-    repo gitolite-admin
-        -   =   gitweb daemon
-        option deny-rules = 1
-
-    repo @all
-        R   =   gitweb daemon
-
-Note that the order matters; the `-` rule must come *before* the `R` rule.
diff --git a/doc/setup.mkd b/doc/setup.mkd
deleted file mode 100644
index ef866c2..0000000
--- a/doc/setup.mkd
+++ /dev/null
@@ -1,48 +0,0 @@
-# setting up gitolite
-
-This is the second step in using gitolite, after [install][].  This also
-happens on the server,  (The next step is [clone][]).
-
-----
-
-Installing the software gets you ready to use it, but the first "use" of it is
-always the "setup" command.
-
-The first time you run it, you need to have a public key file (usually from
-the admin's workstation) ready.  If the main gitolite admin's username is
-"alice", this file should be named "alice.pub".  Then run
-
-    gitolite setup -pk alice.pub
-
-If that command completes without any warnings, you should be done.  If it had
-a warning, you probably supplied a key which already has shell access to the
-server.  That won't work.
-
->   ----
-
->   Normally, gitolite is hosted on a user that no one accesses directly --
->   you log on to the server using some other userid, and then `su - git`.  In
->   this scenario, there *is* no key being used for shell access, so there is
->   no conflict.
-
->   An alternative method is to use two different keys, and a [host
->   alias][ssh-ha] to distinguish the two.
-
->   [common errors][ce] has some links to background information on this
->   issue.
-
->   ----
-
-The 'setup' command has other uses, so you will be running it at other times
-after the install as well:
-
-  * To setup the update hook when you move [existing][] repos to gitolite.
-    This also applies if someone has been fiddling with the hooks on some
-    repos and you want to put them all right quickly.
-
-  * To replace a [lost admin key][lost-key].
-
-  * To setup gitolite for http mode (run 'gitolite setup -h' for more info).
-
-When in doubt, run 'gitolite setup' anyway; it doesn't do any harm, though it
-may take a minute or so if you have more than a few thousand repos!
diff --git a/doc/special.mkd b/doc/special.mkd
deleted file mode 100644
index 4ff6d75..0000000
--- a/doc/special.mkd
+++ /dev/null
@@ -1,102 +0,0 @@
-# special features and setups
-
-----
-
-[[TOC]]
-
-----
-
-## #elsewhere putting 'repositories' and '.gitolite' somewhere else
-
-Gitolite insists that the "repositories" and ".gitolite" directories be in
-`$HOME`.  If you want them somewhere else:
-
-  * do the install as normal,
-  * *then* move those directories to wherever you want and replace them with
-    symlinks pointing to the new location.
-
-## #writable disabling pushes to take backups
-
-The `writable` command allows you to disable pushes to all repos or just the
-named repo, in order to do file-system level things to the repo directory that
-require it not to change, like using normal backup software.
-
-Run `gitolite writable -h` for more info.
-
-## #pers "personal" branches
-
-"personal" branches are great for environments where developers need to share
-work but can't directly pull from each other (usually due to either a
-networking or authentication related reason, both common in corporate setups).
-
-Personal branches exist **in a namespace** of their own.  The syntax is
-
-        RW+ personal/USER/  =   @userlist
-
-where the "personal" can be anything you like (but cannot be empty), and the
-"/USER/" part is **necessary (including both slashes)**.
-
-A user "alice" (if she's in the userlist) can then push any branches inside
-`personal/alice/`.  Which means she can push `personal/alice/foo` and
-`personal/alice/bar`, but NOT `personal/alice`.
-
-(Background: at runtime the "USER" component will be replaced by the name of
-the invoking user.  Access is determined by the right hand side, as usual).
-
-Compared to using arbitrary branch names on the same server, this:
-
-  * Reduces namespace pollution by corralling all these ad hoc branches into
-    the "personal/" namespace.
-  * Reduces branch name collision by giving each developer her own
-    sub-hierarchy within that.
-  * Removes the need to think about access control, because a user can push
-    only to his own sub-hierarchy.
-
-## delegating access control responsibilities
-
-See [this][deleg].
-
-## #keysonly using pubkeys obtained from elsewhere
-
-If you're not managing keys via the gitolite-admin repo, but getting them from
-somewhere else, you'll want to periodically "update" the keys.
-
-To do that, first edit your rc file and add something like this:
-
-    SSH_AUTHKEYS                =>
-        [
-            'post-compile/ssh-authkeys',
-        ],
-
-Then write a script that
-
-  * gets all the keys and dumps them into `$HOME/.gitolite/keydir` (or into a
-    subdirectory of it).
-
-  * runs `gitolite trigger SSH_AUTHKEYS`.
-
-Run this from cron or however you want.
-
-## #gh giving users their own repos
-
-(Please see [this][wild] for background on the ideas in this section).
-
-It's very easy to give users their own set of repos to create, with the
-username at the top level.  The simplest setup is:
-
-    repo CREATOR/..*
-        C   =   @all
-        RW+ =   CREATOR
-        RW  =   WRITERS
-        R   =   READERS
-
-Now users can create any repo under their own name simply by cloning it or
-pushing to it, then use the [perms][] command to add other users to their
-WRITERS and READERS lists.
-
-Of course you can get much more creative if you add a few more roles (see
-"roles" in [this][wild] page).
-
-<font color="gray">(I prefer using some prefix, say "u", as in `repo
-u/CREATOR/..*`.  This helps to keep user-created repos separate, and avoid
-name clashes in some far-fetched scenarios).</font>
diff --git a/doc/sskm.mkd b/doc/sskm.mkd
deleted file mode 100644
index 4ac908d..0000000
--- a/doc/sskm.mkd
+++ /dev/null
@@ -1,242 +0,0 @@
-# changing keys -- self service key management
-
-[Note on g3 version: this has been manually spot-tested; there is no test suite.  Changes from g2 version are minimal so it should all work fine but please report errors!]
-
-Follow this guide to add keys to or remove keys from your account. Note that you cannot use this method to add your *first* key to the account; you must still email your initial key to your admin.
-
-The key management is done using a command called `sskm`.  This command must be enabled for remote use by the admin (see [here][commands] for more on this).
-
-----
-
-[[TOC]]
-
-----
-
-## Important!
-
-There are a few things that you should know before using the key management system. Please do not ignore this section!
-
-### Key fingerprints
-
-Keys are identified in some of these subcommands by their fingerprints. To see the fingerprint for a public key on your computer, use the following syntax:
-
-    ssh-keygen -l -f <path_to_public_key.pub>
-
-You'll get output like:
-
-    jeff at baklava ~  $  ssh-keygen -l -f .ssh/jeffskey.pub 
-    2048 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 .ssh/jeffskey.pub (RSA)
-
-### Active keys
-
-Any keys that you can use to interact with the system are active keys. (Inactive keys are keys that are, for instance, scheduled to be added or removed.) Keys are identified with their `keyid`; see the section below on listing keys.
-
-If you have no current active keys, you will be locked out of the system (in which case email your admin for help). Therefore, be sure that you are never removing your only active key!
-
-### Selecting which key to use
-
-Although you can identify yourself to the Gitolite system with any of your active keys on the server, at times it is necessary to specifically pick which key you are identifying with. To pick the key to use, pass the `-i` flag into `ssh`:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git info
-    hello jeff, the gitolite version here is v2.0.1-11-g1cd3414
-    the gitolite config gives you the following access:
-     @C  R   W      [a-zA-Z0-9][a-zA-Z0-9_\-\.]+[a-zA-Z0-9]
-    ....
-
-*N.B.*: If you have any keys loaded into `ssh-agent` (i.e., `ssh-add -l` shows
-at least one key), then this may not work properly.  `ssh` has a bug which
-makes it ignore `-i` values when that key has not been loaded into the agent.
-One solution is to add the key you want to use (e.g., `ssh-add
-.ssh/jeffskey`).  The other is to remove *all* the keys from the agent or
-disable the agent, using one of these commands:
-
-* Terminate `ssh-agent` or use `ssh-add -D` flag to remove identities from it
-* If using `keychain`, run `keychain --clear` to remove identities
-* Unset the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` variables in the current shell
-
-### Public vs. private keys
-
-In this guide, all keys are using their full suffix. In other words, if you see a `.pub` at the end of a key, it's the public key; if you don't, it's the private key. For instance, when using the `-i` flag with `ssh`, you are specifying private keys to use. When you are submitting a key for addition to the system, you are using the public key.
-
-## Listing your existing keys
-
-To see a list of your existing keys, use the `list` argument to `sskm`:
-
-    jeff at baklava ~  $  ssh git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-
-Notice the `@` sign in each key's name? That sign and the text after that up until the `.pub` is the `keyid`. This is what you will use when identifying keys to the system. Above, for instance, one of my keys has the `keyid` of `@key3`.
-
-A keyid may be *empty*; in fact to start with you may only have a single
-`jeff.pub` key, depending on how your admin added your initial key.  You can
-use any keyid you wish when adding keys (like `@home`, `@laptop`, ...); the
-only rules are that it must start with the `@` character and after that
-contain only digits, letters, or underscores.
-
-## Adding or Replacing a key
-
-### Step 1: Adding the Key
-
-Adding and replacing a key is the same process. What matters is the `keyid`. When adding a new key, use a new `keyid`; when replacing a key, pass in the `keyid` of the key you want to replace, as found by using the `list` subcommand. Pretty simple!
-
-To add a key, pipe in the text of your new key using `cat` to the `add` subcommand. In the example below, I explicitly select which existing, active pubkey to identify with for the command (using the `-i` parameter to ssh) for clarity:
-
-    jeff at baklava ~  $  cat .ssh/newkey.pub | ssh -i .ssh/jeffskey git at git sskm add @key4
-    hello jeff, you are currently using a normal ("active") key
-    please supply the new key on STDIN.  (I recommend you
-            don't try to do this interactively, but use a pipe)
-
-If you now run the `list` command you'll see that it's scheduled for addition:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-    == keys marked for addition/replacement ==
-    1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
-
-### Step 2: Confirming the addition
-
-Gitolite uses Git internally to store the keys. Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-add` subcommand to do this, *but*: to verify that you truly have ownership of the corresponding private key, you *must* use the key you are adding itself to do the confirmation! (Inconvenient like most security, but very necessary from a security perspective.) This is where using the `-i` flag of `ssh` comes in handy:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm confirm-add @key4
-    hello jeff, you are currently using a key in the 'marked for add' state
-
-Listing keys again shows that all four keys are now active:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
-
-### Optional: Undoing a mistaken add (before confirmation)
-
-Another advantage of Gitolite using Git internally is that that if we mistakenly add the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to remove into the `undo-add` subcommand:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm undo-add @key4
-    hello jeff, you are currently using a normal ("active") key
-
-Listing the keys shows that that new key has been removed:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-
-## Removing a key
-
-### Step 1: Mark the key for deletion
-
-Deleting a key works very similarly to adding a key, with `del` substituted for `add`.
-
-Let's say that I have my four keys from the example above:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
-
-I would like to remove the key that on my box is called `newkey` and in the Gitolite system is known as `@key4`.
-
-I simply pass in the identifier to the `del` subcommand of `sskm`:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm del @key4
-    hello jeff, you are currently using a normal ("active") key
-
-Listing the keys now shows that it is marked for deletion:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
-    hello jeff, you are currently using a key in the 'marked for del' state
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-    == keys marked for deletion ==
-    1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
-
-### Step 2: Confirming the deletion
-
-Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-del` subcommand to do this, *but*: unlike the `confirm-add` subcommand, you *must* use a *different* key than the key you are deleting to do the confirmation! This prevents you from accidentally locking yourself out of the system by removing all active keys:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm confirm-del @key4
-    hello jeff, you are currently using a normal ("active") key
-
-Listing keys again shows that the fourth key has been removed:
-
-    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-
-### Optional: Undoing a mistaken delete (before confirmation)
-
-Another advantage of Gitolite using Git internally is that that if we mistakenly delete the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to keep into the `undo-del` subcommand. Note that this operation *must* be performed using the private key that corresponds to the key you are trying to keep! (Security reasons, similar to the reason that you must confirm an addition this way; it prevents anyone from undoing a deletion, and therefore keeping in the system, a key that they cannot prove (by having the corresponding private key) should stay in the system):
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm undo-del @key4
-    hello jeff, you are currently using a key in the 'marked for del' state
-
-    You're undeleting a key that is currently marked for deletion.
-        Hit ENTER to undelete this key
-        Hit Ctrl-C to cancel the undelete
-    Please see documentation for caveats on the undelete process as well as how to
-    actually delete it.
-
-(Go ahead and hit ENTER there; the caveats are really only on the administrative side of things.)
-
-Listing the keys shows that that new key is now marked active again:
-
-    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
-    hello jeff, you are currently using a normal ("active") key
-    you have the following keys:
-    == active keys ==
-    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
-    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
-    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
-    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
-
-----
-
-## important notes for the admin
-
-These are the things that can break if you allow your users to use this
-command:
-
-  * "sskm" clones, changes, and pushes back the gitolite-admin repo.  This
-    means, even if you're the only administrator, you should never 'git push
-    -f', in case you end up overwriting something sskm did.
-
-  * There is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using
-    this command.  You can distinguish `foo/alice.pub` from
-    `bar/alice at home.pub`, but that's not because of the foo and bar, it's
-    because the two files have different keyids.
-
-    In other words, sskm only works with the older style, not with the
-    "subdirectory" style of [multi-key][] management.
-
-  * Keys placed in specific folders (for whatever reasons), will probably not
-    stay in those folders if this command is used.  Even a key delete, followed
-    by undoing the delete, will cause the key to effectively move to the root
-    of the key store (i.e., the `keydir` directory in the gitolite-admin repo).
diff --git a/doc/syntax.mkd b/doc/syntax.mkd
deleted file mode 100644
index 5d3b6b5..0000000
--- a/doc/syntax.mkd
+++ /dev/null
@@ -1,46 +0,0 @@
-# #syntax basic syntax
-
-In general, everything is **space separated**; there are no commas,
-semicolons, etc., in the syntax.
-
-**Comments** are in the usual shell-ish style.
-
-**User names** and **repo names** are as simple as possible; they must start
-with an alphanumeric, but after that they can also contain `.`, `_`, or `-`.
-
-Usernames can optionally be followed by an `@` and a domainname containing at
-least one `.` (this allows you to use an email address as someone's username).
-Reponames can contain `/` characters (this allows you to put your repos in a
-tree-structure for convenience)
-
-There are no continuation lines by default.  You do not need them; the section
-on "groups" will tell you how you can break up large lists of names in a group
-definition into multiple lines.
-
-<font color="gray">If you *must* have them, you can optionally enable them;
-see the syntactic [sugar][] section.</font>
-
-## #include include files
-
-Gitolite allows you to break up the configuration into multiple files and
-include them in the main file for convenience.
-
-    include     "foo.conf"
-
-will include the contents of the file "foo.conf".
-
-Details:
-
-  * You can also use a glob (`include "*.conf"`), or put your include files
-    into subdirectories of "conf" (`include "foo/bar.conf"`), or both
-    (`include "repos/*.conf"`).
-
-  * Included files are always searched from the gitolite-admin repo's "conf/"
-    directory, unless you supplied an absolute path.  (Note: in the interests
-    of cloning the admin-repo sanely you should avoid absolute paths!)
-
-  * If you ended up recursing, files that have been already processed once are
-    skipped, with a warning.
-
-<font color="gray">Advanced users: `subconf`, a command that is very closely
-related to `include`, is documented [here][subconf].</font>
diff --git a/doc/testing.mkd b/doc/testing.mkd
deleted file mode 100644
index bf3eb4d..0000000
--- a/doc/testing.mkd
+++ /dev/null
@@ -1,50 +0,0 @@
-# testing gitolite
-
-Here's how to *run* the tests.
-
-<font color="red">**WARNING: they will clobber lots of things in your `$HOME`,
-so be sure to use a throwaway userid**.</font>
-
-    git clone git://github.com/sitaramc/gitolite
-    cd gitolite
-    prove
-
-Make sure:
-
-  * `$HOME/bin` is in `$PATH`
-  * sshd allows incoming ssh to this userid, at least from localhost
-
-Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
-Take a look at some of the scripts and you will see what it looks like.  It
-has a few quirks and nuances; if you really care, email me.
-
-[tsh]: http://github.com/sitaramc/tsh
-
-The tests also use a somewhat convoluted system of environment variables in
-order to run *entirely* as a local user, without going through ssh at all.
-This lets a complete test suite run in about a fifth or less of the time it
-would otherwise take.
-
-If you think that defeats the purpose of the testing, you haven't read
-[this][auth] yet.
-
-## #trying trying out gitolite
-
-It's easy to take gitolite for a trial run, in ssh mode, and play with all of
-its features (except mirroring).
-
-Create a **throw-away userid**, log in to it, then do what the "testing
-gitolite" section above says, except instead of running `prove`, you run
-`prove t/ssh*`.
-
-When this is done, you get a gitolite installation with 7 gitolite users
-("admin", and "u1" through "u6").
-
-Don't forget that the client and the server are all on the same user on the
-same machine; we're *simulating* 7 gitolite users using ssh keys!  (How?
-Maybe `~/.ssh/config` will give you a hint).
-
-URLs look like `user:repo`, so for example you can clone the admin repo by
-`git clone admin:gitolite-admin`.  Remote commands look like `ssh u1 info`.
-
-So start by cloning the admin repo, and try out whatever you want!
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
deleted file mode 100644
index 9ecdbc9..0000000
--- a/doc/triggers.mkd
+++ /dev/null
@@ -1,161 +0,0 @@
-# gitolite triggers
-
-[[TOC]]
-
-## intro and sample rc excerpt
-
-Gitolite fires off external commands at 7 different times.  The [rc][] file
-specifies what commands to run at each trigger point, but for illustration,
-here's an excerpt:
-
-    %RC = (
-
-        <...several lines later...>
-
-        # comment out or uncomment as needed
-        # these will run in sequence after post-update
-        POST_COMPILE                =>
-            [
-                'post-compile/ssh-authkeys',
-                'post-compile/update-git-configs',
-                'post-compile/update-gitweb-access-list',
-                'post-compile/update-git-daemon-access-list',
-            ],
-
-        # comment out or uncomment as needed
-        # these will run in sequence after a new wild repo is created
-        POST_CREATE                 =>
-            [
-                'post-compile/update-git-configs',
-                'post-compile/update-gitweb-access-list',
-                'post-compile/update-git-daemon-access-list',
-            ],
-
-(As you can see, post-create runs 3 programs that also run from post-compile.
-This is perfectly fine, by the way)
-
-## types of trigger programs
-
-There are two types of trigger programs.  Standalone scripts are placed in
-src/triggers or its subdirectories.  They are invoked by being added to a
-trigger list (using the path after "src/triggers/", as you can see).  Such
-scripts are quick and easy to write in any language of your choice.
-
-Triggers written as perl modules are placed in src/lib/Gitolite/Triggers.
-They are invoked by being listed with the package+function name, although even
-here the 'Gitolite::Triggers' part is skipped.  Perl modules have to follow
-some conventions (see some of the shipped modules to ideas) but the advantage
-is that they can set environment variables and change the argument list of the
-gitolite-shell program that invokes them.
-
-If this does not make sense, please examine a default install of gitolite,
-paying attention to:
-
-  * the path names in various trigger lists in the rc file
-  * corresponding path names in the src/ directory in gitolite source
-  * and for perl modules, the package names and function names within.
-
-## manually firing triggers
-
-...from the server command line is easy.  For example:
-
-    gitolite trigger POST_COMPILE
-
-However if the triggered code depends on arguments (see next section) this
-won't work.  (The `POST_COMPILE` trigger programs all just happen to not
-require any arguments, so it works).
-
-## common arguments
-
-Triggers receive the following arguments:
-
-1.  any arguments mentioned in the rc file (for an example, see the renice
-    command in the PRE_GIT trigger sequence),
-
-2.  the name of the trigger as a string (example, `"POST_COMPILE"`), so you
-    can call the same program from multiple triggers and it can know where it
-    was called from,
-
-3.  followed by zero or more arguments specific to the trigger, as given in
-    the next section.
-
-## trigger-specific arguments and other details
-
-Here are the **rest of** the arguments for each trigger, plus a brief
-description of when the trigger runs:
-
-  * `INPUT` runs before pretty much anything else.  INPUT trigger scripts
-    *must* be in perl, since they manipulate the arguments to and the
-    environment of the 'gitolite-shell' program itself.  Most commonly they
-    will read/change `@ARGV`, and/or `$ENV{SSH_ORIGINAL_COMMAND}`.
-
-    There are certain conventions to adhere to; please see some of the shipped
-    samples or ask me if you need help writing your own.
-
-  * `ACCESS_1` runs after the first access check.  Extra arguments:
-      * repo
-      * user
-      * 'R' or 'W'
-      * 'any'
-      * result (see notes below)
-
-    'result' is the return value of the access() function.  If it contains the
-    uppercase word "DENIED", the access was rejected.  Otherwise it is the
-    refex that caused the access to succeed.
-
-    *Please note that if access is rejected, gitolite-shell will die as soon
-    as it returns from the trigger*.
-
-  * `ACCESS_2` runs after the second access check, which is invoked by the
-    update hook to check the ref.  Extra arguments:
-      * repo
-      * user
-      * any of W, +, C, D, WM, +M, CM, DM
-      * the ref being updated (e.g., 'refs/heads/master')
-      * result
-      * old SHA
-      * new SHA
-
-    `ACCESS_2` also runs on each [VREF][vref] that gets checked.  In this case
-    the "ref" argument will start with "VREF/", and the last two arguments
-    won't be passed.
-
-    'result' is similar to `ACCESS_1`, except that it is the *update hook*
-    which dies as soon as access is rejected for the ref or any of the VREFs.
-    Control then returns to git, and then to gitolite-shell, so the `POST_GIT`
-    trigger *will* run.
-
-  * `PRE_GIT` and `POST_GIT` run just before and after the git command.
-    Extra arguments:
-      * repo
-      * user
-      * 'R' or 'W'
-      * 'any'
-      * the git command ('git-receive-pack', 'git-upload-pack', or
-        'git-upload-archive') being invoked.
-
-  * `PRE_CREATE` and `POST_CREATE` run just before and after a new "[wild][]"
-    repo is created by user action.  Extra arguments:
-      * repo
-      * user
-      * invoking operation
-          * 'R' for fetch/clone/ls-remote, 'W' for push
-          * can also be anything set by the external command running the
-            trigger (e.g., see the perms and fork commands).
-
-    They are also run when a *normal* repo is created (say by adding a "repo
-    foo" line to the conf file).  This case has only one extra argument:
-      * repo
-
-  * `POST_COMPILE` runs after an admin push has successfully "compiled" the
-    config file.  By default, the next thing is to update the ssh authkeys
-    file, then all the 'git-config's, gitweb access, and daemon access.
-
-    No extra arguments.
-
-## tips
-
-If you have code that latches onto more than one trigger, collecting data
-(such as for logging), then the outputs may be intermixed.  You can record the
-value of the environment variable `GL_TID` to tie together related entries.
-
diff --git a/doc/user.mkd b/doc/user.mkd
deleted file mode 100644
index a0a767e..0000000
--- a/doc/user.mkd
+++ /dev/null
@@ -1,97 +0,0 @@
-# what users (not admins) need to know about gitolite
-
-...written for the one guy in the world no one will think of as "just a normal
-user" ;-)
-
-----
-
-[[TOC]]
-
-----
-
-## accessing gitolite
-
-The most common setup is based on ssh, where your admin asks you to send him
-your public key, and uses that to setup your access.
-
-Your actual access is either a git command (like `git clone
-git at server:reponame`, and we won't be discussing these any more in this
-document), or an ssh command (like `ssh git at server info`).
-
-Note that you do *not* get a shell on the server -- the whole point of
-gitolite is to prevent that!
-
-## #info the info command
-
-The only command that is *always* available to every user is the `info`
-command (run `ssh git at host info -h` for help), which tells you what version of
-gitolite and git are on the server, and what repositories you have access to.
-The list of repos is very useful if you have doubts about the spelling of some
-new repo that you know was setup.
-
-## digression: two kinds of repos
-
-Gitolite has two kinds of repos.  Normal repos are specified by their full
-names in the config file.  "Wildcard" repos are specified by a regex in the
-config file.  Try the [`info` command][info] and see if it shows any lines
-that look like regex patterns, (with a "C" permission).
-
-If you see any, it means you are allowed to create brand new repos whose names
-fit that pattern.  When you create such a repo, your "ownership" of it (as far
-as gitolite is concerned) is automatically recorded by gitolite.
-
-## other commands
-
-### #perms set/get additional permissions for repos you created
-
-The gitolite config may have several permissions lines for your repo, like so:
-
-    repo pub/CREATOR/..*
-        RW+     =   CREATOR
-        RW      =   user1 user2
-        R       =   user3
-
-If that's all it had, you really can't do much.  Any changes to access must be
-done by the administrator.  (Note that "CREATOR" is a reserved word that gets
-expanded to your userid in some way, so the admin can literally add just the
-first two lines, and *every* authenticated user now has his own personal repo
-namespace, starting with `pub/<username>/`).
-
-To give some flexibility to users, the admin could add rules like this:
-
-        RW      =   WRITERS
-        R       =   READERS
-
-(he could also add other roles but then he needs to read the documentation).
-
-Once he does this, you can then use the `perms` command (run `ssh git at host
-perms -h` for help) to set permissions for other users by specifying which
-users are in the list of "READERS", and which in "WRITERS".
-
-If you think of READERS and WRITERS as "roles", it will help.  You can't
-change what access a role has, but you *can* say which users have that role.
-
-**Note**: there isn't a way for you to see the actual rule set unless you're
-given read access to the special 'gitolite-admin' repo.  Sorry.  The idea is
-that your admin will tell you what "roles" he added into rules for your repos,
-and what permissions those roles have.
-
-### #desc adding a description to repos you created
-
-The `desc` command is extremely simple.  Run `ssh git at host desc -h` for help.
-
-## "site-local" commands
-
-The main purpose of gitolite is to prevent you from getting a shell.  But
-there are commands that you often need to run on the server (i.e., cannot be
-done by pushing something to a repo).
-
-To enable this, gitolite allows the admin to setup scripts in a special
-directory that users can then run.  Gitolite comes with a set of working
-scripts that your admin may install, or may use as a starting point for his
-own, if he chooses.
-
-Think of these commands as equivalent to those in `COMMAND_DIR` in `man
-git-shell`.
-
-You can get a list of available commands by running `ssh git at host help`.
diff --git a/doc/users.mkd b/doc/users.mkd
deleted file mode 100644
index 6211278..0000000
--- a/doc/users.mkd
+++ /dev/null
@@ -1,67 +0,0 @@
-# adding and removing users
-
-Strictly speaking, gitolite doesn't know where users come from.  If that
-surprises you, read [this][auth].  However, gitolite does help with ssh-based
-authentication, so here's some info on adding and removing users.
-
->   ----
-
->   *WARNING: Do NOT add new repos or users manually on the server.  Gitolite
->   users, repos, and access rules are maintained by making changes to a
->   special repo called 'gitolite-admin' and pushing those changes to the
->   server.*
-
->   ----
-
-All operations are in a clone of the gitolite-admin repo.
-
-To **add** a user, say Alice, obtain her public key (typically
-`$HOME/.ssh/id_rsa.pub` on her workstation), copy it to `keydir` with the user
-name as the basename (e.g., 'alice.pub' for user alice), then `git add
-keydir/alice.pub`.  (All keys files must have names ending in ".pub", and must
-be in openssh's default format).
-
-To **remove** a user, `git rm keydir/alice.pub`.
-
-In both cases, you must commit and push.  On receiving the push, gitolite will
-carry out the changes specified.
-
-The user name is simply the base name of the public key file name.  So
-'alice.pub', 'foo/alice.pub' and 'bar/alice.pub', all resolve to user "alice".
-
-## #multi-key multiple keys per user
-
-The simplest and most understandable is to put their keys in different
-subdirectories, (alice.pub, home/alice.pub, laptop/alice.pub, etc).
-
-### old style multi-keys
-
-There is another way that involves creating key files like `alice at home.pub`
-and `alice at laptop.pub`, but there is a complication because gitolite also
-allows *full email addresses* as user names.  (I.e., `sitaramc at gmail.com.pub`
-denotes the user called `sitaramc at gmail.com`).
-
-This older method of enabling multi-keys was developed to deal with that.  It
-will continue to work and be supported in *code*, simply because I prefer it.
-But I will not accept questions or doc patches for it, because it seems it is
-too difficult to understand for a lot of people.  This table of sample pubkey
-filenames and the corresponding derived usernames is all you get:
-
-  * plain username, no multikey
-
-        sitaramc.pub                            sitaramc
-
-  * plain username, with multikeys
-
-        sitaramc at laptop.pub                     sitaramc
-        sitaramc at desktop.pub                    sitaramc
-
-  * email address as username, no multikey
-
-        sitaramc at gmail.com.pub                  sitaramc at gmail.com
-
-  * email address as username, with multikeys
-
-        sitaramc at gmail.com@laptop.pub           sitaramc at gmail.com
-        sitaramc at gmail.com@desktop.pub          sitaramc at gmail.com
-
diff --git a/doc/vref.mkd b/doc/vref.mkd
deleted file mode 100644
index 0f7c382..0000000
--- a/doc/vref.mkd
+++ /dev/null
@@ -1,308 +0,0 @@
-# virtual refs
-
-**IMPORTANT**: fallthru is success in VREFs, unlike the normal refs.  That
-won't make sense until you read further, but I had to put it up here for folks
-who stop reading halfway!
-
-----
-
-[[TOC]]
-
-----
-
-Here's an example to start you off.
-
-    repo    r1
-        RW+                         =   lead_dev dev2 dev3
-        -   VREF/COUNT/9            =   dev2 dev3
-        -   VREF/COUNT/3/NEWFILES   =   dev2 dev3
-
-Now dev2 and dev3 cannot push changes that affect more than 9 files at a time,
-nor those that have more than 3 new files.
-
-----
-
-## rule matching recap
-
-You won't get any joy out of this if you don't understand at least
-[refex][]es and how [rules][] are processed.
-
-But VREFs have one **very important difference** from normal rules.  With
-VREFs, a **fallthru results in success**.  You'll see why this is more
-convenient as you read on.
-
-----
-
-## what is a virtual ref
-
-A ref like `refs/heads/master` is the main property of a push that gitolite
-uses to make its yes/no decision.  I call this a "real" ref.
-
-Any *other* property of the push that you want to use to help in the decision
-is therefore a *virtual* ref.  This could be a property that git knows about,
-like in the example above, or comes from outside git like, say, the current
-time; see examples section later for some ideas.
-
-## #vref-fallthru fallthru is success here
-
-Notice that you didn't need to add an `RW+ VREF/...` rule for user `lead_dev`
-in our example.  This section explains why.
-
-**Virtual refs are best used as additional "deny" rules**, performing extra
-checks that core gitolite cannot.
-
-Making fallthru be a "fail" forces you to add rules for all users, instead of
-just the ones who should have those extra checks.  Worse, since every virtual
-ref involves calling an external program, many of these calls may be wasted.
-
-There's another advantage to doing it this way: a VREF can choose to simply
-die if things look bad, and it will have the same effect, assuming you used
-the VREF only in "deny" rules.
-
-This in turn means any existing update hook can be used as a VREF *as-is*, as
-long as it (a) prints nothing on success and (b) dies on failure.  See the
-email-check example later.
-
-## how it works -- overview
-
-Briefly, a refex starting with `VREF/FOO` triggers a call to a program called
-`FOO` in `$GL_BINDIR/VREF`.
-
-That program is expected to print zero or more lines to its STDOUT; each line
-that starts with `VREF/` is taken by gitolite as a new "ref" to be matched
-against all the refexes for this user in the config.  Including the refex that
-caused the vref call, of course.
-
-Normally, you send back the refex itself, if the test determines that the rule
-should be matched, otherwise nothing.  So, in our example, we print
-`VREF/COUNT/9` if the count was indeed greater than 9.  Otherwise we just
-exit.
-
-## how it works -- details
-
-  * The VREF code is only called if there are any VREF rules for the user,
-    which means when the lead developer pushes, the VREF is not called at all.
-
-    Side note: this is enormously more efficient than adding additional
-    `update` hooks, which will get executed whether they are needed or not,
-    for every repo and every user!
-
-  * When dev2 or dev3 push, gitolite first checks the real ref
-    (`ref/heads/master` or whatever).  After this it looks at VREF rules, and
-    calls an external program for every one it finds.  Specifically, in a line
-    like
-
-           -   VREF/COUNT/3/NEWFILES    =   user
-
-    COUNT is the vref name, so the program called is
-    `$GL_BINDIR/VREF/COUNT`.
-
-    The program is passed **nine arguments** in this case (see next section
-    for details).
-
-  * The script can print anything it wants to STDOUT.  Lines not starting with
-    `VREF/` are printed as is (so your VREF can do mostly-normal printing to
-    STDOUT).
-
-    For lines starting with `VREF/`, the first word in each such line will be
-    treated as a virtual ref to be matched against all the rules, while the
-    rest, if any, is a message to be added to the standard "...DENIED..."
-    message that gitolite prints if that refex matches.
-
-    Usually it only makes sense to either
-
-      * Print nothing that starts with `VREF/` -- if you don't want the rule
-        that triggered it to match (ie., whatever condition being tested was
-        not violated; like if the count of changed files did not exceed 9, in
-        our earlier example).
-      * Print the refex itself (plus an optional message), so that it matches
-        the line which invoked it.
-
-### #vref-args arguments passed to the vref code
-
-  * Arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
-    to the update hook (see 'man githooks').
-
-    This, combined with the fact that non-zero exits are detected, mean that
-    you can simply use an existing update.secondary as a new VREF as-is, no
-    changes needed.
-
-  * Arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs.  These are the
-    same as the oldsha and newsha values, except if one of them is all-0.
-    (indicating a ref creation or deletion).  In that case the corresponding
-    'tree' SHA is set (by gitolite, as a courtesy) to the special SHA
-    `4b825dc642cb6eb9a060e54bf8d69288fbee4904`, which is the hash of an empty
-    tree.
-
-    (None of these shenanigans would have been needed if `git diff $oldsha
-    $newsha` would not error out when passed an all-0 SHA.)
-
-  * Argument **6**: the attempted access flag.  Typically `W` or `+`, but
-    could also be `C`, `D`, or any of these 4 followed by `M`.  If you have to
-    ask what they mean, you haven't read enough gitolite documentation to be
-    able to make virtual refs work.
-
-  * Argument **7**: is the entire refex; in our example
-    `VREF/COUNT/3/NEWFILES`.
-
-  * Arguments **8 onward**: are the split out (by `/`) portions of the refex,
-    excluding the first two components.  In our example they would be `3`
-    followed by `NEWFILES`.
-
-Yes, argument 7 is redundant if you have 8 and 9.  It's meant to make it easy
-to write vref scripts in any language.  See script examples in source.
-
-## what (else) can the vref code pass back
-
-Actually, the vref code can pass anything back; each line in its output that
-starts with `VREF/` will be matched against all the rules as usual (with the
-exception that fallthru is not failure).
-
-For example, you could have a ruleset like this:
-
-    repo r1
-        # ... normal rules ...
-
-        -   VREF/TIME/WEEKEND       =   @interns
-        -   VREF/TIME/WEEKNIGHT     =   @interns
-        -   VREF/TIME/HOLIDAY       =   @interns
-
-and you could write the TIME vref code to passback any or all
-of the times that match.  Then if an intern tried to access the system, each
-rule would trigger a call to gl-bindir/VREF/TIME.
-
-The script should send back any of the applicable times (even more than one,
-or none at all, as the case may be).  So even if it was invoked using the
-first rule, it might pass back (to gitolite) a virtual ref saying
-'VREF/TIME/HOLIDAY', which would promptly cause the request to be denied.
-
-## VREFs shipped with gitolite
-
-### #NAME restricting pushes by dir/file name
-
-The "NAME" VREF allows you to restrict pushes by the names of dirs and files
-changed.  (Side note: the NAME VREF is the only one directly implemented
-within the update hook, so you won't find it in the VREF directory).
-
-Here's an example.  Say you don't want junior developers pushing changes to
-the Makefile, because it's quite complex:
-
-    repo foo
-            RW+                             =   @senior_devs
-            RW                              =   @junior_devs
-
-            -   VREF/NAME/Makefile          =   @junior_devs
-
-When a senior dev pushes, the VREF is not invoked at all.  But when a junior
-dev pushes, the VREF is invoked, and it returns a list of files changed **as
-refs**, looking like this:
-
-    VREF/NAME/file-1
-    VREF/NAME/dir-2/file-3
-    ...etc...
-
-Each of these refs is matched against the access rules.  If one of them
-happens to be the Makefile, then the ref returned (VREF/NAME/Makefile) will
-match the deny rule and kill the push.
-
-Another way to use this is when you know what is allowed instead of what is
-not allowed.  Let's say the QA person is only allowed to touch a file called
-CHANGELOG and any files in a directory called ReleaseNotes:
-
-    repo foo
-            RW+                             =   @senior_devs
-            RW                              =   @junior_devs
-            RW+                             =   QA-guy
-
-            RW+ VREF/NAME/CHANGELOG         =   QA-guy
-            RW+ VREF/NAME/ReleaseNotes/     =   QA-guy
-            -   VREF/NAME/                  =   QA-guy
-
-### number of new files
-
-The COUNT VREF is used like this:
-
-    -   VREF/COUNT/9                    =   @junior-developers
-
-In response, if anyone in the user list pushes a commit series that
-changes more than 9 files, a vref of "VREF/COUNT/9" is returned.  Gitolite
-uses that as a "ref" to match against all the rules, hits the same rule
-that invoked it, and denies the request.
-
-If the user did not push more than 9 files, the VREF code returns nothing,
-and nothing happens.
-
-COUNT can take one more argument:
-
-    -   VREF/COUNT/9/NEWFILES           =   @junior-developers
-
-This is the same as before, but have to be more than 9 *new* files not
-just changed files.
-
-### advanced filetype detection
-
-Note: this is more for illustration than use; it's rather specific to one of
-the projects I manage but the idea is the important thing.
-
-Sometimes a file has a standard extension (that cannot be 'gitignore'd), but
-it is actually automatically generated.  Here's one way to catch it:
-
-     -   VREF/FILETYPE/AUTOGENERATED     =   @all
-
-You can look at `src/VREF/FILETYPE` to see how it handles the
-'AUTOGENERATED' option.  You could also have a more generic option, like
-perhaps BINARY, and handle that in the FILETYPE vref too.
-
-### checking author email
-
-Some people want to ensure that "you can only push your own commits".
-
-If you force it on everyone, this is a very silly idea (see "Philosophical
-Notes" section of `src/VREF/EMAIL-CHECK`).
-
-But there may be value in enforcing it just for the junior developers.
-
-The neat thing is that the existing `contrib/update.email-check` was just
-copied to `src/VREF/EMAIL-CHECK` and it works, because VREFs get
-the same first 3 arguments and those are all that it cares about.  (Note: you
-have to change one subroutine in that script if you want to use it)
-
-### #votes voting on commits
-
-Although gitolite can't/won't do the whole "code review + workflow
-enforcement" thing that Gerrit Code Review does, a basic implementation of
-voting on a commit is surprisingly easy.  See src/VREF/VOTES for details (and
-note that the actual *code* is just 2-3 lines; the rest is inline
-documentation).
-
-## other ideas -- code welcome!
-
-### "no non-merge first-parents"
-
-Shruggar on #gitolite wanted this.  Possible code to implement it would be
-something like this (untested)
-
-    [ -z "$(git rev-list --first-parent --no-merges $2..$3)" ]
-
-This can be implemented using `src/VREF/MERGE-CHECK` as a model.  That script
-does what the 'M' qualifier does in access rules (see last part of
-[this][write-types]), although the syntax to be used in conf/gitolite will be
-quite different.
-
-### other ideas for VREFs
-
-Here are some more ideas:
-
-  * Number of commits (`git rev-list --count $old $new`).
-  * Number of binary files in commit (currently I only know to count
-    occurrences of ` Bin ` in the output of `git diff --stat`.
-  * Number of *new* binary files (count ` Bin 0 ->` in `git diff --stat`
-    output).
-  * Time of day/day of week (see example snippet somewhere above).
-  * IP address.
-  * Phase of the moon.
-
-Note that pretty much anything that involves `$oldsha..$newsha` will have to
-deal with the issue that when you push a new tag or branch, the "old" part
-is all 0's, and unless you consider `--all` existing branches and tags it
-becomes meaningless in terms of "number of new files" etc.
diff --git a/doc/why.mkd b/doc/why.mkd
deleted file mode 100644
index f88f7c1..0000000
--- a/doc/why.mkd
+++ /dev/null
@@ -1,97 +0,0 @@
-# why might you need gitolite
-
-[[TOC]]
-
-----
-
-## basic use case
-
-Gitolite is useful in any server that is going to host multiple git
-repositories, each with many developers, where "anyone can do anything to any
-repo" is not a good idea.  Here're two examples to illustrate.
-
-Example 1, 3 repos, 3 developers with different levels of access to each repo:
-
-    repo foo
-        RW+     =   alice
-        R       =   bob
-
-    repo bar
-        RW+     =   bob
-        R       =   alice
-
-    repo baz
-        RW+     =   carol
-        R       =   alice bob
-
-Example 2, one repo, but different levels of access to different branches and
-tags for different developers:
-
-    repo foo
-        RW+ master                  =   alice
-        RW+ dev/                    =   bob
-        RW  refs/heads/tags/v[0-9]  =   ashok
-
-## #alt alternatives
-
-### unix permissions and ACLs
-
-If you're a masochist, you could probably do example 1 with Unix permissions
-and facls.  But you can't do example 2 -- git is not designed to allow that!
-
-Here are some other disadvantages of the Unix ACL method:
-
-  * Every user needs a userid and password on the server.
-  * Changing access rights involves complex `usermod -G ...` mumblings
-    (I.e., the "pain" mentioned above is not a one-time pain!)
-  * *Viewing* the current set of permissions requires running multiple
-    commands to list directories and their permissions/ownerships, users and
-    their group memberships, and then correlating all these manually.
-  * Auditing historical permissions or permission changes is impossible.
-
-### #gcr Gerrit Code Review
-
-The best real alternative to gitolite is Gerrit Code Review.  If code review
-is an essential part of your workflow, you should use Gerrit.
-
-Here're some high level differences between gitolite and Gerrit:
-
-**Size**: 3000+ lines of perl versus of 56,000+ lines of Java
-
-**Architecture**: Gitolite sits on top of "standard" git and openssh, which
-are assumed to already be installed.  Gerrit includes its own git stack (jgit)
-and sshd (Apache Mina).  In Java tradition, they all come bundled together.
-
-(Corollary: As far as I know jgit does not support the same hooks that 'man
-githooks' talks about).
-
-Gitolite uses a plain text config file; gerrit uses a database.
-
-**User view**: Gitolite is invisible to users except when access is denied.  I
-think Gerrit is much more visible to devs.
-
-On a related note, gitolite does not do anything special with signed or
-annotated tags, nor does it check author/committer identity.  However, it is
-trivial to add your own code to do either (or if someone contributes it, to
-just "enable" what ships with gitolite in a disabled state).
-
-### gitorious
-
-Anecdotally, gitorious is very hard to install.  Comparison with gitolite may
-be useless because I believe it doesn't have branch/tag level access control.
-However, I can't confirm or deny this because I can't find any documentation
-on the website.
-
-In addition, the main website hides the source code very well, so you already
-have a challenge!  [The only link I could find was tucked away at the bottom
-of the About page, in the License section].
-
-### gitlab
-
-Gitlab is built on top of gitolite, but I don't know more than that as yet.
-Patches welcome.
-
-### others
-
-Please send in patches to this doc if you know of other open source git
-hosting solutions that do access control.
diff --git a/doc/wild.mkd b/doc/wild.mkd
deleted file mode 100644
index 4670cc4..0000000
--- a/doc/wild.mkd
+++ /dev/null
@@ -1,157 +0,0 @@
-# "wild" repos (user created repos)
-
-## quick introduction
-
-The wildrepos feature allows you to specify access control rules using regular
-expression patterns, so you can have many actual repos being served by a
-single set of rules in the config file.  The regex pattern can also include
-the word `CREATOR` in it, allowing you to parametrise the name of the user
-creating the repo.
-
-See the section on "repo patterns" later for additional information on what
-counts as a "wild" repo pattern and how it is matched.
-
-## (admin) declaring wild repos in the conf file
-
-Here's an example:
-
-    @prof       =   u1
-    @TAs        =   u2 u3
-    @students   =   u4 u5 u6
-
-    repo    assignments/CREATOR/a[0-9][0-9]
-        C   =   @students
-        RW+ =   CREATOR
-        RW  =   WRITERS @TAs
-        R   =   READERS @prof
-
-Note the "C" permission.  This is a standalone "C", which gives the named
-users the right to *create a repo*.  <font color="gray">This is not to be
-confused with the "RWC" or its variants described elsewhere, which are about
-*branches*, not *repos*.</font>
-
-## #create (user) creating a specific repo
-
-For now, ignore the special usernames READERS and WRITERS, and just create a
-new repo, as user "u4" (a student):
-
-    $ git clone git at server:assignments/u4/a12
-    Initialized empty Git repository in /home/sitaram/a12/.git/
-    Initialized empty Git repository in /home/git/repositories/assignments/u4/a12.git/
-    warning: You appear to have cloned an empty repository.
-
-Notice the *two* empty repo inits, and the order in which they occur ;-)
-
-## a slightly different example
-
-Here's how the same example would look if you did not want the CREATOR's name
-to be part of the actual repo name.
-
-    repo    assignments/a[0-9][0-9]
-        C   =   @students
-        RW+ =   CREATOR
-        RW  =   WRITERS @TAs
-        R   =   READERS @prof
-
-We haven't changed anything except the repo name pattern.  This means that the
-first student that creates, say, `assignments/a12` becomes the owner.
-Mistakes (such as claiming a12 instead of a13) need to be rectified by an
-admin logging on to the back end, though it's not too difficult.
-
-You could also repace the C line like this:
-
-        C   =   @TAs
-
-and have a TA create the repos in advance.
-
-## repo patterns
-
-### pattern versus normal repo
-
-Due to projects like `gtk+`, the `+` character is now considered a valid
-character for an *ordinary* repo.  Therefore, a pattern like `foo/.+` does not
-look like a regex to gitolite.  Use `foo/..*` if you want that.
-
-Also, `..*` by itself is not considered a valid repo pattern.  Try
-`[a-zA-Z0-9].*`.
-
-### line-anchored regexes
-
-A regex like
-
-    repo assignments/S[0-9]+/A[0-9]+
-
-would match `assignments/S02/A37`.  It will not match `assignments/S02/ABC`,
-or `assignments/S02/a37`, obviously.
-
-But you may be surprised to find that it does not match even
-`assignments/S02/A37/B99`.  This is because internally, gitolite
-*line-anchors* the given regex; so that regex actually becomes
-`^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending
-metacharacters.
-
->   ----
-
->   *Side-note: contrast with refexes*
-
->   Just for interest, note that this is in contrast to the refexes for the
->   normal "branch" permissions, as described in `doc/gitolite.conf.mkd` and
->   elsewhere.  These "refexes" are only anchored at the start; a pattern like
->   `refs/heads/master` actually can match `refs/heads/master01/bar` as well,
->   even if no one will actually push such a branch!  You can anchor both
->   sides if you really care, by using `master$` instead of `master`, but that
->   is *not* the default for refexes.
-
->   ----
-
-## roles
-
-The tokens READERS and WRITERS are called "role" names.  The access rules in
-the conf file decide what permissions these roles have, but they don't say
-what users are in each of these roles.
-
-That needs to be done by the creator of the repo, using the `perms` command.
-You can run `ssh git at host perms -h` for detailed help, but in brief, that
-command lets you give and take away roles to users.  [This][perms] has some
-more detail.
-
-## adding other roles
-
-If you want to have more than just the 2 default roles, say something like:
-
-    repo foo/..*
-      C                 =   u1
-      RW    refs/tags/  =   TESTERS
-      -     refs/tags/  =   @all
-      RW+               =   WRITERS
-      RW                =   INTERNS
-      R                 =   READERS
-      RW+D              =   MANAGERS
-
-You can add the new names to the ROLES hash in the [rc][] file.  Be sure to
-run the 2 commands mentioned there after you have added the roles.
-file.  The rc file documentation (`doc/gitolite.rc.mkd`) explains how.
-
-#### #rolenamewarn **IMPORTANT WARNING ABOUT THIS FEATURE**
-
-Please make sure that none of the role names conflict with any of the user
-names or group names in the system.  For example, if you have a user called
-"foo" or a group called "@foo", make sure you do not include "foo" as a valid
-role in the ROLES hash.
-
-You can keep things sane by using UPPERCASE names for roles, while keeping all
-your user and group names lowercase; then you don't have to worry about this
-problem.
-
-## listing wild repos
-
-In order to see what repositories were created from a wildcard, use the 'info'
-command.  Try `ssh git at host info -h` to get help on the info command.
-
-## deleting a wild repo
-
-Run the whimsically named "D" command -- try `ssh git at host D -h` for more info
-on how to delete a wild repo.  (Yes the command is "D"; it's meant to be a
-counterpart to the "C" permission that allowed you to create the repo in the
-first place).  Of course this only works if your admin has enabled the command
-(gitolite ships with the command disabled for remote use).
diff --git a/doc/write-types.mkd b/doc/write-types.mkd
deleted file mode 100644
index 79d8f67..0000000
--- a/doc/write-types.mkd
+++ /dev/null
@@ -1,31 +0,0 @@
-## #write-types different types of write operations
-
-Git supplies enough information to the update hook to be able to distinguish
-several types of writes.
-
-The most common are:
-
-  * `RW` -- create a ref or fast-forward push a ref.  No rewinds or deletes.
-  * `RW+` -- create, fast-forward push, rewind push, or delete a ref.
-
-Sometimes you want to allow people to push, but not *create* a ref.  Or
-rewind, but not *delete* a ref.  The `C` and `D` qualifiers help here.
-
-  * When a rule specifies `RWC` or `RW+C`, then *rules that do NOT have the C
-    qualifier will no longer permit **creating** a ref*.
-
-    <font color="gray">Please do not confuse this with the standalone `C`
-    permission that allows someone to [create][] a **repo**</font>
-
-  * When a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the D
-    qualifier will no longer permit **deleting** a ref*.
-
-Note: These two can be combined, so you can have `RWCD` and `RW+CD` as well.
-
-One very rare need is to reject merge commits (a commit series that is not a
-straight line of commits).  The `M` qualifier helps here:
-
-  * When a rule has `M` appended to the permissions, *rules that do NOT have
-    it will reject a commit sequence that contains a merge commit* (i.e., they
-    only accept a straight line series of commits).
-

commit 53f9a867dfca290628a13b7d012ba92213eebfac
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 21 21:47:56 2012 +0530

    accumulated docfixes...
    
      - non-core documentation reduced to be easier to maintain
      - much reduced progit section submitted to scott chacon, necessitating
        some changes to this copy
      - other minor stuff
      - the "idiot-proof setup" :)
    
    (plus get rid of that silly "dot.pl"; it's not needed any more, if it
    ever was!)

diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 18cd005..a493ee7 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -55,6 +55,10 @@ In addition, you can also look at the comments in src/lib/Gitolite/Easy.pm
 ...is implemented by Gitolite::Easy; the comments in src/lib/Gitolite/Easy.pm
 serve as documentation.
 
+Note that some of the perl functions called by Easy.pm will change the current
+directory to something else, without saving and restoring the directory.
+Patches (to Easy.pm *only*) welcome.
+
 ## writing your own...
 
 ### ...commands
diff --git a/doc/ips.mkd b/doc/ips.mkd
new file mode 100644
index 0000000..dcbff02
--- /dev/null
+++ b/doc/ips.mkd
@@ -0,0 +1,104 @@
+## idiot-proof setup for gitolite
+
+WARNING 1: this document uses my new, Linus-inspired, motto: people who get
+offended, *should* be offended.
+
+WARNING 2: contains more words in ALL CAPS than all my other documents put
+together.
+
+WARNING 3: this document will work for any Linux on which git has already been
+installed.  BSDs, or legacy systems like Solaris, also should work but I can't
+guarantee it or test it.
+
+----
+
+Some people seem to get terribly, **terribly** confused by ssh.  If you've
+read through all the [documentation on ssh][ssh] that came with gitolite, yet
+you *still* cannot get things to work, you have two choices:
+
+1.  exchange your "developer" badge for a "manager" badge and then hire
+    someone to do this.
+
+2.  start over with the instructions below.  They include some DRASTIC
+    measures, requirements, and restrictions, almost NONE of which are
+    normally necessary, but it's either that or a suit and tie from tomorrow
+    so play along.
+
+**IMPORTANT**: Do NOT ask for help on these instructions unless you have kept
+a detailed log of every command you typed, and the complete response you got.
+I do not ask for this information to help you -- that's only a front.  I
+*know* these instructions work (at least on any Linux that already has git
+installed), so the real reason is to find where you mistyped something and
+mock you for that,
+
+### Assumptions
+
+  * your name is Ron.  Substitute accordingly in the instructions below.
+
+  * you have a workstation.
+
+  * you have a server called `server`.
+
+  * you have root access on this server.
+
+### Tasks
+
+1.  Create a new userid on the server, say `git`.  This will be the **hosting
+    user**.  ("hosting user" means when you're done installing, your users
+    will use URLs like `git at server:reponame` or `ssh://git@server/reponame`).
+
+    **Make sure this is a NEW userid**.
+
+    If the name you want already exists, then:
+
+      * log in as root
+      * if you have any data on that user's HOME directory save it somewhere
+        else
+      * delete the userid
+      * obliterate the home directory of the user (since on most systems
+        merely deleting the user does not remove the home directory).
+      * re-create the userid again
+
+2.  If you don't already have one, make yourself an ssh keypair **on your
+    workstation**.
+
+    Do NOT, in a fit of inspiration and energy, add this public key to the
+    authorised keys file on the newly created hosting user!
+
+    Your ONLY access to the new (`git`) userid should be by logging onto the
+    server as root, then running `su - git`.
+
+3.  Now copy the pubkey from your workstation (`~/.ssh/id_rsa.pub`) to the
+    server as `/tmp/ron.pub`.  (Your name is Ron, remember?)
+
+4.  Log on to the server as root.
+
+5.  Switch to the `git` user:
+
+        su - git
+
+6.  Clone the gitolite source code
+
+        git clone git://github.com/sitaramc/gitolite
+
+7.  Install it
+
+        cd $HOME
+        mkdir -p bin
+        gitolite/install -to $HOME/bin
+
+8.  Set it up
+
+        cd $HOME
+        $HOME/bin/gitolite setup -pk /tmp/ron.pub
+
+9.  Now go to your workstation and type in
+
+        git ls-remote git at server:gitolite-admin
+
+    This should return something like
+
+        9dd8aab60bac5e54ccf887a87b4f3d35c96b05e4    HEAD
+        9dd8aab60bac5e54ccf887a87b4f3d35c96b05e4    refs/heads/master
+
+    (do I have to mention that your SHAs will be different?)
diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
index 9e4672a..77cd40f 100644
--- a/doc/master-toc.mkd
+++ b/doc/master-toc.mkd
@@ -2,7 +2,7 @@
 
 ## [Introduction][index]
 
-  * (for [current][g2] gitolite (v2) users)
+  * (for [older][g2] gitolite (v1 or v2) users)
   * [quick links][ql]
   * [what][] is gitolite
   * [why][] might you need it
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index c07c3e3..dc0e188 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -15,16 +15,17 @@ remote user running `ssh git at host help`.
 All the commands that ship with gitolite will respond to `-h`; please report a
 bug to me if they don't.
 
-Here's a list of remote commands that are shipped:
+A particularly interesting command is the 'sudo' command, which allows an
+admin to run any remote command as some other user (though not the other way
+round!)
 
-  * 'desc' -- get/set the 'description' file for a repo
-  * 'fork' -- fork a repo
-  * 'info' -- already documented [here][info]
+Here's a list of some commands where additional information is available
+elsewhere:
+
+  * 'info' -- documented [here][info]
   * 'mirror' -- documented [here][sync]
   * 'perms' -- get/set the gl-perms file; see [perms][] for more
   * 'sskm' -- self-service key management, see [sskm][] for more
-  * 'writable' -- disabling pushes to take backups etc
-  * 'D' -- deleting user-created repos
 
 ## syntactic sugar
 
@@ -45,55 +46,30 @@ The following "sugar" programs are available:
 
 ## triggers
 
-Note that some features need to be enabled in more than one trigger.
-Mirroring is probably the best example -- it needs to hook into the `INPUT`,
-`PRE_GIT`, and the `POST_GIT` triggers to work.
-
-The `INPUT` triggers are:
+Most triggers need to be enabled by adding or uncommenting an appropriate line
+in the [rc][] file.  There are enough examples in there for the syntax to not
+need explanation.
 
-  * CpuTime -- see the `POST_GIT` section below
-  * Shell -- see "giving shell access to gitolite users" in [this][sts] page.
-  * Alias -- documentation is within the source file Alias.pm
-  * Mirroring -- see [doc/mirroring.mkd][mirroring]
+Some features need to be enabled in more than one trigger.  Mirroring is
+probably the best example -- it needs to hook into the `INPUT`, `PRE_GIT`, and
+the `POST_GIT` triggers to work.
 
-The `PRE_GIT` triggers are:
+In general, the source code for each trigger will tell you what it is doing
+and which trigger list you should add it to.  Please note that there are two
+types of [triggers][]; the perl triggers usually have subroutine names that
+reflect what trigger sections they should go into.  (Using mirroring as an
+example again, the Mirroring.pm perl module has sub's named 'input',
+'pre\_git', and 'post\_git').
 
-  * renice -- this renices the entire job to whatever value you specify.
-  * Mirroring -- see [doc/mirroring.mkd][mirroring]
-  * partial-copy -- this has its own section later in this page.
+Please report a bug to me if you could not find the information you wanted on
+any specific trigger.
 
-The `POST_GIT` triggers are:
+Here's a list of some triggers where additional information is available
+elsewhere:
 
+  * Shell -- see "giving shell access to gitolite users" in [this][sts] page.
   * Mirroring -- see [doc/mirroring.mkd][mirroring]
-  * CpuTime -- this is only a sample because it only prints the CPU times
-    data.  In reality you will want to do something else with it.
-
-The `POST_COMPILE` triggers are:
-
-  * post-compile/ssh-authkeys -- takes the pubkeys in keydir and populates
-    `~/.ssh/authorized_keys`.
-
-  * post-compile/update-git-configs -- updates individual 'repo.git/config'
-    files (using the 'git config ...' command) from settings supplied in the
-    conf file.  All sections except 'gitolite-options' are processed.  (The
-    'gitolite-options' section is considered internal to gitolite).
-
-  * post-compile/update-git-daemon-access-list -- create/delete
-    'git-daemon-export-ok' files in each repo based on whether the conf says
-    'daemon' can read the repo or not.
-
-  * post-compile/update-gitweb-access-list -- populates the file named in
-    `GITWEB_PROJECTS_LIST` in the rc file (default: `$HOME/projects.list`)
-    with the list of repos that gitweb is allowed to access.  This could be
-    more than just "R = gitweb"; any repo that has any config setting with the
-    section name 'gitweb' (like 'gitweb.owner', 'gitweb.description', etc) is
-    considered readable by gitweb, so the final list is a union of these two
-    methods.
-
-The `POST_CREATE` triggers are:
-
-  * The last 3 in the `POST_COMPILE` list also run from `POST_CREATE`, for
-    obvious reasons.
+  * partial-copy -- this has its own section later in this page.
 
 ## VREFs
 
diff --git a/doc/progit.mkd b/doc/progit.mkd
index fe4e304..5e293c1 100644
--- a/doc/progit.mkd
+++ b/doc/progit.mkd
@@ -1,9 +1,7 @@
-# (master copy of progit chapter on gitolite)
+# (expanded version of the gitolite chapter in the progit book)
 
 ## Gitolite ##
 
-Note: the latest copy of this section of the ProGit book is always available within the [gitolite documentation][progit].  The author would also like to humbly state that, while this section is accurate, and *can* (and often *has*) been used to install gitolite without reading any other documentation, it is of necessity not complete, and cannot completely replace the enormous amount of documentation that gitolite comes with.
-
 [Update 2012-04-10]: This page has been completely rewritten for gitolite version 3, informally called "g3".  G3 is a *total* rewrite of gitolite to push a lot more features away from "core", give the core a bunch of extension mechanisms, and finally have much better shell and perl APIs to bring all this together.
 
 Git has become very popular in corporate environments, which tend to have some additional requirements in terms of access control.  Gitolite was originally created to help with those requirements, but it turns out that it's equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too.  KDE, and kernel.org, are other very high-profile users of gitolite.
diff --git a/doc/repos.mkd b/doc/repos.mkd
index d32062a..fc73d36 100644
--- a/doc/repos.mkd
+++ b/doc/repos.mkd
@@ -5,10 +5,10 @@ repos into gitolite control, click [here][existing].
 
 >   ----
 
->   *WARNING: Do NOT add new repos directly on the server.  Clone the
->   'gitolite-admin' repo to your workstation, make changes to it, then add,
->   commit, and push.  When the push hits the server, the server "acts" upon
->   your changes.*
+>   *WARNING: Do NOT add new repos or users manually on the server.  Gitolite
+>   users, repos, and access rules are maintained by making changes to a
+>   special repo called 'gitolite-admin' and pushing those changes to the
+>   server.*
 
 >   ----
 
diff --git a/doc/users.mkd b/doc/users.mkd
index f815aaa..6211278 100644
--- a/doc/users.mkd
+++ b/doc/users.mkd
@@ -6,10 +6,10 @@ authentication, so here's some info on adding and removing users.
 
 >   ----
 
->   *WARNING: Do NOT add users directly on the server.  Clone the
->   'gitolite-admin' repo to your workstation, make changes to it, then add,
->   commit, and push.  When the push hits the server, the server "acts" upon
->   your changes.*
+>   *WARNING: Do NOT add new repos or users manually on the server.  Gitolite
+>   users, repos, and access rules are maintained by making changes to a
+>   special repo called 'gitolite-admin' and pushing those changes to the
+>   server.*
 
 >   ----
 
diff --git a/doc/vref.mkd b/doc/vref.mkd
index 147b42e..0f7c382 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -181,7 +181,8 @@ first rule, it might pass back (to gitolite) a virtual ref saying
 ### #NAME restricting pushes by dir/file name
 
 The "NAME" VREF allows you to restrict pushes by the names of dirs and files
-changed.
+changed.  (Side note: the NAME VREF is the only one directly implemented
+within the update hook, so you won't find it in the VREF directory).
 
 Here's an example.  Say you don't want junior developers pushing changes to
 the Makefile, because it's quite complex:
diff --git a/dot.pl b/dot.pl
deleted file mode 100644
index 40b9a7c..0000000
--- a/dot.pl
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-my @a = `grep -r use.Gitolite . | grep -i '^./gitolite'`;
-
-# chomp(@a);
-open( my $fh, "|-", "tee module-tree.gv | dot -Tpng | tee module-tree.png | display" );
-
- at a = map {
-    print $fh "#$_";
-    s/^\.\/gitolite\///i;
-    s/-/_/g;
-    s/\.\///;
-    s/\//_/g;
-    s/\.pm:/ -> /;
-    s/use Gitolite:://;
-    s/::/_/g;
-    s/:/ -> /;
-    s/;//;
-    s/^(\S+) -> \1$//;
-    s/.* -> Rc//;
-    s/.* -> Common//;
-    $_;
-} @a;
-
-# open(my $fh, "|-", "cat > /tmp/junkg3");
-print $fh "digraph G {\n";
-print $fh $_ for @a;
-print $fh "}\n";
-close $fh;

commit 53543ee3e6a837dc3ecca3c990f7ab6bd7ee933b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 23 08:53:37 2012 +0530

    partial-copy would not propagate deletes; fixed

diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 2ad98d8..c07c3e3 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -127,22 +127,28 @@ Here's how:
 
         repo foo-partialcopy-1
             -   secret-branch               =   @temp-emp
-            # other rules should ensure ONLY @temp-emp has ANY ACCESS
-            # NO other user should have access
+
+            # other rules; see notes below
 
             -   VREF/partial-copy           =   @all
             config gitolite.partialCopyOf   =   foo
 
-    **IMPORTANT**: if you're using other VREFs, please make sure this one is
-    placed at the end, after all the others.
+    **IMPORTANT NOTES**:
+
+      * if you're using other VREFs, **make sure** this one is placed at the
+        end, after all the others.
+
+      * remember that any change allowed to be made to the partial-copy repo
+        will propagate to the main repo so make sure you use other rules to
+        restrict pushes to other branches and tags as needed.
 
 And that should be it.  **Please test it and let me know if it doesn't work!**
 
 WARNINGS:
 
   * If you change the config to disallow something that used to be allowed,
-    you should delete the partial repo on the server and then run 'gitolite
-    compile' to let it build again.  See t/partial-copy.t for details.
+    you should delete the partial repo on the server and then run `gitolite
+    compile; gitolite trigger POST_COMPILE` to let it build again.
 
   * Not tested with smart http; probably won't work.
 
diff --git a/src/VREF/partial-copy b/src/VREF/partial-copy
index 19111de..55a7dcf 100755
--- a/src/VREF/partial-copy
+++ b/src/VREF/partial-copy
@@ -23,6 +23,15 @@ main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCop
 rand=$$
 export GL_BYPASS_ACCESS_CHECKS=1
 
+if [ "$new" = "0000000000000000000000000000000000000000" ]
+then
+    # special case for deleting a ref (this is why it is important to put this
+    # VREF as the last one; if we got this far he is allowed to delete it)
+    git push -f $GL_REPO_BASE/$main.git :$ref || die "FATAL: failed to delete $ref"
+
+    exit 0
+fi
+
 git push -f $GL_REPO_BASE/$main.git $new:refs/partial/br-$rand || die "FATAL: failed to send $new"
 
 cd $GL_REPO_BASE/$main.git

commit fb9829a698647bb31098701a925aded18b87acae
Author: Konstantin Gribov <grossws at gmail.com>
Date:   Wed Jun 20 16:31:30 2012 +0400

    Fixed url decoding in http gitolite command bypass.
    
    Only '+' sign was unescaped in `http_simulate_ssh_connection()`.
    When user translates `ssh git at host perms <repo> + <role> <user>` to
    `curl https://host/git/perms?<repo>+%2b+<role>+<user>` nothing happens.
    This commit fixes it modifying url unescaping.
    
    committer notes: changed the regex per 'man URI::Escape'

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 7a1da03..5d48cc9 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -212,6 +212,7 @@ sub http_simulate_ssh_connection {
         my ($verb) = ( $ENV{PATH_INFO} =~ m(^/(\S+)) );
         my $args = $ENV{QUERY_STRING};
         $args =~ s/\+/ /g;
+        $args =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
         $ENV{SSH_ORIGINAL_COMMAND} = $verb;
         $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
         http_print_headers();    # in preparation for the eventual output!

commit 621815611cbb1d6245b035682c42270dfa8d6d06
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 21 06:49:18 2012 +0530

    (duh!) report rc file syntax errors
    
    the "duh!" is because I should have done this long ago...

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 8f8fe7b..23ed421 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -61,7 +61,9 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
-do $rc if -r $rc;
+if (-r $rc) {
+    do $rc or die $@;
+}
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
     say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g2migr.html)";

commit a454111d32753a60cc909b88fe7738eeed41f532
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 21 05:29:14 2012 +0530

    repo-specific umask
    
    manually smoke tested but should be fine

diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm
new file mode 100644
index 0000000..b0a9ad1
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/RepoUmask.pm
@@ -0,0 +1,54 @@
+package Gitolite::Triggers::RepoUmask;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# setting a repo specific umask
+# ----------------------------------------------------------------------
+
+=for usage
+
+  * In the rc file, add 'RepoUmask::pre_git' and 'RepoUmask::post_create' to
+    the corresponding trigger lists.
+
+  * For each repo that is to get a different umask than the default, add a
+    line like this:
+
+        option umask = 0027
+
+=cut
+
+# sadly option/config values are not available at pre_create time for normal
+# repos.  So we have to do a one-time fixup in a post_create trigger.
+sub post_create {
+    my $repo = $_[1];
+
+    my $umask = option($repo, 'umask');
+    _chdir($rc{GL_REPO_BASE});  # because using option() moves us to ADMIN_BASE!
+
+    return unless $umask;
+
+    # unlike the one in the rc file, this is a string
+    $umask = oct($umask);
+    my $mode = "0" . sprintf("%o", $umask ^ 0777);
+
+    system("chmod -R $mode $repo.git >&2");
+}
+
+sub pre_git {
+    my $repo = $_[1];
+
+    my $umask = option($repo, 'umask');
+    _chdir($rc{GL_REPO_BASE});  # because using option() moves us to ADMIN_BASE!
+
+    return unless $umask;
+
+    # unlike the one in the rc file, this is a string
+    umask oct($umask);
+}
+
+1;

commit 858f13cf3128af45351d8b5a9d6fa933beaed6ad
Author: Frode Rystad <frode-git at rystad.no>
Date:   Wed Jun 20 08:59:42 2012 +0200

    Added information about install destinations supported by SELinux to troubleshooting guide

diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index 279a82f..1c68f42 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -321,6 +321,13 @@ This is a quick checklist:
   * Some OSs/distributions require that the "git" user should have a password
     and/or not be a locked account.  You may want to check that as well.
 
+  * If your server is running SELinux, and you install gitolite to
+    `/var/gitolite` or another location unsupported by default SELinux
+    policies, then SELinux will prevent sshd from reading
+    `.ssh/authorized_keys`. Consider installing gitolite to
+    `/var/lib/gitolite`, which is a supported location by default SELinux
+    policies.
+
   * If all that fails, log onto the server as root, `cd /var/log`, and look
     for a file called `auth.log` or `secure` or some such name.  Look inside
     this file for messages matching the approximate time of your last attempt

commit c9d5a13194a1402abcebba518d07c5049cc2bcfa
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jun 19 21:00:53 2012 +0530

    help command learns to deal with LOCAL_CODE

diff --git a/src/commands/help b/src/commands/help
index 15539fa..d7fae78 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -20,20 +20,22 @@ usage() if @ARGV;
 my $user = $ENV{GL_USER} || '';
 print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
 
-_chdir("$ENV{GL_BINDIR}/commands");
-
 print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
 
-for my $c (`find . -type f|sort`) {
-    chomp($c);
-    $c =~ s(^./)();
-    next unless -x $c;
-
-    # if it's from a remote client, show only what he is allowed
-    next if $user and not $rc{COMMANDS}{$c};
-
-    print "\t$c\n";
+my %list = (list_x( $ENV{GL_BINDIR}), list_x($rc{LOCAL_CODE} || ''));
+for (sort keys %list) {
+    print "\t$list{$_}" if $ENV{D};
+    print "\t$_\n" if not $user or $rc{COMMANDS}{$_};
 }
+
 print "\n";
 
 exit 0;
+
+# ------------------------------------------------------------------------------
+sub list_x {
+    my $d = shift;
+    return unless $d;
+    _chdir "$d/commands";
+    return map { $_ => $d } grep { -x $_ } map { chomp; s(^./)(); $_ } `find . -type f|sort`;
+}

commit 7dcb85734735521fd41cba6930f8de72f867900d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 13 14:43:34 2012 +0530

    (accumulated docfixes)

diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
index 41a8d74..9e4672a 100644
--- a/doc/master-toc.mkd
+++ b/doc/master-toc.mkd
@@ -21,7 +21,8 @@
 ## [WARNINGS][]
 
 
-## [trying][] out gitolite
+## [testing][] gitolite
+  * [trying][] out gitolite
 
 
 ## [quick][qi] install, setup, and clone
@@ -93,6 +94,7 @@
 
 ## [special][] features/setups
 
+  * putting 'repositories' and '.gitolite' [somewhere else][elsewhere]
   * [disabling pushes][writable] to take backups
   * [personal][pers] branches
   * ([link][votes]: voting on commits)
@@ -119,14 +121,15 @@
 
 ## [customisation][cust]
 
+  * where do you put custom code?
   * types of non-core programs
-      * ([link][non-core]: non-core programs shipped with gitolite)
-  * [commands][]
-  * [hooks][]
-  * syntactic [sugar][]
-  * ([link][triggers]: triggers)
-  * ([link][vref]: VREFs)
-  * [developer notes][dev-notes]
+      * [commands][]
+      * [hooks][]
+      * syntactic [sugar][]
+      * ([link][triggers]: triggers)
+      * ([link][vref]: VREFs)
+  * ([link][non-core]: non-core programs shipped with gitolite)
+  * [developer notes][dev-notes] -- writing custom code
       * environment variables and other inputs
       * APIs
           * the shell API
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 9689c7c..7b07882 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -94,7 +94,7 @@ On **each** server:
 
   * Run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
     public key to a common area and name it after the host, but with 'server-'
-    prefixed.  So the pubkey for server 'mars' would be stored as
+    prefixed.  For example, the pubkey for server 'mars' must be stored as
     'server-mars.pub'.
 
   * Copy all keys to all the admin repo clones on your workstation and and add
diff --git a/doc/special.mkd b/doc/special.mkd
index 37cfe08..4ff6d75 100644
--- a/doc/special.mkd
+++ b/doc/special.mkd
@@ -6,6 +6,15 @@
 
 ----
 
+## #elsewhere putting 'repositories' and '.gitolite' somewhere else
+
+Gitolite insists that the "repositories" and ".gitolite" directories be in
+`$HOME`.  If you want them somewhere else:
+
+  * do the install as normal,
+  * *then* move those directories to wherever you want and replace them with
+    symlinks pointing to the new location.
+
 ## #writable disabling pushes to take backups
 
 The `writable` command allows you to disable pushes to all repos or just the
@@ -68,3 +77,26 @@ Then write a script that
 
 Run this from cron or however you want.
 
+## #gh giving users their own repos
+
+(Please see [this][wild] for background on the ideas in this section).
+
+It's very easy to give users their own set of repos to create, with the
+username at the top level.  The simplest setup is:
+
+    repo CREATOR/..*
+        C   =   @all
+        RW+ =   CREATOR
+        RW  =   WRITERS
+        R   =   READERS
+
+Now users can create any repo under their own name simply by cloning it or
+pushing to it, then use the [perms][] command to add other users to their
+WRITERS and READERS lists.
+
+Of course you can get much more creative if you add a few more roles (see
+"roles" in [this][wild] page).
+
+<font color="gray">(I prefer using some prefix, say "u", as in `repo
+u/CREATOR/..*`.  This helps to keep user-created repos separate, and avoid
+name clashes in some far-fetched scenarios).</font>
diff --git a/doc/testing.mkd b/doc/testing.mkd
index b6f4cc7..bf3eb4d 100644
--- a/doc/testing.mkd
+++ b/doc/testing.mkd
@@ -9,8 +9,10 @@ so be sure to use a throwaway userid**.</font>
     cd gitolite
     prove
 
-(Make sure sshd allows incoming ssh to this userid at least from localhost.
-Out of scope for this document: sshd config, 'AllowUsers', etc...)
+Make sure:
+
+  * `$HOME/bin` is in `$PATH`
+  * sshd allows incoming ssh to this userid, at least from localhost
 
 Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
 Take a look at some of the scripts and you will see what it looks like.  It

commit 56d11deb55b7725484b3f9281197d5c394d4727f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jun 19 12:04:00 2012 +0530

    (minor) one more 'internal' message bites the dust

diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 78d5be9..80a2448 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -15,7 +15,10 @@ my ( $mode, $master, %slaves, %trusted_slaves );
 # ----------------------------------------------------------------------
 
 sub input {
-    return unless $ARGV[0] =~ /^server-(\S+)$/;
+    unless ($ARGV[0] =~ /^server-(\S+)$/) {
+        _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
+        return;
+    }
 
     # note: we treat %rc as our own internal "poor man's %ENV"
     $rc{FROM_SERVER} = $1;

commit a171053ab3626c69315c4a4d7966dd7aa2799df5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 17 07:55:12 2012 +0530

    (minor) improve SNR of some error messages :)

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 638b122..03008b3 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -251,17 +251,18 @@ sub gl_log {
     my $tid = $ENV{GL_TID} ||= $$;
 
     my $fh;
-    logger_plus_stderr( "$ts no GL_LOGFILE env var", "$ts $msg" ) if not $ENV{GL_LOGFILE};
-    open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr( "open log failed: $!", $msg );
+    logger_plus_stderr( "errors found before logging could be setup", "$msg" ) if not $ENV{GL_LOGFILE};
+    open my $lfh, ">>", $ENV{GL_LOGFILE}
+      or logger_plus_stderr( "errors found before logfile could be created", "$msg" );
     print $lfh "$ts\t$tid\t$msg\n";
     close $lfh;
 }
 
 sub logger_plus_stderr {
     open my $fh, "|-", "logger" or confess "it's really not my day is it...?\n";
-    for ( "FATAL: have errors but logging failed!\n", @_ ) {
-        print STDERR "$_\n";
-        print $fh "$_\n";
+    for ( @_ ) {
+        print STDERR "FATAL: $_\n";
+        print $fh "FATAL: $_\n";
     }
     exit 1;
 }
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 72d3452..69a1173 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -104,7 +104,7 @@ sub setup_glrc {
 
 sub setup_gladmin {
     my ( $admin, $pubkey, $argv ) = @_;
-    _die "no existing conf file found, '-pk' or '-a' required"
+    _die "'-pk' or '-a' required; see 'gitolite setup -h' for more"
       if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
 
     # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is

commit bcef2be640ea08c659a48e76d98d1a4e662857c3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 16 23:29:40 2012 +0530

    (minor) usage message oops in 'install -h'

diff --git a/install b/install
index ca6a864..ad7effa 100755
--- a/install
+++ b/install
@@ -36,7 +36,7 @@ Usage (from gitolite clone directory):
 
 Simplest use, if $HOME/bin exists and is in $PATH, is:
 
-    git clone -b g3 git://github.com/sitaramc/gitolite
+    git clone git://github.com/sitaramc/gitolite
     gitolite/install -ln
 
     # now run setup

commit fbd745958e4c42d49d0fa3e82d3235c3129d50b4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Jun 14 13:17:34 2012 +0530

    PRE_ and POST_CREATE triggers get an extra argument...
    
    ...when invoked by single-repo operations like auto-creating a wild
    repo, or running perms or fork.

diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index c597da9..9ecdbc9 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -138,6 +138,10 @@ description of when the trigger runs:
     repo is created by user action.  Extra arguments:
       * repo
       * user
+      * invoking operation
+          * 'R' for fetch/clone/ls-remote, 'W' for push
+          * can also be anything set by the external command running the
+            trigger (e.g., see the perms and fork commands).
 
     They are also run when a *normal* repo is created (say by adding a "repo
     foo" line to the conf file).  This case has only one extra argument:
diff --git a/src/commands/fork b/src/commands/fork
index fe27035..6cd6eea 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -59,4 +59,4 @@ ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
 echo "$from" > gl-forked-from
 
 # trigger post_create
-gitolite trigger POST_CREATE $to $GL_USER
+gitolite trigger POST_CREATE $to $GL_USER fork
diff --git a/src/commands/perms b/src/commands/perms
index b230e8a..46c4e97 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -55,14 +55,14 @@ if ( $ARGV[0] eq '-c' ) {
 
         require Gitolite::Conf::Store;
         Gitolite::Conf::Store->import;
-        new_wild_repo( $repo, $ENV{GL_USER} );
-        gl_log( 'create', $repo, $ENV{GL_USER} );
+        new_wild_repo( $repo, $ENV{GL_USER}, 'perms-c' );
+        gl_log( 'create', $repo, $ENV{GL_USER}, 'perms-c' );
     }
 }
 
 my $repo = shift;
 setperms(@ARGV);
-_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER} );
+_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER}, 'perms' );
 
 # ----------------------------------------------------------------------
 
diff --git a/src/gitolite-shell b/src/gitolite-shell
index a40611b..7a1da03 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -107,8 +107,8 @@ sub main {
     if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
         require Gitolite::Conf::Store;
         Gitolite::Conf::Store->import;
-        new_wild_repo( $repo, $user );
-        gl_log( 'create', $repo, $user );
+        new_wild_repo( $repo, $user, $aa );
+        gl_log( 'create', $repo, $user, $aa );
     }
 
     # a ref of 'any' signifies that this is a pre-git check, where we don't
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 8fd9f47..c77cac3 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -184,14 +184,14 @@ sub new_repo {
 }
 
 sub new_wild_repo {
-    my ( $repo, $user ) = @_;
+    my ( $repo, $user, $aa ) = @_;
     _chdir( $rc{GL_REPO_BASE} );
 
-    trigger( 'PRE_CREATE', $repo, $user );
+    trigger( 'PRE_CREATE', $repo, $user, $aa );
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
     _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
-    trigger( 'POST_CREATE', $repo, $user );
+    trigger( 'POST_CREATE', $repo, $user, $aa );
 
     _chdir( $rc{GL_ADMIN_BASE} );
 }

commit 57f82ee0444a922fbb90082cd9227a123b0c31eb
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jun 12 09:28:21 2012 +0530

    new 'list-dangling-repos' command

diff --git a/src/commands/list-dangling-repos b/src/commands/list-dangling-repos
new file mode 100755
index 0000000..6889ed9
--- /dev/null
+++ b/src/commands/list-dangling-repos
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Common;
+
+=for usage
+Usage:  gitolite list-dangling-repos
+
+List all existing repos that no one can access remotely any more.  They could
+be normal repos that were taken out of "repo" statements in the conf file, or
+wildcard repos whose matching "wild" pattern was taken out or changed so it no
+longer matches.
+=cut
+
+usage() if @ARGV and $ARGV[0] eq '-h';
+
+# get the two lists we need.  %repos is the list of repos in "repo" statements
+# in the conf file.  %phy_repos is the list of actual repos on disk.  Our job
+# is to cull %phy_repos of all keys that have a matching key in %repos, where
+# "matching" means "string equal" or "regex match".
+my %repos = map { chomp; $_ => 1 } `gitolite list-repos`;
+my %phy_repos = map { chomp; $_ => 1 } `gitolite list-phy-repos`;
+
+# Remove exact matches.  But for repo names like "gtk+", you could have
+# collapsed this into the next step (the regex match).
+for my $pr (keys %phy_repos) {
+    next unless exists $repos{$pr};
+    delete $repos{$pr};
+    delete $phy_repos{$pr};
+}
+
+# Remove regex matches.
+for my $pr (keys %phy_repos) {
+    my $matched = 0;
+    for my $r (keys %repos) {
+        if ($pr =~ /^$r$/) {
+            $matched = 1;
+            next;
+        }
+    }
+    delete $phy_repos{$pr} if $matched;
+}
+
+# what's left in %phy_repos are dangling repos.
+print join("\n", sort keys %phy_repos), "\n";

commit 4373c5c74c5150b8f9619da7d9e9f52e6d99014f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 13 14:43:19 2012 +0530

    GL_BINDIR2 becomes LOCAL_CODE, allows hook propagation also...
    
    plus a bunch of doc changes

diff --git a/doc/cust.mkd b/doc/cust.mkd
index 1cdf07b..8c7607b 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -5,7 +5,9 @@ not considered "core".  This keeps the core simpler, and allows you to enhance
 gitolite for your own purposes without too much fuss.  (As an extreme example,
 even mirroring is not in core now!)
 
-(Also, please see the [developer notes][dev-notes] page).
+This document will tell you about the types of non-core programs, and
+how/where to install your own.  (Actually *writing* the code is described in
+the [developer notes][dev-notes] page).
 
 ----
 
@@ -13,13 +15,13 @@ even mirroring is not in core now!)
 
 ----
 
-## types of non-core programs
+## introduction
 
 There are 5 basic types of non-core programs.
 
   * *Commands* can be run from the shell command line.  Among those, the ones
     listed in the COMMANDS hash of the rc file can also be run remotely.
-  * *Hooks* are standard git hooks; see below.
+  * *Hooks* are standard git hooks.
   * *Sugar scripts* change the conf language for your convenience.  The word
     sugar comes from "syntactic sugar".
   * *Triggers* are to gitolite what hooks are to git.  I just chose a
@@ -29,9 +31,76 @@ There are 5 basic types of non-core programs.
 [Here][non-core] is a list of non-core programs shipped with gitolite, with
 some description of each.
 
-## #commands gitolite "commands"
+## locations
+
+### default/primary location of non-core programs
+
+Regardless of how you installed gitolite, `gitolite query-rc GL_BINDIR` will
+tell you where the programs reside.  Within that directory, the locations of
+non-core programs are:
+
+  * `commands` for commands.
+  * `syntactic-sugar` for sugar scripts.
+  * `triggers` and `lib/Gitolite/Triggers` for triggers ([this][triggers] will
+    explain the difference).
+  * `VREF` for [VREFs][vref].
+
+### #localcode alternate location -- the `LOCAL_CODE` rc variable
+
+If you want to add new non-core programs to your installation, or override the
+shipped non-core programs with your own versions, it's easy enough to simply
+copy your programs to the appropriate directory above, but then they'd get
+wiped out on the next upgrade.
+
+A simple, "git-ish", method is to maintain a "local" branch in your clone of
+the gitolite source repo and make your changes there.  Maintain them using
+rebase or merge when you 'git pull' gitolite itself, then use the rebased or
+merged "local" as the source for your gitolite upgrades.  Works very nicely,
+and uses nothing but your git knowledge.
+
+Sadly, it doesn't work for people installing from RPMs/DEBs; their "primary
+location" has already been setup, so any site-local customisations have to be
+done elsewhere.
+
+This is where `LOCAL_CODE` comes in.  If you define the `LOCAL_CODE` rc
+variable, then its value (**please use a FULL path**) describes a location
+where you can have any or all of these subdirectories:
+
+  * `commands`
+  * `hooks/common`
+  * `syntactic-sugar`
+  * `triggers` and `lib/Gitolite/Triggers`
+  * `VREF`
+
+You might have noticed there's a new `hooks/common` directory here so you can
+add hooks also using this mechanism.  Unlike the rest of the directories,
+adding new hooks to `hooks/common` requires that you follow up with `gitolite
+setup`, or at least `gitolite setup --hooks-only`.
+
+### #pushcode managing custom code via the gitolite-admin repo
+
+The location given in `LOCAL_CODE` could be anywhere on disk.
+
+However, if you point it to someplace inside `$GL_ADMIN_BASE` (i.e.,
+`$HOME/.gitolite`), then you can version those programs using the
+gitolite-admin repo.
+
+I suggest using a directory called "local-code" within the gitolite-admin repo
+that contains as much of the above directory structure you need.  If you do
+that, then this is what you'd have in the rc file:
+
+    LOCAL_CODE                  =>  "$ENV{HOME}/.gitolite/local-code",
+
+When you do this, gitolite takes care of everything automatically, including
+running `gitolite setup --hooks-only` when you change any hooks and push.
+**However, if you do this, anyone who can push changes to the admin repo will
+effectively be able to run any arbitrary command on the server.**
+
+## types of non-core programs
+
+### #commands gitolite "commands"
 
-Gitolite comes with several commands that users can run.  Remote users run the
+Gitolite comes with several commands that users can run.  Remote users run
 commands by saying:
 
     ssh git at host command-name [args...]
@@ -46,23 +115,25 @@ checking for the presence of env var `GL_USER`.
 You can get a **list of available commands** by using the `help` command.
 Naturally, a remote user will see a much smaller list than the server user.
 
-You add commands to the "allowed from remote" list by adding its name (or
-uncommenting it if it's already added but commented out) to the COMMANDS hash
-in the [rc][] file.
+You allow a command to be run from remote clients by adding its name to (or
+uncommenting it if it's already added but commented out) the COMMANDS hash in
+the [rc][] file.
 
-If you write your own commands, put them in src/commands.
+### #hooks hooks and gitolite
 
-## #hooks hooks and gitolite
+You can install any hooks except these:
 
-Gitolite uses the `update` hook for all repos.  In addition, it uses the
-`post-update` hook for the gitolite-admin repo.
+  * (all repos) gitolite reserves the `update` hook.  See the "update hook"
+    section in [dev-notes][] if you want additional update hook functionality.
 
-If you want to add your own hook, it's easy as long as it's not the 'update'
-hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
+  * (gitolite-admin repo only) gitolite reserves the `post-update` hook.
 
-The rest is between you and 'man githooks' :-)
+How/where to install them is described in detail in the "locations" section
+above, especially [this][localcode] and [this][pushcode].  The summary is that
+you put them in the "hooks/common" sub-directory within the directory whose
+name is given in the `LOCAL_CODE` rc variable.
 
-## #sugar syntactic sugar
+### #sugar syntactic sugar
 
 Sugar scripts help you change the perceived syntax of the conf language.  The
 base syntax of the language is very simple, so sugar scripts take something
@@ -74,10 +145,10 @@ lines), while the parser in the core gitolite engine does not change.
 If you want to write your own sugar scripts, please read the "your own sugar"
 section in [dev-notes][] first then email me.
 
-## triggers
+### triggers
 
 Triggers have their own [document][triggers].
 
-## VREFs
+### VREFs
 
 VREFs also have their own [document][vref].
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 501d44f..18cd005 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -7,8 +7,9 @@
 Gitolite has a huge bunch of existing features that gradually need to moved
 over.  Plus you may want to write your own programs to interact with it.
 
-Here are some random notes on developing hooks, commands, triggers, and sugar
-scripts.
+**This document is about *writing* hooks, commands, triggers, VREFS, and sugar
+scripts.  *Installing* them, including "where and how", is described
+[here][localcode]**.
 
 ## environment variables and other inputs
 
@@ -56,14 +57,19 @@ serve as documentation.
 
 ## writing your own...
 
+### ...commands
+
+Commands are standalone programs, in any language you like.  They simply
+receive the arguments you append.  In addition, the env var `GL_USER` is
+available if it is being run remotely.  src/commands/desc is the best example
+at present.
+
 ### ...hooks
 
 #### anything but the update hook
 
-If you want to add your own hook, it's easy as long as it's not the 'update'
-hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
-
-The rest is between you and 'man githooks' :-)
+If you want to add any hook other than the update hook, 'man githooks' is all
+you need.
 
 #### update hook
 
@@ -71,7 +77,8 @@ If you want to add additional `update` hook functionality, do this:
 
   * Write and test your update hook separately from gitolite.
 
-  * Now add the code to src/VREF.  Let's say it is called "foo".
+  * Now add the code as a VREF (see [here][localcode] for details).  Let's say
+    you called it "foo".
 
   * To call your new update hook to all accesses for all repos, add this to
     the end of your conf file:
@@ -79,24 +86,13 @@ If you want to add additional `update` hook functionality, do this:
         repo @all
             -       VREF/foo        =   @all
 
-As you probably guessed, you can now make your additional update hooks more
+As you probably guessed, you can make your additional update hooks more
 selective, applying them only to some repos / users / combinations.
 
 Note: a normal update hook expects 3 arguments (ref, old SHA, new SHA).  A
 VREF will get those three, followed by at least 4 more.  Your VREF should just
 ignore the extra args.
 
-### ...commands
-
-You can add your own commands.  You can run them on the server (example,
-`gitolite access`).  Then you can enable certain commands to be allowed to run
-by a remote user by adding them to the "COMMANDS" hash of the [rc][] file.
-
-Commands are standalone programs, in any language you like.  They simply
-receive the arguments you append.  In addition, the env var `GL_USER` is
-available if it is being run remotely.  src/commands/desc is the best example
-at present.
-
 ### ...trigger programs
 
 Trigger programs run at specific points in gitolite's execution, with specific
diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
index 9da5224..41a8d74 100644
--- a/doc/master-toc.mkd
+++ b/doc/master-toc.mkd
@@ -101,7 +101,7 @@
       * the [subconf][] command
   * ([link][partial-copy]: faking selective READ control)
   * using pubkeys obtained [from elsewhere][keysonly]
-  * [updating hooks][pushhook] via the admin repo
+  * ([link][pushcode]: updating code via the admin repo)
 
 ## interfacing with [external][] tools
 
diff --git a/doc/rc.mkd b/doc/rc.mkd
index 17f2616..98ad1d7 100644
--- a/doc/rc.mkd
+++ b/doc/rc.mkd
@@ -98,24 +98,7 @@ information.
 
         DEFAULT_ROLE_PERMS  =>  "READERS \@all\nWRITERS \@senior_devs",
 
-  * `GL_BINDIR2`, string
+  * `LOCAL_CODE`, string
 
-    This is useful when you install gitolite system-wide, but want to add or
-    override commands, VREFs, triggers, etc., by putting them somewhere in the
-    hosting user's home directory (i.e., without requiring root privileges).
-
-    Add a new variable `GL_BINDIR2` to the the rc file.  Example:
-
-        GL_BINDIR2 => "$ENV{HOME}/gitolite/src2",
-
-    In that directory, create as much of the following directory structure as
-    you need to add your programs:
-
-        .
-        |-- commands
-        |-- lib
-        |   `-- Gitolite
-        |       `-- Triggers
-        |-- syntactic-sugar
-        |-- triggers
-        `-- VREF
+    This is described in more detail [here][localcode].  Please be aware
+    **this must be a FULL path**, not a relative path.
diff --git a/doc/special.mkd b/doc/special.mkd
index 3530920..37cfe08 100644
--- a/doc/special.mkd
+++ b/doc/special.mkd
@@ -68,41 +68,3 @@ Then write a script that
 
 Run this from cron or however you want.
 
-## #pushhook updating hooks via the admin repo
-
-Gitolite by default maintains the distinction between someone who can push to
-the admin repo and someone who has shell access to the server.  As a result,
-you cannot change any hooks merely by pushing the admin repo.
-
-But some people don't care about this and want to do it anyway.  Besides,
-there *is* one advantage: your hooks can also be versioned now.
-
-So here's how to add/change hooks when you push the gitolite-admin repo:
-
-1.  Put all common hooks in a new directory called 'common-hooks'.
-
-2.  Create a trigger program called 'propagate-hooks' in 'src/triggers' that
-    contains this:
-
-        #!/bin/sh
-
-        cd $GL_ADMIN_BASE
-        cp -a common-hooks/* hooks/common
-
-        gitolite setup --hooks-only
-
-3.  Add this to the `POST_COMPILE` trigger list in the rc file.
-
-And that should be it, pretty much.
-
-If you have lots of repos this is inefficient -- because the hook fixup will
-run on *any* change to the admin repo, even if the change has nothing to do
-with hooks.
-
-If you're concerned about this, put the script in 'src/commands' instead of
-'src/triggers'.  (You might want to add some access control; see other
-commands for inspiration, but it's not really needed in this case).
-
-Then, whenever any hooks have changed, the admin can run
-
-    ssh git at host propagate-hooks
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index f4f4cf2..8fd9f47 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -323,14 +323,13 @@ sub store_common {
             $hook_reset++;
         }
 
-        # propagate user hooks
-        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
+        # propagate user-defined (custom) hooks to all repos
+        ln_sf( "$rc{LOCAL_CODE}/hooks/common", "*", "$repo.git/hooks" ) if $rc{LOCAL_CODE};
 
-        # propagate admin hook
+        # override/propagate gitolite defined hooks for all repos
+        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
+        # override/propagate gitolite defined hooks for the admin repo
         ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
-
-        # g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks
-        # and user hooks) anymore.  I don't think anyone used them anyway...
     }
 }
 
diff --git a/src/lib/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
index 3605de0..961db1a 100644
--- a/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -25,11 +25,20 @@ sub post_update {
     tsh_try("git ls-tree --name-only master");
     _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/m;
 
+    my $hooks_changed = 0;
     {
         local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
+
+        tsh_try("git diff --name-only master");
+        $hooks_changed++ if tsh_text() =~ m(/hooks/common/);
+        # the leading slash ensure that this hooks/common directory is below
+        # some top level directory, not *at* the top.  That's LOCAL_CODE, and
+        # it's actual name could be anything but it doesn't matter to us.
+
         tsh_try("git checkout -f --quiet master");
     }
     _system("gitolite compile");
+    _system("gitolite setup --hooks-only") if $hooks_changed;
     _system("gitolite trigger POST_COMPILE");
 
     exit 0;
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 81479fe..8f8fe7b 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -87,7 +87,7 @@ do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 # setup some perl/rc/env vars
 # ----------------------------------------------------------------------
 
-unshift @INC, "$rc{GL_BINDIR2}/lib" if $rc{GL_BINDIR2};
+unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
 
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 
@@ -232,13 +232,13 @@ sub trigger {
 }
 
 sub _which {
-    # looks for a file in GL_BINDIR2 or GL_BINDIR.  Returns whichever exists
-    # (GL_BINDIR2 preferred if defined) or 0 if not found.
+    # looks for a file in LOCAL_CODE or GL_BINDIR.  Returns whichever exists
+    # (LOCAL_CODE preferred if defined) or 0 if not found.
     my $file = shift;
     my $mode = shift;   # could be 'x' or 'r'
 
     my @files = ("$rc{GL_BINDIR}/$file");
-    unshift @files, ("$rc{GL_BINDIR2}/$file") if $rc{GL_BINDIR2};
+    unshift @files, ("$rc{LOCAL_CODE}/$file") if $rc{LOCAL_CODE};
 
     for my $f ( @files ) {
         return $f if -x $f;

commit 3c0f17748157740eefd8f41c15af269c100edc2b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 9 07:48:56 2012 +0530

    Allow user-specified programs to override system-installed ones
    
    (manually tested)
    
      - new rc var: GL_BINDIR2; see doc update in this commit
    
      - added _which() function to search both $GL_BINDIR and $GL_BINDIR2
      - 'gitolite <command>', non-perl triggers, VREFs, and sugar, use this
    
      - unshifted $GL_BINDIR2/lib into @INC upfront in Rc.pm
      - perl triggers use this

diff --git a/doc/rc.mkd b/doc/rc.mkd
index 2c84d88..17f2616 100644
--- a/doc/rc.mkd
+++ b/doc/rc.mkd
@@ -97,3 +97,25 @@ information.
     require the '@' to be escaped:
 
         DEFAULT_ROLE_PERMS  =>  "READERS \@all\nWRITERS \@senior_devs",
+
+  * `GL_BINDIR2`, string
+
+    This is useful when you install gitolite system-wide, but want to add or
+    override commands, VREFs, triggers, etc., by putting them somewhere in the
+    hosting user's home directory (i.e., without requiring root privileges).
+
+    Add a new variable `GL_BINDIR2` to the the rc file.  Example:
+
+        GL_BINDIR2 => "$ENV{HOME}/gitolite/src2",
+
+    In that directory, create as much of the following directory structure as
+    you need to add your programs:
+
+        .
+        |-- commands
+        |-- lib
+        |   `-- Gitolite
+        |       `-- Triggers
+        |-- syntactic-sugar
+        |-- triggers
+        `-- VREF
diff --git a/src/gitolite b/src/gitolite
index aeca6af..2498737 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -73,9 +73,10 @@ if ( $command eq 'setup' ) {
 } elsif ( $command eq 'trigger' ) {
     trigger(@args);
 
-} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
-    trace( 2, "attempting gitolite command $command" );
-    run_command( $command, @args );
+} elsif ( my $c = _which("commands/$command", 'x' ) ) {
+    trace( 2, "attempting gitolite command $c" );
+    _system( $c, @args );
+    exit 0;
 
 } elsif ( $command eq 'list-phy-repos' ) {
     _chdir( $rc{GL_REPO_BASE} );
@@ -99,11 +100,3 @@ sub args {
 }
 
 # ----------------------------------------------------------------------
-
-sub run_command {
-    my $pgm      = shift;
-    my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
-    _die "'$pgm' not found or not executable" if not -x $fullpath;
-    _system( $fullpath, @_ );
-    exit 0;
-}
diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index dca1c44..d02bfa0 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -52,7 +52,7 @@ sub sugar {
                 # perl-ism; apart from keeping the full path separate from the
                 # simple name, this also protects %rc from change by implicit
                 # aliasing, which would happen if you touched $s itself
-                my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s";
+                my $sfp = _which("syntactic-sugar/$s", 'r');
 
                 _warn("skipped sugar script '$s'"), next if not -r $sfp;
                 $lines = SugarBox::run_sugar_script( $sfp, $lines );
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 568cfc4..5bef2ed 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -61,8 +61,8 @@ sub check_vrefs {
             }
         } else {
             my ( $dummy, $pgm, @args ) = split '/', $vref;
-            $pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
-            -x $pgm or _die "'$vref': helper program missing or unexecutable";
+            $pgm = _which("VREF/$pgm", 'x');
+            $pgm or _die "'$vref': helper program missing or unexecutable";
 
             open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
             while (<$fh>) {
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 8dbe1e7..81479fe 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -9,6 +9,7 @@ package Gitolite::Rc;
   query_rc
   version
   trigger
+  _which
 
   $REMOTE_COMMAND_PATT
   $REF_OR_FILENAME_PATT
@@ -83,9 +84,11 @@ unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
 # use an env var that is highly unlikely to appear in real life :)
 do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
-# fix some env vars, setup gitolite internal "env" vars (aka rc vars)
+# setup some perl/rc/env vars
 # ----------------------------------------------------------------------
 
+unshift @INC, "$rc{GL_BINDIR2}/lib" if $rc{GL_BINDIR2};
+
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 
 {
@@ -215,9 +218,9 @@ sub trigger {
                     Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
 
                 } else {
-                    $pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
+                    $pgm = _which("triggers/$pgm", 'x');
 
-                    _warn("skipped command '$s'"), next if not -x $pgm;
+                    _warn("skipped command '$s'"), next if not $pgm;
                     trace( 2, "command: $s" );
                     _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
                 }
@@ -228,6 +231,23 @@ sub trigger {
     trace( 2, "'$rc_section' not found in rc" );
 }
 
+sub _which {
+    # looks for a file in GL_BINDIR2 or GL_BINDIR.  Returns whichever exists
+    # (GL_BINDIR2 preferred if defined) or 0 if not found.
+    my $file = shift;
+    my $mode = shift;   # could be 'x' or 'r'
+
+    my @files = ("$rc{GL_BINDIR}/$file");
+    unshift @files, ("$rc{GL_BINDIR2}/$file") if $rc{GL_BINDIR2};
+
+    for my $f ( @files ) {
+        return $f if -x $f;
+        return $f if -r $f and $mode eq 'r';
+    }
+
+    return 0;
+}
+
 # ----------------------------------------------------------------------
 
 =for args

commit cd37fe7c36f3d883153dace3844d1edd4f5cadf5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 8 17:08:15 2012 +0530

    (test suite) changes in test suite due to upgrading to F17
    
      - git version bumps up, causes minor change in t/merge-check.t
      - for some strange reason apache cannot see /tmp/gitolite-http-authuserfile
        (I thought private /tmp was only if you enabled selinux...)

diff --git a/t/merge-check.t b/t/merge-check.t
index b2642ed..443a848 100755
--- a/t/merge-check.t
+++ b/t/merge-check.t
@@ -34,7 +34,7 @@ try "
 try "
     cd foo;             ok
     ls -Al;             ok;     /\.git/
-    test-commit aa;     ok;     /1 files changed, 1 insertions/
+    test-commit aa;     ok;     /1 file changed, 1 insertion/
     tag start;          ok
     glt push u1 origin master
                         ok;     /new branch.*master.-..master/
diff --git a/t/smart-http.root-setup b/t/smart-http.root-setup
index dd42ad9..310efa3 100755
--- a/t/smart-http.root-setup
+++ b/t/smart-http.root-setup
@@ -68,13 +68,14 @@ SetEnv GIT_HTTP_EXPORT_ALL
     AuthType Basic
     AuthName "Private Git Access"
     Require valid-user
-    AuthUserFile /tmp/gitolite-http-authuserfile
+    AuthUserFile $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
 </Location>
 EOF1
 
 # NOTE: this is for testing only
-htpasswd -bc /tmp/gitolite-http-authuserfile admin admin
-map "htpasswd -b /tmp/gitolite-http-authuserfile % %" u{1..6}
+htpasswd -bc $GITOLITE_HTTP_HOME/gitolite-http-authuserfile admin admin
+map "htpasswd -b $GITOLITE_HTTP_HOME/gitolite-http-authuserfile % %" u{1..6}
+chown apache.apache $GITOLITE_HTTP_HOME/gitolite-http-authuserfile
 
 # restart httpd to make it pick up all the new stuff
 service httpd restart

commit 8e15d3a5107a5e4e7320c46ec72e2ba6a327c506
Author: Randal L. Schwartz <merlyn at stonehenge.com>
Date:   Thu Jun 7 11:48:01 2012 -0700

    gitolite patch to enable keydir to be a symlink

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 910737f..a18b833 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -47,7 +47,7 @@ chomp(@non_gl);
 my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
 
 # pubkey files
-chomp( my @pubkeys = `find keydir -type f -name "*.pub" | sort` );
+chomp( my @pubkeys = `find keydir/ -type f -name "*.pub" | sort` );
 my @gl_keys = ();
 for my $f (@pubkeys) {
     my $fp = fp($f);

commit 1f6a17c1556d245fa077465f25d42fe0b22f4030
Author: milki <milki at rescomp.berkeley.edu>
Date:   Wed Jun 6 12:21:46 2012 -0700

    fix D perm reference

diff --git a/doc/write-types.mkd b/doc/write-types.mkd
index a05ed6c..79d8f67 100644
--- a/doc/write-types.mkd
+++ b/doc/write-types.mkd
@@ -17,7 +17,7 @@ rewind, but not *delete* a ref.  The `C` and `D` qualifiers help here.
     <font color="gray">Please do not confuse this with the standalone `C`
     permission that allows someone to [create][] a **repo**</font>
 
-  * When a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the C
+  * When a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the D
     qualifier will no longer permit **deleting** a ref*.
 
 Note: These two can be combined, so you can have `RWCD` and `RW+CD` as well.

commit 53008091031f1886b6f8e8b6ab5058621b061c92
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 6 20:00:24 2012 +0530

    ACCESS_2 gets 2 more arguments, and gets called for each VREF

diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index d25a0bd..c597da9 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -1,5 +1,7 @@
 # gitolite triggers
 
+[[TOC]]
+
 ## intro and sample rc excerpt
 
 Gitolite fires off external commands at 7 different times.  The [rc][] file
@@ -95,17 +97,33 @@ description of when the trigger runs:
       * user
       * 'R' or 'W'
       * 'any'
-      * result: this is the result of the access() function.  If it contains
-        the uppercase word "DENIED", the access was rejected.  Otherwise
-        result contains the refex that caused the access to succeed.
+      * result (see notes below)
 
-  * `ACCESS_2` runs after the second access check, in the update hook.
-    Extra arguments:
+    'result' is the return value of the access() function.  If it contains the
+    uppercase word "DENIED", the access was rejected.  Otherwise it is the
+    refex that caused the access to succeed.
+
+    *Please note that if access is rejected, gitolite-shell will die as soon
+    as it returns from the trigger*.
+
+  * `ACCESS_2` runs after the second access check, which is invoked by the
+    update hook to check the ref.  Extra arguments:
       * repo
       * user
       * any of W, +, C, D, WM, +M, CM, DM
       * the ref being updated (e.g., 'refs/heads/master')
-      * result (see above)
+      * result
+      * old SHA
+      * new SHA
+
+    `ACCESS_2` also runs on each [VREF][vref] that gets checked.  In this case
+    the "ref" argument will start with "VREF/", and the last two arguments
+    won't be passed.
+
+    'result' is similar to `ACCESS_1`, except that it is the *update hook*
+    which dies as soon as access is rejected for the ref or any of the VREFs.
+    Control then returns to git, and then to gitolite-shell, so the `POST_GIT`
+    trigger *will* run.
 
   * `PRE_GIT` and `POST_GIT` run just before and after the git command.
     Extra arguments:
@@ -130,3 +148,10 @@ description of when the trigger runs:
     file, then all the 'git-config's, gitweb access, and daemon access.
 
     No extra arguments.
+
+## tips
+
+If you have code that latches onto more than one trigger, collecting data
+(such as for logging), then the outputs may be intermixed.  You can record the
+value of the environment variable `GL_TID` to tie together related entries.
+
diff --git a/doc/vref.mkd b/doc/vref.mkd
index 6f7bd2f..147b42e 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -118,7 +118,7 @@ exit.
       * Print the refex itself (plus an optional message), so that it matches
         the line which invoked it.
 
-### arguments passed to the vref code
+### #vref-args arguments passed to the vref code
 
   * Arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
     to the update hook (see 'man githooks').
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 6d3844d..568cfc4 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -29,7 +29,7 @@ sub update {
     trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
+    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret, $oldsha, $newsha );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
@@ -88,6 +88,7 @@ sub check_vref {
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
       if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
     trace( 2, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;

commit 10cd5b9abe62a19f8d7f4dd90d14737c0be1ee7e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Jun 6 05:54:58 2012 +0530

    'upstream' trigger can now be used as a server command also
    
    i.e., a client fetch will invoke the push, (subject to 'nice' setting),
    but you can also force a fetch regardless of last fetch time by running
    this command directly on the server:
    
        gitolite ../triggers/upstream fetch <reponame>
    
    Also, use FETCH_HEAD instead of own sentinel file (idea courtesy Luke Lu)

diff --git a/src/triggers/upstream b/src/triggers/upstream
index fddb446..b66ae87 100755
--- a/src/triggers/upstream
+++ b/src/triggers/upstream
@@ -1,43 +1,46 @@
 #!/bin/bash
 
-# Demo script: manage local, gitolite-controlled, copies of upstream repos.
+# manage local, gitolite-controlled, copies of read-only upstream repos.
 
-# Every time a client fetches such a repo, an upstream fetch happens, then the
-# client fetch is completed.  Optionally, you can be "nice" and impose a
-# minimum delay between 2 fetches.
-
-# See bottom of file for instructions and notes.
-
-# ----------------------------------------------------------------------
-
-repo=$2                     # see doc/triggers.mkd for arguments
+repo=$2
 
 url=$(gitolite git-config $repo gitolite-options.upstream.url)
 [ -z "$url" ] && exit 0     # exit if no url was specified
 
-cd $GL_REPO_BASE/$repo.git
+cd $GL_REPO_BASE/$repo.git || exit 1
 
-nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
-[ -n "$nice" ] && {
-    # exit if lastfetch was less than $nice minutes ago
-    find  .gl.upstream.lastfetch -mmin -$nice | grep . >/dev/null && exit 0
-    touch .gl.upstream.lastfetch
+[ "$1" != "fetch" ] && {
+    nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
+    [ -n "$nice" ] && find FETCH_HEAD -mmin -$nice | grep . >/dev/null && exit 0
 }
 
 git fetch -q "$url" '+refs/*:refs/*'
 
 # ----------------------------------------------------------------------
 
-# To install, add 'upstream' to the PRE_GIT trigger list in the rc file.
+# FEATURES:
+# * invokes upstream fetch on each local fetch
+#   (unless the optional 'nice' setting is enabled)
+# * can force a fetch (ignoring 'nice' value) from server CLI
+
+# INSTRUCTIONS:
+#
+# * add 'upstream' to the PRE_GIT trigger list in the rc file.
+# * add option lines to conf file.  For example:
+#
+#       repo git
+#           R                       =   @all
+#           RW+ my-company/         =   @developers
+#
+#           option  upstream.url    =   git://git.kernel.org/pub/scm/git/git.git
+#           option  upstream.nice   =   120
 #
-# Example setup to manage your own copy of the git repository:
-#   repo git
-#       R                       =   @all
-#       RW+ my-company/         =   @developers
-#       option  upstream.url    =   git://git.kernel.org/pub/scm/git/git.git
-#       option  upstream.nice   =   120     # (optional) minimum 2 hours between fetches
-
-# Notes:
-#   1.  we don't use a remote; due to refspec being +refs/*:refs/* we don't need one
-#   2.  please restrict local pushes, if any, to a namespace that the upstream won't use
-#   3.  if the upstream URL changes, change the conf file accordingly and push the admin repo
+# * to force a fetch on the server shell (or via cron), run this command:
+#       gitolite ../triggers/upstream fetch reponame
+
+# ADDITIONAL NOTES:
+# * restrict local pushes to a namespace that the upstream won't use
+#   (otherwise the next fetch will wipe them out)
+# * if the upstream URL changes, just change the conf and push admin repo
+# * the 'nice' setting is in minutes and is optional; it is the minimum
+#   elapsed time between 2 upstream fetches.

commit 8b78dee18ca851bc7a84a664040422626063fcb1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Jun 5 21:51:52 2012 +0530

    'upstream' -- script to maintain local copies of external repos
    
    instructions and notes in the source

diff --git a/src/triggers/upstream b/src/triggers/upstream
new file mode 100755
index 0000000..fddb446
--- /dev/null
+++ b/src/triggers/upstream
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Demo script: manage local, gitolite-controlled, copies of upstream repos.
+
+# Every time a client fetches such a repo, an upstream fetch happens, then the
+# client fetch is completed.  Optionally, you can be "nice" and impose a
+# minimum delay between 2 fetches.
+
+# See bottom of file for instructions and notes.
+
+# ----------------------------------------------------------------------
+
+repo=$2                     # see doc/triggers.mkd for arguments
+
+url=$(gitolite git-config $repo gitolite-options.upstream.url)
+[ -z "$url" ] && exit 0     # exit if no url was specified
+
+cd $GL_REPO_BASE/$repo.git
+
+nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
+[ -n "$nice" ] && {
+    # exit if lastfetch was less than $nice minutes ago
+    find  .gl.upstream.lastfetch -mmin -$nice | grep . >/dev/null && exit 0
+    touch .gl.upstream.lastfetch
+}
+
+git fetch -q "$url" '+refs/*:refs/*'
+
+# ----------------------------------------------------------------------
+
+# To install, add 'upstream' to the PRE_GIT trigger list in the rc file.
+#
+# Example setup to manage your own copy of the git repository:
+#   repo git
+#       R                       =   @all
+#       RW+ my-company/         =   @developers
+#       option  upstream.url    =   git://git.kernel.org/pub/scm/git/git.git
+#       option  upstream.nice   =   120     # (optional) minimum 2 hours between fetches
+
+# Notes:
+#   1.  we don't use a remote; due to refspec being +refs/*:refs/* we don't need one
+#   2.  please restrict local pushes, if any, to a namespace that the upstream won't use
+#   3.  if the upstream URL changes, change the conf file accordingly and push the admin repo

commit f59ad8cafce78d55af6be2b167726492d69bccc9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 30 14:14:29 2012 +0530

    (accumulated docfixes) esp a large section on the INPUT trigger

diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 040198c..501d44f 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -115,3 +115,51 @@ contents modified as you like and return a ref to it.
 
 There are a couple of examples in src/syntactic-sugar.
 
+## appendix 1: notes on the INPUT trigger
+
+Note: some of this won't make sense if you haven't read about [triggers][].
+
+The INPUT trigger sequence is designed to set or change environment variables
+or the argument list.  (Side note: this means INPUT triggers have to be
+written as perl modules; they cannot be standalone scripts).  This is a very
+powerful idea so an extended description may be useful.
+
+Sshd invokes gitolite-shell with the SSH\_ORIGINAL\_COMMAND env var containing
+the git/gitolite command and one argument: the gitolite username.
+
+  * see [this][glssh] for details on the latter
+  * the *first* thing gitolite does in smart http mode is to use the
+    REMOTE\_USER and the CGI variables that apache provides to *construct*
+    a fake argument list and a fake SSH\_ORIGINAL\_COMMAND env var, so the
+    rest of the code can stay the same
+
+The INPUT trigger is then run.  The purpose of the input trigger is to ensure
+that the first argument *is* the gitolite username, and that the
+SSH\_ORIGINAL\_COMMAND env var contains the actual command to execute.  It can
+also be used to set up any other environment variables that you may decide you
+need.
+
+Wait... didn't we say that's what gitolite-shell gets anyway, just now?
+
+Well, we lied a bit there; it's not always true!
+
+For example, if [this][giving-shell] feature is used, the first argument *may*
+be "-s", with the username in the *second* argument.  Shell.pm deals with
+that.  <font color="gray">(Order matters.  If you use this feature, put the
+`'Shell::input',` line ahead of the others, since it is the only one prepared
+to deal with username not being the first argument).</font>
+
+If you look at CpuTime.pm, you'll see that it's `input()` function doesn't set
+or change anything, but does set a package variable to record the start time.
+Later, when the same module's `post_git()` function is invoked, it uses this
+variable to determine elapsed time.
+
+*(This is a very nice and simple example of how you can implement features by
+latching onto multiple events and sharing data to do something)*.
+
+You can even change the reponame the user sees, behind his back.  Alias.pm
+handles that.
+
+Finally, as an exercise for the reader, consider how you would create a brand
+new env var that contains the *comment* field of the ssh pubkey that was used
+to gain access, using the information [here][kfn].
diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index b9a5e7d..279a82f 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -153,7 +153,7 @@ Done?  OK, now the general outline for ssh troubleshooting is this:
 
 ## random tips, tricks, and notes
 
-### giving shell access to gitolite users
+### #giving-shell giving shell access to gitolite users
 
 Thanks to an idea from Jesse Keating, a single key can allow both gitolite
 access *and* shell access.
@@ -168,7 +168,9 @@ To do this:
 
         SHELL_USERS_LIST        =>  "$ENV{HOME}/.gitolite.shell-users",
 
-  * add the line `'Shell::input',` to the `INPUT` list in the rc file.
+  * add the line `'Shell::input',` to the `INPUT` list in the rc file.  This
+    must be the first item on the list (possibly preceded by CpuTime, if
+    you're using that).
 
   * add the line `'post-compile/ssh-authkeys-shell-users',` to the
     `POST_COMPILE` list, *after* the `'post-compile/ssh-authkeys',` line.
@@ -176,7 +178,7 @@ To do this:
 Then run `gitolite compile; gitolite trigger POST_COMPILE` or push a dummy
 change to the admin repo.
 
-### distinguishing one key from another
+### #kfn distinguishing one key from another
 
 Since a user can have [more than one key][multi-key], it is sometimes useful
 to distinguish one key from another.  Sshd does not tell you even the
diff --git a/doc/locking.mkd b/doc/locking.mkd
index f693ed5..2799003 100644
--- a/doc/locking.mkd
+++ b/doc/locking.mkd
@@ -12,7 +12,12 @@ this situation it should be possible to at least prevent commits from being
 pushed that contains changes to files locked by someone else.
 
 The two "lock" programs (one a command that a user uses, and one a VREF that
-the admin adds to a repo's access rules) together achieve this.
+the admin adds to a repo's access rules) together attempt to achieve this.
+
+Of course, locking by itself is not quite enough.  You may still get into
+merge situations if you make changes in branches.  For best results you should
+actually keep all the binary files in their own branch, separate from the ones
+containing source code.
 
 ----
 
@@ -50,6 +55,8 @@ Here's a summary:
 
 For best results, everyone on the team should:
 
+  * Switch to the branch containing the binary files when wanting to make a
+    change.
   * Run 'git pull' or eqvt, then lock the binary file(s) before editing them.
   * Finish the editing task as quickly as possible, then commit, push, and
     unlock the file(s) so others are not needlessly blocked.
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index b893e0a..2ad98d8 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -45,16 +45,28 @@ The following "sugar" programs are available:
 
 ## triggers
 
-The `PRE_GIT` triggers are:
+Note that some features need to be enabled in more than one trigger.
+Mirroring is probably the best example -- it needs to hook into the `INPUT`,
+`PRE_GIT`, and the `POST_GIT` triggers to work.
 
-  * partial-copy -- this has its own section later in this page.
+The `INPUT` triggers are:
+
+  * CpuTime -- see the `POST_GIT` section below
+  * Shell -- see "giving shell access to gitolite users" in [this][sts] page.
+  * Alias -- documentation is within the source file Alias.pm
+  * Mirroring -- see [doc/mirroring.mkd][mirroring]
+
+The `PRE_GIT` triggers are:
 
   * renice -- this renices the entire job to whatever value you specify.
+  * Mirroring -- see [doc/mirroring.mkd][mirroring]
+  * partial-copy -- this has its own section later in this page.
 
 The `POST_GIT` triggers are:
 
-  * CpuTime, which is only a sample because it only prints the CPU times data.
-    In reality you will want to do something else with it.
+  * Mirroring -- see [doc/mirroring.mkd][mirroring]
+  * CpuTime -- this is only a sample because it only prints the CPU times
+    data.  In reality you will want to do something else with it.
 
 The `POST_COMPILE` triggers are:
 
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 3c19496..d25a0bd 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -32,6 +32,27 @@ here's an excerpt:
 (As you can see, post-create runs 3 programs that also run from post-compile.
 This is perfectly fine, by the way)
 
+## types of trigger programs
+
+There are two types of trigger programs.  Standalone scripts are placed in
+src/triggers or its subdirectories.  They are invoked by being added to a
+trigger list (using the path after "src/triggers/", as you can see).  Such
+scripts are quick and easy to write in any language of your choice.
+
+Triggers written as perl modules are placed in src/lib/Gitolite/Triggers.
+They are invoked by being listed with the package+function name, although even
+here the 'Gitolite::Triggers' part is skipped.  Perl modules have to follow
+some conventions (see some of the shipped modules to ideas) but the advantage
+is that they can set environment variables and change the argument list of the
+gitolite-shell program that invokes them.
+
+If this does not make sense, please examine a default install of gitolite,
+paying attention to:
+
+  * the path names in various trigger lists in the rc file
+  * corresponding path names in the src/ directory in gitolite source
+  * and for perl modules, the package names and function names within.
+
 ## manually firing triggers
 
 ...from the server command line is easy.  For example:
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index e990e06..8dbe1e7 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -291,6 +291,14 @@ __DATA__
 
 # (Tip: perl allows a comma after the last item in a list also!)
 
+# HELP for commands (see COMMANDS list below) can be had by running the
+# command with "-h" as the sole argument.
+
+# HELP for all the other external programs (the syntactic sugar helpers and
+# the various programs/functions in the 8 trigger lists), can be found in
+# doc/non-core.mkd (http://sitaramc.github.com/gitolite/non-core.html) or in
+# the corresponding source file itself.
+
 %RC = (
     # if you're using mirroring, you need a hostname.  This is *one* simple
     # word, not a full domain name.  See documentation if in doubt
@@ -344,15 +352,16 @@ __DATA__
     SYNTACTIC_SUGAR             =>
         [
             # 'continuation-lines',
+            # 'keysubdirs-as-groups',
         ],
 
     # comment out or uncomment as needed
     # these will run in sequence to modify the input (arguments and environment)
     INPUT                       =>
         [
-            # if you use this, make this the first item in the list
             # 'CpuTime::input',
-
+            # 'Shell::input',
+            # 'Alias::input',
             # 'Mirroring::input',
         ],
 
@@ -366,12 +375,8 @@ __DATA__
     # these will run in sequence just before the actual git command is invoked
     PRE_GIT                     =>
         [
-            # if you use this, make this the first item in the list
             # 'renice 10',
-
             # 'Mirroring::pre_git',
-
-            # see docs ("list of non-core programs shipped") for details
             # 'partial-copy',
         ],
 
@@ -386,8 +391,6 @@ __DATA__
     POST_GIT                    =>
         [
             # 'Mirroring::post_git',
-
-            # if you use this, make this the last item in the list
             # 'CpuTime::post_git',
         ],
 

commit 17c41ce63bfb942e0274e5d3e7b98b6869bec555
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 3 13:21:15 2012 +0530

    new 'sudo' command

diff --git a/src/commands/sudo b/src/commands/sudo
new file mode 100755
index 0000000..ac2bb54
--- /dev/null
+++ b/src/commands/sudo
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Usage:    ssh git at host sudo <user> <command> <arguments>
+#
+# Let super-user run commands as any other user.  "Super-user" is defined as
+# "have write access to the gitolite-admin repo".
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+gitolite access -q gitolite-admin $GL_USER W any || die "You are not authorised"
+
+user="$1"; shift
+cmd="$1"; shift
+
+# switch user
+GL_USER="$user"
+
+# figure out if the command is allowed from a remote user
+gitolite query-rc -q COMMANDS $cmd || die "Command '$cmd' not allowed"
+gitolite $cmd "$@"

commit ad34cf2856df1056431b409c809a03f1da24a619
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Jun 2 16:52:11 2012 +0530

    minor backward incompat breakage in 'gitolite query-rc'
    
    'gitolite query-rc' now only queries one variable at a time.  That is,
    you cannot do something like this:
    
        gitolite query-rc UMASK GL_ADMIN_BASE
    
    to query both variables.  I think this is rarely used, plus it is easy
    to work-around (just run two separate commands), so it was sacrificed
    for the ability to do this:
    
        gitolite query-rc -q COMMANDS fork
    
    which tells you whether $rc{COMMANDS}{fork} exists or not.

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 3bcac33..e990e06 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -156,11 +156,36 @@ sub query_rc {
         exit 0;
     }
 
-    my @res = map { $rc{$_} } grep { $rc{$_} } @vars;
-    print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if not $quiet and @res;
-    # shell truth
-    exit 0 if @res;
-    exit 1;
+    my $cv = \%rc;  # current "value"
+    while (@vars) {
+        my $v = shift @vars;
+
+        # dig into the rc hash, using each var as a component
+        if (not ref($cv)) {
+            _warn "unused arguments...";
+            last;
+        } elsif (ref($cv) eq 'HASH') {
+            $cv = $cv->{$v} || '';
+        } elsif (ref($cv) eq 'ARRAY') {
+            $cv = $cv->[$v] || '';
+        } else {
+            _die "dont know what to do with " . ref($cv) . " item in the rc file";
+        }
+    }
+
+    # we've run out of arguments so $cv is what we have.  If we're supposed to
+    # be quiet, we don't have to print anything so let's get that done first:
+    exit ( $cv ? 0 : 1 ) if $quiet;     # shell truth
+
+    # print values (notice we ignore the '-n' option if it's a ref)
+    if (ref($cv) eq 'HASH') {
+        print join("\n", sort keys %$cv), "\n" if %$cv;
+    } elsif (ref($cv) eq 'ARRAY') {
+        print join("\n", @$cv), "\n" if @$cv;
+    } else {
+        print $cv . ( $nonl ? '' : "\n" ) if $cv;
+    }
+    exit ( $cv ? 0 : 1 );   # shell truth
 }
 
 sub version {
@@ -207,21 +232,35 @@ sub trigger {
 
 =for args
 Usage:  gitolite query-rc -a
-        gitolite query-rc [-n] <list of rc variables>
+        gitolite query-rc [-n] [-q] rc-variable
 
-    -a          print all variables and values
-    -n          do not append a newline
+    -a          print all variables and values (first level only)
+    -n          do not append a newline if variable is scalar
     -q          exit code only (shell truth; 0 is success)
 
-Example:
+Query the rc hash.  Second and subsequent arguments dig deeper into the hash.
+The examples are for the default configuration; yours may be different.
 
-    gitolite query-rc GL_ADMIN_BASE UMASK
-    # prints "/home/git/.gitolite<tab>0077" or similar
+Single values:
+    gitolite query-rc GL_ADMIN_BASE     # prints "/home/git/.gitolite" or similar
+    gitolite query-rc UMASK             # prints "63" (that's 0077 in decimal!)
 
-    gitolite query-rc -a
-    # prints all known variables and values, one per line
+Hashes:
+    gitolite query-rc COMMANDS
+        # prints "desc", "help", "info", "perms", "writable", one per line
+    gitolite query-rc COMMANDS help     # prints 1
+    gitolite query-rc -q COMMANDS help  # prints nothing; exit code is 0
+    gitolite query-rc COMMANDS fork     # prints nothing; exit code is 1
 
-Note: '-q' is best used with only one variable.
+Arrays (somewhat less useful):
+    gitolite query-rc POST_GIT          # prints nothing; exit code is 0
+    gitolite query-rc POST_COMPILE      # prints 4 lines
+    gitolite query-rc POST_COMPILE 0    # prints the first of those 4 lines
+
+Explore:
+    gitolite query-rc -a
+    # prints all first level variables and values, one per line.  Any that are
+    # listed as HASH or ARRAY can be explored further in subsequent commands.
 =cut
 
 sub args {

commit 4abadc2b54f8d0a361113e0b8b61e872cc288681
Author: John Keeping <john at keeping.me.uk>
Date:   Sat Jun 2 15:28:05 2012 +0100

    Grant shell access to all keys for shell users
    
    If a user has multiple keys, ssh-authkeys-shell-users will only add the
    "-s" flag to the first key it finds.  Change the substitution to apply
    to all matching lines and hence grant shell access to all of the user's
    keys.
    
    Signed-off-by: John Keeping <john at keeping.me.uk>

diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
index 35b7f90..176e450 100755
--- a/src/triggers/post-compile/ssh-authkeys-shell-users
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -19,7 +19,7 @@ my $sufile = $rc{SHELL_USERS_LIST} or exit 0;
 my $aktext = slurp($akfile);
 
 for my $su ( shell_users() ) {
-    $aktext =~ s(/gitolite-shell $su([" ].*?),no-pty )(/gitolite-shell -s $su$1 );
+    $aktext =~ s(/gitolite-shell $su([" ].*?),no-pty )(/gitolite-shell -s $su$1 )g;
 }
 
 _print( $akfile, $aktext );

commit db70595b877c669dacf52f0fbf59a6f4c29e08c1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Jun 3 07:38:19 2012 +0530

    fixup to pushing wild repos permissions...
    
      - fix docs to explicitly state that mirroring wild repos is a bad idea
        if the authentication data is not the same on the peers.
    
      - an important check against a malicious peer was missed out.  If
        people heed the warning above this check is not really needed but it
        is good for completeness.
    
      - warning about redirected pushes removed, thanks to Ronald Ip having
        tested it and reported it working.

diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 9f88f07..9689c7c 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -41,22 +41,27 @@ Gitolite extends this simple notion in the following ways:
     prepared to receive updates from the master.
 
     However, there is limited support for auto-creating wild card repos and
-    sending 'perms' info across, with the following caveats at present:
+    sending 'perms' info across, with the following caveats at present.  (Some
+    of this text won't make sense unless you know what those features are).
+
+      * *WARNING: it does NOT make sense to mirror wild repos in setups where
+        the authentication data is not the same (i.e., where "alice" on the
+        master and "alice" on a slave maybe totally different people)*.
 
       * This has only been minimally tested.  For example, complex setups or
         asymmetric configs on master and slave, etc. have NOT been tested.
 
-      * This has also not been tested with redirected pushes.  It may not
-        work.
+      * Permission changes will only propagate on the next 'git push'.  Of
+        course, if you know the name of the slave server, you can run
 
-      * As with lots of extra features in gitolite, smart http support is not
-        on my radar.  Don't ask.
+            ssh git at host mirror push slave-server-name repo-name
 
-      * If you change permissions using the 'perms' command, they will only go
-        across on the next push.  Or, if you know the name of the slave
-        server, you can run
+      * Using 'perms' on a slave is allowed but will neither propagate nor
+        persist.  They will be overwritten by whatever perms the master has
+        (even if it is an empty set) on the next 'git push'.
 
-            ssh git at host mirror push slave-server-name repo-name
+      * As with lots of extra features in gitolite, smart http support is not
+        on my radar.  Don't ask.
 
     Please test it out and let me know if something surprising happens.  Be
     aware that I have been known to claim bugs are features if I don't have
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index bc38781..78d5be9 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -17,6 +17,11 @@ my ( $mode, $master, %slaves, %trusted_slaves );
 sub input {
     return unless $ARGV[0] =~ /^server-(\S+)$/;
 
+    # note: we treat %rc as our own internal "poor man's %ENV"
+    $rc{FROM_SERVER} = $1;
+    trace( 3, "from_server: $1" );
+    my $sender = $rc{FROM_SERVER} || '';
+
     # custom peer-to-peer commands.  At present the only one is 'perms -c',
     # sent from a mirror command
     if ($ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/) {
@@ -26,6 +31,7 @@ sub input {
         details($repo);
         _die "$hn: '$repo' is local"  if $mode eq 'local';
         _die "$hn: '$repo' is native" if $mode eq 'master';
+        _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
 
         # this expects valid perms content on STDIN
         _system("gitolite perms -c $repo");
@@ -34,10 +40,6 @@ sub input {
         exit 0;
     }
 
-    # note: we treat %rc as our own internal "poor man's %ENV"
-    $rc{FROM_SERVER} = $1;
-    trace( 3, "from_server: $1" );
-
     if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
         # my ($user, $newsoc, $repo) = ($1, $2, $3);
         $ENV{SSH_ORIGINAL_COMMAND} = $2;

commit 78866f6f28ab05eb05d89b5a373f141a0cb7ec7a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 1 11:47:17 2012 +0530

    (experimental code) push wild repos across a master slave connection
    
    It creates the repo on the remote side (getting the creator name from
    the gl-creator file and sending it across), as well as sending gl-perms
    on subsequent connections.
    
    This has only been minimally tested.  E.g., complex setups or asymmetric
    configs on master and slave, etc. have NOT been tested.
    
    This has also not been tested with redirected pushes.

diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 387bbea..9f88f07 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -38,8 +38,29 @@ Gitolite extends this simple notion in the following ways:
 ## caveats
 
   * Mirroring will never *create* a repo on a slave; it has to exist and be
-    prepared to receive updates from the master.  (For example, [wild][] repos
-    must be created on the slave as well, otherwise they will not propagate).
+    prepared to receive updates from the master.
+
+    However, there is limited support for auto-creating wild card repos and
+    sending 'perms' info across, with the following caveats at present:
+
+      * This has only been minimally tested.  For example, complex setups or
+        asymmetric configs on master and slave, etc. have NOT been tested.
+
+      * This has also not been tested with redirected pushes.  It may not
+        work.
+
+      * As with lots of extra features in gitolite, smart http support is not
+        on my radar.  Don't ask.
+
+      * If you change permissions using the 'perms' command, they will only go
+        across on the next push.  Or, if you know the name of the slave
+        server, you can run
+
+            ssh git at host mirror push slave-server-name repo-name
+
+    Please test it out and let me know if something surprising happens.  Be
+    aware that I have been known to claim bugs are features if I don't have
+    time to fix them immediately :-)
 
   * Mirroring is only for git repos.  Ancillary files like gl-creator and
     gl-perms in the repo directory are not mirrored; you must do that
diff --git a/src/commands/mirror b/src/commands/mirror
index ec6f275..16c14dd 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -45,6 +45,12 @@ if ( $cmd eq 'push' ) {
     _chdir( $rc{GL_REPO_BASE} );
     _chdir("$repo.git");
 
+    if (-f "gl-creator") {
+        # try to propagate the wild repo, including creator name and gl-perms
+        my $creator = `cat gl-creator`; chomp($creator);
+        trace(1, `cat gl-perms 2>/dev/null | ssh $host CREATOR=$creator perms -c \\'$repo\\'`);
+    }
+
     my $errors = 0;
     for (`git push --mirror $host:$repo 2>&1`) {
         print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 435f8aa..bc38781 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -10,11 +10,30 @@ use warnings;
 my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
 my $hn           = $rc{HOSTNAME};
 
+my ( $mode, $master, %slaves, %trusted_slaves );
+
 # ----------------------------------------------------------------------
 
 sub input {
     return unless $ARGV[0] =~ /^server-(\S+)$/;
 
+    # custom peer-to-peer commands.  At present the only one is 'perms -c',
+    # sent from a mirror command
+    if ($ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/) {
+        $ENV{GL_USER} = $1;
+
+        my $repo = $2;
+        details($repo);
+        _die "$hn: '$repo' is local"  if $mode eq 'local';
+        _die "$hn: '$repo' is native" if $mode eq 'master';
+
+        # this expects valid perms content on STDIN
+        _system("gitolite perms -c $repo");
+
+        # we're done.  Yes, really...
+        exit 0;
+    }
+
     # note: we treat %rc as our own internal "poor man's %ENV"
     $rc{FROM_SERVER} = $1;
     trace( 3, "from_server: $1" );
@@ -33,8 +52,6 @@ sub input {
 
 # ----------------------------------------------------------------------
 
-my ( $mode, $master, %slaves, %trusted_slaves );
-
 sub pre_git {
     return unless $hn;
     # nothing, and I mean NOTHING, happens if HOSTNAME is not set

commit 42e0bac48ca415303334d5e8cfa72626675a1b37
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 1 09:52:12 2012 +0530

    'perms' command learns to create repo if needed

diff --git a/src/commands/perms b/src/commands/perms
index 7be3a28..b230e8a 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -21,12 +21,21 @@ Examples:
     ssh git at host perms foo + READERS user2
     ssh git at host perms foo + READERS user3
 
-(Note: a legacy mode of piping in the entire permissions text directly is also
-supported.  If you want to use it, don't mix it with the new "+/-" modes).
+----
+There is also a batch mode useful for scripting and bulk loading.  Do not
+combine this with the +/- mode above.  This mode also accepts an optional "-c"
+flag to create the repo if it does not already exist (assuming $GL_USER has
+permissions to create it).
+
+Examples:
+    cat copy-of-backed-up-gl-perms | ssh git at host perms <repo>
+    cat copy-of-backed-up-gl-perms | ssh git at host perms -c <repo>
 =cut
 
 usage() if not @ARGV or $ARGV[0] eq '-h';
 
+$ENV{GL_USER} or _die "GL_USER not set";
+
 my $list = 0;
 if ( $ARGV[0] eq '-l' ) {
     $list++;
@@ -34,6 +43,23 @@ if ( $ARGV[0] eq '-l' ) {
     getperms(@ARGV);    # doesn't return
 }
 
+# auto-create the repo if -c passed and repo doesn't exist
+if ( $ARGV[0] eq '-c' ) {
+    shift;
+    my $repo = $ARGV[0];
+    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
+
+    if (not -d "$rc{GL_REPO_BASE}/$repo.git") {
+        my $ret = access( $repo, $ENV{GL_USER}, '^C', 'any' );
+        _die $ret if $ret =~ /DENIED/;
+
+        require Gitolite::Conf::Store;
+        Gitolite::Conf::Store->import;
+        new_wild_repo( $repo, $ENV{GL_USER} );
+        gl_log( 'create', $repo, $ENV{GL_USER} );
+    }
+}
+
 my $repo = shift;
 setperms(@ARGV);
 _system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER} );
diff --git a/t/sequence.t b/t/sequence.t
index ef11689..acccb0b 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # uhh, seems to be another rule sequence test
 # ----------------------------------------------------------------------
 
-try "plan 40";
+try "plan 48";
 
 confreset;confadd '
     @staff = u1 u2 u3
@@ -61,6 +61,7 @@ confreset;confadd '
           RW+     = CREATOR
           -       = @staff
           RW      = WRITERS
+          R       = READERS
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
@@ -81,6 +82,7 @@ try "
         /WRITERS u2/
     # expand
     glt info u2
+        !/R W *\tfoo/u1/baz/
         /R W *\tfoo/u1/bar/
         /R W *\ttesting/
 
@@ -94,4 +96,21 @@ try "
         !ok
         reject
         /W refs/heads/master foo/u1/bar u2 DENIED by refs/\\.\\*/
+
+    # auto-create using perms fail
+    echo READERS u5 | glt perms u4 -c foo/u4/baz
+        !/Initialized empty Git repository in .*/foo/u4/baz.git/
+        /FATAL: .C any foo/u4/baz u4 DENIED by fallthru/
+
+    # auto-create using perms
+    echo READERS u2 | glt perms u1 -c foo/u1/baz
+        /Initialized empty Git repository in .*/foo/u1/baz.git/
+
+    glt perms u1 -l foo/u1/baz
+        /READERS u2/
+    # expand
+    glt info u2
+        /R   *\tfoo/u1/baz/
+        /R W *\tfoo/u1/bar/
+        /R W *\ttesting/
 ";

commit d9df70a04f16dd67c87f9e01d06b7ee584fab131
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Jun 1 05:24:27 2012 +0530

    allow getting config settings for non-existant repos also
    
    It's reasonable to want to see config items (for example, mirror
    settings) that *would* apply if the repo existed.

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 3537c4f..70ec14a 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -119,8 +119,11 @@ sub git_config {
     my ( $repo, $key, $empty_values_OK ) = @_;
     $key ||= '.';
 
-    return {} if repo_missing($repo);
-    load($repo);
+    if (repo_missing($repo)) {
+        load_common();
+    } else {
+        load($repo);
+    }
 
     # read comments bottom up
     my %ret =

commit 7170ad91245aedc06b574fb4c59e600e8a3e13c8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 30 14:45:19 2012 +0530

    allow pubkey filename as extra argument to command in authkeys

diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index 73da293..b9a5e7d 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -176,6 +176,34 @@ To do this:
 Then run `gitolite compile; gitolite trigger POST_COMPILE` or push a dummy
 change to the admin repo.
 
+### distinguishing one key from another
+
+Since a user can have [more than one key][multi-key], it is sometimes useful
+to distinguish one key from another.  Sshd does not tell you even the
+fingerprint of the key that finally matched, so normally all you have is the
+`GL_USER` env var.
+
+However, if you replace
+
+    'post-compile/ssh-authkeys',
+
+in the `POST_COMPILE` trigger list in the rc file with
+
+    'post-compile/ssh-authkeys --key-file-name',
+
+then an extra argument is added after the username in the "command" variable
+of the authkeys file.  That is, instead of this:
+
+    command="/home/g3/gitolite/src/gitolite-shell u3",no-port-forwarding,...
+
+you get this:
+
+    command="/home/g3/gitolite/src/gitolite-shell u3 keydir/u3.pub",no-port-forwarding,...
+
+You can then write an INPUT trigger to do whatever you need with the file
+name, which is in `$ARGV[1]` (the second argument).  The actual file is
+available at `$ENV{GL_ADMIN_BASE}/$ARGV[1]` if you need its contents.
+
 ### simulating ssh-copy-id
 
 don't have `ssh-copy-id`?  This is broadly what that command does, if you want
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 7afd3af..910737f 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -3,6 +3,7 @@ use strict;
 use warnings;
 
 use File::Temp qw(tempfile);
+use Getopt::Long;
 
 use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
@@ -10,8 +11,18 @@ use Gitolite::Common;
 
 $|++;
 
-# can be called directly, or as a post-update hook.  Since it ignores
-# arguments anyway, it hardly matters.
+# best called via 'gitolite trigger POST_COMPILE'; other modes at your own
+# risk, especially if the rc file specifies arguments for it.  (That is also
+# why it doesn't respond to "-h" like most gitolite commands do).
+
+# option procesing
+# ----------------------------------------------------------------------
+
+# currently has one option:
+#   -kfn, --key-file-name        adds the keyfilename as a second argument
+
+my $kfn = '';
+GetOptions( 'key-file-name|kfn' => \$kfn, );
 
 tsh_try("sestatus");
 my $selinux = ( tsh_text() =~ /enabled/ );
@@ -130,6 +141,6 @@ sub optionise {
         return '';
     }
     chomp(@line);
-    return "command=\"$glshell $user\",$auth_options $line[0]";
+    return "command=\"$glshell $user" . ( $kfn ? " $f" : "" ) . "\",$auth_options $line[0]";
 }
 
diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
index 91ed857..35b7f90 100755
--- a/src/triggers/post-compile/ssh-authkeys-shell-users
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -19,7 +19,7 @@ my $sufile = $rc{SHELL_USERS_LIST} or exit 0;
 my $aktext = slurp($akfile);
 
 for my $su ( shell_users() ) {
-    $aktext =~ s(/gitolite-shell $su",(.*?),no-pty )(/gitolite-shell -s $su",$1 );
+    $aktext =~ s(/gitolite-shell $su([" ].*?),no-pty )(/gitolite-shell -s $su$1 );
 }
 
 _print( $akfile, $aktext );

commit a64401bd9af99a0601c84bfc5b01b84797f8b868
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 30 14:46:29 2012 +0530

    (doc) document the INPUT trigger

diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 660ccd7..3c19496 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -61,6 +61,14 @@ Triggers receive the following arguments:
 Here are the **rest of** the arguments for each trigger, plus a brief
 description of when the trigger runs:
 
+  * `INPUT` runs before pretty much anything else.  INPUT trigger scripts
+    *must* be in perl, since they manipulate the arguments to and the
+    environment of the 'gitolite-shell' program itself.  Most commonly they
+    will read/change `@ARGV`, and/or `$ENV{SSH_ORIGINAL_COMMAND}`.
+
+    There are certain conventions to adhere to; please see some of the shipped
+    samples or ask me if you need help writing your own.
+
   * `ACCESS_1` runs after the first access check.  Extra arguments:
       * repo
       * user

commit 21dbe53d39c584d3845aaac7fc9458e0654fc92e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 29 20:55:53 2012 +0530

    fix minor bug in handling 'desc = "some description"'
    
        repo foo
            desc = "foo"
            RW  =   u1
            ...etc...
    
    The order of the clauses was parsing this like the old 'reponame = "some
    description"' and end up creating a repo called 'desc'!
    
    It would also, as a side-effect, change the repo so what you thought
    were access rules for 'foo' would become access rules for 'desc'.

diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index 06422b7..dca1c44 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -131,17 +131,17 @@ sub owner_desc {
     #   ->  config gitweb.description = some long description
 
     for my $line (@$lines) {
-        if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
-            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
-            push @ret, "repo $repo";
-            push @ret, "config gitweb.description = $desc";
-            push @ret, "config gitweb.owner = $owner" if $owner;
-        } elsif ( $line =~ /^desc = (\S.*)/ ) {
+        if ( $line =~ /^desc = (\S.*)/ ) {
             push @ret, "config gitweb.description = $1";
         } elsif ( $line =~ /^owner = (\S.*)/ ) {
             push @ret, "config gitweb.owner = $1";
         } elsif ( $line =~ /^category = (\S.*)/ ) {
             push @ret, "config gitweb.category = $1";
+        } elsif ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
+            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
+            push @ret, "repo $repo";
+            push @ret, "config gitweb.description = $desc";
+            push @ret, "config gitweb.owner = $owner" if $owner;
         } else {
             push @ret, $line;
         }

commit 06d3398fb0ef36c835287b73b2794209ecc1eb49
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 27 09:50:50 2012 +0530

    lock binary files... (manually tested)
    
    Remember that true locking is not possible in a DVCS; see
    doc/locking.mkd for details and limitations of what is offered here.

diff --git a/doc/locking.mkd b/doc/locking.mkd
new file mode 100644
index 0000000..f693ed5
--- /dev/null
+++ b/doc/locking.mkd
@@ -0,0 +1,153 @@
+# locking binary files
+
+Locking is useful to make sure that binary files (office docs, images, ...)
+don't get into a merge state.  (<font color="gray">If you think it's not a big
+deal, you have never manually merged independent changes to an ODT or
+something!</font>)
+
+When git is used in a truly distributed fashion, locking is impossible.
+However, in most corporate setups, there is a single central server acting as
+the canonical source of truth and collaboration point for all developers.  In
+this situation it should be possible to at least prevent commits from being
+pushed that contains changes to files locked by someone else.
+
+The two "lock" programs (one a command that a user uses, and one a VREF that
+the admin adds to a repo's access rules) together achieve this.
+
+----
+
+[[TOC]]
+
+## problem description
+
+Our users are alice, bob, and carol.  Our repo is foo.  It has some "odt"
+files in the "doc/" directory.  We want to make sure these odt files never get
+into a "merge" situation.
+
+## admin/setup
+
+First, someone with shell access to the server must add 'lock' to the
+"COMMANDS" list in the rc file.
+
+Next, the gitolite.conf file should have something like this:
+
+    repo foo
+        <...other rules...>
+        -   VREF/lock      =   @all
+
+However, see below for the difference between "RW" and "RW+" from the point of
+view of this feature and adjust permissions accordingly.
+
+## user view
+
+Here's a summary:
+
+  * Any user with "W" permissions to any branch in the repo can "lock" any
+    file.  Once locked, no other user can push changes to that file, *in any
+    branch*, until it is unlocked.
+  * Any user with "+" permissions to any branch in the repo can "break" a lock
+    held by someone else if needed.
+
+For best results, everyone on the team should:
+
+  * Run 'git pull' or eqvt, then lock the binary file(s) before editing them.
+  * Finish the editing task as quickly as possible, then commit, push, and
+    unlock the file(s) so others are not needlessly blocked.
+  * Understand that breaking a lock require additional, (out of band)
+    communication.  It is upto the team's policies what that entails.
+
+## detailed example
+
+Alice declares her intent to work on "d1.odt":
+
+    $ git pull
+    $ ssh git at host lock -l foo doc/d1.odt
+
+Similarly Bob starts on "d2.odt"
+
+    $ git pull
+    $ ssh git at host lock -l foo doc/d2.odt
+
+Carol makes some changes to d2.odt (**without attempting to lock the file or
+checking to see if it is already locked**) and pushes:
+
+    $ ooffice doc/d2.odt
+    $ git add doc/d2.odt
+    $ git commit -m 'added footnotes to d2 in klingon'
+    $ git push
+    <...normal push progress output...>
+    remote: FATAL: W VREF/lock testing carol DENIED by VREF/lock
+    remote: 'doc/d2.odt' locked by 'bob'
+    remote: error: hook declined to update refs/heads/master
+    To u2:testing
+     ! [remote rejected] master -> master (hook declined)
+    error: failed to push some refs to 'carol:foo'
+
+Carol backs out her changes, but saves them away for a "manual merge" later.
+
+    git reset HEAD^
+    git stash save 'klingon changes to d2.odt saved for possible manual merge later'
+
+Note that this still represents wasted work in some sense, because Carol would
+have to somehow re-apply the same changes to the new version of d2.odt after
+pulling it down.  **This is because she did not lock the file before making
+changes on her local repo.  Educating users in doing this is important if this
+scheme is to help you.**
+
+She now decides to work on "d1.odt".  However, she has learned her lesson and
+decides to follow the protocol described above:
+
+    $ git pull
+    $ ssh git at host lock -l foo doc/d1.odt
+    FATAL: 'doc/d1.odt' locked by 'alice' since Sun May 27 17:59:59 2012
+
+Oh damn; can't work on that either.
+
+Carol now decides to see what else there may be.  Instead of checking each
+file to see if she can lock it, she starts with a list of what is already
+locked:
+
+    $ ssh git at host lock -ls foo
+
+    # locks held:
+
+    alice   doc/d1.odt      (Sun May 27 17:59:59 2012)
+    bob     doc/d2.odt      (Sun May 27 18:00:06 2012)
+
+    # locks broken:
+
+Aha, looks like only d1 and d2 are locked.  She picks d3.odt to work on.  This
+time, she starts by locking it:
+
+    $ ssh git at host lock -l foo doc/d3.odt
+    $ ooffice doc/d3.odt
+    <...etc...>
+
+Meanwhile, in a parallel universe where d3.odt doesn't exist, and Alice has
+gone on vacation while keeping d1.odt locked, Carol breaks the lock.  Carol
+can do this because she has RW+ permissions for the repository itself.
+
+However, protocol in this team requires that she get email approval from the
+team lead before doing this and that Alice be in CC in those emails, so she
+does that first, and *then* she breaks the lock:
+
+    $ git pull
+    $ ssh git at host lock --break foo doc/d1.odt
+
+She then locks d1.odt for herself:
+
+    $ ssh git at host lock -l foo doc/d1.odt
+
+When Alice comes back, she can tell who broke her lock and when:
+
+    $ ssh git at host lock -ls foo
+
+    # locks held:
+
+    carol   doc/d1.odt      (Sun May 27 18:17:29 2012)
+    bob     doc/d2.odt      (Sun May 27 18:00:06 2012)
+
+    # locks broken:
+
+    carol   doc/d1.odt      (Sun May 27 18:17:03 2012)      (locked by alice at Sun May 27 17:59:59 2012)
+
diff --git a/src/VREF/lock b/src/VREF/lock
new file mode 100755
index 0000000..e07d8d5
--- /dev/null
+++ b/src/VREF/lock
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Common;
+
+# gitolite VREF to lock and unlock (binary) files.  Requires companion command
+# 'lock' to be enabled; see doc/locking.mkd for details.
+
+# ----------------------------------------------------------------------
+
+# see gitolite docs for what the first 7 arguments mean
+
+die "not meant to be run manually" unless $ARGV[6];
+
+my $ff = "$ENV{GL_REPO_BASE}/$ENV{GL_REPO}.git/gl-locks";
+exit 0 unless -f $ff;
+
+our %locks;
+my $t = slurp($ff);
+eval $t;
+_die "do '$ff' failed with '$@', contact your administrator" if $@;
+
+my ( $oldtree, $newtree, $refex ) = @ARGV[ 3, 4, 6 ];
+
+for my $file (`git diff --name-only $oldtree $newtree` ) {
+    chomp($file);
+
+    if ($locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER}) {
+        print "$refex '$file' locked by '$locks{$file}{USER}'";
+        last;
+    }
+}
+
+exit 0
diff --git a/src/commands/lock b/src/commands/lock
new file mode 100755
index 0000000..f95af6c
--- /dev/null
+++ b/src/commands/lock
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+# gitolite command to lock and unlock (binary) files and deal with locks.
+
+=for usage
+Usage:  ssh git at host lock -l      <repo> <file>     # lock a file
+        ssh git at host lock -u      <repo> <file>     # unlock a file
+        ssh git at host lock --break <repo> <file>     # break someone else's lock
+        ssh git at host lock -ls     <repo>            # list locked files for repo
+
+See doc/locking.mkd for other details.
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+$ENV{GL_USER} or _die "GL_USER not set";
+
+my $op = '';
+$op = 'lock'   if $ARGV[0] eq '-l';
+$op = 'unlock' if $ARGV[0] eq '-u';
+$op = 'break'  if $ARGV[0] eq '--break';
+$op = 'list'   if $ARGV[0] eq '-ls';
+usage() if not $op;
+shift;
+
+my $repo = shift;
+_die "You are not authorised" if access( $repo, $ENV{GL_USER}, 'W', 'any' ) =~ /DENIED/;
+_die "You are not authorised" if $op eq 'break' and access( $repo, $ENV{GL_USER}, '+', 'any' ) =~ /DENIED/;
+
+my $file = shift || '';
+usage() if $op ne 'list' and not $file;
+
+_chdir( $ENV{GL_REPO_BASE} );
+_chdir("$repo.git");
+
+my $ff = "gl-locks";
+
+if ( $op eq 'lock' ) {
+    f_lock( $repo, $file );
+} elsif ( $op eq 'unlock' ) {
+    f_unlock( $repo, $file );
+} elsif ( $op eq 'break' ) {
+    f_break( $repo, $file );
+} elsif ( $op eq 'list' ) {
+    f_list($repo);
+}
+
+# ----------------------------------------------------------------------
+# everything below assumes we have already chdir'd to "$repo.git".  Also, $ff
+# is used as a global.
+
+sub f_lock {
+    my ( $repo, $file ) = @_;
+
+    my %locks = get_locks();
+    _die "'$file' locked by '$locks{$file}{USER}' since " . localtime( $locks{$file}{TIME} ) if $locks{$file}{USER};
+    $locks{$file}{USER} = $ENV{GL_USER};
+    $locks{$file}{TIME} = time;
+    put_locks(%locks);
+}
+
+sub f_unlock {
+    my ( $repo, $file ) = @_;
+
+    my %locks = get_locks();
+    _die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file} || '' ) ne $ENV{GL_USER};
+    delete $locks{$file};
+    put_locks(%locks);
+}
+
+sub f_break {
+    my ( $repo, $file ) = @_;
+
+    my %locks = get_locks();
+    _die "'$file' was not locked" unless $locks{$file};
+    push @{ $locks{BREAKS} }, time . " $ENV{GL_USER} $locks{$file}{USER} $locks{$file}{TIME} $file";
+    delete $locks{$file};
+    put_locks(%locks);
+}
+
+sub f_list {
+    my $repo = shift;
+
+    my %locks = get_locks();
+    print "\n# locks held:\n\n";
+    map { print "$locks{$_}{USER}\t$_\t(" . scalar(localtime($locks{$_}{TIME})) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
+    print "\n# locks broken:\n\n";
+    for my $b ( @{ $locks{BREAKS} } ) {
+        my ( $when, $who, $whose, $how_old, $what ) = split ' ', $b;
+        print "$who\t$what\t(" . scalar( localtime($when) ) . ")\t(locked by $whose at " . scalar( localtime($how_old) ) . ")\n";
+    }
+}
+
+sub get_locks {
+    if ( -f $ff ) {
+        our %locks;
+
+        my $t = slurp($ff);
+        eval $t;
+        _die "do '$ff' failed with '$@', contact your administrator" if $@;
+
+        return %locks;
+    }
+    return ();
+}
+
+sub put_locks {
+    my %locks = @_;
+
+    use Data::Dumper;
+    $Data::Dumper::Indent   = 1;
+    $Data::Dumper::Sortkeys = 1;
+
+    my $dumped_data = Data::Dumper->Dump( [ \%locks ], [qw(*locks)] );
+    _print( $ff, $dumped_data );
+}

commit d623388c9f265b5e9295d4d73b7a5d4a87aa3753
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 27 05:56:55 2012 +0530

    (BSD compat) apparently 'wc -l' on BSD adds spaces in front
    
    Larry was right.  It's probably easier to port a shell than a shell
    script!

diff --git a/src/VREF/COUNT b/src/VREF/COUNT
index d5c3982..6b5cacf 100755
--- a/src/VREF/COUNT
+++ b/src/VREF/COUNT
@@ -32,7 +32,7 @@ nf=
 # count files against all the other commits in the system not just $oldsha
 # (why?  consider what is $oldtree when you create a new branch, or what is
 # $oldsha when you update an old feature branch from master and then push it
-count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | wc -l`
+count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | perl -ne '}{print "$."'`
 
 [[ $count -gt $max ]] && {
     # count has been exceeded.  If $9 was NO_SIGNOFF there's still a chance

commit 8595303c82e99baa085b20c05c00953fae397a1b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 26 21:27:31 2012 +0530

    migrated symbolic-ref command

diff --git a/src/commands/symbolic-ref b/src/commands/symbolic-ref
new file mode 100755
index 0000000..b65c792
--- /dev/null
+++ b/src/commands/symbolic-ref
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Usage:    ssh git at host symbolic-ref <repo> <arguments to git-symbolic-ref>
+#
+# allow 'git symbolic-ref' over a gitolite connection
+
+# Security: remember all arguments to commands must match a very conservative
+# pattern.  Once that is assured, the symbolic-ref command has no security
+# related side-effects, so we don't check arguments at all.
+
+# Note: because of the restriction on allowed characters in arguments, you
+# can't supply an arbitrary string to the '-m' option.  The simplest
+# work-around is-to-just-use-join-up-words-like-this if you feel the need to
+# supply a "reason" string.  In any case this is useless by default; you'd
+# have to have core.logAllRefUpdates set for it to have any meaning.
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+repo=$1; shift
+repo=${repo%.git}
+gitolite access -q "$repo" $GL_USER W any || die You are not authorised
+
+# change head
+cd $GL_REPO_BASE/$repo.git
+
+git symbolic-ref "$@"

commit 6f740908bbae1c7b307bce3664342bad29110cc8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 18 12:48:43 2012 +0530

    (collected docfixes)

diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 02fe445..040198c 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -1,5 +1,9 @@
 # notes for developers
 
+[[TOC]]
+
+----
+
 Gitolite has a huge bunch of existing features that gradually need to moved
 over.  Plus you may want to write your own programs to interact with it.
 
@@ -37,7 +41,7 @@ more info.
   * `gitolite git-config` to check gitolite options or git config variables
     directly from gitolite's "compiled" output, (i.e., without looking at the
     actual `repo.git/config` file or using the `git config` command).  Example
-    use: none yet.
+    use: src/triggers/post-compile/update-gitweb-access-list.
 
   * `gitolite query-rc` to check the value of an RC variable.  Example use:
     src/commands/desc.
diff --git a/doc/emergencies.mkd b/doc/emergencies.mkd
index bc51711..1d0e616 100644
--- a/doc/emergencies.mkd
+++ b/doc/emergencies.mkd
@@ -103,10 +103,12 @@ the answer, but here's a list of files you should blow away.
 
   * `WARNING: split conf not set, gl-conf present for <repo>`
 
-    (Case 1) This can happen if you have a *bare* repo (i.e., some `repo.git`
-    directory) copied from g2 with `GL_BIG_CONFIG` on, and you pushed a change
-    to the conf or ran certain commands *before* adding the newly added repo
-    to the conf file.
+    (Case 1) you copied a bare repo ("repo.git") from another g3 site (or g2
+    with `GL_BIG_CONFIG` on).  Then you pushed a change to "gitolite.conf" or
+    ran certain server-side commands without adding the repo to the conf.
+
+    Conversely, you removed "repo" from "gitolite.conf" but did not remove the
+    actual "repo.git" on disk.
 
     (Case 2) This can also happen if you changed something like this
 
diff --git a/doc/extras/glssh.mkd b/doc/extras/glssh.mkd
index 9bbde0f..f25bc13 100644
--- a/doc/extras/glssh.mkd
+++ b/doc/extras/glssh.mkd
@@ -1,4 +1,8 @@
-## #glssh how gitolite uses ssh
+# #glssh how gitolite uses ssh
+
+[[TOC]]
+
+----
 
 Although other forms of authentications exist (see the document on
 [authentication versus authorisation][auth]), ssh is the one that most git
@@ -15,7 +19,7 @@ gitolite to work, because you'll be attacking the wrong problem.
 So please please please understand this before tearing your hair out and
 blaming ***git/gitolite*** for whatever is going wrong with your setup :-)
 
-### ssh basics
+## ssh basics
 
 Let's start with some basics, focusing *only* on the pieces relevant to
 `gitolite`.  If this is not detailed enough, please use google and learn more
@@ -85,7 +89,7 @@ from somewhere, or maybe buy the OReilly ssh book.
     **This is the backbone of what makes gitolite work; please make sure you
     understand this**.
 
-### how does gitolite use all this ssh magic?
+## how does gitolite use all this ssh magic?
 
 These are two different questions you ought to be having by now: 
 
@@ -93,7 +97,7 @@ These are two different questions you ought to be having by now:
     logging in as the same remote user "git".
   * How does it restrict what I can do within a repository.
 
-#### restricting shell access/distinguishing one user from another
+### restricting shell access/distinguishing one user from another
 
 The answer to the first question is the `command=` we talked about before.  If
 you look in the `authorized_keys` file, you'll see entries like this (I chopped
@@ -124,7 +128,7 @@ at its config file, and either allows or rejects the request.
 But this cannot differentiate between different branches within a repo; that
 has to be done separately.
 
-#### restricting branch level actions
+### restricting branch level actions
 
 [If you look inside the git source tree, there's a file among the "howto"s in
 there called `update-hook-example.txt`, which was the inspiration for this
diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index 843f679..73da293 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -1,15 +1,22 @@
-## #sts ssh troubleshooting
+# #sts ssh troubleshooting and tips
 
 **This document must be read in full the first time.  If you start from some
 nice looking section in the middle it may not help you unless you're already
 an expert at ssh**.
 
 This document should help you troubleshoot ssh-related problems in installing
-and accessing gitolite.
+and accessing gitolite.  It also has a section of random ssh-related tips and
+tricks that gitolite can do.
 
-### IMPORTANT -- READ THIS FIRST
+----
 
-#### caveats
+[[TOC]]
+
+----
+
+## IMPORTANT -- READ THIS FIRST
+
+### caveats
 
   * Before reading this document, it is **mandatory** to read and **completely
     understand** [this][ssh], which is a very detailed look at how gitolite
@@ -28,7 +35,7 @@ and accessing gitolite.
 
     Surprised?  [This][auth] might help explain better.
 
-#### naming conventions used
+### naming conventions used
 
   * Your workstation is the **client**.  Your userid on the client does not
     matter, and it has no relation to your gitolite username.
@@ -37,7 +44,7 @@ and accessing gitolite.
     this is an RPM/DEB install, the hosting user is probably called
     "gitolite", however we will use "git" in this document.
 
-#### taking stock -- relevant files and directories
+### taking stock -- relevant files and directories
 
   * The client has a `~/.ssh` containing a few keypairs.  It may also have a
     `config` file.
@@ -54,7 +61,7 @@ and accessing gitolite.
   * The server also has a `~/.gitolite/keydir` which contains a bunch of
     `*.pub` files.
 
-#### normal gitolite key handling
+### normal gitolite key handling
 
 Here's how normal gitolite key handling works:
 
@@ -80,7 +87,7 @@ Here's how normal gitolite key handling works:
         between gitolite's "marker" lines (`# gitolite start` and `# gitolite
         end`).
 
-### common ssh problems
+## common ssh problems
 
 Since I'm pretty sure at least some of you didn't bother to read the
 "IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
@@ -121,7 +128,7 @@ background info.
     does not appear to be a git repository`, and yet you are sure 'reponame'
     exists, you haven't mis-spelled it, etc.
 
-### step by step
+## step by step
 
 Since I'm pretty sure at least some of you didn't bother to read the
 "IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
@@ -144,9 +151,9 @@ Done?  OK, now the general outline for ssh troubleshooting is this:
     client, instead of the default key.  See [appendix 3][stsapp3] and
     [appendix 4][ssh-ha].
 
-### random tips, tricks, and notes
+## random tips, tricks, and notes
 
-#### giving shell access to gitolite users
+### giving shell access to gitolite users
 
 Thanks to an idea from Jesse Keating, a single key can allow both gitolite
 access *and* shell access.
@@ -169,7 +176,7 @@ To do this:
 Then run `gitolite compile; gitolite trigger POST_COMPILE` or push a dummy
 change to the admin repo.
 
-#### simulating ssh-copy-id
+### simulating ssh-copy-id
 
 don't have `ssh-copy-id`?  This is broadly what that command does, if you want
 to replicate it manually.  The input is your pubkey, typically
@@ -192,7 +199,7 @@ typically) also must be `go-w`, but that needs root.  And typically
 they're already set that way anyway.  (Or if they're not, you've got
 bigger problems than gitolite install not working!)]
 
-#### problems with using non-openssh public keys
+### problems with using non-openssh public keys
 
 Gitolite accepts public keys only in openssh format.  Trying to use an "ssh2"
 key (used by proprietary SSH software) will not be a happy experience.
@@ -206,7 +213,7 @@ be done with it, is:
 
 then use the resulting pubkey as you normally would in gitolite.
 
-#### windows issues
+### windows issues
 
 On windows, I have only used msysgit, and the openssh that comes with it.
 Over time, I have grown to distrust putty/plink due to the number of people
@@ -217,7 +224,7 @@ putty/plink, including environment variables, etc., and then try again.
 
 Thankfully, someone contributed [this][putty].
 
-### #stsapp1 appendix 1: ssh daemon asks for a password
+## #stsapp1 appendix 1: ssh daemon asks for a password
 
 >   **NOTE**: This section should be useful to anyone trying to get
 >   password-less access working.  It is not necessarily specific to gitolite,
@@ -289,7 +296,7 @@ This is a quick checklist:
     this file for messages matching the approximate time of your last attempt
     to login, to see if they tell you what is the problem.
 
-### #sshkeys-lint appendix 2: which key is which -- running sshkeys-lint
+## #sshkeys-lint appendix 2: which key is which -- running sshkeys-lint
 
 The sshkeys-lint program can be run on the server or the client.  Run it with
 '-h' to get a help message.
@@ -324,7 +331,7 @@ need.  Be careful:
   * If you're running ssh-agent, you may have to delete (using `ssh-add -D`)
     and re-add identities for it to pick up the renamed ones correctly.
 
-#### typical cause(s)
+### typical cause(s)
 
 The admin often has passwordless shell access to `git at server` already, and
 then used that same key to get access to gitolite (i.e., copied that same
@@ -340,7 +347,7 @@ as YourName.pub, then run `gitolite setup -pk YourName.pub` on the server.
 Remember to adjust your agent identities using ssh-add -D and ssh-add if
 you're using ssh-agent, otherwise these new keys may not work.
 
-### #stsapp3 appendix 3: ssh client may not be offering the right key
+## #stsapp3 appendix 3: ssh client may not be offering the right key
 
   * Make sure the right private key is being offered.  Run ssh in very
     verbose mode and look for the word "Offering", like so:
@@ -362,7 +369,7 @@ you're using ssh-agent, otherwise these new keys may not work.
     In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try
     the access again.
 
-### #ssh-ha appendix 4: ssh host aliases
+## #ssh-ha appendix 4: ssh host aliases
 
 (or "making git use the right options for ssh")
 
@@ -398,7 +405,7 @@ way to do this, as far as I know.
 
 [tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial
 
-### #ybpfail appendix 5: why bypassing gitolite causes a problem
+## #ybpfail appendix 5: why bypassing gitolite causes a problem
 
 When you bypass gitolite, you end up running your normal shell instead of the
 special gitolite entry point script `gitolite-shell`.
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 916aab2..2783cf8 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -242,6 +242,9 @@ put that contain the words "see docs":
     Setting perms of R and RW will no longer work; you have to say READERS and
     WRITERS now.  Suggested command:
 
+        find `gitolite query-rc GL_REPO_BASE` -name gl-perms |
+            xargs perl -pi -e 's/\bR\b/READERS/;s/\bRW\b/WRITERS/'
+
 ## #rc-preset presetting the rc file
 
 Some rc settings in the older gitolite are such that you cannot directly run

commit 84d123e124fba6970cb4778bf5a8ac1f7afc7c63
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 15:25:48 2012 +0530

    add 2 new sections to "special features"...
    
      - using pubkeys obtained from elsewhere
      - updating hooks via the admin repo

diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
index d244d2b..9da5224 100644
--- a/doc/master-toc.mkd
+++ b/doc/master-toc.mkd
@@ -100,6 +100,8 @@
       * ([link][NAME]: the NAME VREF)
       * the [subconf][] command
   * ([link][partial-copy]: faking selective READ control)
+  * using pubkeys obtained [from elsewhere][keysonly]
+  * [updating hooks][pushhook] via the admin repo
 
 ## interfacing with [external][] tools
 
diff --git a/doc/special.mkd b/doc/special.mkd
index b0bf245..3530920 100644
--- a/doc/special.mkd
+++ b/doc/special.mkd
@@ -46,3 +46,63 @@ Compared to using arbitrary branch names on the same server, this:
 ## delegating access control responsibilities
 
 See [this][deleg].
+
+## #keysonly using pubkeys obtained from elsewhere
+
+If you're not managing keys via the gitolite-admin repo, but getting them from
+somewhere else, you'll want to periodically "update" the keys.
+
+To do that, first edit your rc file and add something like this:
+
+    SSH_AUTHKEYS                =>
+        [
+            'post-compile/ssh-authkeys',
+        ],
+
+Then write a script that
+
+  * gets all the keys and dumps them into `$HOME/.gitolite/keydir` (or into a
+    subdirectory of it).
+
+  * runs `gitolite trigger SSH_AUTHKEYS`.
+
+Run this from cron or however you want.
+
+## #pushhook updating hooks via the admin repo
+
+Gitolite by default maintains the distinction between someone who can push to
+the admin repo and someone who has shell access to the server.  As a result,
+you cannot change any hooks merely by pushing the admin repo.
+
+But some people don't care about this and want to do it anyway.  Besides,
+there *is* one advantage: your hooks can also be versioned now.
+
+So here's how to add/change hooks when you push the gitolite-admin repo:
+
+1.  Put all common hooks in a new directory called 'common-hooks'.
+
+2.  Create a trigger program called 'propagate-hooks' in 'src/triggers' that
+    contains this:
+
+        #!/bin/sh
+
+        cd $GL_ADMIN_BASE
+        cp -a common-hooks/* hooks/common
+
+        gitolite setup --hooks-only
+
+3.  Add this to the `POST_COMPILE` trigger list in the rc file.
+
+And that should be it, pretty much.
+
+If you have lots of repos this is inefficient -- because the hook fixup will
+run on *any* change to the admin repo, even if the change has nothing to do
+with hooks.
+
+If you're concerned about this, put the script in 'src/commands' instead of
+'src/triggers'.  (You might want to add some access control; see other
+commands for inspiration, but it's not really needed in this case).
+
+Then, whenever any hooks have changed, the admin can run
+
+    ssh git at host propagate-hooks

commit 17841e8208248f26701e10a5bac4c4b483285104
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 06:07:10 2012 +0530

    gitolite setup learns --hooks-only option

diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 3fca752..72d3452 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -6,21 +6,30 @@ package Gitolite::Setup;
 =for args
 Usage:  gitolite setup [<option>]
 
-    -pk, --pubkey <file>        pubkey file name
+Setup gitolite, compile conf, run the POST_COMPILE trigger (see rc file) and
+propagate hooks.
+
     -a, --admin <name>          admin name
+    -pk, --pubkey <file>        pubkey file name
+    -ho, --hooks-only           skip other steps and just propagate hooks
 
-Setup gitolite, compile conf, and fixup hooks.  Either the pubkey or the admin
-name is required on the first run, depending on whether you're using ssh mode
-or http mode.
+First run: either the pubkey or the admin name is *required*, depending on
+whether you're using ssh mode or http mode.
 
 Subsequent runs:
 
-  - 'gitolite setup': fix up hooks if you brought in repos from outside, or if
-    someone has been playing around with the hooks and may have deleted some.
+  - Without options, 'gitolite setup' is a general "fix up everything" command
+    (for example, if you brought in repos from outside, or someone messed
+    around with the hooks, or you made an rc file change that affects access
+    rules, etc.)
+
+  - '-pk' can be used to replace the admin key; useful if you lost the admin's
+    private key but do have shell access to the server.
+
+  - '-ho' is mainly for scripting use.  Do not combine with other options.
+
+  - '-a' is ignored
 
-  - 'gitolite setup -pk YourName.pub': replace keydir/YourName.pub and
-    recompile/push.  Useful if you lost your key.  In fact you can do this for
-    any key in keydir (but not in subdirectories).
 =cut
 
 # ----------------------------------------------------------------------
@@ -42,12 +51,15 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub setup {
-    my ( $admin, $pubkey, $argv ) = args();
-    setup_glrc();
-    setup_gladmin( $admin, $pubkey, $argv );
+    my ( $admin, $pubkey, $h_only, $argv ) = args();
 
-    _system("gitolite compile");
-    _system("gitolite trigger POST_COMPILE");
+    unless ($h_only) {
+        setup_glrc();
+        setup_gladmin( $admin, $pubkey, $argv );
+
+        _system("gitolite compile");
+        _system("gitolite trigger POST_COMPILE");
+    }
 
     hook_repos();    # all of them, just to be sure
 }
@@ -57,16 +69,19 @@ sub setup {
 sub args {
     my $admin  = '';
     my $pubkey = '';
+    my $h_only = 0;
     my $help   = 0;
     my $argv   = join( " ", @ARGV );
 
     GetOptions(
-        'admin|a=s'   => \$admin,
-        'pubkey|pk=s' => \$pubkey,
-        'help|h'      => \$help,
+        'admin|a=s'     => \$admin,
+        'pubkey|pk=s'   => \$pubkey,
+        'hooks-only|ho' => \$h_only,
+        'help|h'        => \$help,
     ) or usage();
 
     usage() if $help or ( $pubkey and $admin );
+    usage() if $h_only and ($admin or $pubkey);
 
     if ($pubkey) {
         $pubkey =~ /\.pub$/ or _die "'$pubkey' name does not end in .pub";
@@ -80,7 +95,7 @@ sub args {
         $admin =~ s/\.pub$//;
     }
 
-    return ( $admin || '', $pubkey || '', $argv );
+    return ( $admin || '', $pubkey || '', $h_only || 0, $argv );
 }
 
 sub setup_glrc {

commit e1d9aee98be8bffcf56d36d2d981e305c3e29a3c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 12:38:11 2012 +0530

    delete the 'description' file for new repos

diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index b7faee2..d986fb3 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -5,6 +5,11 @@
 # compatible.
 
 # ----------------------------------------------------------------------
+# delete the 'description' file that 'git init' created if this is run from
+# the post-create trigger
+[ "$1" = "POST_CREATE" ] && rm -f $GL_REPO_BASE/$2.git/description 2>/dev/null
+
+# ----------------------------------------------------------------------
 # skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
 # it's been triggered by a *normal* (not "wild") repo creation, which in turn
 # means a POST_COMPILE should be following so there's no need to waste time

commit 37e97d29fe81c9e641712bc128761c35dcc24eb8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 12:28:14 2012 +0530

    the 3 shipped post-create programs should exit when called on a normal repo creation

diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index bd7ff13..7069f6c 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -17,6 +17,13 @@ my $RB = $rc{GL_REPO_BASE};
 _chdir($RB);
 
 # ----------------------------------------------------------------------
+# skip if arg-0 is POST_CREATE and no arg-2 (user name) exists; this means
+# it's been triggered by a *normal* (not "wild") repo creation, which in turn
+# means a POST_COMPILE should be following so there's no need to waste time
+# running this once for each new repo
+exit 0 if @ARGV and $ARGV[0] eq 'POST_CREATE' and not $ARGV[2];
+
+# ----------------------------------------------------------------------
 # if called from POST_CREATE, we have only a single repo to worry about
 if (@ARGV and $ARGV[0] eq 'POST_CREATE') {
     my $repo = $ARGV[1];
diff --git a/src/triggers/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
index 4585f44..cc865f1 100755
--- a/src/triggers/post-compile/update-git-daemon-access-list
+++ b/src/triggers/post-compile/update-git-daemon-access-list
@@ -2,6 +2,13 @@
 
 # this is probably the *fastest* git-daemon update possible.
 
+# ----------------------------------------------------------------------
+# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
+# it's been triggered by a *normal* (not "wild") repo creation, which in turn
+# means a POST_COMPILE should be following so there's no need to waste time
+# running this once for each new repo
+[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+
 EO=git-daemon-export-ok
 RB=`gitolite query-rc GL_REPO_BASE`
 export EO RB
diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index f97061f..b7faee2 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -4,6 +4,13 @@
 # whatever you want and contribute it back, as long as it is upward
 # compatible.
 
+# ----------------------------------------------------------------------
+# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists; this means
+# it's been triggered by a *normal* (not "wild") repo creation, which in turn
+# means a POST_COMPILE should be following so there's no need to waste time
+# running this once for each new repo
+[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+
 plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 [ -z "$plf" ] && plf=$HOME/projects.list
 

commit 0f3a09ce60e157c552d165f874102139feee678f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 12:26:52 2012 +0530

    PRE_ and POST_CREATE should work for normal repos also

diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 5ae8609..660ccd7 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -61,7 +61,7 @@ Triggers receive the following arguments:
 Here are the **rest of** the arguments for each trigger, plus a brief
 description of when the trigger runs:
 
-  * `ACCESS_1` runs after the first access check.  Arguments:
+  * `ACCESS_1` runs after the first access check.  Extra arguments:
       * repo
       * user
       * 'R' or 'W'
@@ -71,7 +71,7 @@ description of when the trigger runs:
         result contains the refex that caused the access to succeed.
 
   * `ACCESS_2` runs after the second access check, in the update hook.
-    Arguments:
+    Extra arguments:
       * repo
       * user
       * any of W, +, C, D, WM, +M, CM, DM
@@ -79,7 +79,7 @@ description of when the trigger runs:
       * result (see above)
 
   * `PRE_GIT` and `POST_GIT` run just before and after the git command.
-    Arguments:
+    Extra arguments:
       * repo
       * user
       * 'R' or 'W'
@@ -88,12 +88,16 @@ description of when the trigger runs:
         'git-upload-archive') being invoked.
 
   * `PRE_CREATE` and `POST_CREATE` run just before and after a new "[wild][]"
-    repo is created by user action.  Arguments:
+    repo is created by user action.  Extra arguments:
       * repo
       * user
 
+    They are also run when a *normal* repo is created (say by adding a "repo
+    foo" line to the conf file).  This case has only one extra argument:
+      * repo
+
   * `POST_COMPILE` runs after an admin push has successfully "compiled" the
     config file.  By default, the next thing is to update the ssh authkeys
     file, then all the 'git-config's, gitweb access, and daemon access.
 
-    No arguments.
+    No extra arguments.
diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index 8d8c82f..e7ca028 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -34,6 +34,10 @@ sub compile {
     # place to put the individual gl-conf files
     new_repos();
     store();
+
+    for my $repo ( @{ $rc{NEW_REPOS_CREATED} } ) {
+        trigger( 'POST_CREATE', $repo );
+    }
 }
 
 sub parse {
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index 4eb3c67..f4f4cf2 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -164,7 +164,11 @@ sub new_repos {
         # use gl-conf as a sentinel
         hook_1($repo) if -d "$repo.git" and not -f "$repo.git/gl-conf";
 
-        new_repo($repo) if not -d "$repo.git";
+        if (not -d "$repo.git") {
+            push @{ $rc{NEW_REPOS_CREATED} }, $repo;
+            trigger( 'PRE_CREATE', $repo );
+            new_repo($repo);
+        }
     }
 }
 

commit 04367af3e86b518170bb96513c15ba590097e8fe
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 25 11:17:25 2012 +0530

    Revert "simulate POST_CREATE for newly created "normal" repos"
    
    This reverts commit bc3eb3421171a432aa62d784709d0b0e319de183.

diff --git a/src/triggers/new-normal-repos b/src/triggers/new-normal-repos
deleted file mode 100755
index 7e66509..0000000
--- a/src/triggers/new-normal-repos
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/bin/sh
-
-# run some command on every new repo created via the conf file
-# ----------------------------------------------------------------------
-
-# BACKGROUND
-#   The POST_CREATE trigger (see doc/triggers.mkd) only works for repos that a
-#   user creates (see doc/wild.mkd).  It does NOT work for repos created
-#   normally using the conf file.  The POST_COMPILE trigger sequence should
-#   normally be used to trigger anything to happen after a compile.
-#
-#   I have not seen a sane use case for a POST_CREATE trigger when a new repo
-#   is created via the conf file.  (I do not consider "all new repos should
-#   have a default set of branches" to be a sane requirement).  However, on
-#   the off-chance that something turns up at some future time, here's how you
-#   can do this without touching the core gitolite code.
-
-# INSTRUCTIONS FOR USE
-# - (optional) rename this to whatever you want
-# - change the 'post_create_normal' function below to do whatever you want it
-#   to do, or make it call some other command to do it
-# - make the script executable
-# - add 'new-normal-repos' (or whatever you renamed it to) to the PRE_GIT and
-#   the POST_COMPILE trigger lists in the rc file
-
-post_create_normal() {
-    echo "post_create_normal called with repo '$1'" >&2
-}
-
-# tempfile prefix; if you really care, change this also, otherwise leave it alone
-tfp=$HOME/.tmp.$GL_USER.list-repos
-
-# ----------------------------------------------------------------------
-
-if [ "$1" = "PRE_GIT" ]
-then
-    # unless someone is pushing to the admin repo, we don't care
-    [ "$2" = "gitolite-admin" ] || exit 0
-    [ "$4" = "W" ] || exit 0
-
-    # save the list of repos
-    gitolite list-repos > $tfp.1
-elif [ "$1" = "POST_COMPILE" ]
-then
-    # get the new list of repos and compare with the one PRE_GIT created
-    gitolite list-repos > $tfp.2
-    for repo in `grep -x -f $tfp.1 -v $tfp.2`
-    do
-
-        post_create_normal "$repo"
-    done
-else
-    # if you edited your rc file correctly, this line should never be reached
-    echo "ignoring call to" `basename $0` "with arg1 = '$1'" >&2
-fi

commit 62a66662bebb02d8c491379a2b55d02bc089605a
Author: Mike Kelly <pioto at pioto.org>
Date:   Tue May 22 18:03:07 2012 -0400

    Properly migrate [gitosis] section

diff --git a/convert-gitosis-conf b/convert-gitosis-conf
index 999f8f7..9b92f68 100755
--- a/convert-gitosis-conf
+++ b/convert-gitosis-conf
@@ -47,6 +47,9 @@ while (<>)
         $groupname = '';
         $reponame = $1;
         $reponame =~ s/\.git$//;
+    } elsif (/^\[gitosis\]$/) {
+        $groupname = '';
+        $reponame = '@all';
     } elsif (/^gitweb\s*=\s*yes/i) {
         push @{$repos{$reponame}{R}}, 'gitweb';
     } elsif (/^daemon\s*=\s*yes/i) {

commit 75387fd6cb16449c4587f804445b3196c28731b5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 23 03:48:40 2012 +0530

    v3.03

diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index 8271e04..2767b31 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,3 +1,5 @@
+2012-05-23  v3.03   fix major bug that allowed an admin to get a shell
+
 2012-05-20  v3.02   packaging instructions fixed up and smoke tested
 
                     make it easier to give some users a full shell

commit 5298a79cb503c9034de5e2dbc88ed0c9b72a566f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 23 03:17:58 2012 +0530

    MAJOR BUGFIX: disallow "hooks" directory in admin repo
    
    Although this is not a "hole" that allows a normal user to bypass
    controls, I still consider this a hole in the sense that I want to
    separate "admin push" rights from "shell access on server" rights.
    
    (I realise that most people don't make this distinction, but I do, and
    for me and most sites I consult for it is important).
    
    Thanks to drue on #gitolite who pointed it out excitedly, and apologies
    for killing what he thought of as a feature!

diff --git a/src/lib/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
index 9013ee9..3605de0 100644
--- a/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -23,7 +23,7 @@ sub post_update {
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
-    _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/;
+    _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/m;
 
     {
         local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};

commit dd083085cf8714b96746bb881cfb3a400f4f5ed8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 22 22:32:11 2012 +0530

    (fix bugly) info -ld should handle missing description files more gracefully
    
    bugly = bug that makes the output ugly :)

diff --git a/src/commands/info b/src/commands/info
index daf3ad2..d5ff810 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -80,13 +80,17 @@ sub print_phy_repos {
 sub listem {
     my ( $repos, $lc, $ld, @aa ) = @_;
     my $creator = '';
-    my $desc = '';
     for my $repo (@$repos) {
         next unless $repo =~ /$patt/;
         my $perm = '';
         $creator = creator($repo) if $lc;
-        $desc = slurp("$ENV{GL_REPO_BASE}/$repo.git/description") if $ld;
-        chomp($desc);
+
+        my $desc = '';
+        for my $d ("$ENV{GL_REPO_BASE}/$repo.git/description") {
+            next unless $ld and -r $d;
+            $desc = slurp($d);
+            chomp($desc);
+        }
 
         for my $aa (@aa) {
             my $ret = access( $repo, $ENV{GL_USER}, $aa, 'any' );

commit 290756152921d1dba754feebfb06485a4dc4c9c9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 22 13:15:54 2012 +0530

    (minor) solaris doesn't like "shift" when there are no arguments remaining

diff --git a/src/commands/D b/src/commands/D
index 11bff3c..2d96964 100755
--- a/src/commands/D
+++ b/src/commands/D
@@ -45,11 +45,12 @@ die() { echo "$@" >&2; exit 1; }
 usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
 [ -z "$1" ] && usage
 [ "$1" = "-h" ] && usage
+[ "$1" != "list-trash" ] && [ -z "$2" ] && usage
 [ -z "$GL_USER" ] && die GL_USER not set
 
 # ----------------------------------------------------------------------
-cmd=$1; shift
-repo=$1; shift  # may not be present for 'list-trash' command
+cmd=$1
+repo=$2
 # ----------------------------------------------------------------------
 RB=`gitolite query-rc GL_REPO_BASE`;            cd $RB
 TRASH_CAN=`gitolite query-rc TRASH_CAN`;        tcan=Trash;                     TRASH_CAN=${TRASH_CAN:-$tcan}

commit 55d64752ae90439ab619292d1f2a8083ad828936
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 22 11:45:05 2012 +0530

    fix repo alias to work when reponame has leading "/"
    
    as in git at host:repo.git works but ssh://git@host/repo.git doesn't

diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm
index a00b385..acff44b 100755
--- a/src/lib/Gitolite/Triggers/Alias.pm
+++ b/src/lib/Gitolite/Triggers/Alias.pm
@@ -61,7 +61,7 @@ sub input {
     my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
     my $user = $ARGV[0] || '@all';    # user name is undocumented for now
 
-    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '(\S+)'$/ ) {
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '\/?(\S+)'$/ ) {
         my $repo = $1;
         ( my $norm = $repo ) =~ s/\.git$//;    # normalised repo name
 
@@ -73,7 +73,7 @@ sub input {
 
         _warn "'$norm' is an alias for '$target'";
 
-        $ENV{SSH_ORIGINAL_COMMAND} =~ s/'$repo'/'$target'/;
+        $ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/;
     }
 
 }

commit b6ce11a19f00a06e363229c1c7c3955edc9df954
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 21 22:00:38 2012 +0530

    (minor) permissions fixup -- sugar scripts do not need +x

diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index 1e9cd9f..06422b7 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -4,7 +4,7 @@ package SugarBox;
 
 sub run_sugar_script {
     my ( $ss, $lref ) = @_;
-    do $ss if -x $ss;
+    do $ss if -r $ss;
     $lref = sugar_script($lref);
     return $lref;
 }
@@ -54,7 +54,7 @@ sub sugar {
                 # aliasing, which would happen if you touched $s itself
                 my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s";
 
-                _warn("skipped sugar script '$s'"), next if not -x $sfp;
+                _warn("skipped sugar script '$s'"), next if not -r $sfp;
                 $lines = SugarBox::run_sugar_script( $sfp, $lines );
                 $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
             }
diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines
old mode 100755
new mode 100644
diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
old mode 100755
new mode 100644

commit b12a9672724d6d3893c50419e353309f645b8d36
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 21 20:41:11 2012 +0530

    update g2 compat and migr info
    
    thanks to karihre on #gitolite for catching the first of the corrections
    (GL_GET_MEMBERSHIPS_PGM) and so reminding me...

diff --git a/check-g2-compat b/check-g2-compat
index 58c6b2f..11fe7a7 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -39,7 +39,7 @@ sub intro {
     msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
     msg( '', '' );
     msg( INFO => "'see docs' usually means doc/g2migr.mkd" );
-    msg( '',  => "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)" );
+    msg( '',  => "(online at http://sitaramc.github.com/gitolite/g2migr.html)" );
     msg( '', '' );
 }
 
@@ -54,14 +54,13 @@ sub rc_basic {
 sub rest_of_rc {
     msg( SEVERE  => "GIT_PATH found; see docs" )                          if $GIT_PATH;
     msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )           if $GL_ALL_INCLUDES_SPECIAL;
-    msg( SEVERE  => "GL_GET_MEMBERSHIPS_PGM not yet implemented" )        if $GL_GET_MEMBERSHIPS_PGM;
     msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )            if $GL_NO_CREATE_REPOS;
-    msg( SEVERE  => 'htpasswd, rsync, and svnserve not yet implemented' ) if $HTPASSWD_FILE or $RSYNC_BASE or $SVNSERVE;
+    msg( SEVERE  => "rsync not yet implemented" )                         if $RSYNC_BASE;
     msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )       if $ADMIN_POST_UPDATE_CHAINS_TO;
     msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )            if $GL_NO_DAEMON_NO_GITWEB;
     msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )              if $GL_NO_SETUP_AUTHKEYS;
     msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                  if $UPDATE_CHAINS_TO;
-    msg( WARNING => "GL_ADC_PATH found; many ADCs not yet implemented" )  if $GL_ADC_PATH;
+    msg( WARNING => "GL_ADC_PATH found; see docs" )                       if $GL_ADC_PATH;
     msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
 }
 
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
index 03ef93f..6d40949 100644
--- a/doc/dev-status.mkd
+++ b/doc/dev-status.mkd
@@ -10,7 +10,7 @@ Help needed:
 
   * I'd like distro packagers to play with it and help with migration advice
     for distro-upgrades
-  * rsync, htpasswd
+  * rsync
   * git-annexe support (but this has a pre-requisite in the previous list)
 
 Won't be done unless someone asks (saw no evidence that anyone used them in g2
@@ -33,3 +33,4 @@ Done:
   * migration advice for common cases
   * smart http
   * svnserve
+  * htpasswd
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
index 02a3d85..ca54ebc 100644
--- a/doc/g2incompat.mkd
+++ b/doc/g2incompat.mkd
@@ -92,7 +92,7 @@ There are several changes with regard to mirroring:
         option mirror.nightly       =   merry pippin
 
     but they will not be processed by gitolite.  Your cron jobs should use
-    `gitolite git-config` to query this variable, grab the list of repos, and
+    `gitolite git-config` to query this variable, grab the list of peers, and
     run `gitolite mirror` on each of them.
 
   * The external command to resync mirrors is 'mirror', run just like any
diff --git a/doc/g2migr-example.mkd b/doc/g2migr-example.mkd
index 18c2ba8..28ec8b2 100644
--- a/doc/g2migr-example.mkd
+++ b/doc/g2migr-example.mkd
@@ -93,7 +93,7 @@ This is a quick and dirty program to catch some of the big issues.
                 It does NOT attempt to catch all the differences described in the docs.
 
     INFO        'see docs' usually means doc/g2migr.mkd
-                (online at http://sitaramc.github.com/gitolite/g3/g2migr.html)
+                (online at http://sitaramc.github.com/gitolite/g2migr.html)
 
     checking rc file...
     NOTE        GL_ADMINDIR is in the right place; assuming you did not mess with
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index e7f2864..916aab2 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -161,8 +161,7 @@ Some of them have links where there is more detail than I want to put here.
 
   * `GL_WILDREPOS_PERM_CATS` -- is now the ROLES hash in the rc file.
 
-  * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
-    are using any of these.
+  * `RSYNC_BASE` -- needs work.  Email me if you are using this.
 
   * `NICE_VALUE` -- **dropped**.  Uncomment the 'renice 10' line in the rc
     file.  You can also change the 10 to something else if you wish.

commit d04e79d291c832c14cbcc94e5e813d9b3ed8c1af
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 21 15:22:19 2012 +0530

    (minor) single quotes around variables in error messages
    
    (plus a couple of other minor fixups)

diff --git a/src/gitolite b/src/gitolite
index 911eee9..aeca6af 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -103,7 +103,7 @@ sub args {
 sub run_command {
     my $pgm      = shift;
     my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
-    _die "$pgm not found or not executable" if not -x $fullpath;
+    _die "'$pgm' not found or not executable" if not -x $fullpath;
     _system( $fullpath, @_ );
     exit 0;
 }
diff --git a/src/gitolite-shell b/src/gitolite-shell
index c4463ad..a40611b 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -161,7 +161,7 @@ sub parse_soc {
         exit 0;
     }
 
-    _die "unknown git/gitolite command: $soc";
+    _die "unknown git/gitolite command: '$soc'";
 }
 
 sub sanity {
diff --git a/src/lib/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
index a5bda56..8d8c82f 100644
--- a/src/lib/Gitolite/Conf.pm
+++ b/src/lib/Gitolite/Conf.pm
@@ -62,7 +62,7 @@ sub parse {
             my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
             push @validkeys, "gitolite-options\\..*";
             my @matched = grep { $key =~ /^$_$/ } @validkeys;
-            _die "git config $key not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
+            _die "git config '$key' not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
             _die "bad value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
diff --git a/src/lib/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
index f72878b..03f2cb2 100644
--- a/src/lib/Gitolite/Conf/Explode.pm
+++ b/src/lib/Gitolite/Conf/Explode.pm
@@ -52,7 +52,7 @@ sub incsub {
     my $is_subconf = ( +shift eq 'subconf' );
     my ( $new_subconf, $include_glob, $current_subconf, $out ) = @_;
 
-    _die "subconf $current_subconf attempting to run 'subconf'\n" if $is_subconf and $current_subconf ne 'master';
+    _die "subconf '$current_subconf' attempting to run 'subconf'\n" if $is_subconf and $current_subconf ne 'master';
 
     _die "invalid include/subconf file/glob '$include_glob'"
       unless $include_glob =~ /^"(.+)"$/
@@ -63,7 +63,7 @@ sub incsub {
 
     for my $file ( glob($include_glob) ) {
         _warn("included file not found: '$file'"), next unless -f $file;
-        _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$);
+        _die "invalid include/subconf filename '$file'" unless $file =~ m(([^/]+).conf$);
         my $basename = $1;
 
         next if already_included($file);
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 7599727..3537c4f 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -73,7 +73,7 @@ sub access {
     load($repo);
 
     # sanity check the only piece the user can control
-    _die "invalid characters in ref or filename: $ref\n" unless $ref =~ $REF_OR_FILENAME_PATT;
+    _die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ $REF_OR_FILENAME_PATT;
 
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
     # check to give valid results.
@@ -193,11 +193,11 @@ sub load_common {
 
     my $cc = "conf/gitolite.conf-compiled.pm";
 
-    _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+    _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
 
     if ( data_version_mismatch() ) {
         _system("gitolite setup");
-        _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+        _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
         _die "data version update failed; this is serious" if data_version_mismatch();
     }
 }
@@ -223,7 +223,7 @@ sub load_1 {
         _warn "split conf not set, gl-conf present for '$repo'" if not $split_conf{$repo};
 
         my $cc = "gl-conf";
-        _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+        _die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
 
         $last_repo = $repo;
         $repos{$repo} = $one_repo{$repo};
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index ef5b0fd..4eb3c67 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -137,7 +137,7 @@ sub expand_list {
     for my $item (@list) {
         if ( $item =~ /^@/ and $item ne '@all' )    # nested group
         {
-            _die "undefined group $item" unless $groups{$item};
+            _die "undefined group '$item'" unless $groups{$item};
             # add those names to the list
             push @new_list, sort keys %{ $groups{$item} };
         } else {
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index ed4a03f..6d3844d 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -62,9 +62,9 @@ sub check_vrefs {
         } else {
             my ( $dummy, $pgm, @args ) = split '/', $vref;
             $pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
-            -x $pgm or _die "$vref: helper program missing or unexecutable";
+            -x $pgm or _die "'$vref': helper program missing or unexecutable";
 
-            open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "$vref: can't spawn helper program: $!";
+            open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "'$vref': can't spawn helper program: $!";
             while (<$fh>) {
                 # print non-vref lines and skip processing (for example,
                 # normal STDOUT by a normal update hook)
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 55cda82..3bcac33 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -63,7 +63,7 @@ my $rc = glrc('filename');
 do $rc if -r $rc;
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
-    say2 "FATAL: $rc seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)";
+    say2 "FATAL: '$rc' seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g2migr.html)";
 
     exit 1;
 }
@@ -132,7 +132,7 @@ sub glrc {
     } elsif ( $cmd eq 'current-data-version' ) {
         return $current_data_version;
     } else {
-        _die "unknown argument to glrc: $cmd";
+        _die "unknown argument to glrc: '$cmd'";
     }
 }
 
@@ -178,7 +178,7 @@ sub trigger {
 
     if ( exists $rc{$rc_section} ) {
         if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
-            _die "$rc_section section in rc file is not a perl list";
+            _die "'$rc_section' section in rc file is not a perl list";
         } else {
             for my $s ( @{ $rc{$rc_section} } ) {
                 my ( $pgm, @args ) = split ' ', $s;
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 1077c0e..3fca752 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -69,11 +69,11 @@ sub args {
     usage() if $help or ( $pubkey and $admin );
 
     if ($pubkey) {
-        $pubkey =~ /\.pub$/ or _die "$pubkey name does not end in .pub";
-        $pubkey =~ /\@/ and _die "$pubkey name contains '\@'";
-        tsh_try("cat $pubkey")              or _die "$pubkey not a readable file";
-        tsh_lines() == 1                    or _die "$pubkey must have exactly one line";
-        tsh_try("ssh-keygen -l -f $pubkey") or _die "$pubkey does not seem to be a valid ssh pubkey file";
+        $pubkey =~ /\.pub$/ or _die "'$pubkey' name does not end in .pub";
+        $pubkey =~ /\@/ and _die "'$pubkey' name contains '\@'";
+        tsh_try("cat $pubkey")              or _die "'$pubkey' not a readable file";
+        tsh_lines() == 1                    or _die "'$pubkey' must have exactly one line";
+        tsh_try("ssh-keygen -l -f $pubkey") or _die "'$pubkey' does not seem to be a valid ssh pubkey file";
 
         $admin = $pubkey;
         $admin =~ s(.*/)();
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 450144c..7afd3af 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -53,16 +53,16 @@ if (@gl_keys) {
     my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
 
     my $ak = slurp($akfile);
-    _die "$akfile changed between start and end of this program!" if $ak ne $old_ak;
+    _die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
     _print( $akfile, $out );
 }
 
 # ----------------------------------------------------------------------
 
 sub sanity {
-    _die "$glshell not found; this should NOT happen..."                if not -f $glshell;
-    _die "$glshell found but not readable; this should NOT happen..."   if not -r $glshell;
-    _die "$glshell found but not executable; this should NOT happen..." if not -x $glshell;
+    _die "'$glshell' not found; this should NOT happen..."                if not -f $glshell;
+    _die "'$glshell' found but not readable; this should NOT happen..."   if not -r $glshell;
+    _die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;
 
     _warn "$akdir missing; creating a new one"  if not -d $akdir;
     _warn "$akfile missing; creating a new one" if not -f $akfile;
@@ -103,7 +103,7 @@ sub fp_file {
     my $f  = shift;
     my $fp = `ssh-keygen -l -f '$f'`;
     chomp($fp);
-    _die "fingerprinting failed for $f" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
+    _die "fingerprinting failed for '$f'" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
     $fp = $1;
     return $fp;
 }
diff --git a/t/fork.t b/t/fork.t
index 38cb200..d595847 100755
--- a/t/fork.t
+++ b/t/fork.t
@@ -29,7 +29,7 @@ try "
     glt ls-remote u1 file:///foo/u1/u1a;ok;     gsh
                                                 /Initialized empty Git repository in .*/foo/u1/u1a.git/
     # vrc doesn't have the fork command
-    glt fork u1 foo/u1/u1a foo/u1/u1a2; !ok;    /FATAL: unknown git/gitolite command: fork/
+    glt fork u1 foo/u1/u1a foo/u1/u1a2; !ok;    /FATAL: unknown git/gitolite command: \\'fork/
 ";
 
 # allow fork as a valid command
diff --git a/t/git-config.t b/t/git-config.t
index 18f2e78..86a3a7b 100755
--- a/t/git-config.t
+++ b/t/git-config.t
@@ -26,7 +26,7 @@ confreset;confadd '
 
 try "ADMIN_PUSH set1; /FATAL/" or die text();
 try "
-    /git config foo.bar not allowed/
+    /git config \\'foo.bar\\' not allowed/
     /check GIT_CONFIG_KEYS in the rc file/
 ";
 
diff --git a/t/include-subconf.t b/t/include-subconf.t
index 6bdff81..813cb93 100755
--- a/t/include-subconf.t
+++ b/t/include-subconf.t
@@ -78,7 +78,7 @@ confadd 'g2.conf', '
 ';
 
 try "
-    ADMIN_PUSH set3;           ok;     /FATAL: subconf g2 attempting to run 'subconf'/
+    ADMIN_PUSH set3;           ok;     /FATAL: subconf \\'g2\\' attempting to run 'subconf'/
 ";
 
 # ----------------------------------------------------------------------
diff --git a/t/invalid-refnames-filenames.t b/t/invalid-refnames-filenames.t
index d3a3065..bf4fabe 100755
--- a/t/invalid-refnames-filenames.t
+++ b/t/invalid-refnames-filenames.t
@@ -50,7 +50,7 @@ glt push u1 origin master:dd,ee
 
 # push to branch dd=ee fail
 glt push u1 origin master:dd=ee
-        /invalid characters in ref or filename: refs/heads/dd=ee/
+        /invalid characters in ref or filename: \\'refs/heads/dd=ee/
         reject
 ";
 
@@ -84,7 +84,7 @@ glt push u1 origin HEAD
 tc  aa=bb
 glt push u1 origin HEAD
         /To file:///aa/
-        /invalid characters in ref or filename: VREF/NAME/aa=bb/
+        /invalid characters in ref or filename: \\'VREF/NAME/aa=bb/
         reject
 
 # push to branch dd,ee ok
@@ -96,6 +96,6 @@ glt push u1 origin master:dd,ee
 
 # push to branch dd=ee fail
 glt push u1 origin master:dd=ee
-        /invalid characters in ref or filename: refs/heads/dd=ee/
+        /invalid characters in ref or filename: \\'refs/heads/dd=ee/
         reject
 ";
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index 9a897ca..a9b1457 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -60,11 +60,11 @@ try "
 
     # a bad key
     ls -al > bad.pub
-    $pgm;                       !ok;    /fingerprinting failed for keydir/bad.pub/
+    $pgm;                       !ok;    /fingerprinting failed for \\'keydir/bad.pub\\'/
     wc < $ak;                   ok;     /^ *9 .*/;
     # a good key doesn't get added
     ssh-keygen -N '' -q -f good
-    $pgm;                       !ok;    /fingerprinting failed for keydir/bad.pub/
+    $pgm;                       !ok;    /fingerprinting failed for \\'keydir/bad.pub\\'/
     wc < $ak;                   ok;     /^ *9 .*/;
     # till the bad key is removed
     rm bad.pub

commit 20d2120ea5578a9f72bf85c42f2969eb32940a64
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 21 15:20:04 2012 +0530

    move input command check so it works for non-ssh modes also

diff --git a/src/gitolite-shell b/src/gitolite-shell
index af03a36..c4463ad 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -31,6 +31,12 @@ if ( exists $ENV{G3T_USER} ) {
     _die "who the *heck* are you?";
 }
 
+# sanity...
+my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+$soc =~ s/[\n\r]+/<<newline>>/g;
+_die "I don't like newlines in the command: '$soc'\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
+
+# the INPUT trigger massages @ARGV and $ENV{SSH_ORIGINAL_COMMAND} as needed
 trigger('INPUT');
 
 main($id);
@@ -75,9 +81,6 @@ sub in_ssh {
     gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
 
     $ENV{SSH_ORIGINAL_COMMAND} ||= '';
-    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-    $soc =~ s/[\n\r]+/<<newline>>/g;
-    _die "I don't like newlines in the command: $soc\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
 
     return $ip;
 }

commit 3a59f5aff0f68b4836bde537d169dc3552e381b4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 21 13:23:20 2012 +0530

    line up regexes for easier review

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 924fe3f..55cda82 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -43,12 +43,15 @@ $rc{LOG_TEMPLATE}  = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
 # variables that should probably never be changed but someone will want to, I'll bet...
 # ----------------------------------------------------------------------
 
-$REMOTE_COMMAND_PATT  = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$);
-$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
-$REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
-$REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
-$USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
+#<<<
+$REMOTE_COMMAND_PATT  =                qr(^[-0-9a-zA-Z._\@/+ :,\%=]*$);
+$REF_OR_FILENAME_PATT =     qr(^[0-9a-zA-Z][-0-9a-zA-Z._\@/+ :,]*$);
+$REPONAME_PATT        =  qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@/+]*$);
+$REPOPATT_PATT        = qr(^\@?[[0-9a-zA-Z][-0-9a-zA-Z._\@/+\\^$|()[\]*?{},]*$);
+$USERNAME_PATT        =  qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@+]*$);
+
 $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
+#>>>
 
 # ----------------------------------------------------------------------
 

commit 8aba6ec2be3d2263e2bbd8d3620e62c8f8022048
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 20 16:41:21 2012 +0530

    v3.02

diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index 10c045c..8271e04 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,3 +1,19 @@
+2012-05-20  v3.02   packaging instructions fixed up and smoke tested
+
+                    make it easier to give some users a full shell
+
+                    allow aliasing a repo to another name
+
+                    simulate POST_CREATE for new normal (non-wild) repos
+
+                    (just for kicks) a VREF that allows for voting on changes
+                    to a branch
+
+                    bug fix: smart http was not running PRE_ and POST_GIT
+                    triggers
+
+                    htpasswd migrated
+
 2012-04-29  v3.01   mostly BSD and Solaris compat
                     also fork command added
 

commit 72b6a54e0a67e4a2cde6da59ff051a9bfaa7c791
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 20 10:07:29 2012 +0530

    test packaging instructions and make required changes

diff --git a/doc/install.mkd b/doc/install.mkd
index 25e7cbe..519ab52 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -152,15 +152,16 @@ Gitolite has broad similarities to git in terms of packaging requirements.
 
 With that said, here's one way to package gitolite:
 
-  * Put the two executables (gitolite and gitolite-shell) somewhere in PATH.
-    Then change the 2 assignments to `$ENV{GL_BINDIR}`, one in 'gitolite', one
-    in 'gitolite-shell', to "/usr/libexec/gitolite".
+  * Put the executable `gitolite` somewhere in PATH.  Put the executable
+    `gitolite-shell` in /usr/libexec/gitolite (along with those 5 directories).
 
+    Change the 2 assignments to `$ENV{GL_BINDIR}`, one in 'gitolite', one in
+    'gitolite-shell', to "/usr/libexec/gitolite" from `$FindBin::RealBin`.
     This is equivalent to "make" embedding the exec-path into the executable.
 
     **OR**
 
-    Put the two executables `gitolite` and `gitolite-shell` also into
+    Put both executables `gitolite` and `gitolite-shell` also into
     /usr/libexec/gitolite (i.e., as siblings to the 5 directories mentioned
     above).  Then *symlink* `/usr/libexec/gitolite/gitolite` to some directory
     in the PATH.  Do not *copy* it; it must be a symlink.
diff --git a/src/lib/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
index 70e23f8..9013ee9 100644
--- a/src/lib/Gitolite/Hooks/PostUpdate.pm
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -29,8 +29,8 @@ sub post_update {
         local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
         tsh_try("git checkout -f --quiet master");
     }
-    _system("$ENV{GL_BINDIR}/gitolite compile");
-    _system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
+    _system("gitolite compile");
+    _system("gitolite trigger POST_COMPILE");
 
     exit 0;
 }
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 0a677a8..1077c0e 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -46,8 +46,8 @@ sub setup {
     setup_glrc();
     setup_gladmin( $admin, $pubkey, $argv );
 
-    _system("$ENV{GL_BINDIR}/gitolite compile");
-    _system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
+    _system("gitolite compile");
+    _system("gitolite trigger POST_COMPILE");
 
     hook_repos();    # all of them, just to be sure
 }

commit 27c0190b7616779798d46e34841a3e6b2bd6066c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 20 06:20:21 2012 +0530

    packaging instructions make analogy with git for better explanation

diff --git a/doc/install.mkd b/doc/install.mkd
index fb0348c..25e7cbe 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -131,26 +131,44 @@ Creating a symlink doesn't need a separate program but 'install' also runs
     arguments as before).
   * Run `gitolite setup`.
 
-## packaging gitolite
+## #package packaging gitolite
 
-Here are the requirements for gitolite:
+Gitolite has broad similarities to git in terms of packaging requirements.
 
-  * The programs `gitolite` and `gitolite-shell`, and the directories
-    `commands`, `lib`, `syntactic-sugar`, `triggers`, and `VREF` must all be
-    in the same directory.
+  * Git has 150 executables to marshal and put somewhere.  Gitolite has the
+    directories `commands`, `lib`, `syntactic-sugar`, `triggers`, and `VREF`.
 
     It doesn't matter what this directory is.  As an example, Fedora keeps
     git's 150 executables in /usr/libexec/git-core, so /usr/libexec/gitolite
     may be a good choice; it's upto you.
 
-    The rest of this section will assume you chose /usr/libexec/gitolite as
-    the location.  Adjust as needed.
+    *The rest of this section will assume you chose /usr/libexec/gitolite as
+    the location, and that this location contains the 5 directories named
+    above*.
 
-  * The program `/usr/libexec/gitolite/gitolite` must then be *symlinked* to
-    some directory in the PATH.  Do not *copy* it; it must be a symlink.
+  * Git has the `GIT_EXEC_PATH` env var to point to this directory.  Gitolite
+    has `GL_BINDIR`.  However, in git, the "make" process embeds a suitable
+    default into the binary, making the env var optional.
 
-  * The `Gitolite` subdirectory in `/usr/libexec/gitolite/lib` can stay there,
-    **OR**, if your distro policies don't allow that, can be put in any
+With that said, here's one way to package gitolite:
+
+  * Put the two executables (gitolite and gitolite-shell) somewhere in PATH.
+    Then change the 2 assignments to `$ENV{GL_BINDIR}`, one in 'gitolite', one
+    in 'gitolite-shell', to "/usr/libexec/gitolite".
+
+    This is equivalent to "make" embedding the exec-path into the executable.
+
+    **OR**
+
+    Put the two executables `gitolite` and `gitolite-shell` also into
+    /usr/libexec/gitolite (i.e., as siblings to the 5 directories mentioned
+    above).  Then *symlink* `/usr/libexec/gitolite/gitolite` to some directory
+    in the PATH.  Do not *copy* it; it must be a symlink.
+
+    Gitolite will find the exec-path by following the symlink.
+
+  * The `Gitolite` subdirectory in `/usr/libexec/gitolite/lib` can stay right
+    there, **OR**, if your distro policies don't allow that, can be put in any
     directory in perl's `@INC` path (such as `/usr/share/perl5/vendor_perl`).
 
   * Finally, a file called `/usr/libexec/gitolite/VERSION` must contain a

commit 864469050696fbff1581607a9203288f09247c31
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 18 12:48:51 2012 +0530

    (ssh) make it easier to make give some users a full shell

diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index 9252d58..843f679 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -151,20 +151,23 @@ Done?  OK, now the general outline for ssh troubleshooting is this:
 Thanks to an idea from Jesse Keating, a single key can allow both gitolite
 access *and* shell access.
 
-This is done by manually prefixing the username with "-s" as an extra argument
-in the "command=" part of `~/.ssh/authorized_keys`.  For example
+To do this:
 
-    command="/home/g3/gitolite/src/gitolite-shell u1",no-port-[...etc...]
+  * add the list of users who will have shell access -- one username per line,
+    no extra whitespace -- to a plain text file of your choice.
 
-should be edited to be
+  * put the name of this file in a new rc variable `SHELL_USERS_LIST`.  For
+    example it could be
 
-    command="/home/g3/gitolite/src/gitolite-shell -s u1",no-port-[...etc...]
+        SHELL_USERS_LIST        =>  "$ENV{HOME}/.gitolite.shell-users",
 
-and moved out of the gitolite area of the authkeys file.
+  * add the line `'Shell::input',` to the `INPUT` list in the rc file.
 
-It should be easy to make src/triggers/post-compile/ssh-authkeys read a list
-of shell capable users from some file on the server and put in the "-s" for
-those users.  Patches welcome.
+  * add the line `'post-compile/ssh-authkeys-shell-users',` to the
+    `POST_COMPILE` list, *after* the `'post-compile/ssh-authkeys',` line.
+
+Then run `gitolite compile; gitolite trigger POST_COMPILE` or push a dummy
+change to the admin repo.
 
 #### simulating ssh-copy-id
 
diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
new file mode 100755
index 0000000..91ed857
--- /dev/null
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use File::Temp qw(tempfile);
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+$|++;
+
+my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
+my $sufile = $rc{SHELL_USERS_LIST} or exit 0;
+-r $sufile or _die "'$sufile' not readable";
+
+# ----------------------------------------------------------------------
+
+my $aktext = slurp($akfile);
+
+for my $su ( shell_users() ) {
+    $aktext =~ s(/gitolite-shell $su",(.*?),no-pty )(/gitolite-shell -s $su",$1 );
+}
+
+_print( $akfile, $aktext );
+
+sub shell_users {
+    my @ret = grep { not /^#/ } slurp($sufile);
+    chomp(@ret);
+    return @ret;
+}

commit 07169c37ec9abfa7ebd31d745564586bf64fc019
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed May 9 05:56:40 2012 +0530

    allow aliasing a repo to another name
    
    all documentation is inside Alias.pm.

diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm
new file mode 100755
index 0000000..a00b385
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Alias.pm
@@ -0,0 +1,81 @@
+package Gitolite::Triggers::Alias;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# aliasing a repo to another
+# ----------------------------------------------------------------------
+
+=for usage
+
+Why:
+
+    We had an existing repo "foo" that lots of people use.  We wanted to
+    rename it to "foo/code", so that related repos "foo/upstream" and
+    "foo/docs" (both containing stuff we did not want to put in "foo") could
+    also be made and then the whole thing would be structured nicely.
+
+    At the same time we did not want to *force* all the users to change the
+    name.  At least git operations should still work with the old name,
+    although it is OK for "info" and other "commands" to display/require the
+    proper name (i.e., the new name).
+
+How:
+
+  * add a new variable REPO_ALIASES to the rc file, with entries like:
+
+        REPO_ALIASES                =>
+            {
+                'foo'               =>  'foo/code',
+            }
+
+  * add the following line to the INPUT section in the rc file:
+
+        'Alias::input',
+
+Notes:
+
+  * only git operations (clone/fetch/push) are alias aware.  Nothing else in
+    gitolite, such as all the gitolite commands etc., are alias-aware and will
+    always use/require the proper repo name.
+
+  * http mode has not been tested and will not be.  If someone has the time to
+    test it and make it work please let me know.
+
+  * funnily enough, this even works with mirroring!  That is, a master can
+    push a repo "foo" to a slave per its configuration, while the slave thinks
+    it is getting repo "bar" from the master per its configuration.
+
+    Just make sure to put the Alias::input line *before* the Mirroring::input
+    line in the rc file on the slave.
+
+    However, it will probably not work with redirected pushes unless you setup
+    the opposite alias ("bar" -> "foo") on master.
+=cut
+
+sub input {
+    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+    my $user = $ARGV[0] || '@all';    # user name is undocumented for now
+
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '(\S+)'$/ ) {
+        my $repo = $1;
+        ( my $norm = $repo ) =~ s/\.git$//;    # normalised repo name
+
+        my $target;
+
+        return unless $target = $rc{REPO_ALIASES}{$norm};
+        $target = $target->{$user} if ref($target) eq 'HASH';
+        return unless $target;
+
+        _warn "'$norm' is an alias for '$target'";
+
+        $ENV{SSH_ORIGINAL_COMMAND} =~ s/'$repo'/'$target'/;
+    }
+
+}
+
+1;

commit bc3eb3421171a432aa62d784709d0b0e319de183
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon May 14 14:32:13 2012 +0530

    simulate POST_CREATE for newly created "normal" repos
    
    See "background" in new program src/triggers/new-normal-repos

diff --git a/src/triggers/new-normal-repos b/src/triggers/new-normal-repos
new file mode 100755
index 0000000..7e66509
--- /dev/null
+++ b/src/triggers/new-normal-repos
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# run some command on every new repo created via the conf file
+# ----------------------------------------------------------------------
+
+# BACKGROUND
+#   The POST_CREATE trigger (see doc/triggers.mkd) only works for repos that a
+#   user creates (see doc/wild.mkd).  It does NOT work for repos created
+#   normally using the conf file.  The POST_COMPILE trigger sequence should
+#   normally be used to trigger anything to happen after a compile.
+#
+#   I have not seen a sane use case for a POST_CREATE trigger when a new repo
+#   is created via the conf file.  (I do not consider "all new repos should
+#   have a default set of branches" to be a sane requirement).  However, on
+#   the off-chance that something turns up at some future time, here's how you
+#   can do this without touching the core gitolite code.
+
+# INSTRUCTIONS FOR USE
+# - (optional) rename this to whatever you want
+# - change the 'post_create_normal' function below to do whatever you want it
+#   to do, or make it call some other command to do it
+# - make the script executable
+# - add 'new-normal-repos' (or whatever you renamed it to) to the PRE_GIT and
+#   the POST_COMPILE trigger lists in the rc file
+
+post_create_normal() {
+    echo "post_create_normal called with repo '$1'" >&2
+}
+
+# tempfile prefix; if you really care, change this also, otherwise leave it alone
+tfp=$HOME/.tmp.$GL_USER.list-repos
+
+# ----------------------------------------------------------------------
+
+if [ "$1" = "PRE_GIT" ]
+then
+    # unless someone is pushing to the admin repo, we don't care
+    [ "$2" = "gitolite-admin" ] || exit 0
+    [ "$4" = "W" ] || exit 0
+
+    # save the list of repos
+    gitolite list-repos > $tfp.1
+elif [ "$1" = "POST_COMPILE" ]
+then
+    # get the new list of repos and compare with the one PRE_GIT created
+    gitolite list-repos > $tfp.2
+    for repo in `grep -x -f $tfp.1 -v $tfp.2`
+    do
+
+        post_create_normal "$repo"
+    done
+else
+    # if you edited your rc file correctly, this line should never be reached
+    echo "ignoring call to" `basename $0` "with arg1 = '$1'" >&2
+fi

commit 17a680e0f67b129f98464e0434aa2ff1790af65b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 26 06:38:04 2012 +0530

    (collected docfixes)

diff --git a/doc/admin.mkd b/doc/admin.mkd
index df3bdf2..47c3aeb 100644
--- a/doc/admin.mkd
+++ b/doc/admin.mkd
@@ -2,7 +2,8 @@
 
 ## #server server-side administration
 
-The following activities require command line access to the server:
+The following activities require command line access to the server.  They are
+usually one-time or rarely done activities.
 
   * Changing anything in the [rc][] file.
   * Installing custom [hooks][], whether to all repos or just some repos.
@@ -10,12 +11,19 @@ The following activities require command line access to the server:
 
 Please read the [WARNINGS][] page first.
 
-## #conf access control (the gitolite.conf file)
+## #adminrepo access control via the gitolite-admin repo
 
-Most of gitolite's power is in the conf/gitolite.conf file, which specifies
-detailed access control for repos.
+Most day-to-day administration of a gitolite site happens like this:
+
+  * [clone][] the gitolite-admin repo to your workstation
+  * make appropriate changes
+  * add, commit, and push
 
-### #confex example of a conf file
+### #conf the conf/gitolite.conf file
+
+Most of gitolite's power is in the conf/gitolite.conf file, which specifies
+detailed access control for repos.  Everything except [adding users][users]
+happens from this file.
 
 Here is an example of a simple conf/gitolite.conf file.
 
diff --git a/doc/clone.mkd b/doc/clone.mkd
new file mode 100644
index 0000000..f51c2d0
--- /dev/null
+++ b/doc/clone.mkd
@@ -0,0 +1,19 @@
+# cloning the admin repo
+
+This is the third step in using gitolite, after [install][] and [setup][].
+
+To clone the admin repo, go to the workstation where the public key used in
+'setup' came from, and run this:
+
+    git clone git at host:gitolite-admin
+
+NOTE that (1) you must not include the `repositories/` part (gitolite handles
+that internally), and (2) you may include the ".git" at the end but it is
+optional.
+
+If this step succeeds, you can add [users][], [repos][], or anything else
+described [here][adminrepo].
+
+If this step fails, be sure to look at the [ssh][] documentation before asking
+for help.  (A very basic first step is to run `ssh git at host info`;
+[this][info] page tells you what to expect).
diff --git a/doc/extras/ssh.mkd b/doc/extras/ssh.mkd
index 1d7272f..fb51655 100644
--- a/doc/extras/ssh.mkd
+++ b/doc/extras/ssh.mkd
@@ -1,5 +1,10 @@
 # ssh
 
+If you're installing gitolite, you're a "system admin", like it or not.  If
+you're using the default ssh mode (i.e., not [http][] mode), ssh is a
+necessary skill.  Please take the time to learn at least enough to get
+passwordless access working.
+
 There are two documents you need to read, in order:
 
   * [Gitolite and ssh][glssh] explains how gitolite uses openssh features to
diff --git a/doc/install.mkd b/doc/install.mkd
index ad681ec..fb0348c 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -6,6 +6,11 @@ start [here][migr].  RTFM is *mandatory* for migrations.</font>
 
 ----
 
+This is the first step in using gitolite, and happens on the server.  It is
+followed by [setup][], then [clone][].
+
+----
+
 [[TOC]]
 
 ----
@@ -20,6 +25,9 @@ The real user used is called the **hosting user**.  Typically this user is
 RPMs and DEBs create a user called *gitolite* for this, so adjust instructions
 and examples accordingly.
 
+**Unless otherwise stated, everything in this page is to be done by logging in
+as this "hosting user"**.
+
 Notes:
 
   * Any unix user can be a hosting user.
@@ -72,6 +80,9 @@ side.
 
 ## the actual install
 
+**Note**: This section describes installing an ssh-based setup.  For smart
+http setup click [here][http].
+
 Gitolite has only one server side "command" now, much like git itself.  This
 command is `gitolite`.  You don't need to place it anywhere special; worst
 case you run it with the full path.
diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
index 39904ba..d244d2b 100644
--- a/doc/master-toc.mkd
+++ b/doc/master-toc.mkd
@@ -14,7 +14,7 @@
   * [lost][lost-key] admin key/access
   * [bypass][]ing gitolite
   * [clean][]ing out a botched install
-  * [common][ce] errors (TBD)
+  * [common][ce] errors
   * [uncommon][ue] errors
   * things that are [not gitolite problems][ngp]
 
@@ -43,16 +43,18 @@
 ## [setup][]
 
 
+## [clone][]
+
+
 ## gitolite [admin][]istration
 
-  * [server][]-side
+  * ([server-side][server]) settings, hooks, etc.
       * ([link][WARNINGS]: important cautions on server side activity)
       * changing settings in the [rc][] file
       * installing custom [hooks][]
       * ([link][existing]: moving existing repos into gitolite)
-  * [access control][conf] (the gitolite.conf file)
-      * [example][confex] of a conf file
-      * basic [syntax][]
+  * ([client-side][adminrepo]) access control via the gitolite-admin repo
+      * basic [syntax][] of the [conf][] file
           * include files
           * ([link][sugar]: syntactic sugar)
       * [groups][] (of users and repos)
@@ -93,6 +95,7 @@
 
   * [disabling pushes][writable] to take backups
   * [personal][pers] branches
+  * ([link][votes]: voting on commits)
   * [delegating][deleg] access control responsibilities
       * ([link][NAME]: the NAME VREF)
       * the [subconf][] command
@@ -159,4 +162,4 @@
   * log file format, LOG_EXTRA
   * hub
   * mob branches
-  * password access
\ No newline at end of file
+  * password access
diff --git a/doc/qi.mkd b/doc/qi.mkd
index 2ac9470..90a83c2 100644
--- a/doc/qi.mkd
+++ b/doc/qi.mkd
@@ -1,25 +1,9 @@
 # quick install, setup, and clone
 
-(Please do not ignore the "assumptions" list below).
-
-On the server:
-
-    # get the software
-    git clone git://github.com/sitaramc/gitolite
-
-    # install it
-    gitolite/install -ln
-
-    # setup the initial repos with your key
-    gitolite setup -pk your-name.pub
-
-On your workstation:
-
-    # clone the admin repo so you can start adding stuff
-    git clone git at host:gitolite-admin.git
-
 ## ASSUMPTIONS
 
+  * This is an ssh-based setup.  For smart http setup click [here][http].
+
   * This is a fresh install, not a migration from the old gitolite (v1.x,
     v2.x).
 
@@ -34,15 +18,31 @@ On your workstation:
       * If it does, please see [common errors][ce] and fix things before
         continuing, or read the more complete [setup][] page.
 
-## Notes
+## instructions
+
+On the server, as the hosting user (e.g., 'git'):
+
+    # get the software
+    git clone git://github.com/sitaramc/gitolite
+
+    # install it
+    gitolite/install -ln
+
+    # setup the initial repos with your key
+    gitolite setup -pk your-name.pub
+
+On your workstation:
 
-Note that the clone path is NOT "repositories/gitolite-admin.git".  If you
-clone with a path that includes "repositories/", the clone should fail.  If
-the clone *does* succeed, a subsequent push should fail :-)  See
-[this][ybpfail] for some details.  If that doesn't make enough sense read all
-of [ssh][].
+    # clone the admin repo so you can start adding stuff
+    git clone git at host:gitolite-admin.git
+    # Note 1: clone path must not include "repositories/"
+    # Note 2: it may include the ".git" at the end but it is optional
 
 ## next steps
 
-Next steps are usually adding [users][] and [repos][] and learning about
-[access control][conf].
+If this step succeeds, you can add [users][], [repos][], or anything else
+described [here][adminrepo].
+
+If this step fails, be sure to look at the [ssh][] documentation before asking
+for help.  (A very basic first step is to run `ssh git at host info`;
+[this][info] page tells you what to expect).
diff --git a/doc/setup.mkd b/doc/setup.mkd
index 17a238a..ef866c2 100644
--- a/doc/setup.mkd
+++ b/doc/setup.mkd
@@ -1,11 +1,16 @@
 # setting up gitolite
 
+This is the second step in using gitolite, after [install][].  This also
+happens on the server,  (The next step is [clone][]).
+
+----
+
 Installing the software gets you ready to use it, but the first "use" of it is
 always the "setup" command.
 
-The first time you run it, you need to have a public key file ready.  If the
-main gitolite admin's username is "alice", this file should be named
-"alice.pub".  Then run
+The first time you run it, you need to have a public key file (usually from
+the admin's workstation) ready.  If the main gitolite admin's username is
+"alice", this file should be named "alice.pub".  Then run
 
     gitolite setup -pk alice.pub
 
diff --git a/doc/vref.mkd b/doc/vref.mkd
index 5d86c03..6f7bd2f 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -6,6 +6,10 @@ who stop reading halfway!
 
 ----
 
+[[TOC]]
+
+----
+
 Here's an example to start you off.
 
     repo    r1
@@ -262,12 +266,13 @@ copied to `src/VREF/EMAIL-CHECK` and it works, because VREFs get
 the same first 3 arguments and those are all that it cares about.  (Note: you
 have to change one subroutine in that script if you want to use it)
 
-### catching duplicate pubkeys
-
-This checks keydir/ for duplicate keys and aborts the push if it finds any.
-You should use this only on the gitolite-admin repo.
+### #votes voting on commits
 
-We covered this as a teaser example at the start.
+Although gitolite can't/won't do the whole "code review + workflow
+enforcement" thing that Gerrit Code Review does, a basic implementation of
+voting on a commit is surprisingly easy.  See src/VREF/VOTES for details (and
+note that the actual *code* is just 2-3 lines; the rest is inline
+documentation).
 
 ## other ideas -- code welcome!
 

commit e511943a45cdfc77b02896e2a43e8cf3bc3f49ac
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu May 10 10:37:11 2012 +0530

    just for kicks, a VREF that allows voting on changes to a branch
    
    (manually smoke tested; no test script)
    
    I've been meaning to do this for a while, since someone told me that is
    one of gerrit's features they like.
    
    Of course, gitolite can't/won't do the whole "code review" thing (nor
    the workflow enforcement that follows).
    
    But voting is simple -- literally 2-3 lines of code in a VREF.  (The
    rest is inline documentation).

diff --git a/src/VREF/VOTES b/src/VREF/VOTES
new file mode 100755
index 0000000..bb6bf22
--- /dev/null
+++ b/src/VREF/VOTES
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# gitolite VREF to count votes before allowing pushes to certain branches.
+
+# This approximates gerrit's voting (but it is SHA based; I believe Gerrit is
+# more "changeset" based).  Here's how it works:
+
+# - A normal developer "bob" proposes changes to master by pushing a commit to
+#   "pers/bob/master", then informs the voting members by email.
+
+# - Some or all of the voting members fetch and examine the commit.  If they
+#   approve, they "vote" for the commit like so.  For example, say voting
+#   member "alice" fetched bob's proposed commit into "bob-master" on her
+#   clone, then tested or reviewed it.  She would approve it by running:
+#       git push origin bob-master:votes/alice/master
+
+# - Once enough votes have been tallied (hopefully there is normal team
+#   communication that says "hey I approved your commit", or it can be checked
+#   by 'git ls-remote origin' anyway), Bob, or any developer, can push the
+#   same commit (same SHA) to master and the push will succeed.
+
+# - Finally, a "trusted" developer can push a commit to master without
+#   worrying about the voting restriction at all.
+
+# The config for this example would look like this:
+
+#   repo foo
+#       # allow personal branches (to submit proposed changes)
+#       RW+ pers/USER/          =   @devs
+#       -   pers/               =   @all
+#
+#       # allow only voters to vote
+#       RW+ votes/USER/         =   @voters
+#       -   votes/              =   @all
+#
+#       # normal access rules go here; should allow *someone* to push master
+#       RW+                     =   @devs
+#
+#       # 2 votes required to push master, but trusted devs don't have this restriction
+#       RW+ VREF/VOTES/2/master =   @trusted-devs
+#       -   VREF/VOTES/2/master =   @devs
+
+# Note: "2 votes required to push master" means at least 2 refs matching
+# "votes/*/master" have the same SHA as the one currently being pushed.
+
+# ----------------------------------------------------------------------
+
+# see gitolite docs for what the first 7 arguments mean
+
+# inputs:
+#   arg-8 is a number; see below
+#   arg-9 is a simple branch name (i.e., "master", etc).  Currently this code
+#   does NOT do vote counting for branch names with more than one component
+#   (like foo/bar).
+# outputs (STDOUT)
+#   nothing
+# exit status:
+#   always 0
+
+die() { echo "$@" >&2; exit 1; }
+[ -z "$8" ] && die "not meant to be run manually"
+
+ref=$1
+newsha=$3
+refex=$7
+votes_needed=$8
+branch=$9
+
+# nothing to do if the branch being pushed is not "master" (using our example)
+[ "$ref" = "refs/heads/$branch" ] || exit 0
+
+# find how many votes have come in
+votes=`git for-each-ref refs/heads/votes/*/$branch | grep -c $newsha`
+
+# send back a vref if we don't have the minimum votes needed.  For trusted
+# developers this will invoke the RW+ rule and pass anyway, but for others it
+# will invoke the "-" rule and fail.
+[ $votes -ge $votes_needed ] || echo $refex "require at least $votes_needed votes to push $branch"
+
+exit 0

commit fa2893be7c45ac17bac6da570f3270f0e0210103
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 6 19:15:28 2012 +0530

    the dupkeys function was already in ssh-authkeys...
    
    ...so there's no need for the VREF.
    
    Ironically, while I was arguing with Eli that I wouldn't do it and why,
    the code was *already* there, and had been for over a month!  (It must
    have been there for much longer for me to have forgotten!)
    
    TODO: convert from using fingerprint compute to actual key strings when
    the complaints about speed start appearing.
    
    My own personal speed up loop [1] I guess :)
    
    [1]: http://thedailywtf.com/Articles/Classic-WTF-The-Speedup-Loop.aspx

diff --git a/doc/vref.mkd b/doc/vref.mkd
index ac6599b..5d86c03 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -16,12 +16,6 @@ Here's an example to start you off.
 Now dev2 and dev3 cannot push changes that affect more than 9 files at a time,
 nor those that have more than 3 new files.
 
-Another example is detecting duplicate pubkeys in a push to the admin repo:
-
-    repo gitolite-admin
-        # ... normal rules ...
-        -   VREF/DUPKEYS            =   @all
-
 ----
 
 ## rule matching recap
@@ -63,7 +57,7 @@ the VREF only in "deny" rules.
 
 This in turn means any existing update hook can be used as a VREF *as-is*, as
 long as it (a) prints nothing on success and (b) dies on failure.  See the
-email-check and dupkeys examples later.
+email-check example later.
 
 ## how it works -- overview
 
diff --git a/src/VREF/DUPKEYS b/src/VREF/DUPKEYS
deleted file mode 100755
index 7e479fa..0000000
--- a/src/VREF/DUPKEYS
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/bash
-
-# gitolite VREF to detect duplicate public keys
-
-# see gitolite doc/vref.mkd for what the arguments are
-sha=$3
-
-# git sets this; and we don't want it at this point...
-unset GIT_DIR
-
-# paranoia
-set -e
-
-# setup the temp area
-export TMPDIR=$GL_REPO_BASE_ABS
-export tmp=$(mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX);
-trap "rm -rf $tmp" EXIT;
-
-git archive $sha keydir | tar -C $tmp -xf -
-    # DO NOT try, say, 'GIT_WORK_TREE=$tmp git checkout $sha'.  It'll screw up
-    # both the 'index' and 'HEAD' of the repo.git.  Screwing up the index is
-    # BAD because now it goes out of sync with $GL_ADMINDIR.  Think of a push
-    # that had a deleted pubkey but failed a hooklet for some reason.  A
-    # subsequent push that fixes the error will now result in a $GL_ADMINDIR
-    # that still *has* that deleted pubkey!!
-
-    # And this is equally applicable to cases where you're using a
-    # post-receive or similar hook to live update a web site or something,
-    # which is a pretty common usage, I am given to understand.
-
-cd $tmp
-
-for f in `find keydir -name "*.pub"`
-do
-    ssh-keygen -l -f "$f"
-done | perl -ane '
-    die "FATAL: $F[2] is a duplicate of $seen{$F[1]}\n" if $seen{$F[1]};
-    $seen{$F[1]} = $F[2];
-'
-
-# as you can see, a vref can also 'die' if it wishes to, and it'll take the
-# whole update with it if it does.  No messing around with sending back a
-# vref, having it run through the matches, and printing the DENIED message,
-# etc.  However, if your push is running from a script, and that script is
-# looking for the word "DENIED" or something, then this won't work...

commit 699bafa096d21e85c281950e98e60d64104637f9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 6 19:01:53 2012 +0530

    (minor fixup to t/info.t)

diff --git a/t/info.t b/t/info.t
index deaacb8..22b5b94 100755
--- a/t/info.t
+++ b/t/info.t
@@ -93,16 +93,13 @@ try "
 try "
     glt ls-remote u1 file:///foo/one;   ok
     glt info u1 -lc; ok; GS u1
-    put
         /C\tfoo/\\.\\.\\*/
         !/C\tfoo.*u1/
         /R W *\tfoo/one\tu1/
     glt info u2 -lc; ok; GS u2
-    put
         !/C\tfoo/
         !/R W *\tfoo/one/
     glt info u3 -lc; ok; GS u3
-    put
         !/C\tfoo/
         /R W *\tfoo/one\tu1/
 ";

commit e76be7ff110db02ffe78bede9b2001c5aab7cbc6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun May 6 18:53:52 2012 +0530

    move repo/user validity check deeper
    
    (but change repo check to allow repoPATT instead of just repoNAME)
    
    This is because there are/will be some situations where access() is
    called without those two checks being done (i.e., it is not only from
    src/commands/access that it is called).

diff --git a/src/commands/access b/src/commands/access
index cdefacb..636dc61 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -49,8 +49,6 @@ my $ret = '';
 
 if ( $repo ne '%' and $user ne '%' ) {
     # single repo, single user; no STDIN
-    _die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT );
-    _die "invalid user name" if not( $user and $user =~ $USERNAME_PATT );
     $ret = access( $repo, $user, $aa, $ref );
 
     if ( $ret =~ /DENIED/ ) {
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 9f4ddb0..7599727 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -67,6 +67,8 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
+    _die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
+    _die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
     my $deny_rules = option( $repo, 'deny-rules' );
     load($repo);
 

commit 196706c1457d9ad242bd2a9ef833f21f1dd6b607
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat May 5 07:11:59 2012 +0530

    bugfix: smart http mode wasn't running pre_ and post_ git triggers
    
    (while we're about it, we also steal Michael Brown's idea (patch #2 in
    [1] and get rid of GIT_HTTP_BACKEND).
    
    [1]: http://groups.google.com/group/gitolite/msg/adfae758dd28f2a8

diff --git a/doc/http.mkd b/doc/http.mkd
index bfc7544..3b9809e 100644
--- a/doc/http.mkd
+++ b/doc/http.mkd
@@ -52,7 +52,6 @@ Please adjust the instructions below to reflect your setup (users and paths).
 
 Edit your .gitolite.rc and add
 
-    $ENV{GIT_HTTP_BACKEND} = "/usr/libexec/git-core/git-http-backend";
     $ENV{PATH} .= ":/opt/git/bin";
 
 at the very top (as described in `t/smart-http.root-setup`).
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 07e2cd5..af03a36 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -121,11 +121,13 @@ sub main {
         gl_log( "pre_git", $repo, $user, $aa, 'any', "-> $ret" );
     }
 
-    exec $ENV{GIT_HTTP_BACKEND} if $ENV{REQUEST_URI};
-
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
-    my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
-    _system( "git", "shell", "-c", "$verb $repodir" );
+    if ($ENV{REQUEST_URI}) {
+        _system( "git", "http-backend" );
+    } else {
+        my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
+        _system( "git", "shell", "-c", "$verb $repodir" );
+    }
     trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
 }
 
diff --git a/t/smart-http.root-setup b/t/smart-http.root-setup
index ed3d413..dd42ad9 100755
--- a/t/smart-http.root-setup
+++ b/t/smart-http.root-setup
@@ -46,7 +46,6 @@ cd $GITOLITE_HTTP_HOME
 HOME=$GITOLITE_HTTP_HOME gitolite setup -a admin
 
 # insert some essential lines at the beginning of the rc file
-echo '$ENV{GIT_HTTP_BACKEND} = "/usr/libexec/git-core/git-http-backend";' > 1
 echo '$ENV{PATH} .= ":$ENV{GITOLITE_HTTP_HOME}/bin";'  >> 1
 echo >> 1
 cat .gitolite.rc >> 1

commit 6d057fb84c2e0e85dd353061b73e9e8de15d3953
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 4 17:52:43 2012 +0530

    allow info to print description also

diff --git a/src/commands/info b/src/commands/info
index c4f5d6c..daf3ad2 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -10,12 +10,13 @@ use Gitolite::Common;
 use Gitolite::Conf::Load;
 
 =for args
-Usage:  gitolite info [-lc] [<repo name pattern>]
+Usage:  gitolite info [-lc] [-ld] [<repo name pattern>]
 
 List all existing repos you can access, as well as repo name patterns you can
 create repos from (if any).
 
     '-lc'       lists creators as an additional field at the end.
+    '-ld'       lists description as an additional field at the end.
 
 The optional pattern is an unanchored regex that will limit the repos
 searched, in both cases.  It might speed up things a little if you have more
@@ -23,7 +24,7 @@ than a few thousand repos.
 =cut
 
 # these two are globals
-my ( $lc, $patt ) = args();
+my ( $lc, $ld, $patt ) = args();
 
 print_version();
 
@@ -34,18 +35,19 @@ print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
 # ----------------------------------------------------------------------
 
 sub args {
-    my ( $lc, $patt ) = ( '', '' );
+    my ( $lc, $ld, $patt ) = ( '', '', '' );
     my $help = '';
 
     GetOptions(
         'lc' => \$lc,
+        'ld' => \$ld,
         'h'  => \$help,
     ) or usage();
 
     usage() if @ARGV > 1 or $help;
     $patt = shift @ARGV || '.';
 
-    return ( $lc, $patt );
+    return ( $lc, $ld, $patt );
 }
 
 sub print_version {
@@ -61,8 +63,8 @@ sub print_patterns {
     # find repo patterns only, call them with ^C flag included
     @$repos = grep { !/$REPONAME_PATT/ } @{ lister_dispatch('list-repos')->() };
     @aa = qw(R W ^C);
-    listem( $repos, '', @aa );
-    # but squelch the 'lc' flag for these
+    listem( $repos, '', '', @aa );
+    # but squelch the 'lc' and 'ld' flags for these
 }
 
 sub print_phy_repos {
@@ -72,16 +74,19 @@ sub print_phy_repos {
     _chdir( $rc{GL_REPO_BASE} );
     $repos = list_phy_repos(1);
     @aa    = qw(R W);
-    listem( $repos, $lc, @aa );
+    listem( $repos, $lc, $ld, @aa );
 }
 
 sub listem {
-    my ( $repos, $lc, @aa ) = @_;
+    my ( $repos, $lc, $ld, @aa ) = @_;
     my $creator = '';
+    my $desc = '';
     for my $repo (@$repos) {
         next unless $repo =~ /$patt/;
         my $perm = '';
         $creator = creator($repo) if $lc;
+        $desc = slurp("$ENV{GL_REPO_BASE}/$repo.git/description") if $ld;
+        chomp($desc);
 
         for my $aa (@aa) {
             my $ret = access( $repo, $ENV{GL_USER}, $aa, 'any' );
@@ -91,6 +96,7 @@ sub listem {
         next unless $perm =~ /\S/;
         print "$perm\t$repo";
         print "\t$creator" if $lc;
+        print "\t$desc" if $ld;
         print "\n";
     }
 }

commit d8df4a93440ef10c28fed8eb92822538ab3d85aa
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri May 4 16:33:04 2012 +0530

    git-config bugfix + backward compat breakage in usage of 'config'
    
    (1) the backward compat breakage: you can't create empty-valued config
        keys anymore.  That is, you can't do the eqvt of the following shell
        command using gitolite
    
            git config foo.bar ""
    
    (2) fixed a bug where this:
    
            repo foo
                config foo.bar =
    
        when queried using
    
            gitolite git-config -r foo .
    
        would return even the empty valued ones, which -- remember! -- are
        not supposed to exist anymore.
    
        Fixing this bug allows situations like this to not show the admin
        repo in gitweb:
    
            repo [a-z].*
                config gitweb.owner = P-h B
    
            repo gitolite-admin
                config gitweb.owner =
    
    ----
    
    background...
    
    Somewhere in g3 (well actually in 057506b), we lost the ability to
    distinguish
        config foo.bar  =   ""
    from
        config foo.bar =
    
    I decided that conflating them is more intuitive for most people,
    because a survey [1] revealed that no one seemed to want the equivalent
    of the following shell command:
    
    ----
    
    [1] ...of a (small prime greater than 1) number of people on #git

diff --git a/doc/git-config.mkd b/doc/git-config.mkd
index e1c1c00..94487e1 100644
--- a/doc/git-config.mkd
+++ b/doc/git-config.mkd
@@ -23,9 +23,9 @@ For example:
         config foo.bar = ""
         config foo.baz =
 
-This does either a plain "git config section.key value" (for the first 3
-examples above) or "git config --unset-all section.key" (for the last
-example).  Other forms of the `git config` command (`--add`, the
+This does either a plain "git config section.key value" (for the first 2
+examples above) or "git config --unset-all section.key" (for the last 2
+examples).  Other forms of the `git config` command (`--add`, the
 `value_regex`, etc) are not supported.
 
 >   ----
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 8f1ebfe..9f4ddb0 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -114,7 +114,7 @@ sub access {
 }
 
 sub git_config {
-    my ( $repo, $key ) = @_;
+    my ( $repo, $key, $empty_values_OK ) = @_;
     $key ||= '.';
 
     return {} if repo_missing($repo);
@@ -149,6 +149,15 @@ sub git_config {
     # and the final map does this:
     #                 'foo.bar'=>'repo'  ,      'foodbar'=>'repoD'
 
+    # now some of these will have an empty key; we need to delete them unless
+    # we're told empty values are OK
+    unless ($empty_values_OK) {
+        my($k, $v);
+        while (($k, $v) = each %ret) {
+            delete $ret{$k} if not $v;
+        }
+    }
+
     trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
     return \%ret;
 }
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 7f3fb83..bd7ff13 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -37,7 +37,7 @@ for my $pr (@$lpr) {
 sub fixup_config {
     my $pr = shift;
 
-    my $gc = git_config( $pr, '.' );
+    my $gc = git_config( $pr, '.', 1 );
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {

commit 47a0c4454082abef5489df0a9f1742f3f6a5275c
Author: Andreas Stenius <git at astekk.se>
Date:   Wed May 2 18:03:05 2012 +0200

    migrated htpasswd command from g2.
    
    (with some fixups by committer)

diff --git a/src/commands/htpasswd b/src/commands/htpasswd
new file mode 100755
index 0000000..3571cf1
--- /dev/null
+++ b/src/commands/htpasswd
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+=for usage
+Usage:  ssh git at host htpasswd
+
+Sets your htpasswd, assuming your admin has enabled it.
+
+(Admins: You need to add HTPASSWD_FILE to the rc file, pointing to an
+existing, writable, but possibly an initially empty, file, as well as adding
+an entry for 'htpasswd' to the COMMANDS hash).
+=cut
+
+# usage and sanity checks
+usage() if @ARGV and $ARGV[0] eq '-h';
+$ENV{GL_USER} or _die "GL_USER not set";
+my $htpasswd_file = $rc{HTPASSWD_FILE} || '';
+die "htpasswd not enabled\n" unless $htpasswd_file;
+die "$htpasswd_file doesn't exist or is not writable\n" unless -w $htpasswd_file;
+
+# prompt
+$|++;
+print <<EOFhtp;
+Please type in your new htpasswd at the prompt.  You only have to type it once.
+
+NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
+shoulder-surfing, and make sure you clear your screen as well as scrollback
+history after you're done (or close your terminal instance).
+
+EOFhtp
+print "new htpasswd: ";
+
+# get the password and run htpasswd
+my $password = <>;
+$password =~ s/[\n\r]*$//;
+die "empty passwords are not allowed\n" unless $password;
+my $res = system("htpasswd", "-mb", $htpasswd_file, $ENV{GL_USER}, $password);
+die "htpasswd command seems to have failed with return code: $res.\n" if $res;

commit 49d132a969cb1148e289af6bb5633d193263fd19
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 1 13:12:10 2012 +0530

    minor fix to info command output under httpd...
    
    when running under httpd, $ENV{USER} is not set, so we use a (hopefully
    informative) default to print.
    
    Thanks to Thomas Hager (duke at sigsegv dot at) for catching this.

diff --git a/src/commands/info b/src/commands/info
index 0b789bd..c4f5d6c 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -52,7 +52,7 @@ sub print_version {
     chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
     my $gv = substr( `git --version`, 12 );
     $ENV{GL_USER} or _die "GL_USER not set";
-    print "hello $ENV{GL_USER}, this is $ENV{USER}\@$hn running gitolite3 " . version() . " on git $gv\n";
+    print "hello $ENV{GL_USER}, this is " . ($ENV{USER} || "httpd") . "\@$hn running gitolite3 " . version() . " on git $gv\n";
 }
 
 sub print_patterns {

commit c1455288496d1e80dcffc2cf10557293bd451e6d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue May 1 12:05:17 2012 +0530

    (minor typo fix)

diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index effdb96..0a677a8 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -137,7 +137,7 @@ sub setup_gladmin {
     tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
     tsh_try("git config --get user.name")  or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
     tsh_try("git diff --cached --quiet")
-      or tsh_try("git commit -am 'gl-setup $argv'")
+      or tsh_try("git commit -am 'gitolite setup $argv'")
       or _die "setup failed to commit to the admin repo";
     delete $ENV{GIT_WORK_TREE};
 }
diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index 48a2330..e6e5a36 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -47,7 +47,7 @@ try "
     DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
 
     DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
-    DEF CHECK_SETUP = CS_1; git log; ok; /6b18ec2ab0f765122ec133959b36c57f77d4565c/
+    DEF CHECK_SETUP = CS_1; git log; ok; /fa7564c1b903ea3dce49314753f25b34b9e0cea0/
 
     DEF CLONE = glt clone %1 file:///%2
     DEF PUSH  = glt push %1 origin

commit 850882c1a6d95446e38ff8ceaa7d2513e44b57f5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 30 05:35:26 2012 +0530

    allow VREF code to print to STDOUT...
    
    Using a g2-style "chained update hook" as a VREF doesn't *quite* work:
    
      - all STDOUT from the hook is lost
      - worse, all lines get parsed as a ref followed by a message, and if
        the ref doesn't look like a ref it dies
    
    So now we do all this only if the message starts with 'VREF/'.  Any
    other output is just printed out as is.

diff --git a/doc/vref.mkd b/doc/vref.mkd
index a5bfb27..ac6599b 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -71,9 +71,9 @@ Briefly, a refex starting with `VREF/FOO` triggers a call to a program called
 `FOO` in `$GL_BINDIR/VREF`.
 
 That program is expected to print zero or more lines to its STDOUT; each line
-is taken by gitolite as a new "ref" to be matched against all the refexes for
-this user in the config.  Including the refex that caused the vref call, of
-course.
+that starts with `VREF/` is taken by gitolite as a new "ref" to be matched
+against all the refexes for this user in the config.  Including the refex that
+caused the vref call, of course.
 
 Normally, you send back the refex itself, if the test determines that the rule
 should be matched, otherwise nothing.  So, in our example, we print
@@ -102,16 +102,21 @@ exit.
     The program is passed **nine arguments** in this case (see next section
     for details).
 
-  * The script can print anything it wants to STDOUT; the first word in each
-    such line will be treated as a virtual ref to be matched against all the
-    rules, while the rest, if any, is a message to be added to the standard
-    "...DENIED..." message that gitolite prints if that refex matches.
+  * The script can print anything it wants to STDOUT.  Lines not starting with
+    `VREF/` are printed as is (so your VREF can do mostly-normal printing to
+    STDOUT).
+
+    For lines starting with `VREF/`, the first word in each such line will be
+    treated as a virtual ref to be matched against all the rules, while the
+    rest, if any, is a message to be added to the standard "...DENIED..."
+    message that gitolite prints if that refex matches.
 
     Usually it only makes sense to either
 
-      * Print nothing -- if you don't want the rule that triggered it to match
-        (ie., whatever condition being tested was not violated; like if the
-        count of changed files did not exceed 9, in our earlier example).
+      * Print nothing that starts with `VREF/` -- if you don't want the rule
+        that triggered it to match (ie., whatever condition being tested was
+        not violated; like if the count of changed files did not exceed 9, in
+        our earlier example).
       * Print the refex itself (plus an optional message), so that it matches
         the line which invoked it.
 
@@ -151,9 +156,9 @@ to write vref scripts in any language.  See script examples in source.
 
 ## what (else) can the vref code pass back
 
-Actually, the vref code can pass anything back; each line in its output will
-be matched against all the rules as usual (with the exception that fallthru is
-not failure).
+Actually, the vref code can pass anything back; each line in its output that
+starts with `VREF/` will be matched against all the rules as usual (with the
+exception that fallthru is not failure).
 
 For example, you could have a ruleset like this:
 
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 3bcb8cf..ed4a03f 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -66,6 +66,12 @@ sub check_vrefs {
 
             open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "$vref: can't spawn helper program: $!";
             while (<$fh>) {
+                # print non-vref lines and skip processing (for example,
+                # normal STDOUT by a normal update hook)
+                unless (m(^VREF/)) {
+                    print;
+                    next;
+                }
                 my ( $ref, $deny_message ) = split( ' ', $_, 2 );
                 check_vref( $aa, $ref, $deny_message );
             }

commit 88c8d774d099cc9f64dcc95919395d4876760dc4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 29 05:55:37 2012 +0530

    v3.01

diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index cb3626b..10c045c 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,3 +1,6 @@
+2012-04-29  v3.01   mostly BSD and Solaris compat
+                    also fork command added
+
 2012-04-18  v3.0    first release to "master"
                     This is a compete rewrite of gitolite; please see
                     documentation before upgrading.

commit 88b4c86c385f6abfea7b2aae35136538881dcf88
Author: Thomas Hager <thomas.hager at swisscom.com>
Date:   Fri Apr 20 15:28:05 2012 +0200

    Added instructions to make repositories available via http and ssh
    
    This patch adds instructions for configuring Gitolite and Apache 2.x
    to make repositories available to both ssh and http clients.
    
    [minor fixups by committer]

diff --git a/doc/http.mkd b/doc/http.mkd
index 1c935db..bfc7544 100644
--- a/doc/http.mkd
+++ b/doc/http.mkd
@@ -13,10 +13,6 @@ that is the same or even relevant -- that is from 2006 and is quite different
 
   * I have tested this only on stock Fedora 16; YDMV.
 
-  * As before, I have not tried making repos available to both ssh *and* http
-    mode clients but it ought to work.  If you managed it, I'd appreciate a
-    doc patch describing how you did it.
-
 ## assumptions:
 
   * Apache 2.x and git installed.
@@ -38,6 +34,96 @@ that delete files etc.), change values per your system, and only then run it.
 git-http-backend") is no longer optional.  Make sure you set it to some place
 outside apache's `DOCUMENT_ROOT`.</font>
 
+## Making repositories available to both ssh and http mode clients
+
+This section has been contributed by Thomas Hager (duke at sigsegv dot at).
+
+Assumptions:
+
+  * Apache 2.x with CGI and Suexec support installed.
+  * Git and Gitolite installed with user "git" and group "git", and pubkey SSH
+    access configured and working.
+  * Git plumbing installed to /usr/libexec/git-core
+  * Gitolite base located at /opt/git
+  * Apache `DOCUMENT_ROOT` set to /var/www
+  * Apache runs with user www and group www
+
+Please adjust the instructions below to reflect your setup (users and paths).
+
+Edit your .gitolite.rc and add
+
+    $ENV{GIT_HTTP_BACKEND} = "/usr/libexec/git-core/git-http-backend";
+    $ENV{PATH} .= ":/opt/git/bin";
+
+at the very top (as described in `t/smart-http.root-setup`).
+
+Next, check which document root your Apache's suexec accepts:
+
+    # suexec -V
+     -D AP_DOC_ROOT="/var/www"
+     -D AP_GID_MIN=100
+     -D AP_HTTPD_USER="www"
+     -D AP_LOG_EXEC="/var/log/apache/suexec.log"
+     -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
+     -D AP_UID_MIN=100
+     -D AP_USERDIR_SUFFIX="public_html"
+
+We're interested in `AP_DOC_ROOT`, which is set to `/var/www` in our case.
+
+Create a `bin` and a `git` directory in `AP_DOC_ROOT`:
+
+    install -d -m 0755 -o git -g git /var/www/bin
+    install -d -m 0755 -o www -g www /var/www/git
+
+`/var/www/git` is just a dummy directory used as Apache's document root (see below).
+
+Next, create a shell script inside `/var/www/bin` named `gitolite-suexec-wrapper.sh`,
+with mode **0700** and owned by user and group **git**. Add the following content:
+
+    #!/bin/bash
+    #
+    # Suexec wrapper for gitolite-shell
+    #
+
+    export GIT_PROJECT_ROOT="/opt/git/repositories"
+    export GITOLITE_HTTP_HOME="/opt/git"
+
+    exec ${GITOLITE_HTTP_HOME}/gitolite-source/src/gitolite-shell
+
+Edit your Apache's config to add http pull/push support, preferably in
+a dedicated `VirtualHost` section:
+
+    <VirtualHost *:80>
+        ServerName        git.example.com
+        ServerAlias       git
+        ServerAdmin       you at example.com
+
+        DocumentRoot /var/www/git
+        <Directory /var/www/git>
+            Options       None
+            AllowOverride none
+            Order         allow,deny
+            Allow         from all
+        </Directory>
+
+        SuexecUserGroup git git
+        ScriptAlias /git/ /var/www/bin/gitolite-suexec-wrapper.sh/
+        ScriptAlias /gitmob/ /var/www/bin/gitolite-suexec-wrapper.sh/
+
+        <Location /git>
+            AuthType Basic
+            AuthName "Git Access"
+            Require valid-user
+            AuthUserFile /etc/apache/git.passwd
+        </Location>
+    </VirtualHost>
+
+This Apache config is just an example, you probably should adapt the authentication
+section and use https instead of http!
+
+Finally, add an `R = daemon` access rule to all repositories you want to
+make available via http.
+
 ## usage
 
 ### client side

commit 48ed4deb8fa87f5aec22f7f9d9fc7df950d9c325
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 28 12:03:32 2012 +0530

    BSD compat changes
    
    thanks to milki for all the efforts!
    
    Details:
    
      - partial-copy fell afoul of BSD not having $RANDOM
      - test suite: fix bad GNU sort with good perl sort
      - test suite: fix md5sum dependency (which BSD doesn't have or can't
        easily have or requires extra options or whatever...), by doing it
        in perl.  (Requires Digest::MD5, which is probably available
        anyway, but since this is only for the test suite, meh!)

diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index e6f9eab..b893e0a 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -121,6 +121,9 @@ Here's how:
             -   VREF/partial-copy           =   @all
             config gitolite.partialCopyOf   =   foo
 
+    **IMPORTANT**: if you're using other VREFs, please make sure this one is
+    placed at the end, after all the others.
+
 And that should be it.  **Please test it and let me know if it doesn't work!**
 
 WARNINGS:
diff --git a/src/VREF/partial-copy b/src/VREF/partial-copy
index efb73bf..19111de 100755
--- a/src/VREF/partial-copy
+++ b/src/VREF/partial-copy
@@ -20,13 +20,13 @@ exec >&2
 main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
 [ -z "$main" ] && exit 0
 
-rand=$RANDOM
+rand=$$
 export GL_BYPASS_ACCESS_CHECKS=1
 
-git push -f $GL_REPO_BASE/$main.git $new:refs/heads/br-$rand || die "FATAL: failed to send $new"
+git push -f $GL_REPO_BASE/$main.git $new:refs/partial/br-$rand || die "FATAL: failed to send $new"
 
 cd $GL_REPO_BASE/$main.git
-git update-ref -d refs/heads/br-$rand
+git update-ref -d refs/partial/br-$rand
 git update-ref $ref $new $old || die "FATAL: update-ref for $ref failed"
 
 exit 0
diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index 07838f0..48a2330 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -8,15 +8,18 @@ package Gitolite::Test;
   try
   put
   text
+  lines
   dump
   confreset
   confadd
   cmp
+  md5sum
 );
 #>>>
 use Exporter 'import';
 use File::Path qw(mkpath);
 use Carp qw(carp cluck croak confess);
+use Digest::MD5 qw(md5_hex);
 
 use Gitolite::Common;
 
@@ -25,6 +28,7 @@ BEGIN {
     *{'try'}  = \&Tsh::try;
     *{'put'}  = \&Tsh::put;
     *{'text'} = \&Tsh::text;
+    *{'lines'} = \&Tsh::lines;
     *{'cmp'}  = \&Tsh::cmp;
 }
 
@@ -100,4 +104,12 @@ sub confadd {
     put "|cat >> conf/$file", $string;
 }
 
+sub md5sum {
+    my $out = '';
+    for my $file (@_) {
+        $out .= md5_hex(slurp($file)) . "  $file\n";
+    }
+    return $out;
+}
+
 1;
diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index 2097acd..2b7dcee 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -473,9 +473,9 @@ sub fail {
 }
 
 sub cmp {
-    # compare input string with text()
-    my $text = text();
+    # compare input string with second input string or text()
     my $in   = shift;
+    my $text = ( @_ ? +shift : text() );
 
     if ( $text eq $in ) {
         ok();
diff --git a/t/0-me-first.t b/t/0-me-first.t
index a11dfd0..dc8916b 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -32,7 +32,7 @@ try "
     glt clone u1 file://aa u1aa;    ok;     /Cloning into 'u1aa'.../
                                             /warning: You appear to have cloned an empty repository/
     ls -ald --time-style=long-iso u1aa;
-                                    ok;     /drwxr-xr-x 3 $ENV{USER} $ENV{USER} 4096 201.-..-.. ..:.. u1aa/
+                                    ok;     /drwxr-xr-x 3 $ENV{USER} $ENV{USER} \\d+ 201.-..-.. ..:.. u1aa/
 
     # basic clone deny
     glt clone u4 file://aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
diff --git a/t/fork.t b/t/fork.t
index afa88ef..38cb200 100755
--- a/t/fork.t
+++ b/t/fork.t
@@ -59,13 +59,14 @@ try "
 
 # now check the various files that should have been produced
 
-try "cd $rb; find . -name gl-perms | sort | xargs md5sum"; cmp
+my $t;
+try "cd $rb; find . -name gl-perms"; $t = md5sum(sort (lines())); cmp $t,
 '59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
 59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1e.git/gl-perms
 ';
 
-try "cd $rb; find . -name gl-creator | sort | xargs md5sum"; cmp
-'346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1a2.git/gl-creator
-e4774cdda0793f86414e8b9140bb6db4  ./foo/u1/u1a.git/gl-creator
+try "cd $rb; find . -name gl-creator"; $t = md5sum(sort (lines())); cmp $t,
+'e4774cdda0793f86414e8b9140bb6db4  ./foo/u1/u1a.git/gl-creator
+346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1a2.git/gl-creator
 346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1e.git/gl-creator
 ';
diff --git a/t/git-config.t b/t/git-config.t
index 437e1cc..18f2e78 100755
--- a/t/git-config.t
+++ b/t/git-config.t
@@ -15,6 +15,8 @@ try "pwd";
 my $od = text();
 chomp($od);
 
+my $t;  # temp
+
 # try an invalid config key
 confreset;confadd '
 
@@ -62,13 +64,15 @@ try "ADMIN_PUSH set1; !/FATAL/" or die text();
 my $rb = `gitolite query-rc -n GL_REPO_BASE`;
 try "
     cd $rb;                             ok
-    egrep foo\\|bar *.git/config | sort
+    egrep foo\\|bar *.git/config
 ";
-cmp 'bar.git/config:	bare = true
-bar.git/config:	bar = one
+$t = join("\n", sort (lines()));
+
+cmp $t, 'bar.git/config:	bar = one
+bar.git/config:	bare = true
 bar.git/config:[foo]
-foo.git/config:	bare = true
 foo.git/config:	bar = f1
+foo.git/config:	bare = true
 foo.git/config:[foo]
 frob.git/config:	bar = dft
 frob.git/config:	bare = true
@@ -76,8 +80,7 @@ frob.git/config:[foo]
 gitolite-admin.git/config:	bare = true
 testing.git/config:	bar = dft
 testing.git/config:	bare = true
-testing.git/config:[foo]
-';
+testing.git/config:[foo]';
 
 try "cd $od; ok";
 
@@ -97,23 +100,23 @@ try "ADMIN_PUSH set1; !/FATAL/" or die text();
 
 try "
     cd $rb;                             ok
-    egrep foo\\|bar *.git/config | sort
+    egrep foo\\|bar *.git/config
 ";
+$t = join("\n", sort (lines()));
 
-cmp 'bar.git/config:	bare = true
-bar.git/config:	bar = one
+cmp $t, 'bar.git/config:	bar = one
+bar.git/config:	bare = true
 bar.git/config:[foo]
-foo.git/config:	bare = true
 foo.git/config:	bar = f1
+foo.git/config:	bare = true
 foo.git/config:[foo]
-frob.git/config:	bare = true
 frob.git/config:	bar = none
+frob.git/config:	bare = true
 frob.git/config:[foo]
 gitolite-admin.git/config:	bare = true
 testing.git/config:	bar = dft
 testing.git/config:	bare = true
-testing.git/config:[foo]
-';
+testing.git/config:[foo]';
 
 try "cd $od; ok";
 
@@ -129,22 +132,22 @@ try "ADMIN_PUSH set1; !/FATAL/" or die text();
 
 try "
     cd $rb;                             ok
-    egrep foo\\|bar *.git/config | sort
+    egrep foo\\|bar *.git/config
 ";
+$t = join("\n", sort (lines()));
 
-cmp 'bar.git/config:	bare = true
+cmp $t, 'bar.git/config:	bare = true
 bar.git/config:[foo]
-foo.git/config:	bare = true
 foo.git/config:	bar = f1
+foo.git/config:	bare = true
 foo.git/config:[foo]
-frob.git/config:	bare = true
 frob.git/config:	bar = none
+frob.git/config:	bare = true
 frob.git/config:[foo]
 gitolite-admin.git/config:	bare = true
 testing.git/config:	bar = dft
 testing.git/config:	bare = true
-testing.git/config:[foo]
-';
+testing.git/config:[foo]';
 
 try "cd $od; ok";
 
@@ -170,21 +173,20 @@ try "
 
 try "
     cd $rb;                             ok
-    egrep foo\\|bar *.git/config | sort
-    find . -name config | xargs egrep foo\\|bar | sort
+    find . -name config | xargs egrep foo\\|bar
 ";
+$t = join("\n", sort (lines()));
 
-cmp './bar/u2/one.git/config:	bare = true
-./bar/u2/one.git/config:	bar = one
+cmp $t, './bar/u2/one.git/config:	bar = one
+./bar/u2/one.git/config:	bare = true
 ./bar/u2/one.git/config:[foo]
-./foo.git/config:	bare = true
 ./foo.git/config:	bar = f1
+./foo.git/config:	bare = true
 ./foo.git/config:[foo]
-./frob.git/config:	bare = true
 ./frob.git/config:	bar = f1
+./frob.git/config:	bare = true
 ./frob.git/config:[foo]
 ./gitolite-admin.git/config:	bare = true
 ./testing.git/config:	bar = dft
 ./testing.git/config:	bare = true
-./testing.git/config:[foo]
-';
+./testing.git/config:[foo]';
diff --git a/t/partial-copy.t b/t/partial-copy.t
index 6b8dfdc..5bff843 100755
--- a/t/partial-copy.t
+++ b/t/partial-copy.t
@@ -90,20 +90,20 @@ try "
     tc u4n1 u4n2
     PUSH u4 next; ok
         /To .*/foo.git/
-        /new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\\d+/
+        /new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> refs/partial/br-\\d+/
         /file:///foo-pc/
         /52c7716..ca37871  next -> next/
     tag u4/nexttag; glt push u4 --tags
         /To file:///foo-pc/
         /\\[new tag\\]         u4/nexttag -> u4/nexttag/
-        /\\[new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\\d+/
+        /\\[new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> refs/partial/br-\\d+/
 
     checkout master
     checkout -b dev/u4/u4master
     tc devu4m1 devu4m2
     PUSH u4 HEAD; ok
         /To .*/foo.git/
-        /new branch\\]      228353950557ed1eb13679c1fce4d2b4718a2060 -> br-\\d+/
+        /new branch\\]      228353950557ed1eb13679c1fce4d2b4718a2060 -> refs/partial/br-\\d+/
         /file:///foo-pc/
         /new branch.* HEAD -> dev/u4/u4master/
 

commit e919a0b7ca6a519c4aba35cdca2e3bda340299e3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 28 12:02:15 2012 +0530

    solaris doesn't like 'hostname -s'...
    
    (luckily, unlike linux, it doesn't spew a usage message to STDOUT!)

diff --git a/src/commands/info b/src/commands/info
index f3217ee..0b789bd 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -49,7 +49,7 @@ sub args {
 }
 
 sub print_version {
-    chomp( my $hn = `hostname -s` );
+    chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
     my $gv = substr( `git --version`, 12 );
     $ENV{GL_USER} or _die "GL_USER not set";
     print "hello $ENV{GL_USER}, this is $ENV{USER}\@$hn running gitolite3 " . version() . " on git $gv\n";

commit 7d6b04605d5caad2475005592c6eece6f9e18d70
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 28 04:54:05 2012 +0530

    fix test suite's dependency on time zone
    
    I had not remembered that the 'tc' subcommand in tsh adds *text* that
    contains the current time, so commit SHAs were changing.
    
    Thanks to milki for catching this, and in fact being the only person who
    ever appears to have attempted to run the test suite at all!

diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index 581a844..2097acd 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -583,7 +583,7 @@ sub dummy_commits {
             test_tick();
             next;
         }
-        my $ts = ( $tick ? localtime($tick) : localtime() );
+        my $ts = ( $tick ? gmtime($tick+19800) : gmtime() );
         _sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
     }
 }

commit eabcf83deedd6524c57fd5468d9354c27aa7369c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 26 16:33:26 2012 +0530

    (minor) add titles to rendered HTML docs

diff --git a/doc/mkdoc b/doc/mkdoc
index bce1013..72470d3 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -38,9 +38,10 @@ sub main {
     chomp(@ARGV = `find . -name "*.mkd" | cut -c3-`) if not @ARGV;
     @ARGV = grep { /./ } @ARGV;
     my @save = @ARGV;
-    my $css = join("", <DATA>);
+    my $css_block = join("", <DATA>);
 
     my %ct; # chapter tocs
+    my %title;
     my $mf = '';
     my $fh;
 
@@ -49,6 +50,7 @@ sub main {
         my $b = $1;
 
         if (/^(#+) (?:#(\S+) )?(.*)/) {
+            $title{$b} ||= $3;
             if ( length($1) == 1 ) {
                 $ct{$b} .= "\n";
                 $ct{$b} .= "  * [$3][$b]\n";
@@ -80,6 +82,9 @@ sub main {
         $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
         my $b = $1;
 
+        my $css = $css_block;
+        $css =~ s/%TITLE/$title{$b} || $b/e;
+
         open($fh, ">", "../html/$b.html")
           and print $fh $css
           and close $fh;
@@ -95,7 +100,9 @@ sub main {
 
 __DATA__
 
-<head><style>
+<head>
+    <title>%TITLE</title>
+<style>
     body        { background: #fff; text-color: #000; margin-left:  40px;   font-size:  0.9em;  font-family: sans-serif; max-width: 800px; }
     h1          { background: #ffb; text-color: #000; margin-left: -30px;   border-top:    5px  solid #ccc; }
     h2          { background: #ffb; text-color: #000; margin-left: -20px;   border-top:    3px  solid #ddd; }
@@ -104,7 +111,8 @@ __DATA__
     code        { font-size:    1.1em;  background:  #ddf; text-color: #000; }
     pre         { margin-left:  2em;    background:  #ddf; text-color: #000; }
     pre code    { font-size:    1.1em;  background:  #ddf; text-color: #000; }
-</style></head>
+</style>
+</head>
 
 <p style="text-align:center">
     <a href="master-toc.html">master TOC</a>

commit a952f2d6272ce02ea06f65523c54acd9d10acb8f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 26 16:04:46 2012 +0530

    add COPYING file
    
    (from http://www.gnu.org/licenses/gpl-2.0.txt)
    
    thanks to Jon Ciesla for catching this omission...

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..7d5393a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,278 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.

commit e0ed14172bca04614dd6edf8c15aced2a92b0827
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 24 14:09:17 2012 +0530

    add migration example, plus some other little mig doc fixes

diff --git a/doc/g2migr-example.mkd b/doc/g2migr-example.mkd
new file mode 100644
index 0000000..18c2ba8
--- /dev/null
+++ b/doc/g2migr-example.mkd
@@ -0,0 +1,269 @@
+# migration example
+
+This shows what a typical migration would look like, using a test setup.
+
+[[TOC]]
+
+## existing setup
+
+The existing gitolite is the latest in the "g2" (v2.x) branch.
+
+First, the rc file has the following lines different from the default:
+
+    -$GL_WILDREPOS = 0;
+    +$GL_WILDREPOS = 1;
+
+    -$GL_GITCONFIG_KEYS = "";
+    +$GL_GITCONFIG_KEYS = ".*";
+
+Next, the conf/gitolite.conf file in `~/.gitolite`:
+
+    repo    gitolite-admin
+            RW+             =   tester u1
+
+    repo    testing
+            RW+     =   @all
+
+    repo foo
+            RW+             =   u1 u2
+            RW+ NAME/       =   u1
+            RW+ NAME/u2     =   u2
+
+    repo bar
+            RW  =   u2
+
+    repo baz/..*
+            C   =   u3 u4
+            RW+         =   CREATOR
+            config foo.bar = baz
+
+(Note that this conf file has NAME/ rules, which **have changed**
+significantly in g3; see [here][g2i-name] for details).
+
+These are the repos already existing
+
+    $ find repositories -name "*.git" | sort
+    repositories/bar.git
+    repositories/baz/u3.git
+    repositories/baz/u4.git
+    repositories/baz/uthree.git
+    repositories/foo.git
+    repositories/gitolite-admin.git
+    repositories/testing.git
+
+The config entries exist for all the baz/ repos:
+
+    $ grep -2 foo `find repositories -name "config" `
+    repositories/baz/uthree.git/config-[gitweb]
+    repositories/baz/uthree.git/config- owner = u3
+    repositories/baz/uthree.git/config:[foo]
+    repositories/baz/uthree.git/config- bar = baz
+    --
+    repositories/baz/u4.git/config-[gitweb]
+    repositories/baz/u4.git/config-     owner = u4
+    repositories/baz/u4.git/config:[foo]
+    repositories/baz/u4.git/config-     bar = baz
+    --
+    repositories/baz/u3.git/config-[gitweb]
+    repositories/baz/u3.git/config-     owner = u3
+    repositories/baz/u3.git/config:[foo]
+    repositories/baz/u3.git/config-     bar = baz
+
+## preparing for the migration
+
+### getting g3
+
+Fortunately this is easy here; I just happened to have the repo already
+fetched so I just had to switch branches.  You may have to 'git clone ...'
+from github.
+
+    $ cd gitolite
+    $ git checkout master
+    Branch master set up to track remote branch master from origin.
+    Switched to a new branch 'master'
+
+### run check-g2-compat
+
+This is a quick and dirty program to catch some of the big issues.
+
+    $ cd
+    $ gitolite/check-g2-compat
+    INFO        This program only checks for uses that make the new g3 completely unusable
+                or that might end up giving *more* access to someone if migrated as-is.
+                It does NOT attempt to catch all the differences described in the docs.
+
+    INFO        'see docs' usually means doc/g2migr.mkd
+                (online at http://sitaramc.github.com/gitolite/g3/g2migr.html)
+
+    checking rc file...
+    NOTE        GL_ADMINDIR is in the right place; assuming you did not mess with
+                GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED
+
+    checking conf file(s)...
+    SEVERE      NAME rules; see docs
+
+    checking repos...
+    WARNING     found 3 gl-creater files; see docs
+
+    ...all done...
+
+## the actual migration
+
+Here's the actual migration, step by step
+
+### step 1
+
+    $ ls -a bin
+    .                gl-admin-push    gl-install       gl-setup-authkeys  gl-VREF-DUPKEYS
+    ..               gl-auth-command  gl-mirror-push   gl-system-install  gl-VREF-EMAIL_CHECK
+    gitolite_env.pm  gl-compile-conf  gl-mirror-shell  gl-time            gl-VREF-FILETYPE
+    gitolite.pm      gl-conf-convert  gl-query-rc      gl-tool            gl-VREF-MERGE_CHECK
+    gitolite_rc.pm   gl-dryrun        gl-setup         gl-VREF-COUNT      sshkeys-lint
+    $ rm -rf bin;mkdir bin
+
+    $ grep GL_PACKAGE .gitolite.rc
+    $GL_PACKAGE_CONF = "/home/g3/share/gitolite/conf";
+    $GL_PACKAGE_HOOKS = "/home/g3/share/gitolite/hooks";
+    $ rm -rf share
+
+    $GL_PACKAGE_HOOKS = "/home/g3/share/gitolite/hooks";
+    $ rm -rf share
+
+    $ mv .gitolite.rc old.grc
+
+(still on step 1, this is substep 3) notice we are cloning **on the server**,
+using a **full path** to the repo.
+
+    $ git clone repositories/gitolite-admin.git old.ga
+    Cloning into 'old.ga'...
+    done.
+    $ rm -rf repositories/gitolite-admin.git/
+
+Since I'm not interested in preserving the logs and don't have any custom
+hooks:
+
+    $ rm -rf .gitolite
+
+### step 2
+
+I have no variables that *must* be preset, since the report by
+`check-g2-compat` is clear.
+
+### step 3
+
+Here we install the new gitolite.  Remember we already got the new software
+(in order to run 'check-g2-compat').
+
+Just check that bin is empty, then run 'install -ln' from the gitolite source
+tree:
+
+    $ ls -al bin
+    total 8
+    drwxrwxr-x 2 g3 g3 4096 Apr 24 10:57 .
+    drwx------ 8 g3 g3 4096 Apr 24 10:59 ..
+    $ gitolite/install -ln
+    $ ls -al bin
+    total 8
+    drwxrwxr-x 2 g3 g3 4096 Apr 24 11:01 .
+    drwx------ 8 g3 g3 4096 Apr 24 10:59 ..
+    lrwxrwxrwx 1 g3 g3   30 Apr 24 11:01 gitolite -> /home/g3/gitolite/src/gitolite
+
+OK that went well.  Now setup gitolite.  You don't need a key here; just use a
+random name:
+
+    $ gitolite setup -a admin
+    Initialized empty Git repository in /home/g3/repositories/gitolite-admin.git/
+
+### step 4
+
+Now go to your old clone, and push it:
+
+    $ cd old.ga
+    $ gitolite push -f
+        ...usual git progress output deleted...
+    remote: FATAL: git config foo.bar not allowed
+    remote: check GIT_CONFIG_KEYS in the rc file
+    To /home/g3/repositories/gitolite-admin.git
+     + 7eb8163...1474770 master -> master (forced update)
+
+Aaha!  I forgot to set `CONFIG_KEYS` (new name for `GL_GIT_CONFIG_KEYS`) in
+the new rc file so fix that:
+
+    $ vim ~/.gitolite.rc
+    (edit and set it to `.*` for now)
+
+and push again:
+
+    $ gitolite push -f
+    Everything up-to-date
+
+Damn.  We have to make a dummy commit to allow the push to do something.
+
+But wait!  We forgot fix the [NAME/][g2i-name] rules, so may as well fix
+those, add, and push:
+
+    $ vim conf/gitolite.conf
+    # change all NAME/ to VREF/NAME/
+    # append a '- VREF/NAME/ = @all' at the end
+    # save
+    git add conf
+
+    $ git commit -m name-rules
+        ... some output for add...
+
+    $ gitolite push -f
+    Counting objects: 1, done.
+    Writing objects: 100% (1/1), 181 bytes, done.
+    Total 1 (delta 0), reused 0 (delta 0)
+    Unpacking objects: 100% (1/1), done.
+    To /home/g3/repositories/gitolite-admin.git
+       1474770..4c2b41d  master -> master
+
+### step 5
+
+The only thing left is to fix up the gl-creater files:
+
+    $ cd $HOME/repositories
+    $ find . -type d -name "*.git" -prune | while read r
+    > do
+    >     mv $r/gl-creater $r/gl-creator
+    > done 2>/dev/null
+
+And we're done!
+
+## checking things out
+
+Let's see what repos u3 has:
+
+    ssh u3 info
+    hello u3, this is g3 at sita-lt running gitolite3 v3.0-11-g090b0f5 on git 1.7.7.6
+
+         C      baz/..*
+     R W        baz/u3
+     R W        baz/uthree
+     R W        gitolite-admin
+     R W        testing
+
+That's a combination of 'info' and 'expand', by the way.  There is no expand
+command any more.
+
+How about adding a new repo and checking if the config entries made it?
+
+    $ git ls-remote u4:baz/ufour
+    Initialized empty Git repository in /home/g3/repositories/baz/ufour.git/
+    $ grep -A1 foo `find repositories -name "config" `
+    repositories/baz/u3.git/config:[foo]
+    repositories/baz/u3.git/config- bar = baz
+    --
+    repositories/baz/u4.git/config:[foo]
+    repositories/baz/u4.git/config- bar = baz
+    --
+    repositories/baz/ufour.git/config:[foo]
+    repositories/baz/ufour.git/config-      bar = baz
+    --
+    repositories/baz/uthree.git/config:[foo]
+    repositories/baz/uthree.git/config-     bar = baz
+
+And there it is, in the second block of lines...
+
+And now we're really done.
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 46079a8..e7f2864 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -145,7 +145,7 @@ Some of them have links where there is more detail than I want to put here.
     business touching these anyway; if you did, move them into the expected
     default locations before attempting to run `gitolite setup`
 
-  * `GL_GITCONFIG_KEYS` -- is now `GITCONFIG_KEYS`.
+  * `GL_GITCONFIG_KEYS` -- is now `GIT_CONFIG_KEYS`.
 
   * `GL_LOGT` -- now has a fixed value; email me if this is a problem.
 
diff --git a/doc/install.mkd b/doc/install.mkd
index 65697ab..ad681ec 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -166,13 +166,21 @@ are the steps:
     you were to a default install of the old gitolite, the less time a
     migration will take.)
 
+    This includes at least running `check-g2-compat` to see what are the big
+    issues you might need to address during the migration.
+
 ### the actual migration
 
+(Note: You may also like the [example migration][g2migr-example] page).
+
 **Note**: nothing in any of the gitolite install/setup/etc will ever touch the
 *data* in any repository except the gitolite-admin repo.  The only thing it
 will normally touch in normal repos is the `update` hook.
 
-1.  **On the server**, carefully wipe out the old gitolite:
+**Note: all migration happens on the server; you do not need your
+workstation**.
+
+1.  Carefully wipe out the old gitolite:
 
       * The **code**
 
@@ -209,7 +217,11 @@ will normally touch in normal repos is the `update` hook.
     your new rc file.
 
 3.  Install gitolite g3; see [quick install and setup][qi] or [install][]
-    followed by [setup][].
+    followed by [setup][].  However, the 'setup' step need not supply a
+    private key.  You can run it as `gitolite setup -a admin`.
+
+    NOTE: ignore any 'split conf not set, gl-conf present...' errors at this
+    time.  You may see none, some, or many.  It does not matter right now.
 
 4.  Make sure your gitolite-admin clone has the correct pubkey for the
     administrator in its `keydir` directory, then run [`gitolite push

commit 9e1cb5936c984a28b7363c0491ea4cd81de93604
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 09:33:57 2012 +0530

    (some docfixes)

diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index aba2961..46079a8 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -116,6 +116,16 @@ Some of them have links where there is more detail than I want to put here.
 
 (ancillary, non-core, or minor functionality lost)
 
+  * Built-in command `expand` -- **dropped**.  The 'info' command shows you
+    both normal and wild repos now.  The output format is also much simpler.
+
+  * Built-in commands 'getperms', 'setperms' -- **merged** into external
+    command 'perms'.  Run `ssh git at host perms -h` for details.
+
+    Similarly, 'getdesc' and 'setdesc' have been merged into 'desc'.
+
+  * Several 'ADC's -- see the [dev-status][] page for more on this.
+
   * [gl-time][g2i-gl-time]: the CpuTime module replaces gl-time.
 
   * `BIG_INFO_CAP` -- **dropped**.  If you think you must have this, try it
diff --git a/doc/install.mkd b/doc/install.mkd
index e3e925f..65697ab 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -99,10 +99,13 @@ you can run the 'install' command in 3 different ways:
 
     # option 2
     gitolite/install -ln
-    # defaults to $HOME/bin, or use a specific directory:
+    # defaults to $HOME/bin (which is assumed to exist)
+    #   ** or **
+    # or use a specific directory (please supply full path):
     gitolite/install -ln /usr/local/bin
 
     # option 3
+    # (again, please supply a full path)
     gitolite/install -to /usr/local/gitolite/bin
 
 Creating a symlink doesn't need a separate program but 'install' also runs
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index eb0e7aa..e6f9eab 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -22,7 +22,7 @@ Here's a list of remote commands that are shipped:
   * 'info' -- already documented [here][info]
   * 'mirror' -- documented [here][sync]
   * 'perms' -- get/set the gl-perms file; see [perms][] for more
-  * 'sskm' -- self-service key management
+  * 'sskm' -- self-service key management, see [sskm][] for more
   * 'writable' -- disabling pushes to take backups etc
   * 'D' -- deleting user-created repos
 
diff --git a/doc/progit.mkd b/doc/progit.mkd
index 1e7856d..fe4e304 100644
--- a/doc/progit.mkd
+++ b/doc/progit.mkd
@@ -20,6 +20,7 @@ To begin, create a user called `git` on your server and login to this user.  Cop
 
     git clone git://github.com/sitaramc/gitolite
     gitolite/install -ln
+        # assumes $HOME/bin exists and is in your $PATH
     gitolite setup -pk $HOME/YourName.pub
         # for example, I would run 'gitolite setup -pk $HOME/sitaram.pub'
 
diff --git a/doc/qi.mkd b/doc/qi.mkd
index 563071b..2ac9470 100644
--- a/doc/qi.mkd
+++ b/doc/qi.mkd
@@ -23,8 +23,8 @@ On your workstation:
   * This is a fresh install, not a migration from the old gitolite (v1.x,
     v2.x).
 
-  * On the server, your `$PATH` contains `$HOME/bin`.  If you don't like that,
-    there are [other install methods][install].
+  * On the server, `$HOME/bin` exists and is in your `$PATH`.  If you don't
+    like that, there are [other install methods][install].
 
   * "your-name.pub" is your public key from your workstation.
       * Also, this key does not already have shell access to this gitolite
diff --git a/doc/rc.mkd b/doc/rc.mkd
index 04c88f1..2c84d88 100644
--- a/doc/rc.mkd
+++ b/doc/rc.mkd
@@ -80,3 +80,20 @@ information.
     with regular expressions) is to allow anything and everything:
     `$GIT_CONFIG_KEYS = '.*';`
 
+  * `DEFAULT_ROLE_PERMS`, string, default undef
+
+    This sets default wildcard permissions for newly created wildcard repos.
+
+    If set, this value will be used as the default role permissions for new
+    wildcard repositories. The user can change this value with the perms
+    command as desired after repository creation; it is only a default.
+
+    Please be aware this is potentially a multi-line variable.  In most
+    setups, it will be left undefined.  Some installations may benefit from
+    setting it to `READERS @all`.
+
+    If you want multiple roles to be assigned by default, here is how.  Note
+    double quotes this time, due to the embedded newline, which in turn
+    require the '@' to be escaped:
+
+        DEFAULT_ROLE_PERMS  =>  "READERS \@all\nWRITERS \@senior_devs",
diff --git a/doc/sskm.mkd b/doc/sskm.mkd
index e9ce302..4ac908d 100644
--- a/doc/sskm.mkd
+++ b/doc/sskm.mkd
@@ -221,12 +221,12 @@ Listing the keys shows that that new key is now marked active again:
 
 ## important notes for the admin
 
-These are the things that can break if you allows your users to use this command:
+These are the things that can break if you allow your users to use this
+command:
 
-  * If you, as the gitolite admin, are in the habit of force-pushing changes
-    to the admin repo instead of doing a `git pull` (or, even better, a `git
-    pull --rebase`) then you had better not enable this command.  Your users
-    will eventually come after you with pitchforks ;-)
+  * "sskm" clones, changes, and pushes back the gitolite-admin repo.  This
+    means, even if you're the only administrator, you should never 'git push
+    -f', in case you end up overwriting something sskm did.
 
   * There is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using
     this command.  You can distinguish `foo/alice.pub` from

commit 5d366b5c0e3406478183247a235dc60910b8cf83
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 19 20:05:47 2012 +0530

    new VREF: MAX_NEWBIN_SIZE (manual spot testing only)

diff --git a/src/VREF/MAX_NEWBIN_SIZE b/src/VREF/MAX_NEWBIN_SIZE
new file mode 100755
index 0000000..84a9efa
--- /dev/null
+++ b/src/VREF/MAX_NEWBIN_SIZE
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# gitolite VREF to check max size of new binary files
+
+# see gitolite docs for what the first 7 arguments mean
+
+# inputs:
+#   arg-8 is a number
+# outputs (STDOUT)
+#   arg-7 if any new binary files exist that are greater in size than arg-8
+#   *and* there is no "signed-off by" line for such a file in the top commit
+#   message.
+#
+#   Otherwise nothing
+# exit status:
+#   always 0
+
+die "not meant to be run manually" unless $ARGV[7];
+
+my ( $newsha, $oldtree, $newtree, $refex, $max ) = @ARGV[ 2, 3, 4, 6, 7 ];
+
+# / (.*) +\| Bin 0 -> (\d+) bytes/
+
+chomp( my $author_email = `git log --format=%ae -1 $newsha` );
+my $msg = `git cat-file -p $newsha`;
+$msg =~ s/\t/ /g;    # makes our regexes simpler
+
+for my $newbin (`git diff --stat=999,999 $oldtree $newtree | grep Bin.0.-`) {
+    next unless $newbin =~ /^ (.*) +\| +Bin 0 -> (\d+) bytes/;
+    my ( $f, $s ) = ( $1, $2 );
+    next if $s <= $max;
+
+    next if $msg =~ /^ *$f +signed-off by: *$author_email *$/mi;
+
+    print "$refex $f is larger than $max";
+}
+
+exit 0

commit d74f596e231fe349961466dc2f00bfc931f88312
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 23 06:22:03 2012 +0530

    make can_write() in Easy.pm more flexible

diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 280a924..9231d00 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -112,14 +112,18 @@ sub can_read {
 }
 
 # can_write()
-# return true if $ENV{GL_USER} is set and can write to the given repo
+# return true if $ENV{GL_USER} is set and can write to the given repo.
+# Optional second argument can be '+' to check that instead of 'W'.  Optional
+# third argument can be a full ref name instead of 'any'.
 
 # shell equivalent
 #   if gitolite access -q $REPONAME $GL_USER W; then ...
 sub can_write {
     valid_user();
-    my $r = shift;
-    return not( access( $r, $user, 'W', 'any' ) =~ /DENIED/ );
+    my ($r, $aa, $ref) = @_;
+    $aa ||= 'W';
+    $ref ||= 'any';
+    return not( access( $r, $user, $aa, $ref ) =~ /DENIED/ );
 }
 
 # config()
diff --git a/t/easy.t b/t/easy.t
index c626602..dcd6c1a 100755
--- a/t/easy.t
+++ b/t/easy.t
@@ -18,7 +18,7 @@ sub ok { (+shift) ? print "ok\n" : print "not ok\n"; }
 sub nok { (+shift) ? print "not ok\n" : print "ok\n"; }
 sub msg { return unless $ENV{D}; print STDERR "#" . +shift . "\n"; }
 
-try "plan 90";
+try "plan 98";
 
 try "
     cat $ENV{HOME}/.gitolite.rc
@@ -117,6 +117,11 @@ $ENV{GL_USER} = "u2"; ok(can_write("aa"));
 $ENV{GL_USER} = "u3"; nok(can_write("aa"));
 $ENV{GL_USER} = "u4"; nok(can_write("aa"));
 
+$ENV{GL_USER} = "u1"; ok(can_write("aa", "+"));
+$ENV{GL_USER} = "u2"; nok(can_write("aa", "+"));
+$ENV{GL_USER} = "u3"; nok(can_write("aa", "+"));
+$ENV{GL_USER} = "u4"; nok(can_write("aa", "+"));
+
 $ENV{GL_USER} = "u1"; nok(can_write("bb"));
 $ENV{GL_USER} = "u2"; nok(can_write("bb"));
 $ENV{GL_USER} = "u3"; nok(can_write("bb"));
@@ -132,6 +137,11 @@ $ENV{GL_USER} = "u4"; ok(can_write("cc/u4"));
 $ENV{GL_USER} = "u5"; ok(can_write("cc/u4"));
 $ENV{GL_USER} = "u6"; nok(can_write("cc/u4"));
 
+$ENV{GL_USER} = "u3"; nok(can_write("cc/u4", "+"));
+$ENV{GL_USER} = "u4"; ok(can_write("cc/u4", "+"));
+$ENV{GL_USER} = "u5"; ok(can_write("cc/u4", "+"));
+$ENV{GL_USER} = "u6"; nok(can_write("cc/u4", "+"));
+
 # config
 try("glt ls-remote u4 cc/sub/one; /Initialized empty.*cc/sub/one/");
 try("glt ls-remote u4 cc/two; /Initialized empty.*cc/two/");

commit 198dcfd4c8100b4993c75cd2d8b6c57e86cf0aaa
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 21:25:54 2012 +0530

    POST_CREATE efficiency... (please read below if you care)
    
    The POST_CREATE trigger is called when
    
      * a user creates a new "wild" repo,
      * a user uses the "perms" command, and
      * a user uses the "fork" command.
    
    The trigger calls 3 programs (see rc file):
    
        post-compile/update-git-configs
        post-compile/update-gitweb-access-list
        post-compile/update-git-daemon-access-list
    
    (They are also called by the POST_COMPILE trigger, by the way.)
    
    However, the 3 programs shown are a bit wasteful -- they run through
    *all* the repos when really only *one* repo has been affected.
    
    This patch
    
      * passes the repo name to the 3 programs (duh!)
    
      * adds the optimisation to the first of the 3 programs listed above
        (the one dealing with 'git config').
    
    For the other two programs (gitweb and git-daemon), you have 3 choices:
    
      * if you don't have too many repos, ignore the problem.
    
      * take out the 2nd and 3rd lines from the POST_CREATE list in the rc
        file, so they don't run.
    
        Then run 'gitolite trigger POST_COMPILE' from cron at regular
        intervals.  (Note that is POST_COMPILE not POST_CREATE!)  However,
        this means that gitweb and daemon permissions won't be current
        immediately after someone adds a new repo or sets perms etc.; they
        get updated only on the next cron run.
    
      * patch the programs to add this optimisation (and send me the
        patches).  The optimisation would check if arg-1 ($1 in shell,
        $ARGV[0] in perl) is 'POST_CREATE', and if it is, take the *next*
        argument as a repo name that may have changed.

diff --git a/src/commands/fork b/src/commands/fork
index fb49d92..fe27035 100755
--- a/src/commands/fork
+++ b/src/commands/fork
@@ -59,4 +59,4 @@ ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
 echo "$from" > gl-forked-from
 
 # trigger post_create
-gitolite trigger POST_CREATE
+gitolite trigger POST_CREATE $to $GL_USER
diff --git a/src/commands/perms b/src/commands/perms
index dce271c..7be3a28 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -34,8 +34,9 @@ if ( $ARGV[0] eq '-l' ) {
     getperms(@ARGV);    # doesn't return
 }
 
+my $repo = shift;
 setperms(@ARGV);
-_system( "gitolite", "trigger", "POST_CREATE" );
+_system( "gitolite", "trigger", "POST_CREATE", $repo, $ENV{GL_USER} );
 
 # ----------------------------------------------------------------------
 
@@ -50,7 +51,6 @@ sub getperms {
 }
 
 sub setperms {
-    my $repo = shift;
     _die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 446b28a..7f3fb83 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -13,13 +13,30 @@ use Gitolite::Conf::Load;
 use strict;
 use warnings;
 
-# ----------------------------------------------------------------------
-
 my $RB = $rc{GL_REPO_BASE};
 _chdir($RB);
+
+# ----------------------------------------------------------------------
+# if called from POST_CREATE, we have only a single repo to worry about
+if (@ARGV and $ARGV[0] eq 'POST_CREATE') {
+    my $repo = $ARGV[1];
+    fixup_config($repo);
+
+    exit 0;
+}
+
+# ----------------------------------------------------------------------
+# else it's all repos (i.e., called from POST_COMPILE)
+
 my $lpr = list_phy_repos();
 
 for my $pr (@$lpr) {
+    fixup_config($pr);
+}
+
+sub fixup_config {
+    my $pr = shift;
+
     my $gc = git_config( $pr, '.' );
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;

commit 895b3614ed9211dcee8b76fd07a594f7a9c50a31
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 21:44:06 2012 +0530

    (minor) add a bit more detail on usage text for 'info'

diff --git a/src/commands/info b/src/commands/info
index 4eca761..f3217ee 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -12,7 +12,8 @@ use Gitolite::Conf::Load;
 =for args
 Usage:  gitolite info [-lc] [<repo name pattern>]
 
-List all repos/repo groups you can access.
+List all existing repos you can access, as well as repo name patterns you can
+create repos from (if any).
 
     '-lc'       lists creators as an additional field at the end.
 

commit 6b65e7853fe373169f9e9ff54688f0f53d416ec6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 19:49:56 2012 +0530

    (minor) add quotes to make repo name stand out in error message

diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 6878a70..8f1ebfe 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -209,7 +209,7 @@ sub load_1 {
     }
 
     if ( -f "gl-conf" ) {
-        _warn "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
+        _warn "split conf not set, gl-conf present for '$repo'" if not $split_conf{$repo};
 
         my $cc = "gl-conf";
         _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
@@ -218,7 +218,7 @@ sub load_1 {
         $repos{$repo} = $one_repo{$repo};
         $configs{$repo} = $one_config{$repo} if $one_config{$repo};
     } else {
-        _die "split conf set, gl-conf not present for $repo" if $split_conf{$repo};
+        _die "split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
     }
 }
 

commit 1ad0a761f791663204e94a58c74329d58b59e5fb
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 19:03:07 2012 +0530

    install should fail more gracefully if the '-ln' directory does not exist
    
    thanks to EugeneKay for catching this

diff --git a/install b/install
index 1cf7853..ca6a864 100755
--- a/install
+++ b/install
@@ -20,14 +20,21 @@ Usage (from gitolite clone directory):
     ./install
         to run gitolite using an absolute or relative path, for example
         'src/gitolite' or '/full/path/to/this/dir/src/gitolite'
+
     ./install -ln [<dir>]
         to symlink just the gitolite executable to some <dir> that is in
-        $PATH.  <dir> defauls to $HOME/bin if <dir> not specified.
+        $PATH.  <dir> defaults to $HOME/bin if <dir> not specified.  <dir> is
+        assumed to exist; gitolite will not create it.
+
+        Please provide a full path, not a relative path.
+
     ./install -to <dir>
         to copy the entire 'src' directory to <dir>.  If <dir> is not in
         $PATH, use the full path to run gitolite commands.
 
-Simplest use, if you have $HOME/bin in $PATH, is:
+        Please provide a full path, not a relative path.
+
+Simplest use, if $HOME/bin exists and is in $PATH, is:
 
     git clone -b g3 git://github.com/sitaramc/gitolite
     gitolite/install -ln
@@ -46,6 +53,12 @@ GetOptions(
 );
 usage() if $to and $ln or $help;
 $ln = "$ENV{HOME}/bin" if defined($ln) and not $ln;
+for my $d ($ln, $to) {
+    if ($d and not -d $d) {
+        print STDERR "FATAL: '$d' does not exist.\n";
+        usage();
+    }
+}
 
 chdir($ENV{GL_BINDIR});
 my $version = `git describe --tags --long --dirty=-dt`;

commit c3ec518cefba685ed36f6da40e17beb4fb9d0aee
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 22 09:34:06 2012 +0530

    fork command, and some core changes to make it work...
    
      - access command allows checking ^C
      - ^C check will fail when the repo exists

diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 207036a..eb0e7aa 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -18,6 +18,7 @@ bug to me if they don't.
 Here's a list of remote commands that are shipped:
 
   * 'desc' -- get/set the 'description' file for a repo
+  * 'fork' -- fork a repo
   * 'info' -- already documented [here][info]
   * 'mirror' -- documented [here][sync]
   * 'perms' -- get/set the gl-perms file; see [perms][] for more
diff --git a/src/commands/access b/src/commands/access
index db3ece0..cdefacb 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -42,7 +42,7 @@ if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
 my ( $repo, $user, $aa, $ref ) = @ARGV;
 $aa  ||= '+';
 $ref ||= 'any';
-_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M)$/ );
+_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M|\^C)$/ );
 _die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
 
 my $ret = '';
diff --git a/src/commands/fork b/src/commands/fork
new file mode 100755
index 0000000..fb49d92
--- /dev/null
+++ b/src/commands/fork
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+# Usage:    ssh git at host fork <repo1> <repo2>
+#
+# Forks repo1 to repo2.  You must have read permissions on repo1, and create
+# ("C") permissions for repo2, which of course must not exist.
+#
+# A fork is functionally the same as cloning repo1 to a client and pushing it
+# to a new repo2.  It's just a little more efficient, not just in network
+# traffic but because it uses git clone's "-l" option to share the object
+# store also, so it is likely to be almost instantaneous, regardless of how
+# big the repo actually is.
+#
+# The only caveat is that the repo you cloned *from* must not later become
+# unavailable in any way.  If you cannot be sure of this, take the scenic
+# route (clone repo1, push to repo2).
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+from=$1; shift
+to=$1; shift
+[ -z "$to" ] && usage
+
+gitolite access -q "$from" $GL_USER R any || die "'$from' does not exist or you are not allowed to read it"
+gitolite access -q "$to"   $GL_USER ^C any || die "'$to' already exists or you are not allowed to create it"
+
+# ----------------------------------------------------------------------
+# IMPORTANT NOTE: checking whether someone can create a repo is done as above.
+# However, make sure that the env var GL_USER is set, and that too to the same
+# value as arg-2 of the access command), otherwise it won't work.
+
+# Ideally, you'll leave such code to me.  There's a reason ^C is not listed in
+# the help message for 'gitolite access'.
+# ----------------------------------------------------------------------
+
+# clone $from to $to
+git clone --bare -l $GL_REPO_BASE/$from.git $GL_REPO_BASE/$to.git
+[ $? -ne 0 ] && exit 1
+
+echo "$from forked to $to" >&2
+
+# fix up creator, default role permissions (gl-perms), and hooks
+cd $GL_REPO_BASE/$to.git
+echo $GL_USER > gl-creator
+
+if gitolite query-rc -q DEFAULT_ROLE_PERMS
+then
+    gitolite query-rc DEFAULT_ROLE_PERMS > gl-perms
+fi
+
+ln -sf `gitolite query-rc GL_ADMIN_BASE`/hooks/common/* hooks
+
+# record where you came from
+echo "$from" > gl-forked-from
+
+# trigger post_create
+gitolite trigger POST_CREATE
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 3417d34..6878a70 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -80,6 +80,11 @@ sub access {
         $iret =~ s/\^C/$aa/;
         return $iret if $iret =~ /DENIED/;
     }
+    # similarly, ^C must be denied if the repo exists
+    if ( $aa eq '^C' and not repo_missing($repo) ) {
+        trace( 2, "DENIED by existence" );
+        return "$aa $ref $repo $user DENIED by existence";
+    }
 
     my @rules = rules( $repo, $user );
     trace( 2, scalar(@rules) . " rules found" );
@@ -367,7 +372,8 @@ sub generic_name {
     # get the creator name.  For not-yet-born repos this is $ENV{GL_USER},
     # which should be set in all cases that we care about, viz., where we are
     # checking ^C permissions before new_wild_repo(), and the info command.
-    # In particular, 'gitolite access' can't be used to check ^C perms.
+    # In particular, 'gitolite access' can't be used to check ^C perms on wild
+    # repos that contain "CREATOR" if GL_USER is not set.
     $creator = creator($base);
 
     $base2 = $base;
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 6386651..924fe3f 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -288,6 +288,7 @@ __DATA__
         {
             'help'              =>  1,
             'desc'              =>  1,
+            # 'fork'            =>  1,
             'info'              =>  1,
             # 'mirror'          =>  1,
             'perms'             =>  1,
diff --git a/t/fork.t b/t/fork.t
new file mode 100755
index 0000000..afa88ef
--- /dev/null
+++ b/t/fork.t
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src/lib";
+use Gitolite::Test;
+
+# fork command
+# ----------------------------------------------------------------------
+
+try "plan 30";
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+
+confreset;confadd '
+
+    repo foo/CREATOR/..*
+        C   =   u1 u2
+        RW+ =   CREATOR
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+
+    # make the initial repo
+    glt ls-remote u1 file:///foo/u1/u1a;ok;     gsh
+                                                /Initialized empty Git repository in .*/foo/u1/u1a.git/
+    # vrc doesn't have the fork command
+    glt fork u1 foo/u1/u1a foo/u1/u1a2; !ok;    /FATAL: unknown git/gitolite command: fork/
+";
+
+# allow fork as a valid command
+$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
+put "$ENV{G3T_RC}", "\$rc{COMMANDS}{fork} = 1;\n\$rc{DEFAULT_ROLE_PERMS} = 'READERS \@all';\n";
+
+try "
+    # now the fork succeeds
+    glt fork u1 foo/u1/u1a foo/u1/u1a2; ok;     /Cloning into bare repository '.*/foo/u1/u1a2.git'/
+                                                /foo/u1/u1a forked to foo/u1/u1a2/
+
+    # now the actual testing starts
+    # read error
+    glt fork u1 foo/u1/u1c foo/u1/u1d;  !ok;    /'foo/u1/u1c' does not exist or you are not allowed to read it/
+    glt fork u2 foo/u1/u1a foo/u1/u1d;  !ok;    /'foo/u1/u1a' does not exist or you are not allowed to read it/
+
+    # write error
+    glt fork u1 foo/u1/u1a foo/u2/u1d;  !ok;    /'foo/u2/u1d' already exists or you are not allowed to create it/
+
+    # no error
+    glt fork u1 foo/u1/u1a foo/u1/u1e;  ok;     /Cloning into bare repository '.*/foo/u1/u1e.git'/
+                                                /warning: You appear to have cloned an empty repository/
+                                                /foo/u1/u1a forked to foo/u1/u1e/
+    # both exist
+    glt fork u1 foo/u1/u1a foo/u1/u1e;  !ok;    /'foo/u1/u1e' already exists or you are not allowed to create it/
+";
+
+# now check the various files that should have been produced
+
+try "cd $rb; find . -name gl-perms | sort | xargs md5sum"; cmp
+'59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1a2.git/gl-perms
+59b3a74b4d33c7631f08e75e7b60c7ce  ./foo/u1/u1e.git/gl-perms
+';
+
+try "cd $rb; find . -name gl-creator | sort | xargs md5sum"; cmp
+'346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1a2.git/gl-creator
+e4774cdda0793f86414e8b9140bb6db4  ./foo/u1/u1a.git/gl-creator
+346955ff2eadbf76e19373f07dd370a9  ./foo/u1/u1e.git/gl-creator
+';
diff --git a/t/glt b/t/glt
index b335cc0..1bf31e8 100755
--- a/t/glt
+++ b/t/glt
@@ -13,6 +13,7 @@ my %extcmds = (
     help        => 1,
     info        => 1,
     desc        => 1,
+    fork        => 1,
     perms       => 1,
     writable    => 1,
 );

commit cf3dd885fcfc7ed63d83b7e4eaa1b9d517ed29fd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 19 06:33:36 2012 +0530

    (some docfixes)

diff --git a/README.mkd b/README.mkd
index e41f513..6400dc4 100644
--- a/README.mkd
+++ b/README.mkd
@@ -12,20 +12,27 @@ If you're reading this on the main gitolite page on github, several
     branch, and is now the actively maintained and supported software.  Do NOT
     try to merge this with your old "master" branch!
 
+    The [main page][h-mp] leads to several useful starting points.  The [table
+    of contents][h-mt] is a much more meaningfully ordered/structured list of
+    links (instead of putting them in alphabetical order of the filename, like
+    in g2!)
+
+    If you are an existing (g2) user and wish to migrate, you MUST read
+    [this](http://sitaramc.github.com/gitolite/install.html#migr).
+
 2.  Versions v2.x are on branch "g2".  It will be supported for security
     issues and serious bugs in core functionality, but not for anything less
-    critical.  Versions v1.x are completely unsupported now.
-
-If you're an existing (v1.x, v2.x) gitolite user please spend some time with
-the documentation for the new version before upgrading.  The [main page][h-mp]
-leads to several useful starting points.  The [table of contents][h-mt] is a
-much more meaningfully ordered/structured list of links (instead of putting
-them in alphabetical order of the filename, like in g2!)
+    critical.  Versions v1.x are completely unsupported now.  (Documentation
+    links for this version are [here][o1] and [here][o2]).
 
 [h-mp]: http://sitaramc.github.com/gitolite/
 [h-mt]: http://sitaramc.github.com/gitolite/master-toc.html
+[o1]: http://sitaramc.github.com/gitolite/g2/
+[o2]: http://sitaramc.github.com/gitolite/g2/master-toc.html
 
 ----
 
 License information for code and documentation is at the end of doc/index.mkd
-(or you can read it online [here][license]).
+(or you can read it online
+[here](http://sitaramc.github.com/gitolite/index.html#license)).
+
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
index c9af92a..03ef93f 100644
--- a/doc/dev-status.mkd
+++ b/doc/dev-status.mkd
@@ -2,7 +2,6 @@
 
 Not yet done (will be tackled in this order unless someone asks):
 
-  * svnserve (someone is testing it)
   * mechanism for ADCs using unchecked arguments -- this is not just a matter
     of writing it; I have to think about *how* it will be done.  (AFAIK the
     only tool affected is git-annexe; if there are more let me know)
@@ -33,3 +32,4 @@ Done:
   * distro packaging instructions
   * migration advice for common cases
   * smart http
+  * svnserve
diff --git a/doc/index.mkd b/doc/index.mkd
index b153b50..9552975 100644
--- a/doc/index.mkd
+++ b/doc/index.mkd
@@ -13,7 +13,7 @@ For users of gitolite v2.x (call it "g2" for convenience),
 
   * [Why][g3why] I rewrote gitolite.
   * Development [status][dev-status].
-  * Specific migration [issues and steps][g2migr].
+  * Information on [migrating][migr] from g2 to g3.
 
 </font>
 
diff --git a/doc/install.mkd b/doc/install.mkd
index 14f68ea..e3e925f 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -4,6 +4,12 @@
 settings that MUST be dealt with **before** running `gitolite setup`; please
 start [here][migr].  RTFM is *mandatory* for migrations.</font>
 
+----
+
+[[TOC]]
+
+----
+
 ## notes and naming conventions
 
 Gitolite uses a single "real" (i.e., unix) user to provide secure access to
@@ -138,8 +144,8 @@ Here are the requirements for gitolite:
 
 ## #migr migrating
 
-<font color="gray">If you're migrating from gitosis, [this][gsmigr] is what
-you want to start with.</font>
+<font color="gray">This section is about migrating from older gitolite to
+"g3".  If you're migrating from gitosis, see [here][gsmigr].</font>
 
 First things first: g2 will be supported for a good long time for critical
 bugs, although enhancements and new features won't happen.
@@ -147,7 +153,7 @@ bugs, although enhancements and new features won't happen.
 If you're an existing (gitolite v1.x or v2.x) user, and wish to migrate , here
 are the steps:
 
-### pre-migration
+### pre-migration checks
 
 1.  Check the [dev-status][] page to make sure all the features you want have
     been implemented in g3.
@@ -157,13 +163,13 @@ are the steps:
     you were to a default install of the old gitolite, the less time a
     migration will take.)
 
-### migration
+### the actual migration
 
 **Note**: nothing in any of the gitolite install/setup/etc will ever touch the
 *data* in any repository except the gitolite-admin repo.  The only thing it
 will normally touch in normal repos is the `update` hook.
 
-1.  Carefully wipe out the old gitolite
+1.  **On the server**, carefully wipe out the old gitolite:
 
       * The **code**
 
@@ -203,8 +209,8 @@ will normally touch in normal repos is the `update` hook.
     followed by [setup][].
 
 4.  Make sure your gitolite-admin clone has the correct pubkey for the
-    administrator in its `keydir` directory, then `git push -f` to the server
-    to overwrite the "default" admin repo created by the install.
+    administrator in its `keydir` directory, then run [`gitolite push
+    -f`][bypass] to overwrite the "default" admin repo created by the install.
 
 5.  Handle any errors, look for migration issues, etc., as described in the
     links at the top of this page.
diff --git a/doc/mkdoc b/doc/mkdoc
index 2f44d6c..bce1013 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -113,3 +113,6 @@ __DATA__
 |
     <a href="index.html#license">license</a>
 </p>
+<p style="text-align:center">
+<font color="gray">This is for gitolite "g3"; for older (v2.x) documentation click <a href="http://sitaramc.github.com/gitolite/g2/master-toc.html">here</a></font>
+</p>

commit 3a7b547759aac7f808b11ffa5a71e1076936c471
Author: Thomas Hager <duke at sigsegv.at>
Date:   Wed Apr 18 08:35:21 2012 +0200

    replaced /bin/echo with printf, Solaris echo doesn't recognize -n
    
    gitolite setup fails to check admin pubkey, because $text always
    contains 2 or more lines after tsh_try() (the key and -n).
    
    [committer adds:
        I wasn't sure if 'printf' would work on cygwin, so I chose what
        looked like a safer option, but apparently it wasn't safe enough and
        fell afoul of Solaris.
    
        Anyway I managed to check (using a small test program) with someone
        who runs gitolite on cygwin, and it works.
    
        If you're wondering why I didn't just use echo followed by chomp(),
        that would of course have been the easy way out but I wanted to see
        how you'd do it without a post-processing option.  It became a
        frustrating challenge of sorts because it seems such a trivial thing!
    ]

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 947819a..638b122 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -277,7 +277,7 @@ sub logger_plus_stderr {
 
     sub tsh_try {
         my $cmd = shift; die "try: expects only one argument" if @_;
-        $text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
+        $text = `( $cmd ) 2>&1; printf RC=\$?`;
         if ( $text =~ s/RC=(\d+)$// ) {
             $rc = $1;
             trace( 3, $text );

commit aaccb367ec48ad193413a67fd52565aba320fc00
Author: Thomas Hager <duke at sigsegv.at>
Date:   Wed Apr 18 09:09:24 2012 +0200

    changes to support Solaris default shell
    
    Solaris default bourne shell does not recognize $(), and does not allow
    exporting a variable and assigning a value to it in one step.

diff --git a/src/triggers/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
index feb4a37..4585f44 100755
--- a/src/triggers/post-compile/update-git-daemon-access-list
+++ b/src/triggers/post-compile/update-git-daemon-access-list
@@ -2,8 +2,9 @@
 
 # this is probably the *fastest* git-daemon update possible.
 
-export EO=git-daemon-export-ok
-export RB=$(gitolite query-rc GL_REPO_BASE)
+EO=git-daemon-export-ok
+RB=`gitolite query-rc GL_REPO_BASE`
+export EO RB
 
 gitolite list-phy-repos | gitolite access % daemon R any |
     perl -lane '
diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
index d7f5a95..f97061f 100755
--- a/src/triggers/post-compile/update-gitweb-access-list
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -4,7 +4,7 @@
 # whatever you want and contribute it back, as long as it is upward
 # compatible.
 
-plf=$(gitolite query-rc GITWEB_PROJECTS_LIST)
+plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
 [ -z "$plf" ] && plf=$HOME/projects.list
 
 (

commit 51833fccfbe7fa7736f2ab2494b452cd23decf5e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 18 06:53:50 2012 +0530

    added new changelog

diff --git a/doc/CHANGELOG b/doc/CHANGELOG
new file mode 100644
index 0000000..cb3626b
--- /dev/null
+++ b/doc/CHANGELOG
@@ -0,0 +1,3 @@
+2012-04-18  v3.0    first release to "master"
+                    This is a compete rewrite of gitolite; please see
+                    documentation before upgrading.

commit 2c8e0dfd2f1debdd9a14fba693f83e6786b26bd6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 17 06:43:13 2012 +0530

    (doc) general cleanup of docs

diff --git a/README.mkd b/README.mkd
index afbcf3f..e41f513 100644
--- a/README.mkd
+++ b/README.mkd
@@ -12,16 +12,15 @@ If you're reading this on the main gitolite page on github, several
     branch, and is now the actively maintained and supported software.  Do NOT
     try to merge this with your old "master" branch!
 
-2.  Versions v2.x is on branch "g2".  It will be supported for security issues
-    and serious bugs in core functionality, but not for anything less
+2.  Versions v2.x are on branch "g2".  It will be supported for security
+    issues and serious bugs in core functionality, but not for anything less
     critical.  Versions v1.x are completely unsupported now.
 
 If you're an existing (v1.x, v2.x) gitolite user please spend some time with
 the documentation for the new version before upgrading.  The [main page][h-mp]
-leads to quick install as well as several other useful starting points.  The
-[table of contents][h-mt] is a much more meaningfully ordered/structured list
-of links (instead of putting them in alphabetical order of the filename, like
-in g2!)
+leads to several useful starting points.  The [table of contents][h-mt] is a
+much more meaningfully ordered/structured list of links (instead of putting
+them in alphabetical order of the filename, like in g2!)
 
 [h-mp]: http://sitaramc.github.com/gitolite/
 [h-mt]: http://sitaramc.github.com/gitolite/master-toc.html
diff --git a/doc/admin.mkd b/doc/admin.mkd
index 0dc21aa..df3bdf2 100644
--- a/doc/admin.mkd
+++ b/doc/admin.mkd
@@ -2,11 +2,11 @@
 
 ## #server server-side administration
 
-The following activities require command line access to the server
+The following activities require command line access to the server:
 
-  * changing anything in the [rc][] file
-  * installing custom [hooks][], whether to all repos or just some repos
-  * moving [existing][] repos into gitolite control
+  * Changing anything in the [rc][] file.
+  * Installing custom [hooks][], whether to all repos or just some repos.
+  * Moving [existing][] repos into gitolite control.
 
 Please read the [WARNINGS][] page first.
 
@@ -32,11 +32,11 @@ Here is an example of a simple conf/gitolite.conf file.
 
 Use the following links to learn more:
 
-  * the basic [syntax][] -- comments, whitespace, include files, etc.
-  * defining [groups][], as in lines 1 and 2
-  * adding and removing [users][]
-  * adding and removing [repos][], as in line 3
-  * defining access [rules][], as in lines 4, 5, 6, and 7
-  * gitolite [options][]
-  * [git config][git-config] keys and values, as in line 8
-  * ["wild"][wild] repos -- ad hoc, user-created, repos
+  * The basic [syntax][] -- comments, whitespace, include files, etc.
+  * Defining [groups][], as in lines 1 and 2.
+  * Adding and removing [users][].
+  * Adding and removing [repos][], as in line 3.
+  * Defining access [rules][], as in lines 4, 5, 6, and 7.
+  * Gitolite [options][].
+  * [Git config][git-config] keys and values, as in line 8.
+  * ["Wild"][wild] repos -- ad hoc, user-created, repos.
diff --git a/doc/cust.mkd b/doc/cust.mkd
index 5004732..1cdf07b 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -17,12 +17,12 @@ even mirroring is not in core now!)
 
 There are 5 basic types of non-core programs.
 
-  * *commands* can be run from the shell command line.  Among those, the ones
+  * *Commands* can be run from the shell command line.  Among those, the ones
     listed in the COMMANDS hash of the rc file can also be run remotely.
-  * *hooks* are standard git hooks; see below.
-  * *sugar scripts* change the conf language for your convenience.  The word
+  * *Hooks* are standard git hooks; see below.
+  * *Sugar scripts* change the conf language for your convenience.  The word
     sugar comes from "syntactic sugar".
-  * *triggers* are to gitolite what hooks are to git.  I just chose a
+  * *Triggers* are to gitolite what hooks are to git.  I just chose a
     different name to avoid confusion and constant disambiguation in the docs.
   * **VREFs** are extensions to the access control check part of gitolite.
 
diff --git a/doc/deleg.mkd b/doc/deleg.mkd
index 5855c89..f693432 100644
--- a/doc/deleg.mkd
+++ b/doc/deleg.mkd
@@ -38,21 +38,22 @@ And that's it.
 
 Subconf is exactly like the include command in syntax:
 
-    subconf "foo/bar.conf"
+    subconf "foo.conf"
 
-but while reading the included file, it sets a "subconf name" of "foo".
+but while reading the included file (as well as anything included from it),
+gitolite sets the "subconf name" to "foo".
 
-When a subconf name is in effect, there are some restrictions on what repos
-can be managed.
+A "subconf" imposes some restrictions on what repos can be managed.
 
-For example, in the include file in the above example, you can only have
-"repo" lines for:
+For example, while the subconf name is "foo", as in the above example,
+gitolite will only process "repo" lines for:
 
-  * a repo called "foo"
-  * a group called "@foo" defined outside the include file
-  * a member of a group called "@foo" (again, defined outside)
-  * a repo that matches a member of a group called "@foo" if that member is a
-    regular expression pattern
+  * A repo called "foo".
+  * A group called "@foo", as long as the group is defined in the main conf
+    file (i.e., *outside* "foo.conf").
+  * A member of a group called "@foo" (again, defined outside).
+  * A repo that matches a member of a group called "@foo" if that member is a
+    regular expression pattern.
 
 Here's an example.  If the main conf file contains
 
@@ -61,6 +62,9 @@ Here's an example.  If the main conf file contains
 then the subconf can only accept repo statements that refer to 'foo', '@foo',
 'aa', 'bb', or any repo whose name starts with 'cc/'.
 
+**Note**: the subconf name "master" is special; it is the default subconf in
+effect for the main conf file and has no restrictions.
+
 ### how the "subconf name" is derived
 
 For subconf lines that look just like include statements, i.e.,
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index ae9ab40..02fe445 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -29,15 +29,15 @@ easily with gitolite.  Each of them responds to `-h` so please run that for
 more info.
 
   * `gitolite access` to check access rights given repo, user, type of access
-    (R, W, ...) and refname (optional).  Example use: src/commands/desc
+    (R, W, ...) and refname (optional).  Example use: src/commands/desc.
 
   * `gitolite creator` to get/check the creator of a repo.  Example use:
-    src/commands/desc
+    src/commands/desc.
 
   * `gitolite git-config` to check gitolite options or git config variables
     directly from gitolite's "compiled" output, (i.e., without looking at the
     actual `repo.git/config` file or using the `git config` command).  Example
-    use: none yet
+    use: none yet.
 
   * `gitolite query-rc` to check the value of an RC variable.  Example use:
     src/commands/desc.
@@ -65,11 +65,11 @@ The rest is between you and 'man githooks' :-)
 
 If you want to add additional `update` hook functionality, do this:
 
-  * write and test your update hook separately from gitolite
+  * Write and test your update hook separately from gitolite.
 
-  * now add the code to src/VREF.  Let's say it is called "foo".
+  * Now add the code to src/VREF.  Let's say it is called "foo".
 
-  * to call your new update hook to all accesses for all repos, add this to
+  * To call your new update hook to all accesses for all repos, add this to
     the end of your conf file:
 
         repo @all
diff --git a/doc/emergencies.mkd b/doc/emergencies.mkd
index 60b1cb0..bc51711 100644
--- a/doc/emergencies.mkd
+++ b/doc/emergencies.mkd
@@ -46,19 +46,19 @@ just rewind) and push that.  Here's how to do that:
 If you've read the [files involved in gitolite][files] page, you probably know
 the answer, but here's a list of files you should blow away.
 
-  * **gitolite sources** -- can be found by running `which gitolite`.  If it's
+  * **Gitolite sources** -- can be found by running `which gitolite`.  If it's
     a symlink, go to its target directory.
 
-  * **gitolite admin directory** -- `$HOME/.gitolite`.  Save the 'logs'
+  * **Gitolite admin directory** -- `$HOME/.gitolite`.  Save the 'logs'
     directory if you want to preserve them for any reason.
 
-  * **the rc file** -- `$HOME/.gitolite.rc`.  If you made any changes to it
+  * **The rc file** -- `$HOME/.gitolite.rc`.  If you made any changes to it
     you can save it as some other name instead of deleting it.
 
-  * **the gitolite-admin repo** -- `$HOME/repositories/gitolite-admin.git`.
+  * **The gitolite-admin repo** -- `$HOME/repositories/gitolite-admin.git`.
     You can clone it somewhere to save it before blowing it away if you wish.
 
-  * **git repositories** -- `$HOME/repositories`.  The install process will
+  * **Git repositories** -- `$HOME/repositories`.  The install process will
     not touch any existing repos except 'gitolite-admin.git', so you do not
     have to blow away (or move) your work repos to fix a botched install.
 
@@ -66,7 +66,7 @@ the answer, but here's a list of files you should blow away.
     will those repos be touched.  And even then all that happens is that the
     update hook, if any, is replaced with gitolite's own hook.
 
-  * **ssh stuff** -- exercise caution when doing this, but in general it
+  * **Ssh stuff** -- exercise caution when doing this, but in general it
     should be safe to delete all lines between the "gitolite start" and
     "gitolite end" markers in `$HOME/.ssh/authorized_keys`.
 
@@ -103,12 +103,12 @@ the answer, but here's a list of files you should blow away.
 
   * `WARNING: split conf not set, gl-conf present for <repo>`
 
-    (case 1) This can happen if you have a *bare* repo (i.e., some `repo.git`
+    (Case 1) This can happen if you have a *bare* repo (i.e., some `repo.git`
     directory) copied from g2 with `GL_BIG_CONFIG` on, and you pushed a change
     to the conf or ran certain commands *before* adding the newly added repo
     to the conf file.
 
-    (case 2) This can also happen if you changed something like this
+    (Case 2) This can also happen if you changed something like this
 
         repo foo
             ...<some rules>...
@@ -141,7 +141,7 @@ cannot help with most of these (although the good folks on irc or the mailing
 list -- see [contact][] -- might be able to; they certainly appear to have a
 lot more patience than I do, bless 'em!)
 
-  * **client side software**
+  * **Client side software**
 
       * putty/plink
       * jgit/Eclipse
@@ -151,7 +151,7 @@ lot more patience than I do, bless 'em!)
       * ...probably some more I forgot; will update this list as I remember...
       * did I mention putty/plink?
 
-  * **ssh**
+  * **Ssh**
 
     The *superstar* of the "not a gitolite problem" category is actually ssh.
 
@@ -162,7 +162,7 @@ lot more patience than I do, bless 'em!)
     Everything I know is in that latter link.  Please email me about ssh ONLY
     if you find something wrong or missing in those documents.
 
-  * **git**
+  * **Git**
 
     I wish I had a dollar for each time someone did a *first push* on a new
     repo, got an error because there were "no refs in common (etc.)", and
diff --git a/doc/extras/glssh.mkd b/doc/extras/glssh.mkd
index 1fa265e..9bbde0f 100644
--- a/doc/extras/glssh.mkd
+++ b/doc/extras/glssh.mkd
@@ -31,18 +31,18 @@ from somewhere, or maybe buy the OReilly ssh book.
     and give the server the public key.  (I need not add that the "private"
     key must be, well, kept *private*!)
 
-  * **generating a key pair on your workstation** is done by running the
+  * **Generating a key pair on your workstation** is done by running the
     command `ssh-keygen -t rsa`.  This produces two files in `~/.ssh`.  One is
     `id_rsa`; this is the **private** key -- ***never*** let it out of your
     machine.  The other is `id_rsa.pub`, which is the corresponding public
     key.  This public key is usually just one long line of text.
 
-    * on Windows machines with msysgit installed, you should do this from
+    * On Windows machines with msysgit installed, you should do this from
       within a "git bash" window.  The command will report the full path where
       the files have been written; make a note of this, and use those files in
-      any of the description that follows
+      any of the description that follows.
 
-  * **adding your public key to the server**'s `~/.ssh/authorized_keys`
+  * **Adding your public key to the server**'s `~/.ssh/authorized_keys`
     file is how ssh uses pubkeys to authenticate users.  Let's say
     sita at work.station is trying to log in as git at serv.er.  What you have to do
     is take the `~/.ssh/id_rsa.pub` file for user sita on work.station and
@@ -62,12 +62,12 @@ from somewhere, or maybe buy the OReilly ssh book.
 
     But in the gitolite case, it's different; we'll get to that in a minute.
 
-    * **troubleshooting pubkey authentication failures**: if you are unable to
+    * **Troubleshooting pubkey authentication failures**: if you are unable to
       get ssh access to the server after doing all this, you'll have to look
       in `/var/log/secure` or `/var/log/auth.log` or some such file on the
       server to see what specific error `sshd` is complaining about.
 
-  * **restricting users to specific commands** is very important for gitolite.
+  * **Restricting users to specific commands** is very important for gitolite.
     If you read `man sshd` and look for `authorized_keys file format`, you'll
     see a lot of options you can add to the public key line, which restrict
     the incoming user in various ways.  In particular, note the `command=`
@@ -89,9 +89,9 @@ from somewhere, or maybe buy the OReilly ssh book.
 
 These are two different questions you ought to be having by now: 
 
-  * how does it distinguish between me and someone else, since we're all
-    logging in as the same remote user "git"
-  * how does it restrict what I can do within a repository
+  * How does it distinguish between me and someone else, since we're all
+    logging in as the same remote user "git".
+  * How does it restrict what I can do within a repository.
 
 #### restricting shell access/distinguishing one user from another
 
diff --git a/doc/extras/sts.mkd b/doc/extras/sts.mkd
index f4b8d28..9252d58 100644
--- a/doc/extras/sts.mkd
+++ b/doc/extras/sts.mkd
@@ -33,16 +33,16 @@ and accessing gitolite.
   * Your workstation is the **client**.  Your userid on the client does not
     matter, and it has no relation to your gitolite username.
 
-  * the server is called **server** and the "hosting user" is **git**.  If
+  * The server is called **server** and the "hosting user" is **git**.  If
     this is an RPM/DEB install, the hosting user is probably called
     "gitolite", however we will use "git" in this document.
 
 #### taking stock -- relevant files and directories
 
-  * the client has a `~/.ssh` containing a few keypairs.  It may also have a
+  * The client has a `~/.ssh` containing a few keypairs.  It may also have a
     `config` file.
 
-  * the client also has a clone of the "gitolite-admin" repo, which contains a
+  * The client also has a clone of the "gitolite-admin" repo, which contains a
     bunch of `*.pub` files in `keydir`.  We assume this clone is in `$HOME`;
     if it is not, adjust instructions accordingly when needed.
 
@@ -51,29 +51,29 @@ and accessing gitolite.
     **authkeys** to save typing, and it always means the one on the server
     (we're not interested in this file on the client side).
 
-  * the server also has a `~/.gitolite/keydir` which contains a bunch of
+  * The server also has a `~/.gitolite/keydir` which contains a bunch of
     `*.pub` files.
 
 #### normal gitolite key handling
 
 Here's how normal gitolite key handling works:
 
-  * (on client) pub key changes like adding new ones, deleting old ones, etc.,
+  * (On client) pub key changes like adding new ones, deleting old ones, etc.,
     are done in the `keydir` directory in the gitolite-admin repo clone.  Then
     the admin `git add`s and `git commit`s those changes, then `git push`es
     them to the server.
 
-  * (on server) a successful push from the client makes git invoke the
+  * (On server) a successful push from the client makes git invoke the
     post-update hook in the gitolite-admin repo.  This hook is installed by
     gitolite, and it does a bunch of things which are quite transparent to
     the admin, but we'll describe briefly here:
 
-      * the pubkey files from this push are checked-out into
+      * The pubkey files from this push are checked-out into
         `~/.gitolite/keydir` (and similarly the config files into
-        `~/.gitolite/conf`)
+        `~/.gitolite/conf`).
 
-      * the "compile" script then runs, which uses these files to populate
-        `~/.ssh/authorized_keys` on the server
+      * The "compile" script then runs, which uses these files to populate
+        `~/.ssh/authorized_keys` on the server.
 
         The authkeys file may have other, (non-gitolite) keys also.  Those
         lines are preserved.  Gitolite only touches lines that are found
@@ -92,7 +92,7 @@ The following problem(s) indicate that pubkey access is not working at all, so
 you should start with [appendix 1][stsapp1].  If that doesn't fix the problem, continue
 with the other appendices in sequence.
 
-  * running any git clone/fetch/ls-remote or just `ssh git at server info` asks
+  * Running any git clone/fetch/ls-remote or just `ssh git at server info` asks
     you for a password.
 
 The following problem(s) indicate that your pubkey is bypassing gitolite and
@@ -100,23 +100,23 @@ going straight to a shell.  You should start with [appendix 2][sshkeys-lint]
 and continue with the rest in sequence.  [Appendix 5][ybpfail] has some
 background info.
 
-  * running `ssh git at server info` gets you the output of the GNU 'info'
+  * Running `ssh git at server info` gets you the output of the GNU 'info'
     command instead of gitolite's version and access info.
 
-  * running `git clone git at server:repositories/reponame` (note presence of
+  * Running `git clone git at server:repositories/reponame` (note presence of
     `repositories/` in URL) works.
 
     [A proper gitolite key will only let you `git clone git at server:reponame`
     (note absence of `repositories/`)]
 
-  * you are able to clone repositories but are unable to push changes back
+  * You are able to clone repositories but are unable to push changes back
     (the error complains about the `GL_BINDIR` environment variable not being
     set, and the `hooks/update` failing in some way).
 
     [If you run `git remote -v` you will find that your clone URL included the
     `repositories/` described above!]
 
-  * conversely, using the correct syntax, `git clone git at server:reponame`
+  * Conversely, using the correct syntax, `git clone git at server:reponame`
     (note absence of `repositories/` in the URL), gets you `fatal: 'reponame'
     does not appear to be a git repository`, and yet you are sure 'reponame'
     exists, you haven't mis-spelled it, etc.
@@ -129,16 +129,16 @@ you there again.  Especially the first bullet.
 
 Done?  OK, now the general outline for ssh troubleshooting is this:
 
-  * make sure the server's overall setup even *allows* pubkey based login.
+  * Make sure the server's overall setup even *allows* pubkey based login.
     I.e., check that git fetch/clone/ls-remote commands or a plain `ssh
     git at server info` do NOT ask for a password.  If you do get asked for a
     password, see [appendix 1][stsapp1].
 
-  * match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
+  * Match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
     file.  To do this, run `sshkeys-lint`, which tells you in detail what key
     has what access.  See [appendix 2][sshkeys-lint].
 
-  * at this point, we know that we have the right key, and that if sshd
+  * At this point, we know that we have the right key, and that if sshd
     receives that key, things will work.  But we're not done yet.  We still
     need to make sure that this specific key is being offered/sent by the
     client, instead of the default key.  See [appendix 3][stsapp3] and
@@ -172,12 +172,12 @@ don't have `ssh-copy-id`?  This is broadly what that command does, if you want
 to replicate it manually.  The input is your pubkey, typically
 `~/.ssh/id_rsa.pub` from your client/workstation.
 
-  * it copies it to the server as some file
+  * It copies it to the server as some file.
 
-  * it appends that file to `~/.ssh/authorized_keys` on the server
-    (creating it if it doesn't already exist)
+  * It appends that file to `~/.ssh/authorized_keys` on the server
+    (creating it if it doesn't already exist).
 
-  * it then makes sure that all these files/directories have go-w perms
+  * It then makes sure that all these files/directories have go-w perms
     set (assuming user is "git"):
 
         /home/git/.ssh/authorized_keys
@@ -233,7 +233,7 @@ try, you are being asked for a password.
 
 This is a quick checklist:
 
-  * make sure you're being asked for a password and not a pass*phrase*.  Do
+  * Make sure you're being asked for a password and not a pass*phrase*.  Do
     not confuse or mistake a prompt saying `Enter passphrase for key
     '/home/sitaram/.ssh/id_rsa':` for a password prompt from the remote
     server!
@@ -254,34 +254,34 @@ This is a quick checklist:
     discussing one more potential trouble-spot with ssh-agent (see below),
     further discussion of ssh-agent/keychain is out of scope of this document.
 
-  * ssh is very sensitive to permissions.  An extremely conservative setup is
+  * Ssh is very sensitive to permissions.  An extremely conservative setup is
     given below, but be sure to do this on **both the client and the server**:
 
         cd $HOME
         chmod go-rwx .
         chmod -R go-rwx .ssh
 
-  * actually, every component of the path to `~/.ssh/authorized_keys` all the
+  * Actually, every component of the path to `~/.ssh/authorized_keys` all the
     way upto the root directory must be at least `chmod go-w`.  So be sure to
     check `/` and `/home` also.
 
-  * while you're doing this, make sure the owner and group info for each of
+  * While you're doing this, make sure the owner and group info for each of
     these components are correct.  `ls -ald ~ ~/.ssh ~/.ssh/authorized_keys`
     will tell you what they are.
 
-  * you may also want to check `/etc/ssh/sshd_config` to see if the "git" user
+  * You may also want to check `/etc/ssh/sshd_config` to see if the "git" user
     is allowed to login at all.  For example, if that file contains an
     `AllowUsers` config entry, then only users mentioned in that line are
     allowed to log in!
 
-  * while you're in there, check that file does NOT have a setting for
+  * While you're in there, check that file does NOT have a setting for
     `AuthorizedKeysFile`.  See `man sshd_config` for details.  This setting is
     a show stopper for gitolite to use ssh.
 
-  * some OSs/distributions require that the "git" user should have a password
+  * Some OSs/distributions require that the "git" user should have a password
     and/or not be a locked account.  You may want to check that as well.
 
-  * if all that fails, log onto the server as root, `cd /var/log`, and look
+  * If all that fails, log onto the server as root, `cd /var/log`, and look
     for a file called `auth.log` or `secure` or some such name.  Look inside
     this file for messages matching the approximate time of your last attempt
     to login, to see if they tell you what is the problem.
@@ -298,12 +298,12 @@ especially good at finding duplicate keys and such.
 To run it on the client you have to copy the file src/commands/sshkeys-lint
 from some gitolite clone, then follow these steps:
 
-  * get a copy of `~/.ssh/authorized_keys` from the server and put it in
-    `/tmp/foo` or something
+  * Get a copy of `~/.ssh/authorized_keys` from the server and put it in
+    `/tmp/foo` or something.
 
-  * cd to `~/.ssh`
+  * cd to `~/.ssh`.
 
-  * run `/path/to/sshkeys-lint *.pub < /tmp/foo`
+  * Run `/path/to/sshkeys-lint *.pub < /tmp/foo`.
 
 Note that it is not trying to log in or anything -- it's just comparing
 fingerprints as computed by `ssh-keygen -l`.
@@ -314,12 +314,12 @@ the server, you're done with this step.
 Otherwise you have to rename some keypairs and try again to get the effect you
 need.  Be careful:
 
-  * do not just rename the ".pub" file; you will have to rename the
+  * Do not just rename the ".pub" file; you will have to rename the
     corresponding private key also (the one with the same basename but without
-    an extension)
+    an extension).
 
-  * if you're running ssh-agent, you may have to delete (using `ssh-add -D`)
-    and re-add identities for it to pick up the renamed ones correctly
+  * If you're running ssh-agent, you may have to delete (using `ssh-add -D`)
+    and re-add identities for it to pick up the renamed ones correctly.
 
 #### typical cause(s)
 
@@ -339,7 +339,7 @@ you're using ssh-agent, otherwise these new keys may not work.
 
 ### #stsapp3 appendix 3: ssh client may not be offering the right key
 
-  * make sure the right private key is being offered.  Run ssh in very
+  * Make sure the right private key is being offered.  Run ssh in very
     verbose mode and look for the word "Offering", like so:
 
         ssh -vvv user at host pwd 2> >(grep -i offer)
@@ -413,11 +413,11 @@ the server.  (This is by design.  Don't argue...)
 
 This means that, you get 2 kinds of errors if you bypass gitolite
 
-  * when you use `git at server:reponame` with a key that bypasses gitolite
+  * When you use `git at server:reponame` with a key that bypasses gitolite
     (i.e., gets you a shell), this prefixing does not happen, and so the repo
     is not found.  Neither a clone/fetch nor a push will work.
 
-  * conversely, consider `git at server:repositories/reponame.git`.  The clone
+  * Conversely, consider `git at server:repositories/reponame.git`.  The clone
     operation will work -- you're using the full Unix path, (assuming default
     `$REPO_BASE` setting), and so the shell finds the repo where you said it
     would be.  However, when you push, gitolite's **update hook** kicks in,
diff --git a/doc/files.mkd b/doc/files.mkd
index 077078d..1cb7b2d 100644
--- a/doc/files.mkd
+++ b/doc/files.mkd
@@ -10,8 +10,7 @@ Let's say you start from a totally clean slate:
     $ ls -a
     .  ..  .bash_logout  .bash_profile  .bashrc
 
-You run `git clone -b g3 git://github.com/sitaramc/gitolite` (the '-b' option
-does the equivalent of 'git checkout g3' after the clone is done).
+You run `git clone git://github.com/sitaramc/gitolite`.
 
 Now you have
 
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
index 6c0fa96..02a3d85 100644
--- a/doc/g2incompat.mkd
+++ b/doc/g2incompat.mkd
@@ -56,23 +56,23 @@ functionality, and invoke *that* from the rc file instead.
 
 There are several changes with regard to mirroring:
 
-  * there is no 'post-receive' hook to be installed.  Mirroring is handled by
+  * There is no 'post-receive' hook to be installed.  Mirroring is handled by
     g3's [triggers][] mechanism.  Gitolite triggers are enabled by adding (or
     uncommenting, in this case) appropriate lines in the rc file.
 
-  * the `GL_HOSTNAME` variable is now `HOSTNAME`.  (Note that the rc file
+  * The `GL_HOSTNAME` variable is now `HOSTNAME`.  (Note that the rc file
     syntax itself has changed quite a bit; to be accurate, HOSTNAME is not a
     variable but a hash key with an associated value).
 
-  * the `GL_GITCONFIG_KEYS` variable is now `GIT_CONFIG_KEYS`, **but** you no
+  * The `GL_GITCONFIG_KEYS` variable is now `GIT_CONFIG_KEYS`, **but** you no
     longer need to set it to anything for mirroring to work.
 
-  * the `gl-tool` program does not exist anymore.  Adding keys for peer
+  * The `gl-tool` program does not exist anymore.  Adding keys for peer
     servers is done just like adding user keys, except that the pubkey file
     name must start with `server-`.  For example, to add a peer host called
     frodo, you will acquire its pubkey and add it as `server-frodo.pub`.
 
-  * the config variables are quite different now.  The main ones now look like
+  * The config variables are quite different now.  The main ones now look like
     this:
 
         option mirror.master        =   sam
@@ -86,7 +86,7 @@ There are several changes with regard to mirroring:
 
         option mirror.redirectOK    =   all
 
-  * there are no more mirroring "keys", (lists of servers named in config keys
+  * There are no more mirroring "keys", (lists of servers named in config keys
     like 'gitolite.mirror.nightly', etc).  You can certainly add lines like
 
         option mirror.nightly       =   merry pippin
@@ -95,7 +95,7 @@ There are several changes with regard to mirroring:
     `gitolite git-config` to query this variable, grab the list of repos, and
     run `gitolite mirror` on each of them.
 
-  * the external command to resync mirrors is 'mirror', run just like any
+  * The external command to resync mirrors is 'mirror', run just like any
     other [command][commands].  In particular, you can run `gitolite mirror
     -h` to get help.  It cannot be run from a slave to ask a master to push
     (unlike in the old system) but what's more convenient is that any user who
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 7674222..aba2961 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -17,11 +17,11 @@ differences are in the rc file, mirroring, "NAME/" rules, and delegation.
 
 >   ----
 
->   **presetting the rc file**
+>   **Presetting the rc file**
 
 >   Some rc settings in the older gitolite are such that you cannot directly
->   run `gitolite setup` when you're ready to migrate.  Doing that will
->   **clobber** something important.  See [presetting the rc file][rc-preset]
+>   run `gitolite setup` when you're ready to migrate.  **Doing that will
+>   clobber something important**.  See [presetting the rc file][rc-preset]
 >   for details.
 
 >   ----
@@ -48,7 +48,7 @@ Some of them have links where there is more detail than I want to put here.
   * [subconf][g2i-subconf]: if you're using [delegation][deleg], there is no
     implicit "subconf" at the end; you'll have to add it in.
 
-  * there are several important differences in mirroring.  You can start from
+  * There are several important differences in mirroring.  You can start from
     scratch by reading the new [mirroring][mirroring] doc or
     [migrate][g2i-mirroring] (carefully!).
 
@@ -236,8 +236,8 @@ put that contain the words "see docs":
 ## #rc-preset presetting the rc file
 
 Some rc settings in the older gitolite are such that you cannot directly run
-`gitolite setup` when you're ready to migrate.  Doing that will **clobber**
-something important.  You have to create a default rc file, edit it
+`gitolite setup` when you're ready to migrate.  **Doing that will clobber
+something important**.  You have to create a default rc file, edit it
 appropriately, and *then* run `gitolite setup`.
 
 The most serious example of this is `GL_NO_SETUP_AUTHKEYS`, which tells the
diff --git a/doc/gsmigr.mkd b/doc/gsmigr.mkd
index 72bc357..0f54bbb 100644
--- a/doc/gsmigr.mkd
+++ b/doc/gsmigr.mkd
@@ -44,10 +44,10 @@ Here are the steps on the server:
 Now, log off the server and get back to the client.  All subsequent
 instructions are to be read as "on gitolite admin's workstation".
 
-  * **clone** the new gitolite-admin repo to your workstation.  (You already
+  * **Clone** the new gitolite-admin repo to your workstation.  (You already
     have a clone of the gitosis-admin repo so now you have both).
 
-  * **convert** your gitosis config file and append it to your gitolite config
+  * **Convert** your gitosis config file and append it to your gitolite config
     file.  Substitute the path for your gitosis-admin clone in `$GSAC` below,
     and similarly the path for your gito**lite**-admin clone in `$GLAC`.  (The
     convert-gitosis-conf program is a standalone program that you can bring
@@ -63,7 +63,7 @@ instructions are to be read as "on gitolite admin's workstation".
   * Remove the entry for the 'gitosis-admin' repo.  You do not need it here
     and it may cause confusion.
 
-  * **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC)
+  * **Copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC)
 
         cp $GSAC/keydir/* $GLAC/keydir
 
@@ -122,5 +122,4 @@ instructions are to be read as "on gitolite admin's workstation".
     "sitaram at laptop.pub" and "sitaram at desktop.pub" or whatever.  *Please check
     the files to make sure this worked properly*
 
-  * Check all your changes to your gitolite-admin clone, commit, and push
-
+  * Check all your changes to your gitolite-admin clone, commit, and push.
diff --git a/doc/http.mkd b/doc/http.mkd
index 9263d4c..1c935db 100644
--- a/doc/http.mkd
+++ b/doc/http.mkd
@@ -11,34 +11,17 @@ that is the same or even relevant -- that is from 2006 and is quite different
   * Please read [authentication versus authorisation][auth] first, and make
     sure you understand what is gitolite's responsibility and what isn't.
 
-  * The 'gitolite' command (for example, 'gitolite compile', 'gitolite
-    query-rc', and so on) *can* be run on the server, but it's not
-    straightforward.  Assuming you installed using the exact same values as in
-    this document:
-
-      * get a shell by using, say, `su -s /bin/bash - apache`
-      * run `export HOME=$HOME/gitolite-home`
-      * run `export PATH=$PATH:$HOME/bin`
-
-    Now you can run `gitolite <subcommand>`
-
-  * I have tested only on stock Fedora 16; YDMV
+  * I have tested this only on stock Fedora 16; YDMV.
 
   * As before, I have not tried making repos available to both ssh *and* http
     mode clients but it ought to work.  If you managed it, I'd appreciate a
     doc patch describing how you did it.
 
-## additional requirements
-
-  * requires `GIT_PROJECT_ROOT` (see "man git-http-backend" for what this is)
-    set explicitly (i.e., it is no longer optional).  Please set it to some
-    place outside apache's `DOCUMENT_ROOT`.
-
 ## assumptions:
 
-  * apache 2.x and git installed.
-  * httpd runs under the "apache" userid; adjust instructions below if not.
-  * similarly for "/var/www" and other file names/locations.
+  * Apache 2.x and git installed.
+  * Httpd runs under the "apache" userid; adjust instructions below if not.
+  * Similarly for "/var/www" and other file names/locations.
 
 ## instructions
 
@@ -51,8 +34,14 @@ install gitolite in smart http mode.
 Make a copy of the script, go through it carefully, (possibly removing lines
 that delete files etc.), change values per your system, and only then run it.
 
+<font color="gray">Note that the `GIT_PROJECT_ROOT` variable (see "man
+git-http-backend") is no longer optional.  Make sure you set it to some place
+outside apache's `DOCUMENT_ROOT`.</font>
+
 ## usage
 
+### client side
+
 Git URLs look like `http://user:password@server/git/reponame.git`.
 
 The custom commands, like "info", "expand" should be handled as follows.  The
@@ -68,3 +57,16 @@ the arguments, with `+` representing a space.  Here are some examples:
 
 With a few nice shell aliases, you won't even notice the horrible convolutions
 here ;-)  See t/smart-http for a couple of useful ones.
+
+### server side
+
+The 'gitolite' command (for example, 'gitolite compile', 'gitolite query-rc',
+and so on) *can* be run on the server, but it's not straightforward.  Assuming
+you installed exactly as given in this document, you should
+
+  * get a shell by using, say, `su -s /bin/bash - apache`
+  * run `export HOME=$HOME/gitolite-home`
+  * run `export PATH=$PATH:$HOME/bin`
+
+and *then* you can run `gitolite <subcommand>`
+
diff --git a/doc/index.mkd b/doc/index.mkd
index a44f76f..b153b50 100644
--- a/doc/index.mkd
+++ b/doc/index.mkd
@@ -19,6 +19,7 @@ For users of gitolite v2.x (call it "g2" for convenience),
 
 ## #ql quick links
 
+  * [Trying][trying] out gitolite.
   * Minimum [requirements][req].
   * Here's how to do a [quick install, setup, and clone][qi].
   * Don't know ssh well enough?  [Learn][ssh].  It's **IMPORTANT**.
@@ -37,12 +38,12 @@ most people see:
 
   * Use a single unix user ("real" user) on the server.
   * Provide access to many gitolite users:
-      * they are not "real" users
-      * they do not get shell access
+      * they are not "real" users,
+      * they do not get shell access.
   * Control access to many git repositories:
-      * read access controlled at the repo level
+      * read access controlled at the repo level,
       * write access controlled at the branch/tag/file/directory level,
-        including who can rewind, create, and delete branches/tags
+        including who can rewind, create, and delete branches/tags.
   * Can be installed without root access, assuming git and perl are already
     installed.
   * Authentication is most commonly done using sshd, but you can also use
diff --git a/doc/install.mkd b/doc/install.mkd
index 642c4ac..14f68ea 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -42,11 +42,12 @@ Notes:
 
 ### server
 
-  * any Unix system with a posix compatible "sh".
-  * git version 1.6.6 or later
-  * perl 5.8.8 or later
-  * openssh (almost any version).  Optional if you're using [smart http][http]
-  * a dedicated Unix userid to be the hosting user, usually "git" but it can
+  * Any Unix system with a posix compatible "sh".
+  * Git version 1.6.6 or later.
+  * Perl 5.8.8 or later.
+  * Openssh (almost any version).  Optional if you're using [smart
+    http][http].
+  * A dedicated Unix userid to be the hosting user, usually "git" but it can
     be any user, even your own normal one.  (If you're using an RPM/DEB the
     install probably created one called "gitolite").
 
@@ -55,17 +56,13 @@ side.
 
 ### client
 
-  * openssh client
-  * git 1.6.6 or later.  Almost any git client will work, as long as it knows
+  * Openssh client.
+  * Git 1.6.6 or later.  Almost any git client will work, as long as it knows
     how to use ssh keys and send the right one along.
 
 ## getting the software
 
-    git clone -b g3 git://github.com/sitaramc/gitolite
-
-The -b g3' is needed until g3 becomes "master".  Current estimates put this
-around June 2012, when the old gitolite (upto v2.x) will be retired and
-supported only for security issues.
+    git clone git://github.com/sitaramc/gitolite
 
 ## the actual install
 
@@ -87,7 +84,7 @@ Option 2 is the best for general use.
 There is a program called 'install' that helps you do these easily.  Assuming
 your cloned the repo like this:
 
-    git clone -b g3 git://github.com/sitaramc/gitolite
+    git clone git://github.com/sitaramc/gitolite
 
 you can run the 'install' command in 3 different ways:
 
@@ -168,35 +165,35 @@ will normally touch in normal repos is the `update` hook.
 
 1.  Carefully wipe out the old gitolite
 
-      * the **code**
+      * The **code**
 
-          * delete or move away all the old gitolite scripts.  Check the path
+          * Delete or move away all the old gitolite scripts.  Check the path
             to the gl-auth-command in `~/.ssh/authorized_keys` if you forgot
             where you put them.
 
-          * delete or move away the two directories named in the two variables
-            `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` in `~/.gitolite.rc`
+          * Delete or move away the two directories named in the two variables
+            `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` in `~/.gitolite.rc`.
 
-      * the **rc file**
+      * The **rc file**
 
-          * rename `~/.gitolite.rc` to something else
+          * Rename `~/.gitolite.rc` to something else.
 
-      * the **admin repo**
+      * The **admin repo**
 
           * clone `~/repositories/gitolite-admin.git` to someplace safe
           * then delete `~/repositories/gitolite-admin.git`
 
-        (make sure you do not delete any other repos!)
+        (Make sure you do not delete any other repos!)
 
-      * the **admin directory**
+      * The **admin directory**.
 
-          * if you need to preserve logs, move the ~/.gitolite/logs` directory
-            somewhere else
+          * If you need to preserve logs, move the ~/.gitolite/logs` directory
+            somewhere else.
 
-          * if you added any custom hooks and wish to preserve them, move the
-            ~/.gitolite/hooks` directory somewhere else
+          * If you added any custom hooks and wish to preserve them, move the
+            ~/.gitolite/hooks` directory somewhere else.
 
-          * delete `~/.gitolite`
+          * Delete `~/.gitolite`.
 
 2.  Read about [presetting][rc-preset] the rc file; if you're using any
     variables listed as requiring preset, follow those instructions to create
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 28b6a22..387bbea 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -17,19 +17,19 @@ world they are at best read-only.
 
 Gitolite extends this simple notion in the following ways:
 
-  * different masters and sets of slaves for different repos
+  * Different masters and sets of slaves for different repos
 
-    This lets you do things like
+    This lets you do things like:
 
-      * use the server closest to *most* of its developers as the master for
-        that repo
-      * not mirror a repo at all to some servers
-      * have repos that are purely local to a server (not mirrored at all)
-      * negotiate mirroring with servers that are not even under your control
-      * push to a slave on demand or via cron (helps deal with bandwidth or
+      * Use the server closest to *most* of its developers as the master for
+        that repo.
+      * Not mirror a repo at all to some servers.
+      * Have repos that are purely local to a server (not mirrored at all).
+      * Negotiate mirroring with servers that are not even under your control.
+      * Push to a slave on demand or via cron (helps deal with bandwidth or
         connectivity constraints).
 
-  * pushes to a slave can be transparently forwarded to the real master
+  * Pushes to a slave can be transparently forwarded to the real master.
 
     Your developers need not worry about where a repo's master is -- they just
     write to their local mirror for *all* repos, even if their local mirror is
@@ -37,16 +37,16 @@ Gitolite extends this simple notion in the following ways:
 
 ## caveats
 
-  * mirroring will never *create* a repo on a slave; it has to exist and be
+  * Mirroring will never *create* a repo on a slave; it has to exist and be
     prepared to receive updates from the master.  (For example, [wild][] repos
     must be created on the slave as well, otherwise they will not propagate).
 
-  * mirroring is only for git repos.  Ancillary files like gl-creator and
+  * Mirroring is only for git repos.  Ancillary files like gl-creator and
     gl-perms in the repo directory are not mirrored; you must do that
     separately.  Files in the admin directory (like log files) are also not
     mirrored.
 
-  * if you ever do a [bypass push][bypass], mirroring will not work.
+  * If you ever do a [bypass push][bypass], mirroring will not work.
     Mirroring checks also will not work -- for example, you can push to a
     slave, which is not usually a good idea.  So don't bypass gitolite if the
     repo is mirrored!
@@ -60,24 +60,24 @@ file settings and syntax.
 
 On **each** server:
 
-  * install gitolite normally.  Make clones of the server's 'gitolite-admin'
+  * Install gitolite normally.  Make clones of the server's 'gitolite-admin'
     repo on your workstation so you can admin them all from one place.
 
-  * give the server a short, simple, "hostname" and set the HOSTNAME in the
+  * Give the server a short, simple, "hostname" and set the HOSTNAME in the
     rc file to this name, for example 'mars'.
 
-  * run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
+  * Run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
     public key to a common area and name it after the host, but with 'server-'
     prefixed.  So the pubkey for server 'mars' would be stored as
     'server-mars.pub'.
 
-  * copy all keys to all the admin repo clones on your workstation and and add
+  * Copy all keys to all the admin repo clones on your workstation and and add
     them as usual.  This is an `O(N^2)` operation ;-)
 
     You may have guessed that the prefix 'server-' is special, and
     distinguishes a human user from a mirroring peer.
 
-  * create "host" aliases to refer to all other machines.  See [here][ssh-ha]
+  * Create "host" aliases to refer to all other machines.  See [here][ssh-ha]
     for what/how.
 
     The host alias for a host (in all other machines' `~/.ssh/config` files)
@@ -88,7 +88,7 @@ On **each** server:
     Normally you should be able to build one common file and append it to all
     the servers' `~/.ssh/config` files.
 
-  * the following **MUST** work for **each pair** of servers that must talk to
+  * The following **MUST** work for **each pair** of servers that must talk to
     each other:
 
         # on server mars
@@ -101,7 +101,7 @@ On **each** server:
     Check this command from *everywhere to everywhere else*, and make sure you
     get expected results.  **Do NOT proceed otherwise.**
 
-  * setup the gitolite.conf file on all the servers.  If the slaves are to be
+  * Setup the gitolite.conf file on all the servers.  If the slaves are to be
     exact copies of the master, you need to do the complete configuration only
     on the master; the slaves can have just this:
 
@@ -114,7 +114,7 @@ On **each** server:
     because on the first push to the master it will update all the slaves
     anyway.
 
-  * when that is all done and tested, **enable mirroring** by going through
+  * When that is all done and tested, **enable mirroring** by going through
     the rc file and uncommenting all the lines mentioning `Mirroring`.
 
 ### conf file settings and syntax
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 8a3a636..207036a 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -29,11 +29,11 @@ Here's a list of remote commands that are shipped:
 
 The following "sugar" programs are available:
 
-  * continuation-lines -- allow the use of C-style backslash escaped
+  * Continuation-lines -- allow the use of C-style backslash escaped
     continuation lines in the conf file.  I don't like it but some people do,
     and now I can support them without bulking up the "core" conf parser!
 
-  * keysubdirs-as-groups -- someone wanted the sub-directory name (of
+  * Keysubdirs-as-groups -- someone wanted the sub-directory name (of
     "keydir/") in which the pubkey was placed to be a group to which the user
     automatically belonged.  A very unusual requirement, and one which would
     *never* have seen the light of day in g2, but in g3 it's easy, and doesn't
@@ -46,9 +46,9 @@ The following "sugar" programs are available:
 
 The `PRE_GIT` triggers are:
 
-  * partial-copy -- this has its own section later in this page
+  * partial-copy -- this has its own section later in this page.
 
-  * renice -- this renices the entire job to whatever value you specify
+  * renice -- this renices the entire job to whatever value you specify.
 
 The `POST_GIT` triggers are:
 
@@ -79,7 +79,7 @@ The `POST_COMPILE` triggers are:
 
 The `POST_CREATE` triggers are:
 
-  * the last 3 in the `POST_COMPILE` list also run from `POST_CREATE`, for
+  * The last 3 in the `POST_COMPILE` list also run from `POST_CREATE`, for
     obvious reasons.
 
 ## VREFs
@@ -124,10 +124,10 @@ And that should be it.  **Please test it and let me know if it doesn't work!**
 
 WARNINGS:
 
-  * if you change the config to disallow something that used to be allowed,
+  * If you change the config to disallow something that used to be allowed,
     you should delete the partial repo on the server and then run 'gitolite
     compile' to let it build again.  See t/partial-copy.t for details.
 
-  * not tested with smart http; probably won't work
+  * Not tested with smart http; probably won't work.
 
-  * also not tested with mirroring, or with wild card repos.
+  * Also not tested with mirroring, or with wild card repos.
diff --git a/doc/progit.mkd b/doc/progit.mkd
index 053ffa9..1e7856d 100644
--- a/doc/progit.mkd
+++ b/doc/progit.mkd
@@ -18,13 +18,11 @@ Gitolite is somewhat unusual as far as "server" software goes -- access is via s
 
 To begin, create a user called `git` on your server and login to this user.  Copy your ssh pubkey (a file called `~/.ssh/id_rsa.pub` if you did a plain `ssh-keygen` with all the defaults) from your workstation, renaming it to `YourName.pub`.  Then run these commands:
 
-    git clone -b g3 git://github.com/sitaramc/gitolite
+    git clone git://github.com/sitaramc/gitolite
     gitolite/install -ln
     gitolite setup -pk $HOME/YourName.pub
         # for example, I would run 'gitolite setup -pk $HOME/sitaram.pub'
 
-(Note: the "-b g3" will not be needed once gitolite v3 becomes the "master" version.)
-
 Finally, back on your workstation, run `git clone git at server:gitolite-admin`.
 
 And you're done!  Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in your workstation.  You administer your gitolite setup by making changes to this repository and pushing.  See adding [users][] and [repos][] to start with.
diff --git a/doc/qi.mkd b/doc/qi.mkd
index bebc1f1..563071b 100644
--- a/doc/qi.mkd
+++ b/doc/qi.mkd
@@ -5,7 +5,7 @@
 On the server:
 
     # get the software
-    git clone -b g3 git://github.com/sitaramc/gitolite
+    git clone git://github.com/sitaramc/gitolite
 
     # install it
     gitolite/install -ln
@@ -20,18 +20,18 @@ On your workstation:
 
 ## ASSUMPTIONS
 
-  * this is a fresh install, not a migration from the old gitolite (v1.x,
+  * This is a fresh install, not a migration from the old gitolite (v1.x,
     v2.x).
 
-  * on the server, your `$PATH` contains `$HOME/bin`.  If you don't like that,
+  * On the server, your `$PATH` contains `$HOME/bin`.  If you don't like that,
     there are [other install methods][install].
 
   * "your-name.pub" is your public key from your workstation.
-      * also, this key does not already have shell access to this gitolite
-        hosting user
+      * Also, this key does not already have shell access to this gitolite
+        hosting user.
 
-  * the setup command does not generate any warnings.
-      * if it does, please see [common errors][ce] and fix things before
+  * The setup command does not generate any warnings.
+      * If it does, please see [common errors][ce] and fix things before
         continuing, or read the more complete [setup][] page.
 
 ## Notes
diff --git a/doc/rare.mkd b/doc/rare.mkd
index e1fca65..bcb1c6a 100644
--- a/doc/rare.mkd
+++ b/doc/rare.mkd
@@ -4,21 +4,21 @@
 
 On the server:
 
-  * move the repos to `$HOME/repositories`.
+  * Move the repos to `$HOME/repositories`.
 
-  * make sure that:
+  * Make sure that:
 
-      * they are all *bare* repos
-      * all the repo names end in ".git"
-      * all the files and directories are owned and writable by the gitolite
-        hosting user (especially true if you copied them as root)
+      * They are all *bare* repos.
+      * All the repo names end in ".git".
+      * All the files and directories are owned and writable by the gitolite
+        hosting user (especially true if you copied them as root).
 
-  * run `gitolite setup`.  **If you forget this step, you can also forget
+  * Run `gitolite setup`.  **If you forget this step, you can also forget
     about write access control!**
 
 Back on your workstation:
 
-  * [add them][repos] to conf/gitolite.conf in your clone of the admin repo,
+  * [Add them][repos] to conf/gitolite.conf in your clone of the admin repo,
     then commit and push the change.
 
     If the repos are already covered by some [wild][] pattern, this is
@@ -42,11 +42,11 @@ servers is this (untested but should work; feedback appreciated):
 3.  [Disable][writable] the old server so people won't push to it.
 
 4.  Copy all the repos over from the old server, including gitolite-admin.
-    Make sure the files end up with right ownership and permissions; if not,
-    chown/chmod them.
+    Make sure the files end up with the right ownership and permissions; if
+    not, chown/chmod them.
 
-5.  On a clone of the old gitolite-admin, add a new remote (or change an
+5.  Run `gitolite setup`.
+
+6.  On a clone of the old gitolite-admin, add a new remote (or change an
     existing one) to point to the new server.  Then `git push -f` to this
     remote.
-
-6.  On the server, run `gitolite setup`.
diff --git a/doc/rc.mkd b/doc/rc.mkd
index a582961..04c88f1 100644
--- a/doc/rc.mkd
+++ b/doc/rc.mkd
@@ -20,7 +20,7 @@ sure the brackets and braces stay matched up!
 Please look at the `~/.gitolite.rc` file that gets installed when you setup
 gitolite.  As you can see there are 3 types of variables in it:
 
-  * simple variables (like UMASK)
+  * simple variables (like `UMASK`)
   * lists (like `POST_COMPILE`, `POST_CREATE`)
   * hashes (like `ROLES`, `COMMANDS`)
 
diff --git a/doc/refex.mkd b/doc/refex.mkd
index 348c3e7..c5359c2 100644
--- a/doc/refex.mkd
+++ b/doc/refex.mkd
@@ -5,12 +5,12 @@ A refex is a word I made up to mean "a regex that matches a ref".  If you know
 
 In addition:
 
-  * if no refex is supplied, it defaults to `refs/.*`, for example in a rule
+  * If no refex is supplied, it defaults to `refs/.*`, for example in a rule
     like this:
 
         RW              =   alice
 
-  * a refex not starting with `refs/` is assumed to start with `refs/heads/`.
+  * A refex not starting with `refs/` is assumed to start with `refs/heads/`.
     This means normal branches can be conveniently written like this:
 
         RW  master      =   alice
@@ -20,7 +20,7 @@ In addition:
 
         RW  refs/tags/v[0-9]    =   bob
 
-  * a refex is implicitly anchored at the start, but not at the end.  In
+  * A refex is implicitly anchored at the start, but not at the end.  In
     regular expression lingo, a `^` is assumed at the start (but no `$` at the
     end is assumed).  So a refex of `master` will match all these:
 
diff --git a/doc/repos.mkd b/doc/repos.mkd
index 9edd44b..d32062a 100644
--- a/doc/repos.mkd
+++ b/doc/repos.mkd
@@ -52,5 +52,5 @@ server and do what you need to.
 **Renaming** a repo is also not automatic.  Here's what you do (and the order
 is important):
 
-  * go to the server and rename the repo at the Unix command line
-  * change the name in the conf/gitolite.conf file and add/commit/push.
+  * Go to the server and rename the repo at the Unix command line.
+  * Change the name in the conf/gitolite.conf file and add/commit/push.
diff --git a/doc/rules.mkd b/doc/rules.mkd
index b12bffc..39cabe9 100644
--- a/doc/rules.mkd
+++ b/doc/rules.mkd
@@ -58,35 +58,35 @@ example will pass the pre-git check.
 
 For the **update check**, git gives us all the information we need.  Then:
 
-  * all the rules for a repo are [accumulated][rule-accum]
+  * All the rules for a repo are [accumulated][rule-accum].
 
-  * the rules pertaining to this repo *and* this user (or to a group to which
-    they belong, respectively) are kept; the rest are ignored
+  * The rules pertaining to this repo *and* this user (or to a group to which
+    they belong, respectively) are kept; the rest are ignored.
 
-  * these rules are examined *in the sequence they appeared in the conf file*.
+  * These rules are examined *in the sequence they appeared in the conf file*.
     For each rule:
 
-      * if the ref does not match the [refex][], the rule is skipped
-      * if it's a deny rule (the permissions field is a `-`), access is
-        **rejected** and the matching stops
-      * if the permission field matches the specific [type of
+      * If the ref does not match the [refex][], the rule is skipped.
+      * If it's a deny rule (the permissions field is a `-`), access is
+        **rejected** and the matching stops.
+      * If the permission field matches the specific [type of
         write][write-types] operation, access is **allowed** and the matching
-        stops
+        stops.
 
-  * if no rule ends with a decision, ("fallthru"), access is **rejected**.
+  * If no rule ends with a decision, ("fallthru"), access is **rejected**.
 
 Now you need to understand how [refex][] matching happens and how the
 permissions match the various [types of write operations][write-types].
 
 Using these, you can see, in our example, that:
 
-  * everyone, even wally, can read the repo.
-  * dilbert can push, rewind, or delete any ref.
-  * alice can push, rewind, or delete any ref whose name starts with 'dev';
+  * Everyone, even wally, can read the repo.
+  * Dilbert can push, rewind, or delete any ref.
+  * Alice can push, rewind, or delete any ref whose name starts with 'dev';
     see [refex][] for details.
-  * alice can also push (but not rewind or delete) any ref whose name starts
+  * Alice can also push (but not rewind or delete) any ref whose name starts
     with 'temp/'.  This applies to bob also.
-  * if it weren't for line 3, the previous statement would apply to wally
+  * If it weren't for line 3, the previous statement would apply to wally
     also.
 
 Interestingly, wally can get past the pre-git check because gitolite ignores
@@ -94,7 +94,7 @@ deny rules for pre-git, but having got past it, he can't actually do anything.
 That's by design, and as I said if you don't like it you can ask gitolite to
 [deny at pre-git][deny-rules].
 
-### #perms summary of permissions
+### #permsum summary of permissions
 
 The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`.  This expands
 to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or
diff --git a/doc/setup.mkd b/doc/setup.mkd
index 075400c..17a238a 100644
--- a/doc/setup.mkd
+++ b/doc/setup.mkd
@@ -31,13 +31,13 @@ server.  That won't work.
 The 'setup' command has other uses, so you will be running it at other times
 after the install as well:
 
-  * to setup the update hook when you move [existing][] repos to gitolite.
+  * To setup the update hook when you move [existing][] repos to gitolite.
     This also applies if someone has been fiddling with the hooks on some
     repos and you want to put them all right quickly.
 
-  * to replace a [lost admin key][lost-key].
+  * To replace a [lost admin key][lost-key].
 
-  * to setup gitolite for http mode (run 'gitolite setup -h' for more info)
+  * To setup gitolite for http mode (run 'gitolite setup -h' for more info).
 
 When in doubt, run 'gitolite setup' anyway; it doesn't do any harm, though it
 may take a minute or so if you have more than a few thousand repos!
diff --git a/doc/special.mkd b/doc/special.mkd
index a9fc461..b0bf245 100644
--- a/doc/special.mkd
+++ b/doc/special.mkd
@@ -34,13 +34,13 @@ A user "alice" (if she's in the userlist) can then push any branches inside
 (Background: at runtime the "USER" component will be replaced by the name of
 the invoking user.  Access is determined by the right hand side, as usual).
 
-Compared to using arbitrary branch names on the same server, this
+Compared to using arbitrary branch names on the same server, this:
 
-  * reduces namespace pollution by corralling all these ad hoc branches into
+  * Reduces namespace pollution by corralling all these ad hoc branches into
     the "personal/" namespace.
-  * reduces branch name collision by giving each developer her own
+  * Reduces branch name collision by giving each developer her own
     sub-hierarchy within that.
-  * removes the need to think about access control, because a user can push
+  * Removes the need to think about access control, because a user can push
     only to his own sub-hierarchy.
 
 ## delegating access control responsibilities
diff --git a/doc/sskm.mkd b/doc/sskm.mkd
index 55e2787..e9ce302 100644
--- a/doc/sskm.mkd
+++ b/doc/sskm.mkd
@@ -223,12 +223,12 @@ Listing the keys shows that that new key is now marked active again:
 
 These are the things that can break if you allows your users to use this command:
 
-  * if you, as the gitolite admin, are in the habit of force-pushing changes
+  * If you, as the gitolite admin, are in the habit of force-pushing changes
     to the admin repo instead of doing a `git pull` (or, even better, a `git
     pull --rebase`) then you had better not enable this command.  Your users
     will eventually come after you with pitchforks ;-)
 
-  * there is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using
+  * There is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using
     this command.  You can distinguish `foo/alice.pub` from
     `bar/alice at home.pub`, but that's not because of the foo and bar, it's
     because the two files have different keyids.
@@ -236,7 +236,7 @@ These are the things that can break if you allows your users to use this command
     In other words, sskm only works with the older style, not with the
     "subdirectory" style of [multi-key][] management.
 
-  * keys placed in specific folders (for whatever reasons), will probably not
+  * Keys placed in specific folders (for whatever reasons), will probably not
     stay in those folders if this command is used.  Even a key delete, followed
     by undoing the delete, will cause the key to effectively move to the root
     of the key store (i.e., the `keydir` directory in the gitolite-admin repo).
diff --git a/doc/testing.mkd b/doc/testing.mkd
index c8b72ba..b6f4cc7 100644
--- a/doc/testing.mkd
+++ b/doc/testing.mkd
@@ -7,9 +7,11 @@ so be sure to use a throwaway userid**.</font>
 
     git clone git://github.com/sitaramc/gitolite
     cd gitolite
-    git checkout -f g3
     prove
 
+(Make sure sshd allows incoming ssh to this userid at least from localhost.
+Out of scope for this document: sshd config, 'AllowUsers', etc...)
+
 Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
 Take a look at some of the scripts and you will see what it looks like.  It
 has a few quirks and nuances; if you really care, email me.
@@ -23,3 +25,24 @@ would otherwise take.
 
 If you think that defeats the purpose of the testing, you haven't read
 [this][auth] yet.
+
+## #trying trying out gitolite
+
+It's easy to take gitolite for a trial run, in ssh mode, and play with all of
+its features (except mirroring).
+
+Create a **throw-away userid**, log in to it, then do what the "testing
+gitolite" section above says, except instead of running `prove`, you run
+`prove t/ssh*`.
+
+When this is done, you get a gitolite installation with 7 gitolite users
+("admin", and "u1" through "u6").
+
+Don't forget that the client and the server are all on the same user on the
+same machine; we're *simulating* 7 gitolite users using ssh keys!  (How?
+Maybe `~/.ssh/config` will give you a hint).
+
+URLs look like `user:repo`, so for example you can clone the admin repo by
+`git clone admin:gitolite-admin`.  Remote commands look like `ssh u1 info`.
+
+So start by cloning the admin repo, and try out whatever you want!
diff --git a/doc/user.mkd b/doc/user.mkd
index 5842fcd..a0a767e 100644
--- a/doc/user.mkd
+++ b/doc/user.mkd
@@ -3,10 +3,6 @@
 ...written for the one guy in the world no one will think of as "just a normal
 user" ;-)
 
-This document has some text, and a lot of links.  Most of this info *is*
-available in the rest of the documentation, but it's scattered and sparse.
-Collecting all of it, or at least links to it, in one place sounds useful.
-
 ----
 
 [[TOC]]
@@ -42,7 +38,7 @@ that look like regex patterns, (with a "C" permission).
 
 If you see any, it means you are allowed to create brand new repos whose names
 fit that pattern.  When you create such a repo, your "ownership" of it (as far
-as gitolite is concerned) is *automatically* recorded by gitolite.
+as gitolite is concerned) is automatically recorded by gitolite.
 
 ## other commands
 
diff --git a/doc/vref.mkd b/doc/vref.mkd
index 97b6a66..a5bfb27 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -82,14 +82,14 @@ exit.
 
 ## how it works -- details
 
-  * the VREF code is only called if there are any VREF rules for the user,
+  * The VREF code is only called if there are any VREF rules for the user,
     which means when the lead developer pushes, the VREF is not called at all.
 
     Side note: this is enormously more efficient than adding additional
     `update` hooks, which will get executed whether they are needed or not,
     for every repo and every user!
 
-  * when dev2 or dev3 push, gitolite first checks the real ref
+  * When dev2 or dev3 push, gitolite first checks the real ref
     (`ref/heads/master` or whatever).  After this it looks at VREF rules, and
     calls an external program for every one it finds.  Specifically, in a line
     like
@@ -102,29 +102,29 @@ exit.
     The program is passed **nine arguments** in this case (see next section
     for details).
 
-  * the script can print anything it wants to STDOUT; the first word in each
+  * The script can print anything it wants to STDOUT; the first word in each
     such line will be treated as a virtual ref to be matched against all the
     rules, while the rest, if any, is a message to be added to the standard
     "...DENIED..." message that gitolite prints if that refex matches.
 
     Usually it only makes sense to either
 
-      * print nothing -- if you don't want the rule that triggered it to match
+      * Print nothing -- if you don't want the rule that triggered it to match
         (ie., whatever condition being tested was not violated; like if the
-        count of changed files did not exceed 9, in our earlier example)
-      * print the refex itself (plus an optional message), so that it matches
-        the line which invoked it
+        count of changed files did not exceed 9, in our earlier example).
+      * Print the refex itself (plus an optional message), so that it matches
+        the line which invoked it.
 
 ### arguments passed to the vref code
 
-  * arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
-    to the update hook (see 'man githooks')
+  * Arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
+    to the update hook (see 'man githooks').
 
     This, combined with the fact that non-zero exits are detected, mean that
     you can simply use an existing update.secondary as a new VREF as-is, no
     changes needed.
 
-  * arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs.  These are the
+  * Arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs.  These are the
     same as the oldsha and newsha values, except if one of them is all-0.
     (indicating a ref creation or deletion).  In that case the corresponding
     'tree' SHA is set (by gitolite, as a courtesy) to the special SHA
@@ -134,15 +134,15 @@ exit.
     (None of these shenanigans would have been needed if `git diff $oldsha
     $newsha` would not error out when passed an all-0 SHA.)
 
-  * argument **6**: the attempted access flag.  Typically `W` or `+`, but
+  * Argument **6**: the attempted access flag.  Typically `W` or `+`, but
     could also be `C`, `D`, or any of these 4 followed by `M`.  If you have to
     ask what they mean, you haven't read enough gitolite documentation to be
     able to make virtual refs work.
 
-  * argument **7**: is the entire refex; in our example
+  * Argument **7**: is the entire refex; in our example
     `VREF/COUNT/3/NEWFILES`.
 
-  * arguments **8 onward**: are the split out (by `/`) portions of the refex,
+  * Arguments **8 onward**: are the split out (by `/`) portions of the refex,
     excluding the first two components.  In our example they would be `3`
     followed by `NEWFILES`.
 
@@ -288,14 +288,14 @@ quite different.
 
 Here are some more ideas:
 
-  * number of commits (`git rev-list --count $old $new`)
-  * number of binary files in commit (currently I only know to count
-    occurrences of ` Bin ` in the output of `git diff --stat`
-  * number of *new* binary files (count ` Bin 0 ->` in `git diff --stat`
-    output)
-  * time of day/day of week (see example snippet somewhere above)
-  * IP address
-  * phase of the moon
+  * Number of commits (`git rev-list --count $old $new`).
+  * Number of binary files in commit (currently I only know to count
+    occurrences of ` Bin ` in the output of `git diff --stat`.
+  * Number of *new* binary files (count ` Bin 0 ->` in `git diff --stat`
+    output).
+  * Time of day/day of week (see example snippet somewhere above).
+  * IP address.
+  * Phase of the moon.
 
 Note that pretty much anything that involves `$oldsha..$newsha` will have to
 deal with the issue that when you push a new tag or branch, the "old" part
diff --git a/doc/why.mkd b/doc/why.mkd
index 2112a0b..f88f7c1 100644
--- a/doc/why.mkd
+++ b/doc/why.mkd
@@ -1,47 +1,97 @@
 # why might you need gitolite
 
-Gitolite is separate from git, and needs to be installed and configured.  So...
-why do we bother?
+[[TOC]]
+
+----
+
+## basic use case
 
 Gitolite is useful in any server that is going to host multiple git
-repositories, each with many developers, where some sort of access control is
-required.
-
-In theory, this can be done with plain old Unix permissions: each user is a
-member of one or more groups, each group "owns" one or more repositories, and
-using unix permissions (especially the setgid bit -- `chmod g+s`) you can
-allow/disallow users access to repos.
-
-But there are several disadvantages here:
-
-  * Every user needs a userid and password on the server.  This is usually a
-    killer, especially in tightly controlled environments.
-  * Adding/removing access rights involves complex `usermod -G ...` mumblings
-    which most admins would rather not deal with.
-  * *Viewing* (aka auditing) the current set of permissions requires running
-    multiple commands to list directories and their permissions/ownerships,
-    users and their group memberships, and then correlating all these
-    manually.
-  * Auditing historical permissions or permission changes is pretty much
-    impossible without extraneous tools.
-  * Errors or omissions in setting the permissions exactly can cause problems
-    of either kind: false accepts or false rejects.
-  * Without going into ACLs it is not possible to give some people read-only
-    access while some others have read-write access to a repo (unless you make
-    it world-readable).  Group access just doesn't have enough granularity.
-  * It is absolutely impossible to restrict pushing by branch name or tag
-    name.
-
-Gitolite does away with all this:
-
-  * It uses ssh magic to remove the need to give actual unix userids to
-    developers.
-  * It uses a simple but powerful config file format to specify access rights.
-  * Access control changes are affected by modifying this file, adding or
-    removing user's public keys, and "compiling" the configuration.
-  * This also makes auditing trivial -- all the data is in one place, and
-    changes to the configuration are also logged, so you can audit them.
-  * Finally, the config file allows distinguishing between read-only and
-    read-write access, not only at the repository level, but at the branch
-    level within repositories.
+repositories, each with many developers, where "anyone can do anything to any
+repo" is not a good idea.  Here're two examples to illustrate.
+
+Example 1, 3 repos, 3 developers with different levels of access to each repo:
+
+    repo foo
+        RW+     =   alice
+        R       =   bob
+
+    repo bar
+        RW+     =   bob
+        R       =   alice
+
+    repo baz
+        RW+     =   carol
+        R       =   alice bob
+
+Example 2, one repo, but different levels of access to different branches and
+tags for different developers:
+
+    repo foo
+        RW+ master                  =   alice
+        RW+ dev/                    =   bob
+        RW  refs/heads/tags/v[0-9]  =   ashok
+
+## #alt alternatives
+
+### unix permissions and ACLs
+
+If you're a masochist, you could probably do example 1 with Unix permissions
+and facls.  But you can't do example 2 -- git is not designed to allow that!
+
+Here are some other disadvantages of the Unix ACL method:
+
+  * Every user needs a userid and password on the server.
+  * Changing access rights involves complex `usermod -G ...` mumblings
+    (I.e., the "pain" mentioned above is not a one-time pain!)
+  * *Viewing* the current set of permissions requires running multiple
+    commands to list directories and their permissions/ownerships, users and
+    their group memberships, and then correlating all these manually.
+  * Auditing historical permissions or permission changes is impossible.
+
+### #gcr Gerrit Code Review
+
+The best real alternative to gitolite is Gerrit Code Review.  If code review
+is an essential part of your workflow, you should use Gerrit.
+
+Here're some high level differences between gitolite and Gerrit:
+
+**Size**: 3000+ lines of perl versus of 56,000+ lines of Java
+
+**Architecture**: Gitolite sits on top of "standard" git and openssh, which
+are assumed to already be installed.  Gerrit includes its own git stack (jgit)
+and sshd (Apache Mina).  In Java tradition, they all come bundled together.
+
+(Corollary: As far as I know jgit does not support the same hooks that 'man
+githooks' talks about).
+
+Gitolite uses a plain text config file; gerrit uses a database.
+
+**User view**: Gitolite is invisible to users except when access is denied.  I
+think Gerrit is much more visible to devs.
+
+On a related note, gitolite does not do anything special with signed or
+annotated tags, nor does it check author/committer identity.  However, it is
+trivial to add your own code to do either (or if someone contributes it, to
+just "enable" what ships with gitolite in a disabled state).
+
+### gitorious
+
+Anecdotally, gitorious is very hard to install.  Comparison with gitolite may
+be useless because I believe it doesn't have branch/tag level access control.
+However, I can't confirm or deny this because I can't find any documentation
+on the website.
+
+In addition, the main website hides the source code very well, so you already
+have a challenge!  [The only link I could find was tucked away at the bottom
+of the About page, in the License section].
+
+### gitlab
+
+Gitlab is built on top of gitolite, but I don't know more than that as yet.
+Patches welcome.
+
+### others
 
+Please send in patches to this doc if you know of other open source git
+hosting solutions that do access control.
diff --git a/doc/wild.mkd b/doc/wild.mkd
index 49f99ba..4670cc4 100644
--- a/doc/wild.mkd
+++ b/doc/wild.mkd
@@ -90,9 +90,11 @@ But you may be surprised to find that it does not match even
 `^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending
 metacharacters.
 
-Side-note: contrast with refexes
+>   ----
 
->   Just for interest, note that this is in contrast to the refexes for the >
+>   *Side-note: contrast with refexes*
+
+>   Just for interest, note that this is in contrast to the refexes for the
 >   normal "branch" permissions, as described in `doc/gitolite.conf.mkd` and
 >   elsewhere.  These "refexes" are only anchored at the start; a pattern like
 >   `refs/heads/master` actually can match `refs/heads/master01/bar` as well,
@@ -100,11 +102,13 @@ Side-note: contrast with refexes
 >   sides if you really care, by using `master$` instead of `master`, but that
 >   is *not* the default for refexes.
 
+>   ----
+
 ## roles
 
-The tokens READERS and WRITERS are called "role" names.  The access rules that
-the admin specifies say what permissions these roles have, but they don't say
-what users are in these roles.
+The tokens READERS and WRITERS are called "role" names.  The access rules in
+the conf file decide what permissions these roles have, but they don't say
+what users are in each of these roles.
 
 That needs to be done by the creator of the repo, using the `perms` command.
 You can run `ssh git at host perms -h` for detailed help, but in brief, that
@@ -146,4 +150,8 @@ command.  Try `ssh git at host info -h` to get help on the info command.
 
 ## deleting a wild repo
 
-TBD
+Run the whimsically named "D" command -- try `ssh git at host D -h` for more info
+on how to delete a wild repo.  (Yes the command is "D"; it's meant to be a
+counterpart to the "C" permission that allowed you to create the repo in the
+first place).  Of course this only works if your admin has enabled the command
+(gitolite ships with the command disabled for remote use).
diff --git a/doc/write-types.mkd b/doc/write-types.mkd
index e899318..a05ed6c 100644
--- a/doc/write-types.mkd
+++ b/doc/write-types.mkd
@@ -11,14 +11,14 @@ The most common are:
 Sometimes you want to allow people to push, but not *create* a ref.  Or
 rewind, but not *delete* a ref.  The `C` and `D` qualifiers help here.
 
-  * when a rule specifies `RWC` or `RW+C`, then *rules that do NOT have the C
-    qualifier will no longer permit **creating** a ref*
+  * When a rule specifies `RWC` or `RW+C`, then *rules that do NOT have the C
+    qualifier will no longer permit **creating** a ref*.
 
     <font color="gray">Please do not confuse this with the standalone `C`
     permission that allows someone to [create][] a **repo**</font>
 
-  * when a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the C
-    qualifier will no longer permit **deleting** a ref*
+  * When a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the C
+    qualifier will no longer permit **deleting** a ref*.
 
 Note: These two can be combined, so you can have `RWCD` and `RW+CD` as well.
 

commit 95e6c2ae8b9d57d3589a353428e62ae4a47a4dd3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 17 06:42:57 2012 +0530

    (doc) switch from mindmap to mkd...
    
    we don't need the flexibility any more

diff --git a/doc/fm2mt.pl b/doc/fm2mt.pl
deleted file mode 100755
index 4d4d586..0000000
--- a/doc/fm2mt.pl
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use 5.10.0;
-
-# usage: ./fm2mt.pl < g3-master-toc.mm > master-toc.mkd
-
-use HTML::Entities;
-
-sub out { my $out = shift; print $out; }
-
-# freemind to "dense" HTML
-my @in = fm2indent();
-
-out("# gitolite documentation");
-my $started = 0;
-for (@in) {
-    my($indent, $text) = split ' ', $_, 2;
-    $indent--;
-
-    if (not $indent) {
-        out "\n\n## $text\n";
-        $started = 0;
-        next;
-    }
-
-    if ($indent == 1) {
-        # (dense mode) $text = color("red", $text);
-    }
-
-    if ($indent <= 2) {
-        # (dense mode) $text = size(2 - $indent, $text);
-    } else {
-        # 3 or more
-        # (dense mode) $text = ("/" x ($indent-4)) . color("gray", $text);
-    }
-
-    # normal mode
-    $text = "\n" . ("    " x ($indent-1)) . "  * $text";
-    # (dense mode) out " -- " if $started++;
-    out $text;
-}
-
-sub size {
-    my ($s, $t) = @_;
-    return "<font size=\"+" . $s . "\">$t</font>" if $s;
-    return $t;
-}
-
-sub color {
-    my ($c, $t) = @_;
-    return "<font color=\"$c\">$t</font>";
-}
-
-sub get_indent {
-    my $_ = shift;
-    chomp;
-    return () unless /\S/;
-    if (/^(#+) (.*)/) {
-        return (length($1)-1, $2);
-    }
-    if (/^( +)  \* (.*)/) {
-        my $t = $2;
-        my $i = length($1);
-        die 1 if $i % 4;
-        $i = $i/4 + 3;
-
-        return ($i, $t);
-    }
-    return ();
-}
-
-sub fm2indent {
-    my @out = ();
-    my $indent=0;
-
-    while (<>)
-    {
-        next unless /^<node / or /^<\/node/;
-        if (/^<\/node>$/)
-        {
-            $indent--;
-            next;
-        }
-        next unless /TEXT="([^"]*)"/;
-        my $text = decode_entities($1);
-
-        push @out, "\n$indent $text" if $indent;
-
-        $indent++ unless (/\/>/);
-    }
-
-    return @out;
-}
diff --git a/doc/g3-master-toc.mm b/doc/g3-master-toc.mm
deleted file mode 100755
index 9d89535..0000000
--- a/doc/g3-master-toc.mm
+++ /dev/null
@@ -1,160 +0,0 @@
-<map version="0.9.0">
-<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->
-<node CREATED="1331107648858" ID="ID_187992057" MODIFIED="1333299050498" TEXT="g3-master-index">
-<node CREATED="1333299038338" ID="ID_1574733847" MODIFIED="1333536460445" POSITION="right" TEXT="[Introduction][index]">
-<node CREATED="1333466035663" ID="ID_974293172" MODIFIED="1333536536267" TEXT="(for [current][g2] gitolite (v2) users)"/>
-<node CREATED="1333466047943" ID="ID_1283272433" MODIFIED="1333536530576" TEXT="[quick links][ql]"/>
-<node CREATED="1333326589821" ID="ID_195282410" MODIFIED="1333340829999" TEXT="[what][] is gitolite"/>
-<node CREATED="1333326593927" ID="ID_1110367572" MODIFIED="1333340841907" TEXT="[why][] might you need it"/>
-<node CREATED="1333536552704" ID="ID_1607033994" MODIFIED="1333536835577" TEXT="[contact][] info, mailing list, IRC channel"/>
-<node CREATED="1333536577819" ID="ID_1858469252" MODIFIED="1333536835576" TEXT="[license][] info"/>
-</node>
-<node CREATED="1333300010980" ID="ID_1728019284" MODIFIED="1333616327427" POSITION="right" TEXT="help for [emergencies][]">
-<node CREATED="1333300016176" ID="ID_30257493" MODIFIED="1333453799784" TEXT="[lost][lost-key] admin key/access"/>
-<node CREATED="1333327185236" ID="ID_910862972" MODIFIED="1333471145297" TEXT="[bypass][]ing gitolite"/>
-<node CREATED="1333328183948" ID="ID_564351456" MODIFIED="1333453834497" TEXT="[clean][]ing out a botched install"/>
-<node CREATED="1333326808789" ID="ID_54380370" MODIFIED="1333631901528" TEXT="[common][ce] errors (TBD)"/>
-<node CREATED="1333597279461" ID="ID_547537696" MODIFIED="1333631907824" TEXT="[uncommon][ue] errors"/>
-<node CREATED="1333328539885" ID="ID_162892604" MODIFIED="1333527642132" TEXT="things that are [not gitolite problems][ngp]"/>
-</node>
-<node CREATED="1333466070125" ID="ID_1329733023" MODIFIED="1333527838066" POSITION="right" TEXT="[WARNINGS][]"/>
-<node CREATED="1333326560775" ID="ID_1030406941" MODIFIED="1333631954561" POSITION="right" TEXT="[quick][qi] install, setup, and clone"/>
-<node CREATED="1333465293309" ID="ID_1405252383" MODIFIED="1333526188455" POSITION="right" TEXT="[install][]">
-<node CREATED="1333327826145" ID="ID_519306224" MODIFIED="1333467083667" TEXT="notes and naming conventions"/>
-<node CREATED="1333328069297" ID="ID_624460163" MODIFIED="1333465312762" TEXT="requirements">
-<node CREATED="1333328127550" ID="ID_368487912" MODIFIED="1333537067223" TEXT="your skills"/>
-<node CREATED="1333328123855" ID="ID_1146334636" MODIFIED="1333339551947" TEXT="server"/>
-<node CREATED="1333328126057" ID="ID_899541385" MODIFIED="1333328127275" TEXT="client"/>
-</node>
-<node CREATED="1333328151051" ID="ID_1577506173" MODIFIED="1333465323159" TEXT="getting the software"/>
-<node CREATED="1333328156575" ID="ID_208746322" MODIFIED="1333465333727" TEXT="the actual install"/>
-<node CREATED="1333467553366" ID="ID_926614739" MODIFIED="1333467556140" TEXT="upgrading"/>
-<node CREATED="1333328592876" ID="ID_237795046" MODIFIED="1333466108368" TEXT="packaging gitolite"/>
-<node CREATED="1333327776230" ID="ID_1431739921" MODIFIED="1334308813624" TEXT="[migr][]ating"/>
-</node>
-<node CREATED="1333301194129" ID="ID_746196740" MODIFIED="1333471816303" POSITION="right" TEXT="[setup][]"/>
-<node CREATED="1333299674481" ID="ID_714407264" MODIFIED="1333528325006" POSITION="right" TEXT="gitolite [admin][]istration">
-<node CREATED="1333299694885" ID="ID_1916098884" MODIFIED="1333537908466" TEXT="[server][]-side">
-<node CREATED="1333327917312" ID="ID_1266878047" MODIFIED="1333537539764" TEXT="([link][WARNINGS]: important cautions on server side activity)"/>
-<node CREATED="1333301196071" ID="ID_760433725" MODIFIED="1333537537075" TEXT="changing settings in the [rc][] file"/>
-<node CREATED="1333537546089" ID="ID_2500021" MODIFIED="1333537553267" TEXT="installing custom [hooks][]"/>
-<node CREATED="1333537563152" ID="ID_472486055" MODIFIED="1333537616205" TEXT="([link][existing]: moving existing repos into gitolite)"/>
-</node>
-<node CREATED="1333299702754" ID="ID_609351656" MODIFIED="1333537921248" TEXT="[access control][conf] (the gitolite.conf file)">
-<node CREATED="1333566502470" ID="ID_1931486515" MODIFIED="1333583575642" TEXT="[example][confex] of a conf file"/>
-<node CREATED="1333300715360" ID="ID_41940351" MODIFIED="1333547591053" TEXT="basic [syntax][]">
-<node CREATED="1333300732151" ID="ID_1494555007" MODIFIED="1333300733944" TEXT="include files"/>
-<node CREATED="1333300741424" ID="ID_416012685" MODIFIED="1333513004487" TEXT="([link][sugar]: syntactic sugar)"/>
-</node>
-<node CREATED="1333300461624" ID="ID_1848362678" MODIFIED="1333529103568" TEXT="[groups][] (of users and repos)">
-<node CREATED="1333300884900" ID="ID_1412135290" MODIFIED="1333300893755" TEXT="special: '@all'"/>
-<node CREATED="1333300437860" ID="ID_897670108" MODIFIED="1333300770020" TEXT="(link: storing user group info in LDAP)"/>
-</node>
-<node CREATED="1333299715546" ID="ID_1916374076" MODIFIED="1333529107922" TEXT="adding and removing [users][]">
-<node CREATED="1333326840977" ID="ID_1723439755" MODIFIED="1333517370375" TEXT="multiple keys per user"/>
-</node>
-<node CREATED="1333299719741" ID="ID_275665792" MODIFIED="1333529112764" TEXT="adding and removing [repos][]">
-<node CREATED="1333327426500" ID="ID_1964948889" MODIFIED="1333518450199" TEXT="renaming repos"/>
-</node>
-<node CREATED="1333300309235" ID="ID_1437714859" MODIFIED="1333529121211" TEXT="defining access [rules][]">
-<node CREATED="1333584760127" ID="ID_469376519" MODIFIED="1333584767343" TEXT="what does a rule look like?"/>
-<node CREATED="1333326976405" ID="ID_563906836" MODIFIED="1333584770230" TEXT="when are the rules checked?"/>
-<node CREATED="1333535584526" ID="ID_957313656" MODIFIED="1333584774497" TEXT="how are the rules matched?"/>
-<node CREATED="1333301045521" ID="ID_1228046254" MODIFIED="1333536127886" TEXT="summary of [permissions][perms]"/>
-<node CREATED="1333599586661" ID="ID_1785519868" MODIFIED="1333599590134" TEXT="additional topics">
-<node CREATED="1333599591055" ID="ID_1379421123" MODIFIED="1333599606966" TEXT="[rule accumulation][rule-accum]"/>
-<node CREATED="1333599613910" ID="ID_1760840175" MODIFIED="1333599634987" TEXT="applying [deny rules][deny-rules] during the pre-git check"/>
-</node>
-<node CREATED="1333535705100" ID="ID_1768473150" MODIFIED="1333535718347" TEXT="([link][refex]: refexes)"/>
-<node CREATED="1333535719180" ID="ID_431454064" MODIFIED="1333626093118" TEXT="([link][write-types]: different types of write operations)"/>
-<node CREATED="1333300541352" ID="ID_1407372791" MODIFIED="1333536011832" TEXT="([link][vref]: virtual refs)"/>
-</node>
-<node CREATED="1333521966550" ID="ID_462181887" MODIFIED="1333529126015" TEXT="gitolite [options][]"/>
-<node CREATED="1333521972558" ID="ID_1180232837" MODIFIED="1333536152124" TEXT=""[git config][git-config]" keys and values"/>
-<node CREATED="1333299726939" ID="ID_1229702100" MODIFIED="1333601090414" TEXT="["wild"][wild] repos (user created repos)">
-<node CREATED="1333329088398" ID="ID_533855844" MODIFIED="1333630553509" TEXT="quick introduction"/>
-<node CREATED="1333599529910" ID="ID_1393418948" MODIFIED="1333630403135" TEXT="(admin) declaring wild repos in the conf file"/>
-<node CREATED="1333629999807" ID="ID_139250181" MODIFIED="1333630432141" TEXT="(user) [creating][create] a specific repo"/>
-<node CREATED="1333599928516" ID="ID_439610891" MODIFIED="1333600901677" TEXT="repo patterns"/>
-<node CREATED="1333329137043" ID="ID_1876270574" MODIFIED="1333599539626" TEXT="roles"/>
-<node CREATED="1333329119800" ID="ID_1196899132" MODIFIED="1333600898568" TEXT="adding other roles">
-<node CREATED="1333630582435" ID="ID_792555451" MODIFIED="1333630596161" TEXT="[IMPORTANT WARNING ABOUT THIS FEATURE][rolenamewarn]"/>
-</node>
-<node CREATED="1333299760157" ID="ID_1787203633" MODIFIED="1333600934866" TEXT="listing wild repos"/>
-<node CREATED="1333299751513" ID="ID_1503072743" MODIFIED="1333600958915" TEXT="deleting wild repos"/>
-</node>
-</node>
-</node>
-<node CREATED="1333326619348" ID="ID_697668065" MODIFIED="1333537363837" POSITION="right" TEXT="what your [user][]s need to know"/>
-<node CREATED="1333300236173" ID="ID_1026631043" MODIFIED="1333607025161" POSITION="right" TEXT="[special][] features/setups">
-<node CREATED="1333327232490" ID="ID_849345095" MODIFIED="1333631333199" TEXT="[disabling pushes][writable] to take backups"/>
-<node CREATED="1333300243659" ID="ID_106425369" MODIFIED="1333604404937" TEXT="[personal][pers] branches"/>
-<node CREATED="1333300501314" ID="ID_1819387758" MODIFIED="1333606161557" TEXT="[delegating][deleg] access control responsibilities">
-<node CREATED="1333606177736" ID="ID_1153299625" MODIFIED="1333606191734" TEXT="([link][NAME]: the NAME VREF)"/>
-<node CREATED="1333300531135" ID="ID_206710372" MODIFIED="1333606536438" TEXT="the [subconf][] command"/>
-</node>
-<node CREATED="1333300925828" ID="ID_1848845001" MODIFIED="1333626115598" TEXT="([link][partial-copy]: faking selective READ control)"/>
-</node>
-<node CREATED="1333327469941" ID="ID_1230549116" MODIFIED="1333607053576" POSITION="right" TEXT="interfacing with [external][] tools">
-<node CREATED="1333327476469" ID="ID_1887257091" MODIFIED="1333327480072" TEXT="gitweb">
-<node CREATED="1333504877358" ID="ID_1448276401" MODIFIED="1333625357439" TEXT="changing the [UMASK][umask]"/>
-</node>
-<node CREATED="1333327480559" ID="ID_513431483" MODIFIED="1333327483014" TEXT="git-daemon"/>
-</node>
-<node CREATED="1333326753755" ID="ID_16865998" MODIFIED="1333625410973" POSITION="right" TEXT="[mirroring][]"/>
-<node CREATED="1333299976094" ID="ID_1948442779" MODIFIED="1333625437290" POSITION="right" TEXT="[rare][]/one-time activities">
-<node CREATED="1333299985915" ID="ID_530631392" MODIFIED="1333471109149" TEXT="moving [existing][] repos into gitolite"/>
-<node CREATED="1333300099453" ID="ID_1521618753" MODIFIED="1333625463686" TEXT="[moving][] servers"/>
-</node>
-<node CREATED="1333299141061" ID="ID_37630832" MODIFIED="1333625569805" POSITION="right" TEXT="[customisation][cust]">
-<node CREATED="1333299458493" ID="ID_992003929" MODIFIED="1333625583059" TEXT="types of non-core programs">
-<node CREATED="1333625624553" ID="ID_1205434644" MODIFIED="1333639834277" TEXT="([link][non-core]: non-core programs shipped with gitolite)"/>
-</node>
-<node CREATED="1333625674563" ID="ID_603892527" MODIFIED="1333625679247" TEXT="[commands][]"/>
-<node CREATED="1333625679584" ID="ID_230664255" MODIFIED="1333625682401" TEXT="[hooks][]"/>
-<node CREATED="1333625689987" ID="ID_112339937" MODIFIED="1333625696128" TEXT="syntactic [sugar][]"/>
-<node CREATED="1333625703698" ID="ID_1165978069" MODIFIED="1333625715099" TEXT="([link][triggers]: triggers)"/>
-<node CREATED="1333625717605" ID="ID_1483309372" MODIFIED="1333625734532" TEXT="([link][vref]: VREFs)"/>
-<node CREATED="1333625757836" ID="ID_1102952567" MODIFIED="1333625766871" TEXT="[developer notes][dev-notes]">
-<node CREATED="1333625797339" ID="ID_1760918724" MODIFIED="1333625812051" TEXT="environment variables and other inputs"/>
-<node CREATED="1333625816655" ID="ID_420128580" MODIFIED="1333625818681" TEXT="APIs">
-<node CREATED="1333625820871" ID="ID_672321981" MODIFIED="1333625823262" TEXT="the shell API"/>
-<node CREATED="1333625823632" ID="ID_206993094" MODIFIED="1333625825547" TEXT="the perl API"/>
-</node>
-<node CREATED="1333625864473" ID="ID_1009525945" MODIFIED="1333625888705" TEXT="writing your own...">
-<node CREATED="1333625889803" ID="ID_323248071" MODIFIED="1333625890797" TEXT="hooks"/>
-<node CREATED="1333625891081" ID="ID_774724756" MODIFIED="1333625895322" TEXT="commands"/>
-<node CREATED="1333625956866" ID="ID_1107908272" MODIFIED="1333625960910" TEXT="trigger programs"/>
-<node CREATED="1333625961452" ID="ID_159826002" MODIFIED="1333625967173" TEXT="sugar"/>
-</node>
-</node>
-</node>
-<node CREATED="1334308529127" ID="ID_1328767320" MODIFIED="1334308538750" POSITION="right" TEXT="[non-core][] programs shipped with gitolite">
-<node CREATED="1334308547171" ID="ID_1481929835" MODIFIED="1334308550136" TEXT="commands"/>
-<node CREATED="1334308552822" ID="ID_1632638638" MODIFIED="1334308556005" TEXT="syntactic sugar"/>
-<node CREATED="1334308559968" ID="ID_759401290" MODIFIED="1334308561948" TEXT="triggers"/>
-<node CREATED="1334308570911" ID="ID_118147118" MODIFIED="1334308585866" TEXT="([link][vref]: VREFs)"/>
-<node CREATED="1334308589293" ID="ID_843158319" MODIFIED="1334308591424" TEXT="special cases">
-<node CREATED="1334308633132" ID="ID_1213286105" MODIFIED="1334308757373" TEXT="[partial-copy][]: selective read control for branches"/>
-</node>
-</node>
-<node CREATED="1333300577608" ID="ID_583410113" MODIFIED="1333537235253" POSITION="right" TEXT="background info">
-<node CREATED="1333299088617" ID="ID_1919571562" MODIFIED="1333537208005" TEXT="[files and directories][files] involved in install+setup"/>
-<node CREATED="1333300378018" ID="ID_584192327" MODIFIED="1333536676126" TEXT="[auth][]entication versus authorisation">
-<node CREATED="1333300390847" ID="ID_384452374" MODIFIED="1333453722001" TEXT="interfacing with [other authentication][otherauth] systems"/>
-<node CREATED="1333300437860" ID="ID_99922512" MODIFIED="1333453750215" TEXT="getting user group info from [LDAP][ldap]"/>
-</node>
-<node CREATED="1333301124871" ID="ID_931083276" MODIFIED="1333453771459" TEXT="[ssh][]"/>
-<node CREATED="1333301127955" ID="ID_1527577153" MODIFIED="1333453780482" TEXT="[regular expressions][regex]"/>
-</node>
-<node CREATED="1333300587911" ID="ID_91544543" MODIFIED="1333641141649" POSITION="right" TEXT="contributed software, tools, and documentation">
-<node CREATED="1333300608550" ID="ID_1486318266" MODIFIED="1333626152668" TEXT="TBD"/>
-</node>
-<node CREATED="1333301296248" ID="ID_571518549" MODIFIED="1333526198202" POSITION="right" TEXT="TBD">
-<node CREATED="1333327082853" ID="ID_488765250" MODIFIED="1333327089444" TEXT="log file format, LOG_EXTRA"/>
-<node CREATED="1333301308136" ID="ID_1900285587" MODIFIED="1333301311863" TEXT="hub"/>
-<node CREATED="1333328274461" ID="ID_248606591" MODIFIED="1333328277083" TEXT="mob branches"/>
-<node CREATED="1333328277387" ID="ID_1027016949" MODIFIED="1333328280083" TEXT="password access"/>
-</node>
-</node>
-</map>
diff --git a/doc/master-toc.mkd b/doc/master-toc.mkd
new file mode 100644
index 0000000..39904ba
--- /dev/null
+++ b/doc/master-toc.mkd
@@ -0,0 +1,162 @@
+# gitolite documentation
+
+## [Introduction][index]
+
+  * (for [current][g2] gitolite (v2) users)
+  * [quick links][ql]
+  * [what][] is gitolite
+  * [why][] might you need it
+  * [contact][] info, mailing list, IRC channel
+  * [license][] info
+
+## help for [emergencies][]
+
+  * [lost][lost-key] admin key/access
+  * [bypass][]ing gitolite
+  * [clean][]ing out a botched install
+  * [common][ce] errors (TBD)
+  * [uncommon][ue] errors
+  * things that are [not gitolite problems][ngp]
+
+## [WARNINGS][]
+
+
+## [trying][] out gitolite
+
+
+## [quick][qi] install, setup, and clone
+
+
+## [install][]
+
+  * notes and naming conventions
+  * requirements
+      * your skills
+      * server
+      * client
+  * getting the software
+  * the actual install
+  * upgrading
+  * packaging gitolite
+  * [migr][]ating
+
+## [setup][]
+
+
+## gitolite [admin][]istration
+
+  * [server][]-side
+      * ([link][WARNINGS]: important cautions on server side activity)
+      * changing settings in the [rc][] file
+      * installing custom [hooks][]
+      * ([link][existing]: moving existing repos into gitolite)
+  * [access control][conf] (the gitolite.conf file)
+      * [example][confex] of a conf file
+      * basic [syntax][]
+          * include files
+          * ([link][sugar]: syntactic sugar)
+      * [groups][] (of users and repos)
+          * special: '@all'
+          * (link: storing user group info in LDAP)
+      * adding and removing [users][]
+          * multiple keys per user
+      * adding and removing [repos][]
+          * renaming repos
+      * defining access [rules][]
+          * what does a rule look like?
+          * when are the rules checked?
+          * how are the rules matched?
+          * summary of [permissions][permsum]
+          * additional topics
+              * [rule accumulation][rule-accum]
+              * applying [deny rules][deny-rules] during the pre-git check
+          * ([link][refex]: refexes)
+          * ([link][write-types]: different types of write operations)
+          * ([link][vref]: virtual refs)
+      * gitolite [options][]
+      * "[git config][git-config]" keys and values
+      * ["wild"][wild] repos (user created repos)
+          * quick introduction
+          * (admin) declaring wild repos in the conf file
+          * (user) [creating][create] a specific repo
+          * repo patterns
+          * roles
+          * adding other roles
+              * [IMPORTANT WARNING ABOUT THIS FEATURE][rolenamewarn]
+          * listing wild repos
+          * deleting wild repos
+
+## what your [user][]s need to know
+
+
+## [special][] features/setups
+
+  * [disabling pushes][writable] to take backups
+  * [personal][pers] branches
+  * [delegating][deleg] access control responsibilities
+      * ([link][NAME]: the NAME VREF)
+      * the [subconf][] command
+  * ([link][partial-copy]: faking selective READ control)
+
+## interfacing with [external][] tools
+
+  * gitweb
+      * changing the [UMASK][umask]
+  * git-daemon
+
+## [mirroring][]
+
+
+## [rare][]/one-time activities
+
+  * moving [existing][] repos into gitolite
+  * [moving][] servers
+
+## [customisation][cust]
+
+  * types of non-core programs
+      * ([link][non-core]: non-core programs shipped with gitolite)
+  * [commands][]
+  * [hooks][]
+  * syntactic [sugar][]
+  * ([link][triggers]: triggers)
+  * ([link][vref]: VREFs)
+  * [developer notes][dev-notes]
+      * environment variables and other inputs
+      * APIs
+          * the shell API
+          * the perl API
+      * writing your own...
+          * hooks
+          * commands
+          * trigger programs
+          * sugar
+
+## [non-core][] programs shipped with gitolite
+
+  * commands
+  * syntactic sugar
+  * triggers
+  * ([link][vref]: VREFs)
+  * special cases
+      * [partial-copy][]: selective read control for branches
+
+## background info
+
+  * [files and directories][files] involved in install+setup
+  * [auth][]entication versus authorisation
+      * interfacing with [other authentication][otherauth] systems
+      * getting user group info from [LDAP][ldap]
+  * [ssh][]
+  * [regular expressions][regex]
+
+## contributed software, tools, and documentation
+
+  * TBD
+
+## TBD
+
+  * log file format, LOG_EXTRA
+  * hub
+  * mob branches
+  * password access
\ No newline at end of file
diff --git a/doc/mkdoc b/doc/mkdoc
index 8646988..2f44d6c 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -13,8 +13,6 @@ use Tsh;
 $ENV{TSH_ERREXIT} = 1;
 
 try "
-    ./fm2mt.pl < g3-master-toc.mm > master-toc.mkd
-
     mkdir ../html;                      ok
     git status -s -uno;                 !/./
     git log --oneline -1

commit 9006b07d2ea8aa224a479682f21a2feecf029b28
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 16 10:37:45 2012 +0530

    (doc) switch g2 <-> g3

diff --git a/doc/index.mkd b/doc/index.mkd
index 25e3da1..a44f76f 100644
--- a/doc/index.mkd
+++ b/doc/index.mkd
@@ -7,9 +7,9 @@ Here's more on [what][] it is and [why][] you might need it.
 
 <font color="gray">
 
-## #g2 (for current gitolite (v2) users)
+## #g2 (for older gitolite (v1.x and v2.x) users)
 
-For current gitolite (call it "g2" for convenience) users,
+For users of gitolite v2.x (call it "g2" for convenience),
 
   * [Why][g3why] I rewrote gitolite.
   * Development [status][dev-status].
diff --git a/doc/mkdoc b/doc/mkdoc
index d03372b..8646988 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -28,12 +28,11 @@ try "
     git checkout gh-pages;              ok
     git reset --hard github/gh-pages;   ok
     cd ..;                              ok
-    git rm g3/*.html;                   ok
-    mkdir g3;                           ok
-    mv html/*.html g3;                  ok
-    git add g3;                         ok
+    git rm *.html;                      ok
+    mv html/*.html .;                   ok
+    git add *.html;                     ok
     git commit -m '$head';              ok
-    git checkout g3;                    ok
+    git checkout master;                ok
     rmdir html;                         ok
 " or die 2;
 

commit 1dc68b540d34a37b77bab205b71cc8dbd570c30d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 17 05:05:51 2012 +0530

    (access.t) added a specific rule accum test

diff --git a/t/access.t b/t/access.t
index 208e96a..34e015f 100755
--- a/t/access.t
+++ b/t/access.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # test 'gitolite access'
 # ----------------------------------------------------------------------
 
-try "plan 185";
+try "plan 208";
 
 confreset;confadd '
     @admins     =   admin dev1
@@ -150,3 +150,38 @@ try "
     gitolite access r2 tester W refs/heads/va;          ok;     /refs/heads/; !/DENIED/
 ";
 
+confreset;confadd '
+    repo foo
+        RW+ =   u1
+
+    @gr1 = foo bar
+
+    repo @gr1
+        RW  =   u2
+        R   =   u3
+
+    repo @all
+        R   =   gitweb
+';
+
+try "ADMIN_PUSH set4; !/FATAL/" or die text();
+
+try "
+    gitolite access foo u1 +;           ok
+    gitolite access foo u2 +;           !ok
+    gitolite access foo u3 +;           !ok
+    gitolite access foo u4 +;           !ok
+    gitolite access foo gitweb +;       !ok
+
+    gitolite access foo u1 W;           ok
+    gitolite access foo u2 W;           ok
+    gitolite access foo u3 W;           !ok
+    gitolite access foo u4 W;           !ok
+    gitolite access foo gitweb W;       !ok
+
+    gitolite access foo u1 R;           ok
+    gitolite access foo u2 R;           ok
+    gitolite access foo u3 R;           ok
+    gitolite access foo u4 R;           !ok
+    gitolite access foo gitweb R;       ok
+";

commit 1c15b4cc2d6d3ac174b53812fbacf7b15f38805a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 18 06:26:18 2012 +0530

    (perltidy)

diff --git a/src/commands/mirror b/src/commands/mirror
index be0d401..ec6f275 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -3,6 +3,7 @@ use strict;
 use warnings;
 
 my $tid;
+
 BEGIN {
     $tid = $ENV{GL_TID} || 0;
     delete $ENV{GL_TID};
@@ -33,15 +34,15 @@ usage() if not @ARGV or $ARGV[0] eq '-h';
 
 _die "HOSTNAME not set" if not $rc{HOSTNAME};
 
-my ($cmd, $host, $repo) = @ARGV;
+my ( $cmd, $host, $repo ) = @ARGV;
 usage() if not $repo;
 
-if ($cmd eq 'push') {
-    valid_slave($host, $repo) if exists $ENV{GL_USER};
+if ( $cmd eq 'push' ) {
+    valid_slave( $host, $repo ) if exists $ENV{GL_USER};
     # will die if host not in slaves for repo
 
-    trace(1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started");
-    _chdir($rc{GL_REPO_BASE});
+    trace( 1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started" );
+    _chdir( $rc{GL_REPO_BASE} );
     _chdir("$repo.git");
 
     my $errors = 0;
@@ -50,19 +51,19 @@ if ($cmd eq 'push') {
         chomp;
         if (/FATAL/) {
             $errors++;
-            gl_log('mirror', $_);
+            gl_log( 'mirror', $_ );
         } else {
-            trace(1, "mirror: $_");
+            trace( 1, "mirror: $_" );
         }
     }
     exit $errors;
 }
 
 sub valid_slave {
-    my ($host, $repo) = @_;
+    my ( $host, $repo ) = @_;
     _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
 
-    my $ref = git_config($repo, "^gitolite-options\\.mirror\\.slaves.*");
+    my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.slaves.*" );
     my %list = map { $_ => 1 } map { split } values %$ref;
 
     _die "'$host' not a valid slave for '$repo'" unless $list{$host};
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 6dcc8ba..07e2cd5 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -72,7 +72,7 @@ sub in_ssh {
     my $ip;
     ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
 
-    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || ''), "FROM=$ip" );
+    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || '' ), "FROM=$ip" );
 
     $ENV{SSH_ORIGINAL_COMMAND} ||= '';
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
@@ -172,24 +172,24 @@ sub sanity {
 sub http_setup_die_handler {
 
     $SIG{__DIE__} = sub {
-        my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ?  'git-receive-pack' : 'git-upload-pack');
+        my $service = ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack' );
         my $message = shift; chomp($message);
         print STDERR "$message\n";
 
         # format the service response, then the message.  With initial
         # help from Ilari and then a more detailed email from Shawn...
         $service = "# service=$service\n"; $message = "ERR $message\n";
-        $service = sprintf("%04X", length($service)+4) . "$service";        # no CRLF on this one
-        $message = sprintf("%04X", length($message)+4) . "$message";
+        $service = sprintf( "%04X", length($service) + 4 ) . "$service";    # no CRLF on this one
+        $message = sprintf( "%04X", length($message) + 4 ) . "$message";
 
         http_print_headers();
         print $service;
-        print "0000";       # flush-pkt, apparently
+        print "0000";                                                       # flush-pkt, apparently
         print $message;
         print STDERR $service;
         print STDERR $message;
-        exit 0;     # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
-    }
+        exit 0;                                                             # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
+      }
 }
 
 sub http_simulate_ssh_connection {
@@ -197,26 +197,27 @@ sub http_simulate_ssh_connection {
     # http-backend.c for how I got that.  Also note that "info" is overloaded;
     # git uses "info/refs...", while gitolite uses "info" or "info?...".  So
     # there's a "/" after info in the list below
-    if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) {
+    if ( $ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$)) ) {
         my $repo = $1;
-        my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ?  'git-receive-pack' : 'git-upload-pack';
+        my $verb = ( $ENV{REQUEST_URI} =~ /git-receive-pack/ ) ? 'git-receive-pack' : 'git-upload-pack';
         $ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
     } else {
         # this is one of our custom commands; could be anything really,
         # because of the adc feature
-        my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+)));
+        my ($verb) = ( $ENV{PATH_INFO} =~ m(^/(\S+)) );
         my $args = $ENV{QUERY_STRING};
         $args =~ s/\+/ /g;
         $ENV{SSH_ORIGINAL_COMMAND} = $verb;
         $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
-        http_print_headers();  # in preparation for the eventual output!
+        http_print_headers();    # in preparation for the eventual output!
     }
     $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
 }
 
 my $http_headers_printed = 0;
+
 sub http_print_headers {
-    my($code, $text) = @_;
+    my ( $code, $text ) = @_;
 
     return if $http_headers_printed++;
     $code ||= 200;
diff --git a/src/lib/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
index 5100a61..3417d34 100644
--- a/src/lib/Gitolite/Conf/Load.pm
+++ b/src/lib/Gitolite/Conf/Load.pm
@@ -392,12 +392,12 @@ sub creator {
 
     sub ext_grouplist {
         my $user = shift;
-        my $pgm = $rc{GROUPLIST_PGM};
+        my $pgm  = $rc{GROUPLIST_PGM};
         return [] if not $pgm;
 
         return $cache{$user} if $cache{$user};
         my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
-        return ($cache{$user} = \@extgroups);
+        return ( $cache{$user} = \@extgroups );
     }
 }
 
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 2e4fdd0..3bcb8cf 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -42,7 +42,7 @@ sub update {
 sub bypass {
     require Cwd;
     Cwd->import;
-    gl_log( 'update', getcwd(), '(' . ($ENV{USER} || '?') . ')', 'bypass', @ARGV );
+    gl_log( 'update', getcwd(), '(' . ( $ENV{USER} || '?' ) . ')', 'bypass', @ARGV );
     exit 0;
 }
 
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index f6dfcce..6386651 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -32,11 +32,11 @@ our %rc;
 # pre-populate some important rc keys
 # ----------------------------------------------------------------------
 
-$rc{GL_BINDIR}    = $ENV{GL_BINDIR};
-$rc{GL_LIBDIR}    = $ENV{GL_LIBDIR};
+$rc{GL_BINDIR} = $ENV{GL_BINDIR};
+$rc{GL_LIBDIR} = $ENV{GL_LIBDIR};
 
 # these keys could be overridden by the rc file later
-$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
+$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
 $rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
 $rc{LOG_TEMPLATE}  = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
 
@@ -183,7 +183,7 @@ sub trigger {
                 if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
 
                     require Gitolite::Triggers;
-                    trace(1, 'trigger', $module, $sub, @args, $rc_section, @_ );
+                    trace( 1, 'trigger', $module, $sub, @args, $rc_section, @_ );
                     Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
 
                 } else {
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
index 430f662..435f8aa 100644
--- a/src/lib/Gitolite/Triggers/Mirroring.pm
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -8,7 +8,7 @@ use strict;
 use warnings;
 
 my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
-my $hn = $rc{HOSTNAME};
+my $hn           = $rc{HOSTNAME};
 
 # ----------------------------------------------------------------------
 
@@ -17,14 +17,14 @@ sub input {
 
     # note: we treat %rc as our own internal "poor man's %ENV"
     $rc{FROM_SERVER} = $1;
-    trace(3, "from_server: $1");
+    trace( 3, "from_server: $1" );
 
     if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
         # my ($user, $newsoc, $repo) = ($1, $2, $3);
         $ENV{SSH_ORIGINAL_COMMAND} = $2;
-        @ARGV = ($1);
-        $rc{REDIRECTED_PUSH} = 1;
-        trace(3, "redirected_push for user $1");
+        @ARGV                      = ($1);
+        $rc{REDIRECTED_PUSH}       = 1;
+        trace( 3, "redirected_push for user $1" );
     } else {
         # master -> slave push, no access checks needed
         $ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
@@ -33,14 +33,14 @@ sub input {
 
 # ----------------------------------------------------------------------
 
-my ($mode, $master, %slaves, %trusted_slaves);
+my ( $mode, $master, %slaves, %trusted_slaves );
 
 sub pre_git {
     return unless $hn;
     # nothing, and I mean NOTHING, happens if HOSTNAME is not set
-    trace(1, "pre_git() on $hn");
+    trace( 1, "pre_git() on $hn" );
 
-    my ($repo, $user, $aa) = @_[1, 2, 3];
+    my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
 
     my $sender = $rc{FROM_SERVER} || '';
     $user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
@@ -55,18 +55,17 @@ sub pre_git {
     # exclude this host from both the master and slave lists)
     return if $aa eq 'R';
 
-    trace(1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
-      ($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
+    trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
 
     # ------------------------------------------------------------------
     # case 1: we're master or slave, normal user pushing to us
-    if ($user and not $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 1, user push");
+    if ( $user and not $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 1, user push" );
         return if $mode eq 'local' or $mode eq 'master';
-        if ($trusted_slaves{$hn}) {
-            trace(3, "redirecting to $master");
-            trace(1, "redirect to $master");
-            exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}");
+        if ( $trusted_slaves{$hn} ) {
+            trace( 3, "redirecting to $master" );
+            trace( 1, "redirect to $master" );
+            exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
         } else {
             _die "$hn: pushing '$repo' to slave '$hn' not allowed";
         }
@@ -74,9 +73,9 @@ sub pre_git {
 
     # ------------------------------------------------------------------
     # case 2: we're slave, master pushing to us
-    if ($sender and not $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 2, master push");
-        _die "$hn: '$repo' is local" if $mode eq 'local';
+    if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 2, master push" );
+        _die "$hn: '$repo' is local"  if $mode eq 'local';
         _die "$hn: '$repo' is native" if $mode eq 'master';
         _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
         return;
@@ -84,12 +83,12 @@ sub pre_git {
 
     # ------------------------------------------------------------------
     # case 3: we're master, slave sending a redirected push to us
-    if ($sender and $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 2, slave redirect");
-        _die "$hn: '$repo' is local"  if $mode eq 'local';
+    if ( $sender and $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 2, slave redirect" );
+        _die "$hn: '$repo' is local"      if $mode eq 'local';
         _die "$hn: '$repo' is not native" if $mode eq 'slave';
         _die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
-        _die "$hn: redirection not allowed from '$sender'" if not $trusted_slaves{$sender};
+        _die "$hn: redirection not allowed from '$sender'"     if not $trusted_slaves{$sender};
         return;
     }
 
@@ -102,9 +101,9 @@ sub pre_git {
 sub post_git {
     return unless $hn;
     # nothing, and I mean NOTHING, happens if HOSTNAME is not set
-    trace(1, "post_git() on $hn");
+    trace( 1, "post_git() on $hn" );
 
-    my ($repo, $user, $aa) = @_[1, 2, 3];
+    my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
     # we don't deal with any reads
     return if $aa eq 'R';
 
@@ -115,13 +114,12 @@ sub post_git {
     # now you know the repo, get its mirroring details
     details($repo);
 
-    trace(1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
-      ($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
+    trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
 
     # ------------------------------------------------------------------
     # case 1: we're master or slave, normal user pushing to us
-    if ($user and not $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 1, user push");
+    if ( $user and not $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 1, user push" );
         return if $mode eq 'local';
         # slave was eliminated earlier anyway, so that leaves 'master'
 
@@ -133,16 +131,16 @@ sub post_git {
 
     # ------------------------------------------------------------------
     # case 2: we're slave, master pushing to us
-    if ($sender and not $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 2, master push");
+    if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 2, master push" );
         # nothing to do
         return;
     }
 
     # ------------------------------------------------------------------
     # case 3: we're master, slave sending a redirected push to us
-    if ($sender and $rc{REDIRECTED_PUSH}) {
-        trace(3, "case 2, slave redirect");
+    if ( $sender and $rc{REDIRECTED_PUSH} ) {
+        trace( 3, "case 2, slave redirect" );
 
         # find all slaves and push to each of them
         push_to_slaves($repo);
@@ -158,40 +156,40 @@ sub post_git {
         my $repo = shift;
         return if $lastrepo eq $repo;
 
-        $master = master($repo);
-        %slaves = slaves($repo);
-        $mode = mode($repo);
+        $master         = master($repo);
+        %slaves         = slaves($repo);
+        $mode           = mode($repo);
         %trusted_slaves = trusted_slaves($repo);
-        trace(3, $master, $mode, join(",", sort keys %slaves), join(",", sort keys %trusted_slaves) );
+        trace( 3, $master, $mode, join( ",", sort keys %slaves ), join( ",", sort keys %trusted_slaves ) );
     }
 
     sub master {
-        return option(+shift, 'mirror.master');
+        return option( +shift, 'mirror.master' );
     }
 
     sub slaves {
-        my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.slaves.*");
+        my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.slaves.*" );
         my %out = map { $_ => 1 } map { split } values %$ref;
         return %out;
     }
 
     sub trusted_slaves {
-        my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.redirectOK.*");
+        my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
         # the list of trusted slaves (where we accept redirected pushes from)
         # is either explicitly given...
         my @out = map { split } values %$ref;
         my %out = map { $_ => 1 } @out;
         # ...or it's all the slaves mentioned if the list is just a "all"
-        %out = %slaves if (@out == 1 and $out[0] eq 'all');
+        %out = %slaves if ( @out == 1 and $out[0] eq 'all' );
         return %out;
     }
 
     sub mode {
         my $repo = shift;
-        return 'local' if not $hn;
+        return 'local'  if not $hn;
         return 'master' if $master eq $hn;
-        return 'slave' if $slaves{$hn};
-        return 'local' if not $master and not %slaves;
+        return 'slave'  if $slaves{$hn};
+        return 'local'  if not $master and not %slaves;
         _die "$hn: '$repo' is mirrored but not here";
     }
 }
@@ -200,9 +198,9 @@ sub push_to_slaves {
     my $repo = shift;
 
     my $u = $ENV{GL_USER};
-    delete  $ENV{GL_USER};  # why?  see src/commands/mirror
+    delete $ENV{GL_USER};    # why?  see src/commands/mirror
 
-    for my $s (sort keys %slaves) {
+    for my $s ( sort keys %slaves ) {
         system("gitolite mirror push $s $repo &");
     }
 

commit 2cb7d8313e5bb9411f87e46c1d129077b9e26b5e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 16 17:38:57 2012 +0530

    (minor) make trigger function name consistent with other similar uses
    
    writable() in Writable.pm renamed to "access_1" to be consistent; i.e.,
    reflect the trigger name

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 0ec997b..f6dfcce 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -73,7 +73,7 @@ if ( defined($GL_ADMINDIR) ) {
 # ----------------------------------------------------------------------
 
 # is the server/repo in a writable state (i.e., not down for maintenance etc)
-unshift @{ $rc{ACCESS_1} }, 'Writable::writable';
+unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
 
 # (testing only) override the rc file silently
 # ----------------------------------------------------------------------
diff --git a/src/lib/Gitolite/Triggers/Writable.pm b/src/lib/Gitolite/Triggers/Writable.pm
index 837145c..ed86e12 100644
--- a/src/lib/Gitolite/Triggers/Writable.pm
+++ b/src/lib/Gitolite/Triggers/Writable.pm
@@ -3,7 +3,7 @@ package Gitolite::Triggers::Writable;
 use Gitolite::Rc;
 use Gitolite::Common;
 
-sub writable {
+sub access_1 {
     my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ];
     return if $aa eq 'R' or $result =~ /DENIED/;
 

commit 2629d7f00a5463a479af9c1a511113a9b835665a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 16 17:33:12 2012 +0530

    (minor) add remote tests for the 'writable' command

diff --git a/t/writable.t b/t/writable.t
index e778414..a649323 100755
--- a/t/writable.t
+++ b/t/writable.t
@@ -13,7 +13,7 @@ my $workdir = getcwd();
 
 my $sf = ".gitolite.down";
 
-try "plan 58";
+try "plan 66";
 try "DEF POK = !/DENIED/; !/failed to push/";
 
 # delete the down file
@@ -27,7 +27,7 @@ confreset;confadd '
 
     repo bar/..*
         C   =   u2 u4 u6
-        RW  =   CREATOR
+        RW  =   CREATOR u3
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
@@ -112,4 +112,13 @@ try "
     cd ../u2
     tc h4
     PUSH u2;                    !ok;    /the bar is closed/
+
+    ssh u3 writable bar/u2 on;  !ok;    /you are not authorized/
+    ssh u3 writable \@all on;   !ok;    /you are not authorized/
+
+    ssh u2 writable bar/u2 on;  ok
+    ssh u2 writable \@all on;   !ok;    /you are not authorized/
+
+    ssh admin writable \@all on;
+                                ok
 ";

commit 581e79d745504443280da0103acdc90545809a60
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 16 17:14:03 2012 +0530

    logging die and warn messages
    
      - change a few important die()s to _die()s
      - setup SIGs for both die and warn so any others will get caught

diff --git a/src/commands/writable b/src/commands/writable
index fdaa540..d6426b2 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -25,9 +25,9 @@ my $repo = shift;
 my $on = ( shift eq 'on' );
 
 if ( $repo eq '@all' ) {
-    die "you are not authorized\n" if $ENV{GL_USER} and not is_admin();
+    _die "you are not authorized" if $ENV{GL_USER} and not is_admin();
 } else {
-    die "you are not authorized\n" if $ENV{GL_USER} and not owns($repo);
+    _die "you are not authorized" if $ENV{GL_USER} and not owns($repo);
 }
 
 my $msg = join( " ", @ARGV );
diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 5a4b4dc..947819a 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -64,6 +64,7 @@ sub dbg {
 }
 
 sub _warn {
+    gl_log( 'warn', @_ );
     if ( $ENV{D} and $ENV{D} >= 3 ) {
         cluck "WARNING: ", @_, "\n";
     } elsif ( defined( $ENV{D} ) ) {
@@ -72,6 +73,7 @@ sub _warn {
         warn "WARNING: ", @_, "\n";
     }
 }
+$SIG{__WARN__} = \&_warn;
 
 sub _die {
     gl_log( 'die', @_ );
@@ -83,6 +85,7 @@ sub _die {
         die "FATAL: " . join( ",", @_ ) . "\n";
     }
 }
+$SIG{__DIE__} = \&_die;
 
 sub usage {
     _warn(shift) if @_;
diff --git a/src/lib/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
index 60574a0..280a924 100644
--- a/src/lib/Gitolite/Easy.pm
+++ b/src/lib/Gitolite/Easy.pm
@@ -25,6 +25,8 @@ package Gitolite::Easy;
   %rc
   say
   say2
+  _die
+  _warn
   _print
   usage
 );
diff --git a/src/lib/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
index 1fd5530..2e4fdd0 100644
--- a/src/lib/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -62,14 +62,14 @@ sub check_vrefs {
         } else {
             my ( $dummy, $pgm, @args ) = split '/', $vref;
             $pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
-            -x $pgm or die "$vref: helper program missing or unexecutable\n";
+            -x $pgm or _die "$vref: helper program missing or unexecutable";
 
-            open( my $fh, "-|", $pgm, @_, $vref, @args ) or die "$vref: can't spawn helper program: $!\n";
+            open( my $fh, "-|", $pgm, @_, $vref, @args ) or _die "$vref: can't spawn helper program: $!";
             while (<$fh>) {
                 my ( $ref, $deny_message ) = split( ' ', $_, 2 );
                 check_vref( $aa, $ref, $deny_message );
             }
-            close($fh) or die $!
+            close($fh) or _die $!
               ? "Error closing sort pipe: $!"
               : "$vref: helper program exit status $?";
         }
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index 1e995c2..effdb96 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -138,7 +138,7 @@ sub setup_gladmin {
     tsh_try("git config --get user.name")  or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
     tsh_try("git diff --cached --quiet")
       or tsh_try("git commit -am 'gl-setup $argv'")
-      or die "setup failed to commit to the admin repo";
+      or _die "setup failed to commit to the admin repo";
     delete $ENV{GIT_WORK_TREE};
 }
 
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 7fd1e03..450144c 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -34,7 +34,6 @@ my $old_ak = slurp($akfile);
 my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
 chomp(@non_gl);
 my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
-# die 1;
 
 # pubkey files
 chomp( my @pubkeys = `find keydir -type f -name "*.pub" | sort` );

commit 67327ebfb450bfc95cd4811ea4fff9ddd9b07fa6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 18 06:22:46 2012 +0530

    (minor) add svnserve command

diff --git a/src/commands/svnserve b/src/commands/svnserve
new file mode 100755
index 0000000..6e68acf
--- /dev/null
+++ b/src/commands/svnserve
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+my $svnserve = $rc{SVNSERVE} || '';
+$svnserve ||= "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u";
+
+my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
+
+die "expecting 'svnserve -t', got '$cmd'\n" unless $cmd eq 'svnserve -t';
+
+$svnserve =~ s/%u/$ENV{GL_USER}/g;
+exec $svnserve;
+die "svnserve exec failed\n";

commit 273e6fd627d5657c94538c712d809c4c91db7be2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 17 10:04:34 2012 +0530

    "fake Unix" strikes again...
    
    The fix is easy enough, but I hate having to code work-arounds for
    proprietary OSs when the same code works fine on Linux and BSD.
    
    /me wisely avoids words like posix in his rant ;-)
    
    Thanks to Franck Zoccolo for help in finding what the problem was and
    when and why it occurred.
    
    ----
    
    Someday there will be some issue that requires a fix with significant
    code change (or worse, a change that is incompatible with Linux), and I
    will probably refuse.  Of course, I will be properly regretful about my
    inability to fix it.[1]

diff --git a/src/lib/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
index 79c905d..5a4b4dc 100644
--- a/src/lib/Gitolite/Common.pm
+++ b/src/lib/Gitolite/Common.pm
@@ -274,7 +274,7 @@ sub logger_plus_stderr {
 
     sub tsh_try {
         my $cmd = shift; die "try: expects only one argument" if @_;
-        $text = `( $cmd ) 2>&1; echo -n RC=\$?`;
+        $text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
         if ( $text =~ s/RC=(\d+)$// ) {
             $rc = $1;
             trace( 3, $text );
diff --git a/src/lib/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
index 3281a34..581a844 100644
--- a/src/lib/Gitolite/Test/Tsh.pm
+++ b/src/lib/Gitolite/Test/Tsh.pm
@@ -333,7 +333,7 @@ sub _sh {
     # TODO: switch to IPC::Open3 or something...?
 
     dbg( 4, "  running: ( $cmd ) 2>&1" );
-    $text = `( $cmd ) 2>&1; echo -n RC=\$?`;
+    $text = `( $cmd ) 2>&1; /bin/echo -n RC=\$?`;
     $lec  = $cmd;
     dbg( 4, "  results:\n$text" );
 

commit b5024027caa758f0d4655588b5624081ea3d49fd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 15 11:17:44 2012 +0530

    yaaay! http is finally done!

diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
index 28f0866..c9af92a 100644
--- a/doc/dev-status.mkd
+++ b/doc/dev-status.mkd
@@ -2,7 +2,6 @@
 
 Not yet done (will be tackled in this order unless someone asks):
 
-  * smart http
   * svnserve (someone is testing it)
   * mechanism for ADCs using unchecked arguments -- this is not just a matter
     of writing it; I have to think about *how* it will be done.  (AFAIK the
@@ -33,3 +32,4 @@ Done:
   * migration documentation
   * distro packaging instructions
   * migration advice for common cases
+  * smart http
diff --git a/doc/g3-master-toc.mm b/doc/g3-master-toc.mm
index 3e1b569..9d89535 100755
--- a/doc/g3-master-toc.mm
+++ b/doc/g3-master-toc.mm
@@ -152,7 +152,6 @@
 </node>
 <node CREATED="1333301296248" ID="ID_571518549" MODIFIED="1333526198202" POSITION="right" TEXT="TBD">
 <node CREATED="1333327082853" ID="ID_488765250" MODIFIED="1333327089444" TEXT="log file format, LOG_EXTRA"/>
-<node CREATED="1333301298504" ID="ID_60946303" MODIFIED="1333301300418" TEXT="smart http"/>
 <node CREATED="1333301308136" ID="ID_1900285587" MODIFIED="1333301311863" TEXT="hub"/>
 <node CREATED="1333328274461" ID="ID_248606591" MODIFIED="1333328277083" TEXT="mob branches"/>
 <node CREATED="1333328277387" ID="ID_1027016949" MODIFIED="1333328280083" TEXT="password access"/>
diff --git a/doc/http.mkd b/doc/http.mkd
new file mode 100644
index 0000000..9263d4c
--- /dev/null
+++ b/doc/http.mkd
@@ -0,0 +1,70 @@
+# how to setup gitolite to use smart http mode
+
+**Note**: "smart http" refers to the feature that came with git 1.6.6, late
+2009 or so.  The base documentation for this is `man git-http-backend`.  Do
+**NOT** read `Documentation/howto/setup-git-server-over-http.txt` and think
+that is the same or even relevant -- that is from 2006 and is quite different
+(and arguably obsolete).
+
+## WARNINGS and important notes
+
+  * Please read [authentication versus authorisation][auth] first, and make
+    sure you understand what is gitolite's responsibility and what isn't.
+
+  * The 'gitolite' command (for example, 'gitolite compile', 'gitolite
+    query-rc', and so on) *can* be run on the server, but it's not
+    straightforward.  Assuming you installed using the exact same values as in
+    this document:
+
+      * get a shell by using, say, `su -s /bin/bash - apache`
+      * run `export HOME=$HOME/gitolite-home`
+      * run `export PATH=$PATH:$HOME/bin`
+
+    Now you can run `gitolite <subcommand>`
+
+  * I have tested only on stock Fedora 16; YDMV
+
+  * As before, I have not tried making repos available to both ssh *and* http
+    mode clients but it ought to work.  If you managed it, I'd appreciate a
+    doc patch describing how you did it.
+
+## additional requirements
+
+  * requires `GIT_PROJECT_ROOT` (see "man git-http-backend" for what this is)
+    set explicitly (i.e., it is no longer optional).  Please set it to some
+    place outside apache's `DOCUMENT_ROOT`.
+
+## assumptions:
+
+  * apache 2.x and git installed.
+  * httpd runs under the "apache" userid; adjust instructions below if not.
+  * similarly for "/var/www" and other file names/locations.
+
+## instructions
+
+The detailed instructions I used to have in g2 have now been replaced by a
+script called `t/smart-http.root-setup`.  **Do NOT run this script as is -- it
+is actually meant for my testing setup and deletes stuff**.  However, it does
+provide an excellent (and working!) narration of what you need to do to
+install gitolite in smart http mode.
+
+Make a copy of the script, go through it carefully, (possibly removing lines
+that delete files etc.), change values per your system, and only then run it.
+
+## usage
+
+Git URLs look like `http://user:password@server/git/reponame.git`.
+
+The custom commands, like "info", "expand" should be handled as follows.  The
+command name will come just after the `/git/`, followed by a `?`, followed by
+the arguments, with `+` representing a space.  Here are some examples:
+
+    # ssh git at server info
+    curl http://user:password@server/git/info
+    # ssh git at server info repopatt
+    curl http://user:password@server/git/info?repopatt
+    # ssh git at server info repopatt user1 user2
+    curl http://user:password@server/git/info?repopatt+user1+user2
+
+With a few nice shell aliases, you won't even notice the horrible convolutions
+here ;-)  See t/smart-http for a couple of useful ones.
diff --git a/doc/index.mkd b/doc/index.mkd
index a2ec9c1..25e3da1 100644
--- a/doc/index.mkd
+++ b/doc/index.mkd
@@ -46,7 +46,7 @@ most people see:
   * Can be installed without root access, assuming git and perl are already
     installed.
   * Authentication is most commonly done using sshd, but you can also use
-    httpd if you prefer (this may require root access).
+    [http][] if you prefer (this may require root access).
 
 ## #contact contact
 
diff --git a/doc/install.mkd b/doc/install.mkd
index e5799b0..642c4ac 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -45,8 +45,7 @@ Notes:
   * any Unix system with a posix compatible "sh".
   * git version 1.6.6 or later
   * perl 5.8.8 or later
-  * openssh (almost any version).  Optional if you're using the http backend
-    (which is still a TODO item!)
+  * openssh (almost any version).  Optional if you're using [smart http][http]
   * a dedicated Unix userid to be the hosting user, usually "git" but it can
     be any user, even your own normal one.  (If you're using an RPM/DEB the
     install probably created one called "gitolite").
diff --git a/doc/setup.mkd b/doc/setup.mkd
index 2b3921a..075400c 100644
--- a/doc/setup.mkd
+++ b/doc/setup.mkd
@@ -37,5 +37,7 @@ after the install as well:
 
   * to replace a [lost admin key][lost-key].
 
+  * to setup gitolite for http mode (run 'gitolite setup -h' for more info)
+
 When in doubt, run 'gitolite setup' anyway; it doesn't do any harm, though it
 may take a minute or so if you have more than a few thousand repos!
diff --git a/src/gitolite-shell b/src/gitolite-shell
index f3cec61..6dcc8ba 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -8,6 +8,10 @@ use FindBin;
 BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
 BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
 use lib $ENV{GL_LIBDIR};
+
+# set HOME
+BEGIN { $ENV{HOME} = $ENV{GITOLITE_HTTP_HOME} if $ENV{GITOLITE_HTTP_HOME}; }
+
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
@@ -48,7 +52,20 @@ sub in_file {
 }
 
 sub in_http {
-    _die 'http not yet implemented...';
+    http_setup_die_handler();
+
+    _die "GITOLITE_HTTP_HOME not set" unless $ENV{GITOLITE_HTTP_HOME};
+
+    _die "fallback to DAV not supported" if $ENV{REQUEST_METHOD} eq 'PROPFIND';
+
+    # fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http,
+    # so the rest of the code stays the same (except the exec at the end).
+    http_simulate_ssh_connection();
+
+    $ENV{REMOTE_USER} ||= $rc{HTTP_ANON_USER};
+    @ARGV = ( $ENV{REMOTE_USER} );
+
+    return 'http';
 }
 
 sub in_ssh {
@@ -104,6 +121,8 @@ sub main {
         gl_log( "pre_git", $repo, $user, $aa, 'any', "-> $ret" );
     }
 
+    exec $ENV{GIT_HTTP_BACKEND} if $ENV{REQUEST_URI};
+
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
     _system( "git", "shell", "-c", "$verb $repodir" );
@@ -146,3 +165,67 @@ sub sanity {
     _die "'$repo' ends with a '/'"         if $repo =~ m(/$);
     _die "'$repo' contains '..'"           if $repo =~ m(\.\.$);
 }
+
+# ----------------------------------------------------------------------
+# helper functions for "in_http"
+
+sub http_setup_die_handler {
+
+    $SIG{__DIE__} = sub {
+        my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ?  'git-receive-pack' : 'git-upload-pack');
+        my $message = shift; chomp($message);
+        print STDERR "$message\n";
+
+        # format the service response, then the message.  With initial
+        # help from Ilari and then a more detailed email from Shawn...
+        $service = "# service=$service\n"; $message = "ERR $message\n";
+        $service = sprintf("%04X", length($service)+4) . "$service";        # no CRLF on this one
+        $message = sprintf("%04X", length($message)+4) . "$message";
+
+        http_print_headers();
+        print $service;
+        print "0000";       # flush-pkt, apparently
+        print $message;
+        print STDERR $service;
+        print STDERR $message;
+        exit 0;     # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
+    }
+}
+
+sub http_simulate_ssh_connection {
+    # these patterns indicate normal git usage; see "services[]" in
+    # http-backend.c for how I got that.  Also note that "info" is overloaded;
+    # git uses "info/refs...", while gitolite uses "info" or "info?...".  So
+    # there's a "/" after info in the list below
+    if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) {
+        my $repo = $1;
+        my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ?  'git-receive-pack' : 'git-upload-pack';
+        $ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
+    } else {
+        # this is one of our custom commands; could be anything really,
+        # because of the adc feature
+        my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+)));
+        my $args = $ENV{QUERY_STRING};
+        $args =~ s/\+/ /g;
+        $ENV{SSH_ORIGINAL_COMMAND} = $verb;
+        $ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
+        http_print_headers();  # in preparation for the eventual output!
+    }
+    $ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
+}
+
+my $http_headers_printed = 0;
+sub http_print_headers {
+    my($code, $text) = @_;
+
+    return if $http_headers_printed++;
+    $code ||= 200;
+    $text ||= "OK - gitolite";
+
+    $|++;
+    print "Status: $code $text\r\n";
+    print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
+    print "Pragma: no-cache\r\n";
+    print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
+    print "\r\n";
+}
diff --git a/src/lib/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
index e876a50..1e995c2 100644
--- a/src/lib/Gitolite/Setup.pm
+++ b/src/lib/Gitolite/Setup.pm
@@ -7,9 +7,11 @@ package Gitolite::Setup;
 Usage:  gitolite setup [<option>]
 
     -pk, --pubkey <file>        pubkey file name
+    -a, --admin <name>          admin name
 
-Setup gitolite, compile conf, and fixup hooks.  The pubkey is required on the
-first run.
+Setup gitolite, compile conf, and fixup hooks.  Either the pubkey or the admin
+name is required on the first run, depending on whether you're using ssh mode
+or http mode.
 
 Subsequent runs:
 
@@ -87,7 +89,7 @@ sub setup_glrc {
 
 sub setup_gladmin {
     my ( $admin, $pubkey, $argv ) = @_;
-    _die "no existing conf file found, '-a' required"
+    _die "no existing conf file found, '-pk' or '-a' required"
       if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
 
     # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
diff --git a/t/smart-http b/t/smart-http
new file mode 100755
index 0000000..2ce6afc
--- /dev/null
+++ b/t/smart-http
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+die() { echo "$@"; exit 1; }
+
+# git clone `url u1 r1`
+url() {
+    echo http://$1:$1@localhost/git/$2.git
+}
+
+# `cmd sitaram info`
+cmd() {
+    c="curl http://$1:$1@localhost/git"
+    shift
+    c="$c/$1"
+    shift
+
+    if [ -n "$1" ]
+    then
+        c="$c?$1"
+        shift
+    fi
+    while [ -n "$1" ]
+    do
+        c="$c+$1"
+        shift
+    done
+
+    echo $c
+}
+
+export tmp=$(mktemp -d);
+trap "rm -rf $tmp" 0;
+cd $tmp
+
+tsh "plan 28"
+
+tsh "
+    ## ls-remote admin admin
+    git ls-remote `url admin gitolite-admin`
+        ok
+        /HEAD/
+        /refs.heads.master/
+    ## clone
+    git clone `url admin gitolite-admin`
+        ok
+        /Cloning into/
+    ls -al gitolite-admin/conf
+        /gitolite.conf/
+" || die "step 1"
+
+cd gitolite-admin
+echo repo t2 >> conf/gitolite.conf
+echo 'RW+  = u1 u2' >> conf/gitolite.conf
+
+tsh "
+    ## add, commit, push
+    git add conf/gitolite.conf
+        ok
+        !/./
+    git commit -m t2
+        ok
+        /1 file.*changed/
+    git push
+        ok
+        /Initialized.*var.www.gitolite-home.repositories.t2.git/
+        /To http:..admin:admin.localhost.git.gitolite-admin.git/
+        /master -. master/
+    ## various ls-remotes
+    git ls-remote `url u1 gitolite-admin`
+        !ok
+        /FATAL: R any gitolite-admin u1 DENIED by fallthru/
+    git ls-remote `url u1 t2`
+        ok
+        !/./
+    git ls-remote `url u2 t2`
+        ok
+        !/./
+    git ls-remote `url u3 t2`
+        !ok
+        /FATAL: R any t2 u3 DENIED by fallthru/
+    ## push to u1:t2
+    git push      `url u1 t2` master:master
+        ok
+        /To http:..u1:u1.localhost.git.t2.git/
+        /master -. master/
+    git ls-remote `url u2 t2`
+        ok
+        /HEAD/
+        /refs.heads.master/
+" || die "step 2"
diff --git a/t/smart-http.root-setup b/t/smart-http.root-setup
new file mode 100755
index 0000000..ed3d413
--- /dev/null
+++ b/t/smart-http.root-setup
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+# ----------------------------------------------------------------------
+# please do not even LOOK at this file without reading doc/http.mkd
+# ----------------------------------------------------------------------
+
+die() { echo "$@"; exit 1; }
+
+# scare the sh*t out of people who run it blindly
+[ -f /tmp/gitolite-smart-http-test-OK ] || {
+    # scary message
+    echo '+ rm -rf /'
+    # lots of disk activity
+    find / >/dev/null 2>/dev/null
+    # and it he's still clueless, God bless!
+    echo 'root file system erased successfully.  Goodbye and God bless!'
+    exit 1
+}
+
+id | grep '=0(root)' || die "you must run this as root"
+
+# delete any existing apache conf for gitolite
+rm /etc/httpd/conf.d/gitolite.conf
+
+# build your "home within a home"
+cd ~apache
+rm -rf gitolite-home
+mkdir gitolite-home
+export GITOLITE_HTTP_HOME=$PWD/gitolite-home
+
+# get the gitolite sources
+cd gitolite-home
+git clone /tmp/gitolite.git gitolite-source
+# NOTE: I use a bare repo in /tmp for convenience; you'd use
+# 'git://github.com/sitaramc/gitolite'
+
+# make the bin directory, and add it to PATH
+cd gitolite-source
+mkdir         $GITOLITE_HTTP_HOME/bin
+./install -ln $GITOLITE_HTTP_HOME/bin
+export PATH=$PATH:$GITOLITE_HTTP_HOME/bin
+
+# come back to base, then run setup.  Notice that you have to point HOME to
+# the right place, even if it is just for this command
+cd $GITOLITE_HTTP_HOME
+HOME=$GITOLITE_HTTP_HOME gitolite setup -a admin
+
+# insert some essential lines at the beginning of the rc file
+echo '$ENV{GIT_HTTP_BACKEND} = "/usr/libexec/git-core/git-http-backend";' > 1
+echo '$ENV{PATH} .= ":$ENV{GITOLITE_HTTP_HOME}/bin";'  >> 1
+echo >> 1
+cat .gitolite.rc >> 1
+\mv 1 .gitolite.rc
+
+# fix up ownership
+chown -R apache.apache $GITOLITE_HTTP_HOME
+
+# create the apache config.  Note the trailing slashes on the 2 ScriptAlias
+# lines.  (The second one is optional for most sites).  NOTE: you also need to
+# give the AuthUserFile a better name/location than what I have below.
+cat <<EOF1 > /etc/httpd/conf.d/gitolite.conf
+SetEnv GIT_PROJECT_ROOT $GITOLITE_HTTP_HOME/repositories
+ScriptAlias /git/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
+ScriptAlias /gitmob/ $GITOLITE_HTTP_HOME/gitolite-source/src/gitolite-shell/
+SetEnv GITOLITE_HTTP_HOME $GITOLITE_HTTP_HOME
+SetEnv GIT_HTTP_EXPORT_ALL
+
+<Location /git>
+    AuthType Basic
+    AuthName "Private Git Access"
+    Require valid-user
+    AuthUserFile /tmp/gitolite-http-authuserfile
+</Location>
+EOF1
+
+# NOTE: this is for testing only
+htpasswd -bc /tmp/gitolite-http-authuserfile admin admin
+map "htpasswd -b /tmp/gitolite-http-authuserfile % %" u{1..6}
+
+# restart httpd to make it pick up all the new stuff
+service httpd restart
+

commit b60dd9c34933064b0bdc928186b92701fe595a9c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 14 12:45:53 2012 +0530

    (minor) packaging notes updated

diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 3b1a6f1..ae9ab40 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -42,12 +42,12 @@ more info.
   * `gitolite query-rc` to check the value of an RC variable.  Example use:
     src/commands/desc.
 
-In addition, you can also look at the comments in src/Gitolite/Easy.pm (the
-perl API module) for ideas.
+In addition, you can also look at the comments in src/lib/Gitolite/Easy.pm
+(the perl API module) for ideas.
 
 ### the perl API
 
-...is implemented by Gitolite::Easy; the comments in src/Gitolite/Easy.pm
+...is implemented by Gitolite::Easy; the comments in src/lib/Gitolite/Easy.pm
 serve as documentation.
 
 ## writing your own...
diff --git a/doc/install.mkd b/doc/install.mkd
index 4aef457..e5799b0 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -113,24 +113,32 @@ Creating a symlink doesn't need a separate program but 'install' also runs
   * Update your clone of the gitolite source.
   * Repeat the install command you used earlier (make sure you use the same
     arguments as before).
-  * Run `gitolite setup` just in case the hooks changed.
+  * Run `gitolite setup`.
 
 ## packaging gitolite
 
-1.  Put src/Gitolite in `/usr/share/perl5/vendor_perl` or some such place.
+Here are the requirements for gitolite:
 
-2.  Put the rest of src anywhere your distro policy allows.  (Fedora keeps
-    git's 150 executables in /usr/libexec/git-core, so maybe
-    /usr/libexec/gitolite?)
+  * The programs `gitolite` and `gitolite-shell`, and the directories
+    `commands`, `lib`, `syntactic-sugar`, `triggers`, and `VREF` must all be
+    in the same directory.
 
-3.  Symlink 'gitolite' to /usr/bin or something, similar to option 2.
+    It doesn't matter what this directory is.  As an example, Fedora keeps
+    git's 150 executables in /usr/libexec/git-core, so /usr/libexec/gitolite
+    may be a good choice; it's upto you.
 
-**Bottom line**:
+    The rest of this section will assume you chose /usr/libexec/gitolite as
+    the location.  Adjust as needed.
 
-  * `GL_BINDIR` must point to a place that contains `commands`, `VREF`, and
-    `syntactic-sugar` (so they must all be sibling directories).
-  * The `Gitolite` directory can also be there, or it can be anywhere in
-    perl's `@INC` path.
+  * The program `/usr/libexec/gitolite/gitolite` must then be *symlinked* to
+    some directory in the PATH.  Do not *copy* it; it must be a symlink.
+
+  * The `Gitolite` subdirectory in `/usr/libexec/gitolite/lib` can stay there,
+    **OR**, if your distro policies don't allow that, can be put in any
+    directory in perl's `@INC` path (such as `/usr/share/perl5/vendor_perl`).
+
+  * Finally, a file called `/usr/libexec/gitolite/VERSION` must contain a
+    suitable version string.
 
 ## #migr migrating
 

commit 04a6f75e5c212b47961b7d6a706a2c03cd8a186f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 13 14:50:35 2012 +0530

    (doc updates, mostly migration)
    
      - migration and non-core reachable from master-toc now
      - migration flow changed.  install.mkd, migration section, is [migr]
        now, drives the whole thing now, links to g2migr
      - more details on how to wipe out old gitolite
    
    plus some minor fixes

diff --git a/doc/g3-master-toc.mm b/doc/g3-master-toc.mm
index 10becc5..3e1b569 100755
--- a/doc/g3-master-toc.mm
+++ b/doc/g3-master-toc.mm
@@ -30,7 +30,7 @@
 <node CREATED="1333328156575" ID="ID_208746322" MODIFIED="1333465333727" TEXT="the actual install"/>
 <node CREATED="1333467553366" ID="ID_926614739" MODIFIED="1333467556140" TEXT="upgrading"/>
 <node CREATED="1333328592876" ID="ID_237795046" MODIFIED="1333466108368" TEXT="packaging gitolite"/>
-<node CREATED="1333327776230" ID="ID_1431739921" MODIFIED="1333467569937" TEXT="migrating"/>
+<node CREATED="1333327776230" ID="ID_1431739921" MODIFIED="1334308813624" TEXT="[migr][]ating"/>
 </node>
 <node CREATED="1333301194129" ID="ID_746196740" MODIFIED="1333471816303" POSITION="right" TEXT="[setup][]"/>
 <node CREATED="1333299674481" ID="ID_714407264" MODIFIED="1333528325006" POSITION="right" TEXT="gitolite [admin][]istration">
@@ -129,6 +129,15 @@
 </node>
 </node>
 </node>
+<node CREATED="1334308529127" ID="ID_1328767320" MODIFIED="1334308538750" POSITION="right" TEXT="[non-core][] programs shipped with gitolite">
+<node CREATED="1334308547171" ID="ID_1481929835" MODIFIED="1334308550136" TEXT="commands"/>
+<node CREATED="1334308552822" ID="ID_1632638638" MODIFIED="1334308556005" TEXT="syntactic sugar"/>
+<node CREATED="1334308559968" ID="ID_759401290" MODIFIED="1334308561948" TEXT="triggers"/>
+<node CREATED="1334308570911" ID="ID_118147118" MODIFIED="1334308585866" TEXT="([link][vref]: VREFs)"/>
+<node CREATED="1334308589293" ID="ID_843158319" MODIFIED="1334308591424" TEXT="special cases">
+<node CREATED="1334308633132" ID="ID_1213286105" MODIFIED="1334308757373" TEXT="[partial-copy][]: selective read control for branches"/>
+</node>
+</node>
 <node CREATED="1333300577608" ID="ID_583410113" MODIFIED="1333537235253" POSITION="right" TEXT="background info">
 <node CREATED="1333299088617" ID="ID_1919571562" MODIFIED="1333537208005" TEXT="[files and directories][files] involved in install+setup"/>
 <node CREATED="1333300378018" ID="ID_584192327" MODIFIED="1333536676126" TEXT="[auth][]entication versus authorisation">
diff --git a/doc/install.mkd b/doc/install.mkd
index d412782..4aef457 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -2,7 +2,7 @@
 
 <font color="red">**NOTE**: if you're migrating from g2, there are some
 settings that MUST be dealt with **before** running `gitolite setup`; please
-read the [g2 migration][g2migr] page and linked pages.</font>
+start [here][migr].  RTFM is *mandatory* for migrations.</font>
 
 ## notes and naming conventions
 
@@ -132,24 +132,64 @@ Creating a symlink doesn't need a separate program but 'install' also runs
   * The `Gitolite` directory can also be there, or it can be anywhere in
     perl's `@INC` path.
 
-## migrating
+## #migr migrating
 
-There are a lot of migration hints and notes; see links at the top of this
-document.
+<font color="gray">If you're migrating from gitosis, [this][gsmigr] is what
+you want to start with.</font>
 
-But here's the **bottom line** on migrating: nothing in any of the gitolite
-install/setup/etc will ever touch the *data* in any repository except the
-gitolite-admin repo.  The only thing it will normally touch is the `update`
-hook.
+First things first: g2 will be supported for a good long time for critical
+bugs, although enhancements and new features won't happen.
 
-So one fool-proof way of "migrating" from any older gitolite is this:
+If you're an existing (gitolite v1.x or v2.x) user, and wish to migrate , here
+are the steps:
 
-1.  Wipe out the old gitolite, but **carefully**
+### pre-migration
 
-      * clone `~/repositories/gitolite-admin.git` to someplace, then delete it
-        (and only it, none of the other repos!) from `~/repositories`
-      * rename `~/.gitolite.rc` to something else
-      * move the ~/.gitolite/logs` directory somewhere else, then delete `~/.gitolite`
+1.  Check the [dev-status][] page to make sure all the features you want have
+    been implemented in g3.
+
+2.  Read the [g2 migration][g2migr] page to see what changes affect you and
+    your users, and how much time it might take you to migrate.  (The closer
+    you were to a default install of the old gitolite, the less time a
+    migration will take.)
+
+### migration
+
+**Note**: nothing in any of the gitolite install/setup/etc will ever touch the
+*data* in any repository except the gitolite-admin repo.  The only thing it
+will normally touch in normal repos is the `update` hook.
+
+1.  Carefully wipe out the old gitolite
+
+      * the **code**
+
+          * delete or move away all the old gitolite scripts.  Check the path
+            to the gl-auth-command in `~/.ssh/authorized_keys` if you forgot
+            where you put them.
+
+          * delete or move away the two directories named in the two variables
+            `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` in `~/.gitolite.rc`
+
+      * the **rc file**
+
+          * rename `~/.gitolite.rc` to something else
+
+      * the **admin repo**
+
+          * clone `~/repositories/gitolite-admin.git` to someplace safe
+          * then delete `~/repositories/gitolite-admin.git`
+
+        (make sure you do not delete any other repos!)
+
+      * the **admin directory**
+
+          * if you need to preserve logs, move the ~/.gitolite/logs` directory
+            somewhere else
+
+          * if you added any custom hooks and wish to preserve them, move the
+            ~/.gitolite/hooks` directory somewhere else
+
+          * delete `~/.gitolite`
 
 2.  Read about [presetting][rc-preset] the rc file; if you're using any
     variables listed as requiring preset, follow those instructions to create
diff --git a/doc/migr.mkd b/doc/migr.mkd
deleted file mode 100644
index 857f484..0000000
--- a/doc/migr.mkd
+++ /dev/null
@@ -1,6 +0,0 @@
-# migrating to gitolite
-
-If you're an existing (gitolite v1.x or v2.x) user, start with the [g2
-migration][g2migr] page.
-
-If you're migrating from gitosis, [this][gsmigr] is what you want.
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 2ac0491..8a3a636 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -1,5 +1,11 @@
 # non-core programs shipped with gitolite
 
+----
+
+[[TOC]]
+
+----
+
 ## commands
 
 A list of these commands can be obtained by running `gitolite help` on the
@@ -82,7 +88,7 @@ VREFs have their [own page][vref].
 
 ## special cases
 
-### #partial-copy partial-copy
+### #partial-copy partial-copy: selective read control for branches
 
 Git (and therefore gitolite) cannot do selective read control -- allowing
 someone to read branch A but not branch B.  It's the entire repo or nothing.
@@ -92,7 +98,10 @@ because they have their own git stack (and their own sshd, and so on) all in
 one big Java program.  Gerrit is *really* useful if you want code review to be
 part of the access control decision] </font>
 
-Gitolite can now help you do this, as follows:
+Gitolite can now help you do this.  Note that this is only for branches; you
+can't do this for files and directories.
+
+Here's how:
 
 1.  enable 'partial-copy' in the `PRE_GIT` section in the rc file.
 

commit 720729e4b44b770b6e5f13e8e0ca34d0ebf83a08
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 13 13:58:40 2012 +0530

    (minor) do not run `gitolite query-rc` from *perl* programs!
    
    I must have blindly converted from some shell-thinking/shell-code for
    these to have slipped through!
    
    (found when doing an audit of all system, exec, ``, qx, and tsh_)

diff --git a/src/commands/sskm b/src/commands/sskm
index a198dd0..d465816 100755
--- a/src/commands/sskm
+++ b/src/commands/sskm
@@ -13,8 +13,8 @@ in doc/sskm.mkd or online at http://sitaramc.github.com/gitolite/sskm.html.
 
 usage() if @ARGV and $ARGV[0] eq '-h';
 
-my $rb = `gitolite query-rc -n GL_REPO_BASE`;
-my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
+my $rb = $rc{GL_REPO_BASE};
+my $ab = $rc{GL_ADMIN_BASE};
 # get to the keydir
 _chdir("$ab/keydir");
 
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 585ea35..7fd1e03 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -16,11 +16,11 @@ $|++;
 tsh_try("sestatus");
 my $selinux = ( tsh_text() =~ /enabled/ );
 
-my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
+my $ab = $rc{GL_ADMIN_BASE};
 trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
 my $akdir        = "$ENV{HOME}/.ssh";
 my $akfile       = "$ENV{HOME}/.ssh/authorized_keys";
-my $glshell      = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
+my $glshell      = $rc{GL_BINDIR} . "/gitolite-shell";
 my $auth_options = auth_options();
 
 sanity();
@@ -76,8 +76,7 @@ sub sanity {
 }
 
 sub auth_options {
-    my $auth_options = `gitolite query-rc AUTH_OPTIONS`;
-    chomp($auth_options);
+    my $auth_options = $rc{AUTH_OPTIONS};
     $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
 
     return $auth_options;

commit afc2c14a65e1f6a129609594677fdef6cf5a81ed
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 11 19:06:07 2012 +0530

    (minor) t/reset and test scripts were not getting a VERSION file

diff --git a/src/lib/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
index 5f7fa6c..07838f0 100644
--- a/src/lib/Gitolite/Test.pm
+++ b/src/lib/Gitolite/Test.pm
@@ -50,7 +50,8 @@ try "
 
     # clean install
     mkdir -p $ENV{HOME}/bin
-    ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin
+    ln -sf $ENV{PWD}/t/glt ~/bin
+    ./install -ln
     cd; rm -vrf .gito* repositories
     git config --global user.name \"gitolite tester\"
     git config --global user.email \"tester\@example.com\"

commit 4c5bb27739f4311cd3b7a5acb38b20642e3147e7
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 11 18:24:43 2012 +0530

    pre-existing repo instructions were WRONG...
    
      - fix them
      - but (at the cost of some efficiency) try to compensate if the admin
        did not follow those instructions, by running hook_1 anyway

diff --git a/doc/admin.mkd b/doc/admin.mkd
index 60f6370..0dc21aa 100644
--- a/doc/admin.mkd
+++ b/doc/admin.mkd
@@ -6,7 +6,7 @@ The following activities require command line access to the server
 
   * changing anything in the [rc][] file
   * installing custom [hooks][], whether to all repos or just some repos
-  * moving [existing][] (bare) repos into gitolite control
+  * moving [existing][] repos into gitolite control
 
 Please read the [WARNINGS][] page first.
 
diff --git a/doc/rare.mkd b/doc/rare.mkd
index 9e7be4e..e1fca65 100644
--- a/doc/rare.mkd
+++ b/doc/rare.mkd
@@ -2,8 +2,21 @@
 
 ## #existing moving existing repos into gitolite
 
-  * move the repos to `$HOME/repositories`.  Make sure they are all *bare*
-    repos, and the directory names end in ".git".
+On the server:
+
+  * move the repos to `$HOME/repositories`.
+
+  * make sure that:
+
+      * they are all *bare* repos
+      * all the repo names end in ".git"
+      * all the files and directories are owned and writable by the gitolite
+        hosting user (especially true if you copied them as root)
+
+  * run `gitolite setup`.  **If you forget this step, you can also forget
+    about write access control!**
+
+Back on your workstation:
 
   * [add them][repos] to conf/gitolite.conf in your clone of the admin repo,
     then commit and push the change.
@@ -11,8 +24,6 @@
     If the repos are already covered by some [wild][] pattern, this is
     optional.
 
-  * run `gitolite setup` to fix up the hooks on all repos, just in case.
-
 ## #moving moving servers
 
 This is adapted from the "migrating" section of the [install][] page; if
diff --git a/doc/repos.mkd b/doc/repos.mkd
index 624125e..9edd44b 100644
--- a/doc/repos.mkd
+++ b/doc/repos.mkd
@@ -1,8 +1,11 @@
 # adding and removing repos
 
+**NOTE**: this page describes how to add new repos.  To bring already existing
+repos into gitolite control, click [here][existing].
+
 >   ----
 
->   *WARNING: Do NOT add repos directly on the server.  Clone the
+>   *WARNING: Do NOT add new repos directly on the server.  Clone the
 >   'gitolite-admin' repo to your workstation, make changes to it, then add,
 >   commit, and push.  When the push hits the server, the server "acts" upon
 >   your changes.*
diff --git a/src/lib/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
index a568a3b..ef5b0fd 100644
--- a/src/lib/Gitolite/Conf/Store.pm
+++ b/src/lib/Gitolite/Conf/Store.pm
@@ -161,6 +161,9 @@ sub new_repos {
         next unless $repo =~ $REPONAME_PATT;    # skip repo patterns
         next if $repo =~ m(^\@|EXTCMD/);        # skip groups and fake repos
 
+        # use gl-conf as a sentinel
+        hook_1($repo) if -d "$repo.git" and not -f "$repo.git/gl-conf";
+
         new_repo($repo) if not -d "$repo.git";
     }
 }

commit 8c28fd2241f698437dd3cba9e7f40272b08a10ed
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 11 04:51:02 2012 +0530

    D...
    
    (manually tested, no test script)
    
    the whimsically named "D" command deletes repos, and is the opposite of
    the "C" permission that enables the user to create one in the first
    place.  See the usage message for user info, and look in the comments of
    the code itself for admin info.

diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 49215c5..2ac0491 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -17,6 +17,7 @@ Here's a list of remote commands that are shipped:
   * 'perms' -- get/set the gl-perms file; see [perms][] for more
   * 'sskm' -- self-service key management
   * 'writable' -- disabling pushes to take backups etc
+  * 'D' -- deleting user-created repos
 
 ## syntactic sugar
 
diff --git a/src/commands/D b/src/commands/D
new file mode 100755
index 0000000..11bff3c
--- /dev/null
+++ b/src/commands/D
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+# ----------------------------------------------------------------------
+# ADMINISTRATOR NOTES:
+# ----------------------------------------------------------------------
+
+# - set TRASH_CAN in the rc if you don't like the default.  It should be
+#   relative to GL_REPO_BASE or an absolute value.  It should also be on the
+#   same filesystem as GL_REPO_BASE, otherwise the 'mv' will take too long.
+
+# - you could set TRASH_SUFFIX also but I recomend you leave it as it is
+
+# - run a cron job to delete old repos based on age (the TRASH_SUFFIX has a
+#   timestamp); your choice how/how often you do that
+# ----------------------------------------------------------------------
+
+# ----------------------------------------------------------------------
+# Usage:    ssh git at host D <subcommand> <argument>
+#
+# The whimsically named "D" command deletes repos ("D" is a counterpart to the
+# "C" permission which lets you create repos!)
+#
+# There are two kinds of deletions: 'rm' removes a repo completely, while
+# 'trash' moves it to a trashcan which can be recovered later (upto a time
+# limit that your admin will tell you).
+#
+# The 'rm', 'lock', and 'unlock' subcommands:
+#     Initially, all repos are "locked" against 'rm'.  The correct sequence is
+#         ssh git at host D unlock repo
+#         ssh git at host D rm repo
+#     Since the initial condition is always locked, the "lock" command is
+#     rarely used but it is there if you want it.
+#
+# The 'trash', 'list-trash', and 'restore' subcommands:
+#     You can 'trash' a repo, which moves it to a special place:
+#         ssh git at host D trash repo
+#     You can then 'list-trash'
+#         ssh git at host D list-trash
+#     which prints something like
+#         repo/2012-04-11_05:58:51
+#     allowing you to restore by saying
+#         ssh git at host D restore repo/2012-04-11_05:58:51
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+cmd=$1; shift
+repo=$1; shift  # may not be present for 'list-trash' command
+# ----------------------------------------------------------------------
+RB=`gitolite query-rc GL_REPO_BASE`;            cd $RB
+TRASH_CAN=`gitolite query-rc TRASH_CAN`;        tcan=Trash;                     TRASH_CAN=${TRASH_CAN:-$tcan}
+TRASH_SUFFIX=`gitolite query-rc TRASH_SUFFIX`;  tsuf=`date +%Y-%m-%d_%H:%M:%S`; TRASH_SUFFIX=${TRASH_SUFFIX:-$tsuf}
+# ----------------------------------------------------------------------
+
+owner_or_die() {
+    gitolite creator "$repo" $GL_USER || die You are not authorised
+}
+
+# ----------------------------------------------------------------------
+
+if [ "$cmd" = "rm" ]
+then
+
+    owner_or_die
+    [ -f $repo.git/gl-rm-ok ] || die "'$repo' is locked!"
+    rm -rf $repo.git
+    echo "'$repo' is now gone!"
+
+elif [ "$cmd" = "lock" ]
+then
+
+    owner_or_die
+    rm -f $repo.git/gl-rm-ok
+    echo "'$repo' is now locked"
+
+elif [ "$cmd" = "unlock" ]
+then
+
+    owner_or_die
+    touch $repo.git/gl-rm-ok
+    echo "'$repo' is now unlocked"
+
+elif [ "$cmd" = "trash" ]
+then
+
+    owner_or_die
+    mkdir -p $TRASH_CAN/$repo 2>/dev/null || die "failed creating directory in trashcan"
+    [ -d $TRASH_CAN/$repo/$TRASH_SUFFIX ] && die "try again in a few seconds..."
+    mv $repo.git $TRASH_CAN/$repo/$TRASH_SUFFIX
+    echo "'$repo' moved to trashcan"
+
+elif [ "$cmd" = "list-trash" ]
+then
+
+    cd $TRASH_CAN 2>/dev/null || exit 0
+    find . -name gl-creator | sort | while read t
+    do
+        owner=
+        owner=`cat "$t"`
+        [ "$owner" = "$GL_USER" ] && dirname $t
+    done | cut -c3-
+
+elif [ "$cmd" = "restore" ]
+then
+
+    owner=
+    owner=`cat $TRASH_CAN/$repo/gl-creator 2>/dev/null`
+    [ "$owner" = "$GL_USER" ] || die "'$repo' is not yours!"
+
+    cd $TRASH_CAN
+    realrepo=`dirname $repo`
+    [ -d $RB/$realrepo.git ] && die "'$realrepo' already exists"
+    mv $repo $RB/$realrepo.git
+    echo "'$repo' restored to '$realrepo'"
+
+else
+    die "unknown subcommand '$cmd'"
+fi
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 5702da0..0ec997b 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -287,11 +287,13 @@ __DATA__
     COMMANDS                    =>
         {
             'help'              =>  1,
-            'info'              =>  1,
             'desc'              =>  1,
-            'perms'             =>  1,
+            'info'              =>  1,
             # 'mirror'          =>  1,
+            'perms'             =>  1,
+            # 'sskm'            =>  1,
             'writable'          =>  1,
+            # 'D'               =>  1,
         },
 
     # comment out or uncomment as needed

commit bbaacfaee72f1f6b58defa0a0f64a56b504222ec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 9 07:31:33 2012 +0530

    (mostly) doc changes
    
      - minor typo fixes, clarifications, etc.
    
      - keep sts.html url consistent, because many people link to
        http://sitaramc.github.com/gitolite/sts.html
    
      - create a common migration doc, so the old 'migr.html' does not 404
        when g3 docs become "main"
    
      - progit doc done
    
      - add gitosis convert script (FWIW)
    
      - a minor comment fix to Sugar.pm

diff --git a/convert-gitosis-conf b/convert-gitosis-conf
new file mode 100755
index 0000000..999f8f7
--- /dev/null
+++ b/convert-gitosis-conf
@@ -0,0 +1,124 @@
+#!/usr/bin/perl -w
+#
+# migrate gitosis.conf to gitolite.conf format
+#
+# Based on gl-conf-convert by: Sitaram Chamarty
+# Rewritten by: Behan Webster <behanw at websterwood.com>
+#
+
+use strict;
+use warnings;
+
+if (not @ARGV and -t or @ARGV and $ARGV[0] eq '-h') {
+    print "Usage:\n    gl-conf-convert < gitosis.conf > gitolite.conf\n(please see the documentation for details)\n";
+    exit 1;
+}
+
+my @comments = ();
+my $groupname;
+my %groups;
+my $reponame;
+my %repos;
+
+while (<>)
+{
+    # not supported
+    if (/^repositories *=/ or /^map /) {
+        print STDERR "not supported: $_";
+        s/^/NOT SUPPORTED: /;
+        print;
+        next;
+    }
+
+    # normalise whitespace to help later regexes
+    chomp;
+    s/\s+/ /g;
+    s/ ?= ?/ = /;
+    s/^ //;
+    s/ $//;
+
+    if (/^\s*$/ and @comments > 1) {
+        @{$repos{$reponame}{comments}} = @comments if $reponame;
+        @{$groups{$groupname}{comments}} = @comments if $groupname;
+        @comments = ();
+    } elsif (/^\s*#/) {
+        push @comments, $_;
+    } elsif (/^\[repo\s+(.*?)\]$/) {
+        $groupname = '';
+        $reponame = $1;
+        $reponame =~ s/\.git$//;
+    } elsif (/^gitweb\s*=\s*yes/i) {
+        push @{$repos{$reponame}{R}}, 'gitweb';
+    } elsif (/^daemon\s*=\s*yes/i) {
+        push @{$repos{$reponame}{R}}, 'daemon';
+    } elsif (/^description\s*=\s*(.+?)$/) {
+        $repos{$reponame}{desc} = $1;
+    } elsif (/^owner\s*=\s*(.+?)$/) {
+        $repos{$reponame}{owner} = $1;
+    } elsif (/^\[group\s+(.*)\]$/) {
+        $reponame = '';
+        $groupname = $1;
+    } elsif (/^members\s*=\s*(.*)/) {
+        push @{$groups{$groupname}{users}}, map {s/\@([^.]+)$/_$1/g; $_} split(' ', $1);
+    } elsif (/^write?able\s*=\s*(.*)/) {
+        foreach my $repo (split(' ', $1)) {
+            $repo =~ s/\.git$//;
+            push @{$repos{$repo}{RW}}, "\@$groupname";
+        }
+    } elsif (/^readonly\s*=\s*(.*)/) {
+        foreach my $repo (split(' ', $1)) {
+            $repo =~ s/\.git$//;
+            push @{$repos{$repo}{R}}, "\@$groupname";
+        }
+    }
+}
+
+#use Data::Dumper;
+#print Dumper(\%repos);
+#print Dumper(\%groups);
+
+# Groups
+print "#\n# Groups\n#\n\n";
+foreach my $grp (sort keys %groups) {
+    next unless @{$groups{$grp}{users}};
+    printf join("\n", @{$groups{$grp}{comments}})."\n" if $groups{$grp}{comments};
+    printf "\@%-19s = %s\n", $grp, join(' ', @{$groups{$grp}{users}});
+}
+
+# Gitweb
+print "\n#\n# Gitweb\n#\n\n";
+foreach my $repo (sort keys %repos) {
+    if ($repos{$repo}{desc}) {
+        @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}});
+        print $repo;
+        print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner};
+        print " = \"$repos{$repo}{desc}\"\n";
+    }
+}
+
+# Repos
+print "\n#\n# Repos\n#\n";
+foreach my $repo (sort keys %repos) {
+    print "\n";
+    printf join("\n", @{$repos{$repo}{comments}})."\n" if $repos{$repo}{comments};
+    #if ($repos{$repo}{desc}) {
+    #    @{$repos{$repo}{R}} = grep(!/^gitweb$/, @{$repos{$repo}{R}});
+    #}
+    print "repo\t$repo\n";
+    foreach my $access (qw(RW+ RW R)) {
+        next unless $repos{$repo}{$access};
+        my @keys;
+        foreach my $key (@{$repos{$repo}{$access}}) {
+            if ($key =~ /^\@(.*)/) {
+                next unless defined $groups{$1} and @{$groups{$1}{users}};
+            }
+            push @keys, $key;
+        }
+        printf "\t$access\t= %s\n", join(' ', @keys) if @keys;
+    }
+    #if ($repos{$repo}{desc}) {
+    #    print $repo;
+    #    print " \"$repos{$repo}{owner}\"" if $repos{$repo}{owner};
+    #    print " = \"$repos{$repo}{desc}\"\n";
+    #}
+}
diff --git a/doc/extras/gitolite-and-ssh.mkd b/doc/extras/glssh.mkd
similarity index 100%
rename from doc/extras/gitolite-and-ssh.mkd
rename to doc/extras/glssh.mkd
diff --git a/doc/extras/ssh-troubleshooting.mkd b/doc/extras/sts.mkd
similarity index 100%
rename from doc/extras/ssh-troubleshooting.mkd
rename to doc/extras/sts.mkd
diff --git a/doc/gsmigr.mkd b/doc/gsmigr.mkd
new file mode 100644
index 0000000..72bc357
--- /dev/null
+++ b/doc/gsmigr.mkd
@@ -0,0 +1,126 @@
+# migrating from gitosis to gitolite
+
+[2012-04-09] Modified for gitolite g3.  These instructions have not really
+been tested with g3, but are expected to work.
+
+Migrating from gitosis to gitolite is fairly easy, because the basic design is
+the same.
+
+There's only one thing that might trip up people: the userid.  Gitosis uses
+`gitosis`.  Gitolite can use any userid you want; most of the documentation
+uses `git`, while DEB/RPM packages use `gitolite`.
+
+Here are the steps on the server:
+
+  * (as 'gitosis' on the server) **Rename** `~/.ssh/authorized_keys` to
+    something else so that no one can accidentally push while you're doing
+    this.
+
+  * (as 'gitosis' on the server) For added safety, **delete** the post-update
+    hook that gitosis-admin installed
+
+        rm ~/repositories/gitosis-admin.git/hooks/post-update
+
+    or at least rename it to `.sample` like all the other hooks hanging
+    around, or edit it and comment out the line that calls `gitosis-run-hook
+    post-update`.
+
+  * (as 'gitosis' on the server) If you already use the `update` hook for some
+    reason, you'll have to make that a [VREF][vref].  This is because gitolite
+    uses the update hook for checking write access.
+
+  * (as 'root' on the server) copy all of `~/repositories` to the gitolite
+    hosting user's home directory.  Something like
+
+        cp -a /home/gitosis/repositories /home/git
+        chown -R git.git /home/git/repositories
+
+  * (as 'root' and/or 'git' on the server) Follow instructions to install
+    gitolite; see the [install document][install].
+
+    This will give you a gitolite config that has the required entries for the
+    "gitolite-admin" repo.
+
+Now, log off the server and get back to the client.  All subsequent
+instructions are to be read as "on gitolite admin's workstation".
+
+  * **clone** the new gitolite-admin repo to your workstation.  (You already
+    have a clone of the gitosis-admin repo so now you have both).
+
+  * **convert** your gitosis config file and append it to your gitolite config
+    file.  Substitute the path for your gitosis-admin clone in `$GSAC` below,
+    and similarly the path for your gito**lite**-admin clone in `$GLAC`.  (The
+    convert-gitosis-conf program is a standalone program that you can bring
+    over from any gitolite clone; you don't have to install all of gitolite on
+    your workstation to use this):
+
+        ./convert-gitosis-conf < $GSAC/gitosis.conf >> $GLAC/conf/gitolite.conf
+
+    **Be sure to check the file to make sure it converted correctly** -- due
+    it's "I only need it once" nature, this program has not received too much
+    attention from anyone!
+
+  * Remove the entry for the 'gitosis-admin' repo.  You do not need it here
+    and it may cause confusion.
+
+  * **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC)
+
+        cp $GSAC/keydir/* $GLAC/keydir
+
+    If your gitosis-admin key was `you at machine.pub`, and you supplied the same
+    one to gitolite's gl-setup program as `you.pub` when you installed
+    gitolite, then you should remove `you at machine.pub` from the new keydir
+    now.  Otherwise you will have 2 pubkey files (`you.pub` and
+    `you at machine.pub`) which are identical, which is *not* a good idea.
+
+    Similarly, you should replace all occurrences of `you at machine.pub` with
+    `you` in the `conf/gitolite.conf` file.
+
+  * **IMPORTANT**: if you have any users with names like `user at foo`, where the
+    part after the `@` does *not* have a `.` in it (i.e., does not look like
+    an email address), you need to change them, because gitolite uses that
+    syntax for [enabling multi keys][multi-key].
+
+    You have two choices in how to fix this.  You can change the gitolite
+    config so that all mention of `user at foo` is changed to just `user`.
+
+    Or you can change each occurrence of `user at foo` to, say, `user_foo` *and*
+    change the pubkey filename in keydir/ also the same way (`user_foo.pub`).
+
+    Just to repeat, you do NOT need to do this if the username was like
+    `user at foo.bar`, i.e., the part after the `@` had a `.` in it, because then
+    it looks like an email address.
+
+  * **IMPORTANT: expand any multi-key files you may have**.  Gitosis is happy
+    to accept files containing more than one public key (one per line) and
+    assign all the keys to the same user.  Gitolite does not allow that; see
+    [here][multi-key]'s for how gitolite handles multi-keys.
+
+    So if you had any multi-keys in gitosis, they need to be carefully split
+    into individual keys.
+
+    You can split the keys manually, or use the following code (just
+    copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo
+    clone):
+
+        wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b
+        do
+            i=1
+            cat $b|while read l
+            do
+                echo "$l" > ${b%.pub}@$i.pub
+                (( i++ ))
+            done
+            mv $b $b.done
+        done
+
+    This will split each multi-key file (say "sitaram.pub") into individual
+    files called "sitaram at 1.pub", "sitaram at 2.pub", etc., and rename the
+    original to "sitaram.pub.done" so gitolite won't pick it up.
+
+    At this point you can rename the split parts more appropriately, like
+    "sitaram at laptop.pub" and "sitaram at desktop.pub" or whatever.  *Please check
+    the files to make sure this worked properly*
+
+  * Check all your changes to your gitolite-admin clone, commit, and push
+
diff --git a/doc/migr.mkd b/doc/migr.mkd
new file mode 100644
index 0000000..857f484
--- /dev/null
+++ b/doc/migr.mkd
@@ -0,0 +1,6 @@
+# migrating to gitolite
+
+If you're an existing (gitolite v1.x or v2.x) user, start with the [g2
+migration][g2migr] page.
+
+If you're migrating from gitosis, [this][gsmigr] is what you want.
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index a0a5af3..28b6a22 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -170,7 +170,7 @@ second one trusts only some slaves.
 
 Note that you cannot redirect gitolite commands (like perms, etc).
 
-## manually synchronising a slave repo
+## #sync manually synchronising a slave repo
 
 You can use the `gitolite mirror push` command on a master to manually
 synchronise any of its slaves.  Try it with `-h` to get usage info.
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 095cc3b..49215c5 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -9,6 +9,15 @@ remote user running `ssh git at host help`.
 All the commands that ship with gitolite will respond to `-h`; please report a
 bug to me if they don't.
 
+Here's a list of remote commands that are shipped:
+
+  * 'desc' -- get/set the 'description' file for a repo
+  * 'info' -- already documented [here][info]
+  * 'mirror' -- documented [here][sync]
+  * 'perms' -- get/set the gl-perms file; see [perms][] for more
+  * 'sskm' -- self-service key management
+  * 'writable' -- disabling pushes to take backups etc
+
 ## syntactic sugar
 
 The following "sugar" programs are available:
diff --git a/doc/progit.mkd b/doc/progit.mkd
new file mode 100644
index 0000000..053ffa9
--- /dev/null
+++ b/doc/progit.mkd
@@ -0,0 +1,166 @@
+# (master copy of progit chapter on gitolite)
+
+## Gitolite ##
+
+Note: the latest copy of this section of the ProGit book is always available within the [gitolite documentation][progit].  The author would also like to humbly state that, while this section is accurate, and *can* (and often *has*) been used to install gitolite without reading any other documentation, it is of necessity not complete, and cannot completely replace the enormous amount of documentation that gitolite comes with.
+
+[Update 2012-04-10]: This page has been completely rewritten for gitolite version 3, informally called "g3".  G3 is a *total* rewrite of gitolite to push a lot more features away from "core", give the core a bunch of extension mechanisms, and finally have much better shell and perl APIs to bring all this together.
+
+Git has become very popular in corporate environments, which tend to have some additional requirements in terms of access control.  Gitolite was originally created to help with those requirements, but it turns out that it's equally useful in the open source world: the Fedora Project controls access to their package management repositories (over 10,000 of them!) using gitolite, and this is probably the largest gitolite installation anywhere too.  KDE, and kernel.org, are other very high-profile users of gitolite.
+
+Gitolite allows you to specify permissions not just by repository, but also by branch or tag names within each repository.  That is, you can specify that certain people (or groups of people) can only push certain "refs" (branches or tags) but not others.
+
+### Installing ###
+
+Installing Gitolite is very easy, even if you don't read the extensive documentation that comes with it.  You need an account on a Unix server of some kind.  You do not need root access, assuming git, perl, and an openssh compatible ssh server are already installed.  In the examples below, we will use the `git` account on a host called `gitserver`.
+
+Gitolite is somewhat unusual as far as "server" software goes -- access is via ssh, and so every userid on the server is a potential "gitolite host".  Gitolite is at present best installed manually, as the "g3" version does not yet have RPM/DEB support from distros.  We will describe the simplest install method in this article; for the other methods please see the documentation.
+
+To begin, create a user called `git` on your server and login to this user.  Copy your ssh pubkey (a file called `~/.ssh/id_rsa.pub` if you did a plain `ssh-keygen` with all the defaults) from your workstation, renaming it to `YourName.pub`.  Then run these commands:
+
+    git clone -b g3 git://github.com/sitaramc/gitolite
+    gitolite/install -ln
+    gitolite setup -pk $HOME/YourName.pub
+        # for example, I would run 'gitolite setup -pk $HOME/sitaram.pub'
+
+(Note: the "-b g3" will not be needed once gitolite v3 becomes the "master" version.)
+
+Finally, back on your workstation, run `git clone git at server:gitolite-admin`.
+
+And you're done!  Gitolite has now been installed on the server, and you now have a brand new repository called `gitolite-admin` in your workstation.  You administer your gitolite setup by making changes to this repository and pushing.  See adding [users][] and [repos][] to start with.
+
+### Customising the Install ###
+
+While the default, quick, install works for most people, there are some ways to customise the install if you need to.  Some changes can be made simply by editing the [rc file][rc], but if that is not sufficient, there's documentation on [customising gitolite][cust] by using non-core programs.
+
+### Config File and Access Control Rules ###
+
+Once the install is done, you switch to the `gitolite-admin` repository (placed in your HOME directory) and poke around to see what you got:
+
+	$ cd ~/gitolite-admin/
+	$ ls
+	conf/  keydir/
+	$ find conf keydir -type f
+	conf/gitolite.conf
+	keydir/sitaram.pub
+	$ cat conf/gitolite.conf
+
+	repo gitolite-admin
+	    RW+                 = sitaram
+
+	repo testing
+	    RW+                 = @all
+
+Notice that "sitaram" (the name of the pubkey in the gl-setup command you used earlier) has read-write permissions on the `gitolite-admin` repository as well as a public key file of the same name.
+
+The config file syntax for gitolite is [well documented][conf], so we'll only mention some highlights here.
+
+You can group users or repos for convenience.  The group names are just like macros; when defining them, it doesn't even matter whether they are projects or users; that distinction is only made when you *use* the "macro".
+
+	@oss_repos      = linux perl rakudo git gitolite
+	@secret_repos   = fenestra pear
+
+	@admins         = scott     # Adams, not Chacon, sorry :)
+	@interns        = ashok     # get the spelling right, Scott!
+	@engineers      = sitaram dilbert wally alice
+	@staff          = @admins @engineers @interns
+
+You can control permissions at the "ref" level.  In the following example, interns can only push the "int" branch.  Engineers can push any branch whose name starts with "eng-", and tags that start with "rc" followed by a digit.  And the admins can do anything (including rewind) to any ref.
+
+	repo @oss_repos
+	    RW  int$                = @interns
+	    RW  eng-                = @engineers
+	    RW  refs/tags/rc[0-9]   = @engineers
+	    RW+                     = @admins
+
+The expression after the `RW` or `RW+` is a regular expression (regex) that the refname (ref) being pushed is matched against.  So we call it a "refex"!  Of course, a refex can be far more powerful than shown here, so don't overdo it if you're not comfortable with perl regexes.
+
+Also, as you probably guessed, Gitolite prefixes `refs/heads/` as a syntactic convenience if the refex does not begin with `refs/`.
+
+An important feature of the config file's syntax is that all the rules for a repository need not be in one place.  You can keep all the common stuff together, like the rules for all `oss_repos` shown above, then add specific rules for specific cases later on, like so:
+
+	repo gitolite
+	    RW+                     = sitaram
+
+That rule will just get added to the ruleset for the `gitolite` repository.
+
+At this point you might be wondering how the access control rules are actually applied, so let's go over that briefly.
+
+There are two levels of access control in gitolite.  The first is at the repository level; if you have read (or write) access to *any* ref in the repository, then you have read (or write) access to the repository.
+
+The second level, applicable only to "write" access, is by branch or tag within a repository.  The username, the access being attempted (`W` or `+`), and the refname being updated are known.  The access rules are checked in order of appearance in the config file, looking for a match for this combination (but remember that the refname is regex-matched, not merely string-matched).  If a match is found, the push succeeds.  A fallthrough results in access being denied.
+
+### Advanced Access Control with "deny" rules ###
+
+So far, we've only seen permissions to be one or `R`, `RW`, or `RW+`.  However, gitolite allows another permission: `-`, standing for "deny".  This gives you a lot more power, at the expense of some complexity, because now fallthrough is not the *only* way for access to be denied, so the *order of the rules now matters*!
+
+Let us say, in the situation above, we want engineers to be able to rewind any branch *except* master and integ.  Here's how to do that:
+
+	    RW  master integ    = @engineers
+	    -   master integ    = @engineers
+	    RW+                 = @engineers
+
+Again, you simply follow the rules top down until you hit a match for your access mode, or a deny.  Non-rewind push to master or integ is allowed by the first rule.  A rewind push to those refs does not match the first rule, drops down to the second, and is therefore denied.  Any push (rewind or non-rewind) to refs other than master or integ won't match the first two rules anyway, and the third rule allows it.
+
+You can also use deny rules to hide specific repos from people (or gitweb, or git-daemon, etc.), when you have otherwise allowed them access to *all* repos.  For example, a server containing open source repos may nevertheless wish to hide the special 'gitolite-admin' repo from gitweb, even though all the other repos can be made visible:
+
+	    repo gitolite-admin
+		-   =   gitweb daemon
+		[... other access rules ...]
+		option deny-rules = 1
+                # remember this is for gitolite "g3"; the older gitolite had a
+                # different syntax
+
+	    repo @all
+		R   =   gitweb daemon
+
+[This][deny-rules] page has more details.
+
+### Restricting pushes by files changed ###
+
+In addition to restricting what branches a user can push changes to, you can also restrict what files they are allowed to touch.  For example, perhaps the Makefile (or some other program) is really not supposed to be changed by just anyone, because a lot of things depend on it or would break if the changes are not done *just right*.  You can tell gitolite:
+
+    repo foo
+        RW                      =   @junior_devs @senior_devs
+
+        -   VREF/NAME/Makefile  =   @junior_devs
+
+User who are migrating from the older gitolite should note that there is a significant change in behaviour with regard to this feature; please see the [migration guide][migr] for details.
+
+### Personal Branches ###
+
+Gitolite also has a feature called "personal branches" (or rather, "personal branch namespace") that can be very useful in a corporate environment.
+
+A lot of code exchange in the git world happens by "please pull" requests.  In a corporate environment, however, unauthenticated access is a no-no, and a developer workstation cannot do authentication, so you have to push to the central server and ask someone to pull from there.
+
+This would normally cause the same branch name clutter as in a centralised VCS, plus setting up permissions for this becomes a chore for the admin.
+
+Gitolite lets you define a "personal" or "scratch" namespace prefix for each developer (for example, `refs/personal/<devname>/*`); the details are [here][pers].
+
+### "Wildcard" repositories ###
+
+Gitolite allows you to specify repositories with wildcards (actually perl regexes), like, for example `assignments/s[0-9][0-9]/a[0-9][0-9]`, to pick a random example.  It allows you to assign a new permission mode ("C") which allows users to create repositories based on such wild cards, automatically assigns ownership to the specific user who created it, allows him/her to hand out R and RW permissions to other users to collaborate, etc.  This feature is documented [here][wild].
+
+### Other Features ###
+
+We'll round off this discussion with a sampling of other features, all of which, and many more, are described in great detail in the documentation.
+
+**Logging**: Gitolite logs all successful accesses.  If you were somewhat relaxed about giving people rewind permissions (`RW+`) and some kid blew away "master", the log file is a life saver, in terms of easily and quickly finding the SHA that got hosed.
+
+**Access rights reporting**: Another convenient feature is what happens when you try and just ssh to the server.  Gitolite shows you what repos you have access to, and what that access may be.  Here's an example:
+
+        hello sitaram, this is git at git running gitolite3 v0.02-15-g1db50f4 on git 1.7.4.4
+
+             R     anu-wsd
+             R     entrans
+             R  W  git-notes
+             R  W  gitolite
+             R  W  gitolite-admin
+             R     indic_web_input
+             R     shreelipi_converter
+
+**Delegation**: For really large installations, you can delegate responsibility for groups of repositories to various people and have them manage those pieces independently.  This reduces the load on the main admin, and makes him less of a bottleneck.  See [here][deleg] for more on this.
+
+**Mirroring**: Gitolite can help you maintain multiple [mirrors][mirroring], and switch between them easily if the primary server goes down.
+
+[progit]: http://sitaramc.github.com/gitolite/progit.html
diff --git a/doc/users.mkd b/doc/users.mkd
index 520451f..f815aaa 100644
--- a/doc/users.mkd
+++ b/doc/users.mkd
@@ -32,7 +32,7 @@ The user name is simply the base name of the public key file name.  So
 ## #multi-key multiple keys per user
 
 The simplest and most understandable is to put their keys in different
-subdirectories, (alice,pub, home/alice.pub, laptop/alice.pub, etc).
+subdirectories, (alice.pub, home/alice.pub, laptop/alice.pub, etc).
 
 ### old style multi-keys
 
diff --git a/doc/wild.mkd b/doc/wild.mkd
index 9b26e0e..49f99ba 100644
--- a/doc/wild.mkd
+++ b/doc/wild.mkd
@@ -108,7 +108,8 @@ what users are in these roles.
 
 That needs to be done by the creator of the repo, using the `perms` command.
 You can run `ssh git at host perms -h` for detailed help, but in brief, that
-command lets you give and take away roles to users.
+command lets you give and take away roles to users.  [This][perms] has some
+more detail.
 
 ## adding other roles
 
diff --git a/src/lib/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
index c607417..1e9cd9f 100644
--- a/src/lib/Gitolite/Conf/Sugar.pm
+++ b/src/lib/Gitolite/Conf/Sugar.pm
@@ -119,14 +119,14 @@ sub owner_desc {
 
     # owner = "owner name"
     #   ->  config gitweb.owner = owner name
-    # description = "some long description"
+    # desc = "some long description"
     #   ->  config gitweb.description = some long description
     # category = "whatever..."
     #   ->  config gitweb.category = whatever...
 
     # older formats:
     # repo = "some long description"
-    # repo = "owner name" = "some long description"
+    # repo "owner name" = "some long description"
     #   ->  config gitweb.owner = owner name
     #   ->  config gitweb.description = some long description
 

commit c5e0e929a72d5557c75d150ff72f0a2e189c727d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 9 23:16:34 2012 +0530

    sskm: minimum changes, minimally smoke tested!
    
    (thanks to fabian at hirschm.net for testing more thoroughly as well)

diff --git a/doc/g3-master-toc.mm b/doc/g3-master-toc.mm
index 1821a1b..10becc5 100755
--- a/doc/g3-master-toc.mm
+++ b/doc/g3-master-toc.mm
@@ -145,7 +145,6 @@
 <node CREATED="1333327082853" ID="ID_488765250" MODIFIED="1333327089444" TEXT="log file format, LOG_EXTRA"/>
 <node CREATED="1333301298504" ID="ID_60946303" MODIFIED="1333301300418" TEXT="smart http"/>
 <node CREATED="1333301308136" ID="ID_1900285587" MODIFIED="1333301311863" TEXT="hub"/>
-<node CREATED="1333301312124" ID="ID_843247306" MODIFIED="1333301313052" TEXT="sskm"/>
 <node CREATED="1333328274461" ID="ID_248606591" MODIFIED="1333328277083" TEXT="mob branches"/>
 <node CREATED="1333328277387" ID="ID_1027016949" MODIFIED="1333328280083" TEXT="password access"/>
 </node>
diff --git a/doc/sskm.mkd b/doc/sskm.mkd
new file mode 100644
index 0000000..55e2787
--- /dev/null
+++ b/doc/sskm.mkd
@@ -0,0 +1,242 @@
+# changing keys -- self service key management
+
+[Note on g3 version: this has been manually spot-tested; there is no test suite.  Changes from g2 version are minimal so it should all work fine but please report errors!]
+
+Follow this guide to add keys to or remove keys from your account. Note that you cannot use this method to add your *first* key to the account; you must still email your initial key to your admin.
+
+The key management is done using a command called `sskm`.  This command must be enabled for remote use by the admin (see [here][commands] for more on this).
+
+----
+
+[[TOC]]
+
+----
+
+## Important!
+
+There are a few things that you should know before using the key management system. Please do not ignore this section!
+
+### Key fingerprints
+
+Keys are identified in some of these subcommands by their fingerprints. To see the fingerprint for a public key on your computer, use the following syntax:
+
+    ssh-keygen -l -f <path_to_public_key.pub>
+
+You'll get output like:
+
+    jeff at baklava ~  $  ssh-keygen -l -f .ssh/jeffskey.pub 
+    2048 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44 .ssh/jeffskey.pub (RSA)
+
+### Active keys
+
+Any keys that you can use to interact with the system are active keys. (Inactive keys are keys that are, for instance, scheduled to be added or removed.) Keys are identified with their `keyid`; see the section below on listing keys.
+
+If you have no current active keys, you will be locked out of the system (in which case email your admin for help). Therefore, be sure that you are never removing your only active key!
+
+### Selecting which key to use
+
+Although you can identify yourself to the Gitolite system with any of your active keys on the server, at times it is necessary to specifically pick which key you are identifying with. To pick the key to use, pass the `-i` flag into `ssh`:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git info
+    hello jeff, the gitolite version here is v2.0.1-11-g1cd3414
+    the gitolite config gives you the following access:
+     @C  R   W      [a-zA-Z0-9][a-zA-Z0-9_\-\.]+[a-zA-Z0-9]
+    ....
+
+*N.B.*: If you have any keys loaded into `ssh-agent` (i.e., `ssh-add -l` shows
+at least one key), then this may not work properly.  `ssh` has a bug which
+makes it ignore `-i` values when that key has not been loaded into the agent.
+One solution is to add the key you want to use (e.g., `ssh-add
+.ssh/jeffskey`).  The other is to remove *all* the keys from the agent or
+disable the agent, using one of these commands:
+
+* Terminate `ssh-agent` or use `ssh-add -D` flag to remove identities from it
+* If using `keychain`, run `keychain --clear` to remove identities
+* Unset the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` variables in the current shell
+
+### Public vs. private keys
+
+In this guide, all keys are using their full suffix. In other words, if you see a `.pub` at the end of a key, it's the public key; if you don't, it's the private key. For instance, when using the `-i` flag with `ssh`, you are specifying private keys to use. When you are submitting a key for addition to the system, you are using the public key.
+
+## Listing your existing keys
+
+To see a list of your existing keys, use the `list` argument to `sskm`:
+
+    jeff at baklava ~  $  ssh git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+
+Notice the `@` sign in each key's name? That sign and the text after that up until the `.pub` is the `keyid`. This is what you will use when identifying keys to the system. Above, for instance, one of my keys has the `keyid` of `@key3`.
+
+A keyid may be *empty*; in fact to start with you may only have a single
+`jeff.pub` key, depending on how your admin added your initial key.  You can
+use any keyid you wish when adding keys (like `@home`, `@laptop`, ...); the
+only rules are that it must start with the `@` character and after that
+contain only digits, letters, or underscores.
+
+## Adding or Replacing a key
+
+### Step 1: Adding the Key
+
+Adding and replacing a key is the same process. What matters is the `keyid`. When adding a new key, use a new `keyid`; when replacing a key, pass in the `keyid` of the key you want to replace, as found by using the `list` subcommand. Pretty simple!
+
+To add a key, pipe in the text of your new key using `cat` to the `add` subcommand. In the example below, I explicitly select which existing, active pubkey to identify with for the command (using the `-i` parameter to ssh) for clarity:
+
+    jeff at baklava ~  $  cat .ssh/newkey.pub | ssh -i .ssh/jeffskey git at git sskm add @key4
+    hello jeff, you are currently using a normal ("active") key
+    please supply the new key on STDIN.  (I recommend you
+            don't try to do this interactively, but use a pipe)
+
+If you now run the `list` command you'll see that it's scheduled for addition:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+    == keys marked for addition/replacement ==
+    1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
+
+### Step 2: Confirming the addition
+
+Gitolite uses Git internally to store the keys. Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-add` subcommand to do this, *but*: to verify that you truly have ownership of the corresponding private key, you *must* use the key you are adding itself to do the confirmation! (Inconvenient like most security, but very necessary from a security perspective.) This is where using the `-i` flag of `ssh` comes in handy:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm confirm-add @key4
+    hello jeff, you are currently using a key in the 'marked for add' state
+
+Listing keys again shows that all four keys are now active:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
+
+### Optional: Undoing a mistaken add (before confirmation)
+
+Another advantage of Gitolite using Git internally is that that if we mistakenly add the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to remove into the `undo-add` subcommand:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm undo-add @key4
+    hello jeff, you are currently using a normal ("active") key
+
+Listing the keys shows that that new key has been removed:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+
+## Removing a key
+
+### Step 1: Mark the key for deletion
+
+Deleting a key works very similarly to adding a key, with `del` substituted for `add`.
+
+Let's say that I have my four keys from the example above:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
+
+I would like to remove the key that on my box is called `newkey` and in the Gitolite system is known as `@key4`.
+
+I simply pass in the identifier to the `del` subcommand of `sskm`:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm del @key4
+    hello jeff, you are currently using a normal ("active") key
+
+Listing the keys now shows that it is marked for deletion:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
+    hello jeff, you are currently using a key in the 'marked for del' state
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+    == keys marked for deletion ==
+    1: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
+
+### Step 2: Confirming the deletion
+
+Just like with Git, where you commit locally before `push`-ing up to the server, you need to confirm the key addition (see the next section if you made a mistake). We use the `confirm-del` subcommand to do this, *but*: unlike the `confirm-add` subcommand, you *must* use a *different* key than the key you are deleting to do the confirmation! This prevents you from accidentally locking yourself out of the system by removing all active keys:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm confirm-del @key4
+    hello jeff, you are currently using a normal ("active") key
+
+Listing keys again shows that the fourth key has been removed:
+
+    jeff at baklava ~  $  ssh -i .ssh/jeffskey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+
+### Optional: Undoing a mistaken delete (before confirmation)
+
+Another advantage of Gitolite using Git internally is that that if we mistakenly delete the wrong key, we can undo it before it's confirmed by passing in the `keyid` we want to keep into the `undo-del` subcommand. Note that this operation *must* be performed using the private key that corresponds to the key you are trying to keep! (Security reasons, similar to the reason that you must confirm an addition this way; it prevents anyone from undoing a deletion, and therefore keeping in the system, a key that they cannot prove (by having the corresponding private key) should stay in the system):
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm undo-del @key4
+    hello jeff, you are currently using a key in the 'marked for del' state
+
+    You're undeleting a key that is currently marked for deletion.
+        Hit ENTER to undelete this key
+        Hit Ctrl-C to cancel the undelete
+    Please see documentation for caveats on the undelete process as well as how to
+    actually delete it.
+
+(Go ahead and hit ENTER there; the caveats are really only on the administrative side of things.)
+
+Listing the keys shows that that new key is now marked active again:
+
+    jeff at baklava ~  $  ssh -i .ssh/newkey git at git sskm list
+    hello jeff, you are currently using a normal ("active") key
+    you have the following keys:
+    == active keys ==
+    1: 72:ef:a3:e0:f5:06:f8:aa:6f:a2:88:9d:50:86:25:4e  : jeff at key1.pub
+    2: 61:38:a7:9f:ba:cb:99:81:4f:49:2c:8b:c8:63:8e:33  : jeff at key2.pub
+    3: 2d:78:d4:2c:b1:6d:9a:dc:d9:0d:94:3c:d8:c2:65:44  : jeff at key3.pub
+    4: ff:92:a2:20:6d:42:6b:cf:20:e8:a2:4a:3b:b0:32:3a  : jeff at key4.pub
+
+----
+
+## important notes for the admin
+
+These are the things that can break if you allows your users to use this command:
+
+  * if you, as the gitolite admin, are in the habit of force-pushing changes
+    to the admin repo instead of doing a `git pull` (or, even better, a `git
+    pull --rebase`) then you had better not enable this command.  Your users
+    will eventually come after you with pitchforks ;-)
+
+  * there is no way to distinguish `foo/alice.pub` from `bar/alice.pub` using
+    this command.  You can distinguish `foo/alice.pub` from
+    `bar/alice at home.pub`, but that's not because of the foo and bar, it's
+    because the two files have different keyids.
+
+    In other words, sskm only works with the older style, not with the
+    "subdirectory" style of [multi-key][] management.
+
+  * keys placed in specific folders (for whatever reasons), will probably not
+    stay in those folders if this command is used.  Even a key delete, followed
+    by undoing the delete, will cause the key to effectively move to the root
+    of the key store (i.e., the `keydir` directory in the gitolite-admin repo).
diff --git a/src/commands/sskm b/src/commands/sskm
new file mode 100755
index 0000000..a198dd0
--- /dev/null
+++ b/src/commands/sskm
@@ -0,0 +1,280 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+=for usage
+Usage for this command is not that simple.  Please read the full documentation
+in doc/sskm.mkd or online at http://sitaramc.github.com/gitolite/sskm.html.
+=cut
+
+usage() if @ARGV and $ARGV[0] eq '-h';
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
+# get to the keydir
+_chdir("$ab/keydir");
+
+# save arguments for later
+my $operation = shift || 'list';
+my $keyid     = shift || '';
+# keyid must fit a very specific pattern
+$keyid and $keyid !~ /^@[-0-9a-z_]+$/i and die "invalid keyid $keyid\n";
+
+# get the actual userid and keytype
+my $gl_user = $ENV{GL_USER};
+my $keytype = '';
+$keytype = $1 if $gl_user =~ s/^zzz-marked-for-(...)-//;
+print STDERR "hello $gl_user, you are currently using "
+  . (
+    $keytype
+    ? "a key in the 'marked for $keytype' state\n"
+    : "a normal (\"active\") key\n"
+  );
+
+# ----
+# first collect the keys
+
+my ( @pubkeys, @marked_for_add, @marked_for_del );
+# get the list of pubkey files for this user, including pubkeys marked for
+# add/delete
+
+for my $pubkey (`find . -type f -name "*.pub" | sort`) {
+    chomp($pubkey);
+    $pubkey =~ s(^./)();    # artifact of the find command
+
+    my $user = $pubkey;
+    $user =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
+    $user =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
+
+    next unless $user eq $gl_user or $user =~ /^zzz-marked-for-...-$gl_user/;
+
+    if ( $user =~ m(^zzz-marked-for-add-) ) {
+        push @marked_for_add, $pubkey;
+    } elsif ( $user =~ m(^zzz-marked-for-del-) ) {
+        push @marked_for_del, $pubkey;
+    } else {
+        push @pubkeys, $pubkey;
+    }
+}
+
+# ----
+# list mode; just do it and exit
+sub print_keylist {
+    my ( $message, @list ) = @_;
+    return unless @list;
+    print "== $message ==\n";
+    my $count = 1;
+    for (@list) {
+        my $fp = fingerprint($_);
+        s/zzz-marked(\/|-for-...-)//g;
+        print $count++ . ": $fp : $_\n";
+    }
+}
+if ( $operation eq 'list' ) {
+    print "you have the following keys:\n";
+    print_keylist( "active keys",                          @pubkeys );
+    print_keylist( "keys marked for addition/replacement", @marked_for_add );
+    print_keylist( "keys marked for deletion",             @marked_for_del );
+    print "\n\n";
+    exit;
+}
+
+# ----
+# please see docs for details on how a user interacts with this
+
+if ( $keytype eq '' ) {
+    # user logging in with a normal key
+    die "valid operations: add, del, undo-add, confirm-del\n" unless $operation =~ /^(add|del|confirm-del|undo-add)$/;
+    if ( $operation eq 'add' ) {
+        print STDERR "please supply the new key on STDIN.  (I recommend you
+        don't try to do this interactively, but use a pipe)\n";
+        kf_add( $gl_user, $keyid, safe_stdin() );
+    } elsif ( $operation eq 'del' ) {
+        kf_del( $gl_user, $keyid );
+    } elsif ( $operation eq 'confirm-del' ) {
+        die "you dont have any keys marked for deletion\n" unless @marked_for_del;
+        kf_confirm_del( $gl_user, $keyid );
+    } elsif ( $operation eq 'undo-add' ) {
+        die "you dont have any keys marked for addition\n" unless @marked_for_add;
+        kf_undo_add( $gl_user, $keyid );
+    }
+} elsif ( $keytype eq 'del' ) {
+    # user is using a key that was marked for deletion.  The only possible use
+    # for this is that she changed her mind for some reason (maybe she marked
+    # the wrong key for deletion) or is not able to get her client-side sshd
+    # to stop using this key
+    die "valid operations: undo-del\n" unless $operation eq 'undo-del';
+
+    # reinstate the key
+    kf_undo_del( $gl_user, $keyid );
+} elsif ( $keytype eq 'add' ) {
+    die "valid operations: confirm-add\n" unless $operation eq 'confirm-add';
+    # user is trying to validate a key that has been previously marked for
+    # addition.  This isn't interactive, but it *could* be... if someone asked
+    kf_confirm_add( $gl_user, $keyid );
+}
+
+exit;
+
+# ----
+
+# make a temp clone and switch to it
+our $TEMPDIR;
+BEGIN { $TEMPDIR = `mktemp -d -t tmp.XXXXXXXXXX`; }
+END { `/bin/rm -rf $TEMPDIR`; }
+
+sub cd_temp_clone {
+    chomp($TEMPDIR);
+    hushed_git( "clone", "$rb/gitolite-admin.git", "$TEMPDIR" );
+    chdir($TEMPDIR);
+    my $hostname = `hostname`; chomp($hostname);
+    hushed_git( "config", "--get", "user.email" ) and hushed_git( "config", "user.email", $ENV{USER} . "@" . $hostname );
+    hushed_git( "config", "--get", "user.name" )  and hushed_git( "config", "user.name",  "$ENV{USER} on $hostname" );
+}
+
+sub fingerprint {
+    my $fp = `ssh-keygen -l -f $_[0]`;
+    die "does not seem to be a valid pubkey\n" unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+ )/i;
+    return $1;
+}
+
+sub safe_stdin {
+    # read one line from STDIN
+    my $data;
+    my $ret = read STDIN, $data, 4096;
+    # current pubkeys are approx 400 bytes so we go a little overboard
+    die "could not read pubkey data" . ( defined($ret) ? "" : ": $!" ) . "\n" unless $ret;
+    die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
+    return $data;
+}
+
+sub hushed_git {
+    local (*STDOUT) = \*STDOUT;
+    local (*STDERR) = \*STDERR;
+    open( STDOUT, ">", "/dev/null" );
+    open( STDERR, ">", "/dev/null" );
+    system( "git", @_ );
+}
+
+sub highlander {
+    # there can be only one
+    my ( $keyid, $die_if_empty, @a ) = @_;
+    # too many?
+    if ( @a > 1 ) {
+        print STDERR "
+more than one key satisfies this condition, and I can't deal with that!
+The keys are:
+
+";
+        print STDERR "\t" . join( "\n\t", @a ), "\n\n";
+        exit 1;
+    }
+    # too few?
+    die "no keys with " . ( $keyid || "empty" ) . " keyid found\n" if $die_if_empty and not @a;
+
+    return @a;
+}
+
+sub kf_add {
+    my ( $gl_user, $keyid, $keymaterial ) = @_;
+
+    # add a new "marked for addition" key for $gl_user.
+    cd_temp_clone();
+    chdir("keydir");
+
+    mkdir("zzz-marked");
+    _print( "zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub", $keymaterial );
+    hushed_git( "add", "." ) and die "git add failed\n";
+    my $fp = fingerprint("zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub");
+    hushed_git( "commit", "-m", "sskm: add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+sub kf_confirm_add {
+    my ( $gl_user, $keyid ) = @_;
+    # find entries in both @pubkeys and @marked_for_add whose basename matches $gl_user$keyid
+    my @pk  = highlander( $keyid, 0, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
+    my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
+
+    cd_temp_clone();
+    chdir("keydir");
+
+    my $fp = fingerprint( $mfa[0] );
+    if ( $pk[0] ) {
+        hushed_git( "mv", "-f", $mfa[0], $pk[0] );
+        hushed_git( "commit", "-m", "sskm: confirm-add (replace) $pk[0] ($fp)" ) and die "git commit failed\n";
+    } else {
+        hushed_git( "mv", "-f", $mfa[0], "$gl_user$keyid.pub" );
+        hushed_git( "commit", "-m", "sskm: confirm-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
+    }
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+sub kf_undo_add {
+    # XXX some code at start is shared with kf_confirm_add
+    my ( $gl_user, $keyid ) = @_;
+    my @mfa = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-add-$gl_user$keyid.pub$) } @marked_for_add );
+
+    cd_temp_clone();
+    chdir("keydir");
+
+    my $fp = fingerprint( $mfa[0] );
+    hushed_git( "rm", $mfa[0] );
+    hushed_git( "commit", "-m", "sskm: undo-add $gl_user$keyid ($fp)" ) and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+sub kf_del {
+    my ( $gl_user, $keyid ) = @_;
+
+    cd_temp_clone();
+    chdir("keydir");
+
+    mkdir("zzz-marked");
+    my @pk = highlander( $keyid, 1, grep { m(^(.*/)?$gl_user$keyid.pub$) } @pubkeys );
+
+    my $fp = fingerprint( $pk[0] );
+    hushed_git( "mv", $pk[0], "zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub" ) and die "git mv failed\n";
+    hushed_git( "commit", "-m", "sskm: del $pk[0] ($fp)" ) and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+sub kf_confirm_del {
+    my ( $gl_user, $keyid ) = @_;
+    my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
+
+    cd_temp_clone();
+    chdir("keydir");
+
+    my $fp = fingerprint( $mfd[0] );
+    hushed_git( "rm", $mfd[0] );
+    hushed_git( "commit", "-m", "sskm: confirm-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}
+
+sub kf_undo_del {
+    my ( $gl_user, $keyid ) = @_;
+
+    my @mfd = highlander( $keyid, 1, grep { m(^zzz-marked/zzz-marked-for-del-$gl_user$keyid.pub$) } @marked_for_del );
+
+    print STDERR "
+You're undeleting a key that is currently marked for deletion.
+    Hit ENTER to undelete this key
+    Hit Ctrl-C to cancel the undelete
+Please see documentation for caveats on the undelete process as well as how to
+actually delete it.
+";
+    <>;    # yeay... always wanted to do that -- throw away user input!
+
+    cd_temp_clone();
+    chdir("keydir");
+
+    my $fp = fingerprint( $mfd[0] );
+    hushed_git( "mv", "-f", $mfd[0], "$gl_user$keyid.pub" );
+    hushed_git( "commit", "-m", "sskm: undo-del $gl_user$keyid ($fp)" ) and die "git commit failed\n";
+    system("gitolite push >/dev/null 2>/dev/null") and die "git push failed\n";
+}

commit 48f1e7c7817abb363633137dd6eaebc30c771d6d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 8 11:10:06 2012 +0530

    'gitolite git-config' should print only value when regex not used...
    
    not repo<tab>key<tab>value.  Also, honor '-n' (no newline)

diff --git a/src/commands/git-config b/src/commands/git-config
index 293f298..a39da2a 100755
--- a/src/commands/git-config
+++ b/src/commands/git-config
@@ -23,8 +23,7 @@ Examples:
     gitolite git-config -q repo gitweb.owner
     gitolite git-config -r repo gitweb
 
-When the key is treated as a pattern, prints one key+value per line, tab
-separated:
+When the key is treated as a pattern, prints:
 
     reponame<tab>key<tab>value<newline>
 
@@ -57,10 +56,17 @@ if ( $repo ne '%' and $key ne '%' ) {
 
     $ret = git_config( $repo, $key );
 
+    # if the key is not a regex, it should match at most one item
+    _die "found more than one entry for '$key'" if not $regex and scalar( keys %$ret ) > 1;
+
     # unlike access, there's nothing to print if we don't find any matching keys
     exit 1 unless %$ret;
 
-    map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
+    if ($regex) {
+        map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
+    } else {
+        map { print $ret->{$_} . ( $nonl ? "" : "\n" ) } sort keys %$ret unless $quiet;
+    }
     exit 0;
 }
 

commit 39fc5b32bbe4dde3e8659e47d67e77f9301cca74
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 6 13:37:30 2012 +0530

    document overhaul 2 - mostly migration stuff

diff --git a/doc/cust.mkd b/doc/cust.mkd
index e4dbd07..5004732 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -7,6 +7,12 @@ even mirroring is not in core now!)
 
 (Also, please see the [developer notes][dev-notes] page).
 
+----
+
+[[TOC]]
+
+----
+
 ## types of non-core programs
 
 There are 5 basic types of non-core programs.
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
index 08b84f9..28f0866 100644
--- a/doc/dev-status.mkd
+++ b/doc/dev-status.mkd
@@ -2,13 +2,11 @@
 
 Not yet done (will be tackled in this order unless someone asks):
 
-  * svnserve (someone is testing it)
-  * pulling in documentation for things that are the same in g2
   * smart http
-  * special features (no-create-repos, gl-all-read-all, etc)
+  * svnserve (someone is testing it)
   * mechanism for ADCs using unchecked arguments -- this is not just a matter
     of writing it; I have to think about *how* it will be done.  (AFAIK the
-    only tool affected is git-annexe)
+    only tool affected is git-annexe; if there are more let me know)
 
 Help needed:
 
@@ -22,6 +20,7 @@ anyway!):
 
   * mob branches
   * password access
+  * some of the more arcane rc variables!
   * specific ADCs -- there are too many for me to bother without applying
     Pareto somewhere, so I choose to not do any and wait for people to ask :-)
 
@@ -30,6 +29,7 @@ Done:
   * core code
   * test suite
   * mirroring
-  * most documentation
+  * documentation
+  * migration documentation
   * distro packaging instructions
   * migration advice for common cases
diff --git a/doc/g2alt.mkd b/doc/g2alt.mkd
deleted file mode 100644
index ac10c71..0000000
--- a/doc/g2alt.mkd
+++ /dev/null
@@ -1,9 +0,0 @@
-## #g2alt alternate implementations for some g2 features
-
-The following g2 features have been dropped but suitable (or better)
-alternatives exist.
-
-### gl-time for performance measurement
-
-Take a look at the 'CpuTime' trigger module shipped.  Comments within, and
-comments in the default rc file that contain the word "cpu", should be useful.
diff --git a/doc/g2dropped.mkd b/doc/g2dropped.mkd
deleted file mode 100644
index 429cdce..0000000
--- a/doc/g2dropped.mkd
+++ /dev/null
@@ -1,4 +0,0 @@
-## #g2dropped g2 features dropped
-
-(none yet that are not already covered in the rc section in [this][g2migr]
-page).
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
index 50bb944..6c0fa96 100644
--- a/doc/g2incompat.mkd
+++ b/doc/g2incompat.mkd
@@ -1,13 +1,9 @@
-## #g2incompat incompatibility with g2
+# #g2incompat incompatibility with g2
 
-(other than in the rc file, which is dealt with [elsewhere][g2rcdiff])
+This page expands on some incompatibilities that were only briefly mentioned
+in the [migration][g2migr] page.
 
-The following incompatibilities exist, in vaguely decreasing order of
-severity.  **The ones in the first section are IMPORTANT because they allow
-access that was previously not allowed -- please fix your config before using
-the new gitolite!**
-
-### NAME rules
+## #g2i-name NAME rules
 
 1.  NAME/ rules must be changed to VREF/NAME/
 
@@ -18,7 +14,7 @@ the new gitolite!**
 
         -   VREF/NAME/       =   @all
 
-### subconf command in admin repo
+## #g2i-subconf subconf command in admin repo
 
 (This is also affected by the previous issue, 'NAME rules'; please read that
 as well).
@@ -42,3 +38,66 @@ line.  As the [vref documentation][vref] says:
 The second part explicitly says when and where to include the subconf files.
 (Before subconf was invented, this used to happen implicitly at the end of the
 main conf file, and was hardcoded to that specific glob.)
+
+## #g2i-gl-time gl-time for performance measurement
+
+If you've been using gl-time for performance measurement, there's a much
+better system available now.
+
+gl-time used to only log elapsed time.  The new 'CpuTime' trigger module
+shipped with gitolite, if enabled in the rc file, can also report CPU times
+using perl's 'times()' function.  See comments within that file and in the
+default rc file that contain the word "cpu", for more details.
+
+Further, you can copy that module with a different name, add your own
+functionality, and invoke *that* from the rc file instead.
+
+## #g2i-mirroring changes in mirroring setup
+
+There are several changes with regard to mirroring:
+
+  * there is no 'post-receive' hook to be installed.  Mirroring is handled by
+    g3's [triggers][] mechanism.  Gitolite triggers are enabled by adding (or
+    uncommenting, in this case) appropriate lines in the rc file.
+
+  * the `GL_HOSTNAME` variable is now `HOSTNAME`.  (Note that the rc file
+    syntax itself has changed quite a bit; to be accurate, HOSTNAME is not a
+    variable but a hash key with an associated value).
+
+  * the `GL_GITCONFIG_KEYS` variable is now `GIT_CONFIG_KEYS`, **but** you no
+    longer need to set it to anything for mirroring to work.
+
+  * the `gl-tool` program does not exist anymore.  Adding keys for peer
+    servers is done just like adding user keys, except that the pubkey file
+    name must start with `server-`.  For example, to add a peer host called
+    frodo, you will acquire its pubkey and add it as `server-frodo.pub`.
+
+  * the config variables are quite different now.  The main ones now look like
+    this:
+
+        option mirror.master        =   sam
+        option mirror.slaves        =   frodo gollum
+
+    The redirectOK looks like this:
+
+        option mirror.redirectOK    =   frodo
+
+    The special value "true" to say that all slaves are trusted is now "all":
+
+        option mirror.redirectOK    =   all
+
+  * there are no more mirroring "keys", (lists of servers named in config keys
+    like 'gitolite.mirror.nightly', etc).  You can certainly add lines like
+
+        option mirror.nightly       =   merry pippin
+
+    but they will not be processed by gitolite.  Your cron jobs should use
+    `gitolite git-config` to query this variable, grab the list of repos, and
+    run `gitolite mirror` on each of them.
+
+  * the external command to resync mirrors is 'mirror', run just like any
+    other [command][commands].  In particular, you can run `gitolite mirror
+    -h` to get help.  It cannot be run from a slave to ask a master to push
+    (unlike in the old system) but what's more convenient is that any user who
+    has any access to the master can run it remotely (if you allow it) to
+    invoke a push.
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index de50079..7674222 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -1,38 +1,181 @@
-## #g2migr migrating from g2
+# #g2migr migrating from g2
 
-<font color="red">
+<font color="red"> **This document is a *MUST* read if you are currently using
+g2 and want to move to g3.** </font>
 
-**This document is a *MUST* read if you are currently using g2 and want to
-move to g3.**
+----
 
-</font>
+[[TOC]]
 
-First things first: g2 will be supported for a good long time.  My current
-*expert* users do not cause me any load anyway.
+----
 
-Migration should be straightforward, but it is not automatic.  You should run
-the "check-g2-compat" program first, to see any *major* differences that
-affect you.  The bulk of the changes are in the RC file, which must be
-manually handled (links below).  The conf files have very few changes -- they
-apply only if you use "NAME/" or delegation.
+First things first: g2 will be supported for a good long time for critical
+bugs, although enhancements and new features won't happen.
 
-You must first read about [incompatible][g2incompat] features and
-[dropped][g2dropped] features.  Some features have been replaced with
-[alternatives][g2alt].
+Migration should be straightforward, but it is not automatic.  The biggest
+differences are in the rc file, mirroring, "NAME/" rules, and delegation.
 
-Since the majority of changes are in the rc file, they're all listed
-[here][g2rcdiff].
+>   ----
 
-The rest of this page describes the completely standalone "check-g2-compat"
-script that you can find in the repo root (i.e., not in "src/").
+>   **presetting the rc file**
 
-### the "check-g2-compat" program
+>   Some rc settings in the older gitolite are such that you cannot directly
+>   run `gitolite setup` when you're ready to migrate.  Doing that will
+>   **clobber** something important.  See [presetting the rc file][rc-preset]
+>   for details.
+
+>   ----
+
+The `check-g2-compat` program attempts to identify any *big* issues you will
+be facing; run that first.  See [later][cg2c] in this page for what its
+messages mean.  If it does not report any issues, your migrate will probably
+go quickly.  I still suggest you go through the links below in case that
+program missed something.
+
+## incompatible features
+
+Here's a list of incompatible features and what you need to do to migrate.
+Some of them have links where there is more detail than I want to put here.
+
+### high impact
+
+(serious loss of functionality and/or access control compromised)
+
+  * [`NAME/`][g2i-name] rules: thes need to change to `VREF/NAME/`, and you
+    need to add a deny rule at the end because fallthru is "success" for all
+    [VREFs][vref] now, including the "NAME" VREF.
+
+  * [subconf][g2i-subconf]: if you're using [delegation][deleg], there is no
+    implicit "subconf" at the end; you'll have to add it in.
+
+  * there are several important differences in mirroring.  You can start from
+    scratch by reading the new [mirroring][mirroring] doc or
+    [migrate][g2i-mirroring] (carefully!).
+
+  * `ADMIN_POST_UPDATE_CHAINS_TO` -- **dropped**.  Add your script to the
+    `POST_COMPILE` trigger chain.  Your script won't be getting the arguments
+    that *git* sends to the post-update hook, but for the admin repo the only
+    argument that even comes in (or is significant) is "refs/heads/master"
+    anyway.
+
+  * `GL_ALL_INCLUDES_SPECIAL` -- **dropped**, **requires presetting**.
+
+    @all always includes gitweb and daemon now.  Use [deny-rules][] if you
+    want to say `R = @all` but not have the repo(s) be visible to gitweb or
+    daemon.
+
+  * `GL_NO_CREATE_REPOS` -- **dropped**.  If you think you need this, email
+    me.  I know one group who does need this so I will be putting it in
+    eventually but not right away.  It's almost certain to be renamed anyway.
+
+  * `GL_NO_DAEMON_NO_GITWEB` **dropped**, **requires presetting**.  Default
+    will clobber your projects.list file and git-daemon-export-ok files.
+
+    Comment out the appropriate lines in the rc file, in the `POST_COMPILE`
+    *and* the `POST_CREATE` trigger sections.  As a bonus, gitweb and daemon
+    can now be separately disabled, instead of both being tied to the same
+    setting.
+
+  * `GL_NO_SETUP_AUTHKEYS` **dropped**, **requires presetting**.  Default will
+    clobber your authkeys file.
+
+    Comment out all the line(s) that call ssh-authkeys in the rc file.
+
+  * `UPDATE_CHAINS_TO` **dropped**, **requires presetting**.  Default will
+    fail to run this extra check when users push.
+
+    Use a [vref][] instead.  You can directly use any existing chained-to
+    script as a VREF; they'll work.  Don't forget to add a rule that
+    references the new VREF!
+
+  * `GIT_PATH` **dropped**, **requires presetting**.
+
+    If you need its functionality, add these lines to the end of the rc file:
+
+        $ENV{PATH}="...whatever you want...";
+        1;
+
+### medium impact
+
+(important functionality lost, but access control not compromised)
+
+  * `GL_ADMINDIR` -- **dropped**, is now at a fixed location: `~/.gitolite`.
+    If you want it somewhere else go ahead and move it, then place a symlink
+    from the assumed location to the real one.
+
+  * `GL_GET_MEMBERSHIPS_PGM` -- is now `GROUPLIST_PGM`, see
+    [here][ldap].
+
+  * `GL_WILDREPOS_DEFPERMS` -- is now `DEFAULT_ROLE_PERMS`.
+
+  * `REPO_BASE` -- **dropped**, is now at a fixed location: `~/repositories`.
+    If you want it somewhere else go ahead and move it, then place a symlink
+    from the assumed location to the real one.
+
+### low impact
+
+(ancillary, non-core, or minor functionality lost)
+
+  * [gl-time][g2i-gl-time]: the CpuTime module replaces gl-time.
+
+  * `BIG_INFO_CAP` -- **dropped**.  If you think you must have this, try it
+    without and see if there's a difference.  If you *know* you need this,
+    convince me.
+
+  * `GL_ADC_PATH` -- **dropped**.  It is obsolete; use [commands][] or add
+    [your own][dev-notes].
+
+  * `GL_ALL_READ_ALL` -- **dropped**.  If you think you must have this, try it
+    without and see if there's a difference.  If you *know* you need this,
+    convince me.
+
+  * `GL_BIG_CONFIG` -- **dropped**.  This feature is default now.
+
+  * `GL_CONF`, `GL_CONF_COMPILED`, and `GL_KEYDIR` -- **dropped**.  You had no
+    business touching these anyway; if you did, move them into the expected
+    default locations before attempting to run `gitolite setup`
+
+  * `GL_GITCONFIG_KEYS` -- is now `GITCONFIG_KEYS`.
+
+  * `GL_LOGT` -- now has a fixed value; email me if this is a problem.
+
+  * `GL_PACKAGE_CONF` and `GL_PACKAGE_HOOKS` -- **dropped**.  They are not
+    needed anymore, but check if you had any custom hooks set in the latter
+    location and copy them across.
+
+  * `GL_PERFLOGT` -- **dropped**.  See [gl-time][g2i-gl-time].
+
+  * `GL_SITE_INFO` -- is now `SITE_INFO`.
+
+  * `GL_WILDREPOS` -- **dropped**.  This feature is default now.
+
+  * `GL_WILDREPOS_PERM_CATS` -- is now the ROLES hash in the rc file.
+
+  * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
+    are using any of these.
+
+  * `NICE_VALUE` -- **dropped**.  Uncomment the 'renice 10' line in the rc
+    file.  You can also change the 10 to something else if you wish.
+
+  * `PROJECTS_LIST` -- is now `GITWEB_PROJECTS_LIST`.  More importantly, it is
+    only used by update-gitweb-access-list in src/commands/post-compile.  This
+    variable now has nothing to do with gitolite core, and the rc is just
+    helping to store settings for external programs like that one.
+
+  * `REPO_UMASK` -- is now `UMASK`.
+
+  * `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` -- **dropped**.  Patches to the
+    update program to directly do those things are welcome.  Personally, I
+    think people who use spaces and other funky characters in dir/file names
+    should be shot but no one listens to me anyway.
+
+## #cg2c using the "check-g2-compat" program
 
 This program checks a few things only, not everything.  In particular, it
 looks for settings and status that might:
 
   * make g3 unusable for lots of users
-  * make g3 give *more* access than g2 under some conditions.
+  * make g3 give *more* access than g2 under some conditions
 
 It does NOT look for or warn about anything else; you're expected to read (and
 act upon, if needed) the rest of the migration guide links given a few paras
@@ -67,8 +210,9 @@ put that contain the words "see docs":
 
   * `mirroring used`
 
-    There have been quite a few changes to mirroring.  Please see the new
-    [mirroring][mirroring] doc for details.
+    There have been quite a few changes to mirroring.  You can start from
+    scratch by reading the new [mirroring][mirroring] doc or
+    [migrate][g2i-mirroring] (carefully!).
 
   * `found N gl-creater files`
 
@@ -89,24 +233,35 @@ put that contain the words "see docs":
     Setting perms of R and RW will no longer work; you have to say READERS and
     WRITERS now.  Suggested command:
 
-The following variables need to be [preset][rc-preset] in the rc file
-**before** running `gitolite setup`.  Otherwise the default actions will
-clobber something and require some recovery.
+## #rc-preset presetting the rc file
+
+Some rc settings in the older gitolite are such that you cannot directly run
+`gitolite setup` when you're ready to migrate.  Doing that will **clobber**
+something important.  You have to create a default rc file, edit it
+appropriately, and *then* run `gitolite setup`.
+
+The most serious example of this is `GL_NO_SETUP_AUTHKEYS`, which tells the
+(old) gitolite that you want to manage `~/.ssh/authorized_keys` yourself and
+it should not fiddle with it.
+
+If you don't preset the rc (in this case, by commenting out the 'ssh-authkeys'
+line) **before** running `gitolite setup`, **your `~/.ssh/authorized_keys`
+file will get clobbered**.
+
+The actual rc settings that require presetting are listed in the "high
+impact" section above.  This section tells you how to do the presetting.
+
+  * save your old (g2) rc file somewhere, just in case
 
-  * `GL_NO_SETUP_AUTHKEYS` (default will clobber your authkeys file)
+  * run
 
-  * `GL_NO_DAEMON_NO_GITWEB` (default will clobber your projects.list file and
-    git-daemon-export-ok files)
+        gitolite print-default-rc > $HOME/.gitolite.rc
 
-  * `UPDATE_CHAINS_TO` (default will fail to run this extra check when users
-    push)
+  * edit the file
 
-  * `ADMIN_POST_UPDATE_CHAINS_TO` (severity depends on what your code is
-    doing; see [g2rcdiff][] for how to fix this)
+        ${EDITOR:-vim} $HOME/.gitolite.rc
 
-  * `GL_ALL_INCLUDES_SPECIAL` (default will allow gitweb and daemon to be able
-    to read any repos that have `R = @all`)
+    make appropriate changes as described elsewhere in this migration guide,
+    and save it.
 
-  * `GIT_PATH` (presumably your git is in some non-std path so unless you
-    preset `$ENV{PATH}` per instructions in the [rc file
-    differences][g2rcdiff] doc, nothing will work).
+  * *then* you can run [gitolite setup][setup].
diff --git a/doc/g2rcdiff.mkd b/doc/g2rcdiff.mkd
deleted file mode 100644
index 5fa2605..0000000
--- a/doc/g2rcdiff.mkd
+++ /dev/null
@@ -1,125 +0,0 @@
-## #g2rcdiff rc file differences between g2 and g3
-
-The new rc file has far fewer variables; many have been dropped.  You should
-not see much ill effect though, but please read below.
-
-### #rc-preset pre-setting the rc file
-
-Some of these settings are such that you cannot directly run `gitolite setup`
-when you're ready to migrate.  Instead, you need to run
-
-    # (assuming you saved your g2 rc file somewhere)
-    gitolite print-default-rc > $HOME/.gitolite.rc
-    $EDITOR $HOME/.gitolite.rc
-    # make appropriate changes (see below), save
-    gitolite setup
-
-The most serious example of this is `GL_NO_SETUP_AUTHKEYS`.  If you don't
-preset the rc (in this case, by commenting out the 'ssh-authkeys' line)
-**before** running `gitolite setup`, your `~/.ssh/authorized_keys` file will
-get clobbered.
-
-There are several other settings that you may want to look at, with varying
-degrees of severity; **please go through the list below before blindly running
-`gitolite setup`**.  (For example, if you had `GL_NO_DAEMON_NO_GITWEB` in g2
-but forgot to comment out all the gitweb and daemon update lines in the rc
-file, your projects.list and git-daemon-export-ok files might get clobbered.
-Of course this is less severe than ssh authkeys but still...).
-
-### rc file differences
-
-**DROPPED** variables (possible high impact): these could be show-stoppers for
-migration, at least for now.
-
-  * `BIG_INFO_CAP` -- if you think you must have this, try it without and see
-    if there's a difference.  If you *know* you need this, convince me.
-
-  * `GL_ALL_READ_ALL` -- same
-
-  * `GL_NO_CREATE_REPOS` -- if you think you need this, email me.  I know one
-    group who does need this so I will be putting it in eventually but not
-    right away.
-
-  * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
-    are using any of these.
-
-  * `GL_GET_MEMBERSHIPS_PGM` -- is high on my todo list
-
-  * `GL_LOGT` -- is now fixed; you can't change it.  Email me if this is a
-    problem.
-
-**DROPPED** variables (medium impact): these have alternative implementations
-or mechanisms, but you have to do some setup work.
-
-  * `GL_ADMINDIR` -- this is now at a fixed location: `~/.gitolite`.  If you
-    want it somewhere else go ahead and move it, then place a symlink from the
-    assumed location to the real one.
-
-  * `REPO_BASE` -- this is now at a fixed location: `~/repositories`.  If you
-    want it somewhere else go ahead and move it, then place a symlink from the
-    assumed location to the real one.
-
-  * `PROJECTS_LIST` -- it's called `GITWEB_PROJECTS_LIST` now, but more
-    importantly, it is only used by update-gitweb-access-list in
-    src/commands/post-compile.  This variable now has nothing to do with
-    gitolite core, and the rc is just helping to store settings for external
-    programs like that one.
-
-    `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` are also gone; patches to the
-    update program to directly do those things are welcome.  Personally, I
-    think people who use spaces and other funky characters in dir/file names
-    should be shot but luckily no one listens to me :-)
-
-  * `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
-    file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
-
-  * `NICE_VALUE` -- uncomment the 'renice 10' line in the rc file.  You can
-    also change the 10 to something else if you wish.
-
-  * `GIT_PATH` -- gone, not needed.  Just add these lines to the end of the rc
-    file:
-
-        $ENV{PATH}="...whatever you want...";
-        1;
-
-  * `GL_NO_SETUP_AUTHKEYS` -- comment out the lines that call ssh-authkeys, in
-    the rc file.
-
-  * `GL_WILDREPOS_DEFPERMS` -- if you need this, add a `POST_CREATE` script
-    that does it.  Or email me and I will write it for you.
-
-  * `UPDATE_CHAINS_TO` -- use a [vref][] instead.  You can directly use the
-    chained-to script as a VREF; it'll work.
-
-  * `ADMIN_POST_UPDATE_CHAINS_TO` -- add your script to the `POST_COMPILE`
-    trigger chain.  You won't be getting any arguments but for the admin repo
-    the only argument that ever comes in is "refs/heads/master" anyway.
-
-  * `GL_ADC_PATH` -- obsolete; use [commands][] or add [your own][dev-notes].
-
-  * `GL_ALL_INCLUDES_SPECIAL` -- obsolete; @all always includes gitweb and
-    daemon now.  Use [deny-rules][] if you want to say `R = @all` but not have
-    it be visible to gitweb or daemon.
-
-  * `GL_PERFLOGT` -- see the entry for "gl-time" in the [alternative
-    implementations][g2alt] page.
-
-**DROPPED** variables (no impact/low impact): these variables should not
-actually affect anything anyway, so even if you had them set you should not
-feel their loss.
-
-  * `GL_CONF`, `GL_KEYDIR`, and `GL_CONF_COMPILED` -- you had no business
-    touching these anyway; if you did, move them into the expected default
-    locations before attempting to run `gitolite setup`
-  * `GL_PACKAGE_HOOKS` -- not needed anymore, but check if you had any custom
-    hooks set there and copy them across.
-  * `GL_WILDREPOS` -- dropped; this feature is default now.
-  * `GL_BIG_CONFIG` -- dropped; this feature is default now.
-
-**RENAMED** variables (no impact): these are functionally the same but are
-renamed.
-
-  * `REPO_UMASK` is now `UMASK`
-  * `GL_GITCONFIG_KEYS` is now `GITCONFIG_KEYS`
-  * `GL_WILDREPOS_PERM_CATS` is now the ROLES hash in the rc file
-  * `GL_SITE_INFO` is now `SITE_INFO`
diff --git a/doc/install.mkd b/doc/install.mkd
index 368c719..d412782 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -1,9 +1,8 @@
 # installing gitolite
 
-**NOTE**: if you're migrating from g2, there are some settings that MUST be
-dealt with **before** running `gitolite setup`; please read the [g2
-migration][g2migr] page and linked pages, and especially the one on
-[presetting the rc file][rc-preset].
+<font color="red">**NOTE**: if you're migrating from g2, there are some
+settings that MUST be dealt with **before** running `gitolite setup`; please
+read the [g2 migration][g2migr] page and linked pages.</font>
 
 ## notes and naming conventions
 
@@ -138,12 +137,12 @@ Creating a symlink doesn't need a separate program but 'install' also runs
 There are a lot of migration hints and notes; see links at the top of this
 document.
 
-But here's the **bottom line** on migrating:
+But here's the **bottom line** on migrating: nothing in any of the gitolite
+install/setup/etc will ever touch the *data* in any repository except the
+gitolite-admin repo.  The only thing it will normally touch is the `update`
+hook.
 
-Nothing in any of the gitolite install/setup/etc will ever touch the *data* in
-any repository except the gitolite-admin repo.  The only thing it will
-normally touch is the `update` hook.  So one fool-proof way of "migrating"
-from any older gitolite is this:
+So one fool-proof way of "migrating" from any older gitolite is this:
 
 1.  Wipe out the old gitolite, but **carefully**
 
@@ -152,14 +151,18 @@ from any older gitolite is this:
       * rename `~/.gitolite.rc` to something else
       * move the ~/.gitolite/logs` directory somewhere else, then delete `~/.gitolite`
 
-2.  Install gitolite g3; see [quick install and setup][qi] or [install][]
+2.  Read about [presetting][rc-preset] the rc file; if you're using any
+    variables listed as requiring preset, follow those instructions to create
+    your new rc file.
+
+3.  Install gitolite g3; see [quick install and setup][qi] or [install][]
     followed by [setup][].
 
-3.  Make sure your gitolite-admin clone has the correct pubkey for the
+4.  Make sure your gitolite-admin clone has the correct pubkey for the
     administrator in its `keydir` directory, then `git push -f` to the server
     to overwrite the "default" admin repo created by the install.
 
-4.  Handle any errors, look for migration issues, etc., as described in the
+5.  Handle any errors, look for migration issues, etc., as described in the
     links at the top of this page.
 
     This also includes building up your new `~/.gitolite.rc` file.
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 959b085..a0a5af3 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -1,13 +1,13 @@
 # mirroring using gitolite
 
-**WARNING** existing gitolite mirroring users please note: **there are
-significant changes** in syntax and usage compared to g2.  The most important
-change is that `config gitolite.mirror.master` changes to `option
-mirror.master`, and similarly for 'slaves' and redirectOK.  In addition, the
-special value for 'redirectOK' changes from '1' to 'all'.  The second big
-change is the disappearance of the ambiguously named 'keys' (for example
-`gitolite.mirror.hourly` etc., in the old mirroring doc) -- all that
-complexity is yours to handle now :-)
+<font color="red">**WARNING** existing gitolite mirroring users please note:
+**there are [significant changes][g2i-mirroring]** in syntax and usage
+compared to g2.  If you're not the kind who reads documentation before doing
+serious system admin things, well... good luck!</font>
+
+----
+
+[[TOC]]
 
 ----
 
@@ -46,6 +46,11 @@ Gitolite extends this simple notion in the following ways:
     separately.  Files in the admin directory (like log files) are also not
     mirrored.
 
+  * if you ever do a [bypass push][bypass], mirroring will not work.
+    Mirroring checks also will not work -- for example, you can push to a
+    slave, which is not usually a good idea.  So don't bypass gitolite if the
+    repo is mirrored!
+
 ## setting up mirroring
 
 This is in two parts: the initial setup and the rc file, followed by the conf
@@ -180,7 +185,23 @@ Tip: if you want to do this to all the slaves, try this:
 This command can also be run remotely; run `ssh git at host mirror -h` for
 details.
 
-## appendix A: efficiency versus paranoia
+## #HOSTNAME appendix A: HOSTNAME substitution
+
+Wherever gitolite sees the word `%HOSTNAME`, it will replace it with the
+HOSTNAME supplied in the rc file, if one was supplied.  This lets you maintain
+configurations for all servers in one repo, yet havethem act differently on
+different servers, by saying something like:
+
+    subconf "%HOSTNAME/*.conf"
+
+You can use it in other places also, for example:
+
+    RW+     VREF/NAME/subs/%HOSTNAME/       =   @%HOSTNAME-admins
+
+(you still have to define @mars-admins, @phobos-admins, etc., but the actual
+VREF is now one line instead of one for each server!)
+
+## appendix B: efficiency versus paranoia
 
 If you're paranoid enough to use mirrors, you should be paranoid enough to
 set this on each server, despite the possible CPU overhead:
diff --git a/doc/qi.mkd b/doc/qi.mkd
index 9ddbb77..bebc1f1 100644
--- a/doc/qi.mkd
+++ b/doc/qi.mkd
@@ -20,6 +20,9 @@ On your workstation:
 
 ## ASSUMPTIONS
 
+  * this is a fresh install, not a migration from the old gitolite (v1.x,
+    v2.x).
+
   * on the server, your `$PATH` contains `$HOME/bin`.  If you don't like that,
     there are [other install methods][install].
 
diff --git a/doc/special.mkd b/doc/special.mkd
index 9500ce0..a9fc461 100644
--- a/doc/special.mkd
+++ b/doc/special.mkd
@@ -1,5 +1,11 @@
 # special features and setups
 
+----
+
+[[TOC]]
+
+----
+
 ## #writable disabling pushes to take backups
 
 The `writable` command allows you to disable pushes to all repos or just the
diff --git a/doc/user.mkd b/doc/user.mkd
index dda660c..5842fcd 100644
--- a/doc/user.mkd
+++ b/doc/user.mkd
@@ -7,6 +7,12 @@ This document has some text, and a lot of links.  Most of this info *is*
 available in the rest of the documentation, but it's scattered and sparse.
 Collecting all of it, or at least links to it, in one place sounds useful.
 
+----
+
+[[TOC]]
+
+----
+
 ## accessing gitolite
 
 The most common setup is based on ssh, where your admin asks you to send him

commit 7858beb5410fe22e07c2d28c63f56bf3b7f2cf98
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 8 07:25:24 2012 +0530

    (mkdoc) allow internal [[TOC]] again

diff --git a/doc/mkdoc b/doc/mkdoc
index 9cb3ced..d03372b 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -43,7 +43,7 @@ sub main {
     my @save = @ARGV;
     my $css = join("", <DATA>);
 
-    my $mt = "# gitolite master table of contents/index\n";
+    my %ct; # chapter tocs
     my $mf = '';
     my $fh;
 
@@ -53,18 +53,18 @@ sub main {
 
         if (/^(#+) (?:#(\S+) )?(.*)/) {
             if ( length($1) == 1 ) {
-                $mt .= "\n";
-                $mt .= "  * [$3][$b]\n";
+                $ct{$b} .= "\n";
+                $ct{$b} .= "  * [$3][$b]\n";
                 $mf .= "[$b]: $b.html\n";
             } else {
-                $mt .= " " x ( 4 * ( length($1) - 1 ) );
-                $mt .= "  * ";
-                $mt .= (
+                $ct{$b} .= " " x ( 4 * ( length($1) - 1 ) );
+                $ct{$b} .= "  * ";
+                $ct{$b} .= (
                     $2
                     ? "[$3][$2]"
                     : "$3"
                 );
-                $mt .= "\n";
+                $ct{$b} .= "\n";
                 $mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
             }
         }
@@ -89,6 +89,7 @@ sub main {
 
         my $mkt = `cat $mkd`;
         $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
+        $mkt =~ s/^\[\[TOC\]\]/$ct{$b}/mg;
         open($fh, "|-", "$MKD >> ../html/$b.html")
           and print $fh $mkt, "\n\n", $mf
           and close $fh;

commit 55e9b47bd19308b2fc2603f87a929e9d5e47ee35
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Apr 7 19:06:02 2012 +0530

    CpuTime module learns to compute elapsed time

diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
index 2637836..5702da0 100644
--- a/src/lib/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -305,6 +305,9 @@ __DATA__
     # these will run in sequence to modify the input (arguments and environment)
     INPUT                       =>
         [
+            # if you use this, make this the first item in the list
+            # 'CpuTime::input',
+
             # 'Mirroring::input',
         ],
 
diff --git a/src/lib/Gitolite/Triggers/CpuTime.pm b/src/lib/Gitolite/Triggers/CpuTime.pm
index 6ef566e..552bf40 100755
--- a/src/lib/Gitolite/Triggers/CpuTime.pm
+++ b/src/lib/Gitolite/Triggers/CpuTime.pm
@@ -1,26 +1,40 @@
 package Gitolite::Triggers::CpuTime;
 
+use Time::HiRes;
+
 use Gitolite::Rc;
 use Gitolite::Common;
 
 use strict;
 use warnings;
 
+# cpu and elapsed times for gitolite+git operations
+# ----------------------------------------------------------------------
+
+# Ideally, you will (a) write your own code with a different filename so later
+# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
+# to the rc file, and (c) change your rc file to call your program instead.
+
 # ----------------------------------------------------------------------
+my $start_time;
+
+# this trigger is not yet documented; it gets called at the start and does not
+# receive any arguments.
+sub input {
+    _warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$;
+    $start_time = [ Time::HiRes::gettimeofday() ];
+}
 
 sub post_git {
     _warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$;
 
     my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_;
     my ( $utime, $stime, $cutime, $cstime ) = times();
-    gl_log( 'cputime', $utime, $stime, $cutime, $cstime );
+    my $elapsed = Time::HiRes::tv_interval($start_time);
 
-    # now do whatever you want with the data; the following is just an example.
+    gl_log( 'times', $utime, $stime, $cutime, $cstime, $elapsed );
 
-    # Ideally, you will (a) write your own code with a different filename so later
-    # gitolite upgrades won't overwrite your copy, (b) add appropriate variables
-    # to the rc file, and (c) change your rc file to call your program at the
-    # end of the POST_GIT list.
+    # now do whatever you want with the data; the following is just an example.
 
     if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
         my $total = $utime + $cutime + $stime + $cstime;
@@ -32,6 +46,7 @@ sub post_git {
         say2 "perf stats for $verb on repo '$repo':";
         say2 "  user CPU time: " . ( $utime + $cutime );
         say2 "  sys  CPU time: " . ( $stime + $cstime );
+        say2 "   elapsed time: " . $elapsed;
     }
 }
 

commit d3610191d3b7a21d210f62f3a75b512a350a3214
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 6 20:29:05 2012 +0530

    supporting DOS and fake Unix...
    
    I was very, very, tempted to say "sorry; not supported".  Sadly,
    prudence won over juvenile glee...
    
    PS: DOS == dominant operating system

diff --git a/doc/mkdoc b/doc/mkdoc
index 863eebe..9cb3ced 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -7,7 +7,7 @@ my $MKD = "./Markdown.pl";
 use 5.10.0;
 use strict;
 use warnings;
-use lib '../src/Gitolite/Test';
+use lib '../src/lib/Gitolite/Test';
 use Tsh;
 
 $ENV{TSH_ERREXIT} = 1;
diff --git a/install b/install
index f9df125..1cf7853 100755
--- a/install
+++ b/install
@@ -10,7 +10,8 @@ use FindBin;
 
 # meant to be run from the root of the gitolite tree, one level above 'src'
 BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin . "/src"; }
-use lib $ENV{GL_BINDIR};
+BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Common;
 
 =for usage
diff --git a/src/commands/access b/src/commands/access
index 726a346..db3ece0 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/creator b/src/commands/creator
index 94b8cf9..702df73 100755
--- a/src/commands/creator
+++ b/src/commands/creator
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/git-config b/src/commands/git-config
index 2cb8948..293f298 100755
--- a/src/commands/git-config
+++ b/src/commands/git-config
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 use Getopt::Long;
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/help b/src/commands/help
index f954f8e..15539fa 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 
diff --git a/src/commands/info b/src/commands/info
index f68f867..4eca761 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -4,7 +4,7 @@ use warnings;
 
 use Getopt::Long;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/mirror b/src/commands/mirror
index 004ee38..be0d401 100755
--- a/src/commands/mirror
+++ b/src/commands/mirror
@@ -8,7 +8,7 @@ BEGIN {
     delete $ENV{GL_TID};
 }
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/perms b/src/commands/perms
index bbc0e5f..dce271c 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/commands/print-default-rc b/src/commands/print-default-rc
index d877462..79b88c1 100755
--- a/src/commands/print-default-rc
+++ b/src/commands/print-default-rc
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 
 print glrc('default-text');
diff --git a/src/commands/writable b/src/commands/writable
index 2b46fa2..fdaa540 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Easy;
 
 =for usage
diff --git a/src/gitolite b/src/gitolite
index b96c2ee..911eee9 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -36,7 +36,8 @@ written.
 use FindBin;
 
 BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
-use lib $ENV{GL_BINDIR};
+BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 442ae9e..f3cec61 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -6,7 +6,8 @@
 use FindBin;
 
 BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
-use lib $ENV{GL_BINDIR};
+BEGIN { $ENV{GL_LIBDIR} = "$ENV{GL_BINDIR}/lib"; }
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/src/Gitolite/Common.pm b/src/lib/Gitolite/Common.pm
similarity index 100%
rename from src/Gitolite/Common.pm
rename to src/lib/Gitolite/Common.pm
diff --git a/src/Gitolite/Conf.pm b/src/lib/Gitolite/Conf.pm
similarity index 100%
rename from src/Gitolite/Conf.pm
rename to src/lib/Gitolite/Conf.pm
diff --git a/src/Gitolite/Conf/Explode.pm b/src/lib/Gitolite/Conf/Explode.pm
similarity index 100%
rename from src/Gitolite/Conf/Explode.pm
rename to src/lib/Gitolite/Conf/Explode.pm
diff --git a/src/Gitolite/Conf/Load.pm b/src/lib/Gitolite/Conf/Load.pm
similarity index 100%
rename from src/Gitolite/Conf/Load.pm
rename to src/lib/Gitolite/Conf/Load.pm
diff --git a/src/Gitolite/Conf/Store.pm b/src/lib/Gitolite/Conf/Store.pm
similarity index 100%
rename from src/Gitolite/Conf/Store.pm
rename to src/lib/Gitolite/Conf/Store.pm
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/lib/Gitolite/Conf/Sugar.pm
similarity index 100%
rename from src/Gitolite/Conf/Sugar.pm
rename to src/lib/Gitolite/Conf/Sugar.pm
diff --git a/src/Gitolite/Easy.pm b/src/lib/Gitolite/Easy.pm
similarity index 100%
rename from src/Gitolite/Easy.pm
rename to src/lib/Gitolite/Easy.pm
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/lib/Gitolite/Hooks/PostUpdate.pm
similarity index 93%
rename from src/Gitolite/Hooks/PostUpdate.pm
rename to src/lib/Gitolite/Hooks/PostUpdate.pm
index 6effeda..70e23f8 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/lib/Gitolite/Hooks/PostUpdate.pm
@@ -55,10 +55,7 @@ __DATA__
 use strict;
 use warnings;
 
-BEGIN {
-    die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
-}
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Hooks::PostUpdate;
 
 # gitolite post-update hook (only for the admin repo)
diff --git a/src/Gitolite/Hooks/Update.pm b/src/lib/Gitolite/Hooks/Update.pm
similarity index 99%
rename from src/Gitolite/Hooks/Update.pm
rename to src/lib/Gitolite/Hooks/Update.pm
index 7cb17b1..1fd5530 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/lib/Gitolite/Hooks/Update.pm
@@ -152,7 +152,7 @@ __DATA__
 use strict;
 use warnings;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Hooks::Update;
 
 # gitolite update hook
diff --git a/src/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm
similarity index 98%
rename from src/Gitolite/Rc.pm
rename to src/lib/Gitolite/Rc.pm
index 96d638f..2637836 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/lib/Gitolite/Rc.pm
@@ -29,12 +29,14 @@ our %rc;
 
 # ----------------------------------------------------------------------
 
-# variables that could be overridden by the rc file
+# pre-populate some important rc keys
 # ----------------------------------------------------------------------
 
 $rc{GL_BINDIR}    = $ENV{GL_BINDIR};
-$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
+$rc{GL_LIBDIR}    = $ENV{GL_LIBDIR};
 
+# these keys could be overridden by the rc file later
+$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
 $rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
 $rc{LOG_TEMPLATE}  = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
 
diff --git a/src/Gitolite/Setup.pm b/src/lib/Gitolite/Setup.pm
similarity index 100%
rename from src/Gitolite/Setup.pm
rename to src/lib/Gitolite/Setup.pm
diff --git a/src/Gitolite/Test.pm b/src/lib/Gitolite/Test.pm
similarity index 100%
rename from src/Gitolite/Test.pm
rename to src/lib/Gitolite/Test.pm
diff --git a/src/Gitolite/Test/Tsh.pm b/src/lib/Gitolite/Test/Tsh.pm
similarity index 100%
rename from src/Gitolite/Test/Tsh.pm
rename to src/lib/Gitolite/Test/Tsh.pm
diff --git a/src/Gitolite/Triggers.pm b/src/lib/Gitolite/Triggers.pm
similarity index 100%
rename from src/Gitolite/Triggers.pm
rename to src/lib/Gitolite/Triggers.pm
diff --git a/src/Gitolite/Triggers/CpuTime.pm b/src/lib/Gitolite/Triggers/CpuTime.pm
similarity index 100%
rename from src/Gitolite/Triggers/CpuTime.pm
rename to src/lib/Gitolite/Triggers/CpuTime.pm
diff --git a/src/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
similarity index 100%
rename from src/Gitolite/Triggers/Mirroring.pm
rename to src/lib/Gitolite/Triggers/Mirroring.pm
diff --git a/src/Gitolite/Triggers/Shell.pm b/src/lib/Gitolite/Triggers/Shell.pm
similarity index 100%
rename from src/Gitolite/Triggers/Shell.pm
rename to src/lib/Gitolite/Triggers/Shell.pm
diff --git a/src/Gitolite/Triggers/Writable.pm b/src/lib/Gitolite/Triggers/Writable.pm
similarity index 100%
rename from src/Gitolite/Triggers/Writable.pm
rename to src/lib/Gitolite/Triggers/Writable.pm
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 5dc395e..585ea35 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -4,7 +4,7 @@ use warnings;
 
 use File::Temp qw(tempfile);
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 79f9ad8..446b28a 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -5,7 +5,7 @@
 
 use FindBin;
 
-use lib $ENV{GL_BINDIR};
+use lib $ENV{GL_LIBDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
diff --git a/t/0-me-first.t b/t/0-me-first.t
index f131a51..a11dfd0 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # initial smoke tests
diff --git a/t/access.t b/t/access.t
index 047193a..208e96a 100755
--- a/t/access.t
+++ b/t/access.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # test 'gitolite access'
diff --git a/t/all-yall.t b/t/all-yall.t
index 5691cf2..901b1c2 100755
--- a/t/all-yall.t
+++ b/t/all-yall.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # could anything be clearer than "all y'all"?
diff --git a/t/basic.t b/t/basic.t
index 37f9b12..3e8c3aa 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # some more basic tests
diff --git a/t/branch-perms.t b/t/branch-perms.t
index 4e4c5ff..64b2fcb 100755
--- a/t/branch-perms.t
+++ b/t/branch-perms.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # branch permissions test
diff --git a/t/daemon-gitweb-via-perms.t b/t/daemon-gitweb-via-perms.t
index 33b8e90..0d19371 100755
--- a/t/daemon-gitweb-via-perms.t
+++ b/t/daemon-gitweb-via-perms.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # basic tests
diff --git a/t/deleg-1.t b/t/deleg-1.t
index 5f4d6e5..933a17f 100755
--- a/t/deleg-1.t
+++ b/t/deleg-1.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # delegation tests -- part 1
diff --git a/t/deleg-2.t b/t/deleg-2.t
index cf55972..ccf9cc5 100755
--- a/t/deleg-2.t
+++ b/t/deleg-2.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # delegation tests -- part 2
diff --git a/t/deny-create.t b/t/deny-create.t
index 67451ea..a4b7e4f 100755
--- a/t/deny-create.t
+++ b/t/deny-create.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # deny-create, the RW.*C flag
diff --git a/t/deny-rules-2.t b/t/deny-rules-2.t
index d59f144..0ca15fe 100755
--- a/t/deny-rules-2.t
+++ b/t/deny-rules-2.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # more on deny-rules
diff --git a/t/deny-rules.t b/t/deny-rules.t
index 9726ac7..c0e7cbb 100755
--- a/t/deny-rules.t
+++ b/t/deny-rules.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # deny rules
diff --git a/t/easy.t b/t/easy.t
index 9e4bfb4..c626602 100755
--- a/t/easy.t
+++ b/t/easy.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Easy;
 use Gitolite::Test;
 # put this after ::Easy because it chdirs away from where you were and the
diff --git a/t/git-config.t b/t/git-config.t
index 83ad7f4..437e1cc 100755
--- a/t/git-config.t
+++ b/t/git-config.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # git config settings
diff --git a/t/hostname.t b/t/hostname.t
index a6cbde2..dfb8885 100755
--- a/t/hostname.t
+++ b/t/hostname.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # %HOSTNAME tests
diff --git a/t/include-subconf.t b/t/include-subconf.t
index a3d2a57..6bdff81 100755
--- a/t/include-subconf.t
+++ b/t/include-subconf.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # include and subconf
diff --git a/t/info.t b/t/info.t
index ce54027..deaacb8 100755
--- a/t/info.t
+++ b/t/info.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # the info command
diff --git a/t/invalid-refnames-filenames.t b/t/invalid-refnames-filenames.t
index 7cd50a2..d3a3065 100755
--- a/t/invalid-refnames-filenames.t
+++ b/t/invalid-refnames-filenames.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # invalid refnames
diff --git a/t/listers.t b/t/listers.t
index b824dd8..1f7c7eb 100755
--- a/t/listers.t
+++ b/t/listers.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # the various list-* commands
diff --git a/t/merge-check.t b/t/merge-check.t
index 33a5356..b2642ed 100755
--- a/t/merge-check.t
+++ b/t/merge-check.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # merge check -- the M flag
diff --git a/t/mirror-test b/t/mirror-test
index 8793083..6f605b9 100755
--- a/t/mirror-test
+++ b/t/mirror-test
@@ -10,7 +10,7 @@ use warnings;
 $ENV{TSH_ERREXIT} = 1;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 use Cwd;
 my $workdir = getcwd();
diff --git a/t/partial-copy.t b/t/partial-copy.t
index 20a2cfe..6b8dfdc 100755
--- a/t/partial-copy.t
+++ b/t/partial-copy.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # test script for partial copy feature
diff --git a/t/perm-roles.t b/t/perm-roles.t
index 6264644..a1c4f85 100755
--- a/t/perm-roles.t
+++ b/t/perm-roles.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # permissions using role names
diff --git a/t/perms-groups.t b/t/perms-groups.t
index 59c5fb5..a4b6839 100755
--- a/t/perms-groups.t
+++ b/t/perms-groups.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # assigning roles to groups instead of users
diff --git a/t/personal-branches.t b/t/personal-branches.t
index 4c53537..8a08128 100755
--- a/t/personal-branches.t
+++ b/t/personal-branches.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # personal branches
diff --git a/t/reset b/t/reset
index 7805940..502de2b 100755
--- a/t/reset
+++ b/t/reset
@@ -7,7 +7,7 @@ BEGIN {
 }
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 use Cwd;
diff --git a/t/rule-seq.t b/t/rule-seq.t
index b7e3386..0d97558 100755
--- a/t/rule-seq.t
+++ b/t/rule-seq.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # rule sequence
diff --git a/t/sequence.t b/t/sequence.t
index d85f420..ef11689 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # uhh, seems to be another rule sequence test
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index d941922..9a897ca 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # testing the (separate) authkeys handler
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 2ace6ca..ebed2d2 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Common;
 use Gitolite::Test;
 
diff --git a/t/vrefs-1.t b/t/vrefs-1.t
index d7ecaa4..bd5086b 100755
--- a/t/vrefs-1.t
+++ b/t/vrefs-1.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # VREFs - part 1
diff --git a/t/vrefs-2.t b/t/vrefs-2.t
index eb9d43a..6c53341 100755
--- a/t/vrefs-2.t
+++ b/t/vrefs-2.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # VREFs - part 2
diff --git a/t/wild-1.t b/t/wild-1.t
index 2824e37..c957798 100755
--- a/t/wild-1.t
+++ b/t/wild-1.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # wild repos - part 1
diff --git a/t/wild-2.t b/t/wild-2.t
index 0e49538..7f5d338 100755
--- a/t/wild-2.t
+++ b/t/wild-2.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 # wild repos - part 2
diff --git a/t/writable.t b/t/writable.t
index 25d93ec..e778414 100755
--- a/t/writable.t
+++ b/t/writable.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 use Cwd;
 my $workdir = getcwd();
diff --git a/t/z-end.t b/t/z-end.t
index 25edbd9..6c98fe4 100755
--- a/t/z-end.t
+++ b/t/z-end.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "src";
+use lib "src/lib";
 use Gitolite::Test;
 
 try "plan 1; cd $ENV{PWD}; git status -s -uno; !/./ or die" or die "dirty tree";

commit 6e5c9954fd96ba44a5c7f067f23a7f178edcfacb
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 6 07:01:56 2012 +0530

    upgrade instructions forgot about the VERSION file update!

diff --git a/doc/install.mkd b/doc/install.mkd
index 1578161..368c719 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -111,10 +111,10 @@ Creating a symlink doesn't need a separate program but 'install' also runs
 
 ## upgrading
 
-Just put the new version on top of wherever you kept the old one.  That's it.
-
-If you feel it should require a little more effort, pretend I said "you have
-to then run `gitolite setup`".  Won't hurt...
+  * Update your clone of the gitolite source.
+  * Repeat the install command you used earlier (make sure you use the same
+    arguments as before).
+  * Run `gitolite setup` just in case the hooks changed.
 
 ## packaging gitolite
 

commit 057506b73f094e18682a3f5784eb37761ee83a8e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 6 00:06:17 2012 +0530

    remove quotes around option values
    
    for example, this now works (it used to save the quotes also)
    
        option mirror.master = "ilh-95"

diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index 30261aa..a5bda56 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -58,6 +58,7 @@ sub parse {
             }
         } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
             my ( $key, $value ) = ( $1, $2 );
+            $value =~ s/^['"](.*)["']$/$1/;
             my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
             push @validkeys, "gitolite-options\\..*";
             my @matched = grep { $key =~ /^$_$/ } @validkeys;
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index 96e97db..79f9ad8 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -24,7 +24,6 @@ for my $pr (@$lpr) {
     while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
         if ( $value ne "" ) {
-            $value =~ s/^['"](.*)["']$/$1/;
             $value =~ s/%GL_REPO/$pr/g;
             system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {

commit e1c7e546aad445763f581d9c31d9df98cd077737
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Apr 6 06:27:34 2012 +0530

    cpu-time command -> CpuTime trigger module...
    
    ...now that triggers are not restricted to external programs and can be
    perl code called by gitolite-shell (thus in the same PID), there's no
    need to compute and pass along the times() array.
    
    This also changes the arguments to POST_GIT; they're now the same as
    PRE_GIT's.

diff --git a/doc/g2alt.mkd b/doc/g2alt.mkd
index a578805..ac10c71 100644
--- a/doc/g2alt.mkd
+++ b/doc/g2alt.mkd
@@ -5,6 +5,5 @@ alternatives exist.
 
 ### gl-time for performance measurement
 
-Take a look at the 'cpu-time' program that you can set to run from the
-`POST_GIT` trigger.  Just set it to run as the last program in that sequence
-so it covers all previous programs.
+Take a look at the 'CpuTime' trigger module shipped.  Comments within, and
+comments in the default rc file that contain the word "cpu", should be useful.
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index 7583b66..095cc3b 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -36,9 +36,8 @@ The `PRE_GIT` triggers are:
 
 The `POST_GIT` triggers are:
 
-  * cpu-time -- post-git triggers, if you check the [triggers][] doc, receive
-    4 CPU time numbers from the main shell program.  Treat this code as sample
-    and do do with them as you please to do with as you please.
+  * CpuTime, which is only a sample because it only prints the CPU times data.
+    In reality you will want to do something else with it.
 
 The `POST_COMPILE` triggers are:
 
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 4517564..5ae8609 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -78,7 +78,8 @@ description of when the trigger runs:
       * the ref being updated (e.g., 'refs/heads/master')
       * result (see above)
 
-  * `PRE_GIT` runs just before running the git command.  Arguments:
+  * `PRE_GIT` and `POST_GIT` run just before and after the git command.
+    Arguments:
       * repo
       * user
       * 'R' or 'W'
@@ -86,17 +87,6 @@ description of when the trigger runs:
       * the git command ('git-receive-pack', 'git-upload-pack', or
         'git-upload-archive') being invoked.
 
-  * `POST_GIT` runs after the git command returns.  Arguments:
-      * repo
-      * user
-      * 'R' or 'W'
-      * 'any'
-      * the git command ('git-receive-pack', 'git-upload-pack', or
-
-    These are followed by the output of the perl function "times" (i.e., 4 CPU
-    times: user, system, cumulative user, cumulative system) so that's 9
-    arguments in total
-
   * `PRE_CREATE` and `POST_CREATE` run just before and after a new "[wild][]"
     repo is created by user action.  Arguments:
       * repo
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index c7390eb..96d638f 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -259,9 +259,9 @@ __DATA__
 
     # settings used by external programs; uncomment and change as needed.  You
     # can add your own variables for use in your own external programs; take a
-    # look at the cpu-time and desc commands for perl and shell samples.
+    # look at the info and desc commands for perl and shell samples.
 
-    # used by the cpu-time command
+    # used by the CpuTime trigger
     # DISPLAY_CPU_TIME          =>  1,
     # CPU_TIME_WARN_LIMIT       =>  0.1,
     # used by the desc command
@@ -335,9 +335,10 @@ __DATA__
     # these will run in sequence after the git command returns
     POST_GIT                    =>
         [
-            # if you use this, make this the last item in the list
-            # 'cpu-time',
             # 'Mirroring::post_git',
+
+            # if you use this, make this the last item in the list
+            # 'CpuTime::post_git',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/Gitolite/Triggers/CpuTime.pm b/src/Gitolite/Triggers/CpuTime.pm
new file mode 100755
index 0000000..6ef566e
--- /dev/null
+++ b/src/Gitolite/Triggers/CpuTime.pm
@@ -0,0 +1,38 @@
+package Gitolite::Triggers::CpuTime;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub post_git {
+    _warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$;
+
+    my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_;
+    my ( $utime, $stime, $cutime, $cstime ) = times();
+    gl_log( 'cputime', $utime, $stime, $cutime, $cstime );
+
+    # now do whatever you want with the data; the following is just an example.
+
+    # Ideally, you will (a) write your own code with a different filename so later
+    # gitolite upgrades won't overwrite your copy, (b) add appropriate variables
+    # to the rc file, and (c) change your rc file to call your program at the
+    # end of the POST_GIT list.
+
+    if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
+        my $total = $utime + $cutime + $stime + $cstime;
+        # some code to send an email or whatever...
+        say2 "limit = $limit, actual = $total" if $total > $limit;
+    }
+
+    if ( $rc{DISPLAY_CPU_TIME} ) {
+        say2 "perf stats for $verb on repo '$repo':";
+        say2 "  user CPU time: " . ( $utime + $cutime );
+        say2 "  sys  CPU time: " . ( $stime + $cstime );
+    }
+}
+
+1;
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 4d201c7..442ae9e 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -106,7 +106,7 @@ sub main {
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
     _system( "git", "shell", "-c", "$verb $repodir" );
-    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb, times() );
+    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
 }
 
 # ----------------------------------------------------------------------
diff --git a/src/triggers/cpu-time b/src/triggers/cpu-time
deleted file mode 100755
index c7ca8f7..0000000
--- a/src/triggers/cpu-time
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-
-use lib $ENV{GL_BINDIR};
-use Gitolite::Easy;
-
-my ( $trigger, $repo, $user, $aa, $ref, $verb, $utime, $stime, $cutime, $cstime ) = @ARGV;
-
-# now do whatever you want with this data; the following is just an example.
-
-# Ideally, you will (a) write your own code with a different filename so later
-# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
-# to the rc file, and (c) change your rc file to call your program at the end
-# of the POST_GIT list.
-
-if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
-    my $total = $utime + $cutime + $stime + $cstime;
-    # some code to send an email or whatever...
-    say2 "limit = $limit, actual = $total" if $total > $limit;
-}
-
-if ( $rc{DISPLAY_CPU_TIME} ) {
-    say2 "perf stats for $verb on repo '$repo':";
-    say2 "  user CPU time: " . ( $utime + $cutime );
-    say2 "  sys  CPU time: " . ( $stime + $cstime );
-}
-
diff --git a/t/mirror-test-rc b/t/mirror-test-rc
index c5752bb..23cd108 100644
--- a/t/mirror-test-rc
+++ b/t/mirror-test-rc
@@ -7,6 +7,8 @@
 # (Tip: perl allows a comma after the last item in a list also!)
 
 %RC = (
+    # if you're using mirroring, you need a hostname.  This is *one* simple
+    # word, not a full domain name.  See documentation if in doubt
     HOSTNAME                    =>  '%HOSTNAME',
     UMASK                       =>  0077,
     GIT_CONFIG_KEYS             =>  '',
@@ -16,9 +18,9 @@
 
     # settings used by external programs; uncomment and change as needed.  You
     # can add your own variables for use in your own external programs; take a
-    # look at the cpu-time and desc commands for perl and shell samples.
+    # look at the info and desc commands for perl and shell samples.
 
-    # used by the cpu-time command
+    # used by the CpuTime trigger
     # DISPLAY_CPU_TIME          =>  1,
     # CPU_TIME_WARN_LIMIT       =>  0.1,
     # used by the desc command
@@ -70,7 +72,7 @@
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence at the start, before a git operation has started
+    # these will run in sequence just before the actual git command is invoked
     PRE_GIT                     =>
         [
             # if you use this, make this the first item in the list
@@ -89,12 +91,19 @@
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence at the end, after a git operation has ended
+    # these will run in sequence after the git command returns
     POST_GIT                    =>
         [
-            # if you use this, make this the last item in the list
-            # 'cpu-time',
             'Mirroring::post_git',
+
+            # if you use this, make this the last item in the list
+            # 'CpuTime::post_git',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence before a new wild repo is created
+    PRE_CREATE                  =>
+        [
         ],
 
     # comment out or uncomment as needed

commit de40461d9a4ff7c115cc4c83371d01806f662a77
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 5 21:31:23 2012 +0530

    document overhaul
    
      - explicit 'list' gives way to mindmap, ...
      - 'fm2mt.pl' to produce master-toc.mkd from the mindmap
      - mkdoc no longer ignores master-toc.mkd, calls fm2mt.pl itself
    
    and LOTS of changes to the actual docs

diff --git a/doc/WARNINGS.mkd b/doc/WARNINGS.mkd
new file mode 100644
index 0000000..dc364de
--- /dev/null
+++ b/doc/WARNINGS.mkd
@@ -0,0 +1,72 @@
+# WARNINGS
+
+Gitolite does NOT like it if you fiddle with files, directories, permissions,
+etc., on the server except as directed in the documentation.  Gitolite also
+expects all the directories and files it manages/uses to be owned by the
+hosting user and not have strange permissions and ownerships.
+
+Very few people have fallen foul of this, but the ones who *did* became very
+obnoxious about it, hence this warning.
+
+Gitolite depends on several system-installed packages: openssh, git, perl, sh
+being the main ones.  They should all be configured sensibly and with most of
+the normal defaults.  (For example, if your sshd config says the authorized
+keys file should be placed in some directory other than the default, expect
+trouble).
+
+----
+
+For the entertainment of the sensible majority, and as a way of thanking all
+of you, here are some examples of requests (demands in some cases) I have
+received over the last couple of years.
+
+  * deleting environment variables copied from client session
+
+    demand: add code to delete certain environment variables at startup
+    because "the openssh servers in the linux distribution that [he] use[s],
+    are configured to copy `GIT_*` variables to the remote session".
+
+    This is wrong on so many levels it's almost plonk-able!
+
+  * using `cp` instead of `ln`
+
+    Guy has an NTFS file system mounted on Linux.  So... no symlinks (an NTFS
+    file system on Windows works fine because msysgit/cygwin manage to
+    *simulate* them.  NTFS mounted on Linux won't do that!)
+
+    He wanted all the symlink stuff to be replaced by copies.
+
+    No. Way.
+
+  * non-bare repos on the server
+
+    Some guy gave me a complicated spiel about git-svn not liking bare repos
+    or whatever.  I tuned off at the first mention of those 3 letters so I
+    don't really know what the actual problem was.
+
+    But it doesn't matter.  Even if someone (Ralf H) had not chipped in with a
+    workable solution, I still would not do it.  A server repo should be bare.
+    Period.
+
+  * incomplete ownership of `GL_REPO_BASE`
+
+    This guy had a repo-base directory where not all of the files were owned
+    by the git user.  As a result, some of the hooks did not get created.  He
+    claimed my code should detect OS-permissions issues while it's doing its
+    stuff.
+
+    No.  I refuse to have the code constantly look over its shoulder making
+    sure fundamental assumptions are being met.
+
+  * empty template directory
+
+    (See man git-init for what a template directory is).
+
+    The same guy with the environment variables had an empty template
+    directory because he "does not like to have sample hooks in every
+    repository".  So naturally, the hooks directory does not get created when
+    you run a `git init`.  He expects gitolite to compensate for it.
+
+    Granted, it's only a 1-line change.  But again, this falls under
+    "constantly looking over your shoulder to double check fundamental
+    assumptions".  Where does it end?
diff --git a/doc/add.mkd b/doc/add.mkd
deleted file mode 100644
index 21d1a61..0000000
--- a/doc/add.mkd
+++ /dev/null
@@ -1,34 +0,0 @@
-# adding users and repos
-
-Do NOT add repos directly on the server.  Clone the 'gitolite-admin' repo to
-your workstation, make changes to it, then add, commit, and push.  When the
-push hits the server, the server "acts" upon your changes.
-
-Full documentation on the conf file is [here][conf].
-
-Here's a sample sequence, on your workstation, after your install is done
-
-    git clone git at host:gitolite-admin
-    cd gitolite-admin
-    vi conf/gitolite.conf
-
-    # now add lines like these:
-        repo foo
-            RW+ =   me
-            RW  =   alice
-            R   =   wally
-    # now save the file and add it
-    git add conf
-
-    # add a couple of users; get their pubkeys by email or something, then:
-    cp /some/where/alice.pub keydir
-    cp /else/where/wally.pub keydir
-    git add keydir
-
-    # now commit and push
-    git commit -m 'added repo foo'
-    git push
-
-    # at this point gitolite will create the new repo 'foo' (if it did not
-    # already exist) then update the authorized keys file to include alice and
-    # wally's pubkeys
diff --git a/doc/admin.mkd b/doc/admin.mkd
new file mode 100644
index 0000000..60f6370
--- /dev/null
+++ b/doc/admin.mkd
@@ -0,0 +1,42 @@
+# gitolite administration
+
+## #server server-side administration
+
+The following activities require command line access to the server
+
+  * changing anything in the [rc][] file
+  * installing custom [hooks][], whether to all repos or just some repos
+  * moving [existing][] (bare) repos into gitolite control
+
+Please read the [WARNINGS][] page first.
+
+## #conf access control (the gitolite.conf file)
+
+Most of gitolite's power is in the conf/gitolite.conf file, which specifies
+detailed access control for repos.
+
+### #confex example of a conf file
+
+Here is an example of a simple conf/gitolite.conf file.
+
+        @staff              =   dilbert alice           # line 1
+        @projects           =   foo bar                 # line 2
+
+        repo @projects baz                              # line 3
+            RW+             =   @staff                  # line 4
+            -       master  =   ashok                   # line 5
+            RW              =   ashok                   # line 6
+            R               =   wally                   # line 7
+
+            config hooks.emailprefix = '[%GL_REPO] '    # line 8
+
+Use the following links to learn more:
+
+  * the basic [syntax][] -- comments, whitespace, include files, etc.
+  * defining [groups][], as in lines 1 and 2
+  * adding and removing [users][]
+  * adding and removing [repos][], as in line 3
+  * defining access [rules][], as in lines 4, 5, 6, and 7
+  * gitolite [options][]
+  * [git config][git-config] keys and values, as in line 8
+  * ["wild"][wild] repos -- ad hoc, user-created, repos
diff --git a/doc/conf.mkd b/doc/conf.mkd
deleted file mode 100644
index 15c3756..0000000
--- a/doc/conf.mkd
+++ /dev/null
@@ -1,31 +0,0 @@
-# the gitolite.conf file
-
-This file is the crux of all of gitolite's access control.  The basic syntax
-is very simple.
-
-Note: `<user>+` means one or more user or user group names, `<repo>+` means
-one or more repo or repo group names, and `<refex>*` means zero or more
-refexes.
-
-  * [group][group] definitions (optional, for convenience)
-
-        @<group> = <user>+
-        @<group> = <repo>+
-
-  * [repo][repo] definitions and access [rules][]
-
-        repo <repo>+
-            <perm>  <refex>*    =   <user>+
-            # one or more such lines
-
-  * [gitolite options][options] that apply to the repo(s) in the last
-    "repo ..." line, for example:
-
-            option deny-rules = 1
-
-  * [git config][git-config] keys and values that also apply to the last named
-    repo(s), for example:
-
-            config hooks.emailprefix = '[%GL_REPO] '
-
-In addition, you can also have [include][] statements.
diff --git a/doc/cust.mkd b/doc/cust.mkd
index e57f0a7..e4dbd07 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -2,17 +2,20 @@
 
 Much of gitolite (g3)'s functionality comes from programs and scripts that are
 not considered "core".  This keeps the core simpler, and allows you to enhance
-gitolite for your own purposes without too much fuss.
+gitolite for your own purposes without too much fuss.  (As an extreme example,
+even mirroring is not in core now!)
+
+(Also, please see the [developer notes][dev-notes] page).
 
 ## types of non-core programs
 
 There are 5 basic types of non-core programs.
 
-  * *commands* can be run from the shell command line.  Those listed in the
-    COMMANDS hash of the rc file can also be run remotely.
+  * *commands* can be run from the shell command line.  Among those, the ones
+    listed in the COMMANDS hash of the rc file can also be run remotely.
   * *hooks* are standard git hooks; see below.
   * *sugar scripts* change the conf language for your convenience.  The word
-    sugar comes from "syntactics sugar".
+    sugar comes from "syntactic sugar".
   * *triggers* are to gitolite what hooks are to git.  I just chose a
     different name to avoid confusion and constant disambiguation in the docs.
   * **VREFs** are extensions to the access control check part of gitolite.
@@ -22,7 +25,7 @@ some description of each.
 
 ## #commands gitolite "commands"
 
-Gitolite comes with several commands that users can run.  Remote user run the
+Gitolite comes with several commands that users can run.  Remote users run the
 commands by saying:
 
     ssh git at host command-name [args...]
@@ -56,8 +59,8 @@ The rest is between you and 'man githooks' :-)
 ## #sugar syntactic sugar
 
 Sugar scripts help you change the perceived syntax of the conf language.  The
-base syntax of the language is as described [here][conf], so sugar scripts
-take something *else* and convert it into that.
+base syntax of the language is very simple, so sugar scripts take something
+*else* and convert it into that.
 
 That way, the admin sees additional features (like allowing continuation
 lines), while the parser in the core gitolite engine does not change.
diff --git a/doc/deleg.mkd b/doc/deleg.mkd
new file mode 100644
index 0000000..5855c89
--- /dev/null
+++ b/doc/deleg.mkd
@@ -0,0 +1,114 @@
+# delegating access control responsibilities
+
+Delegation allows you to divide up a large conf file into smaller groups of
+repos (called **subconf**s) and hand over responsibility to manage them to
+**sub-admin**s.  Gitolite can prevent one sub-admin from being able to set
+access rules for any other sub-admin's repos.
+
+Delegation is achieved by combining two gitolite features: [subconf][] and the
+[NAME VREF][NAME].
+
+To understand delegation, read both those links then come back to this
+example.
+
+## example
+
+    @webbrowsers        = firefox lynx browsers/..*
+    @webservers         = apache nginx servers/..*
+    @malwares           = conficker storm ms/..*
+        # side note: if anyone objects, we claim ms stands for "metasploit" ;-)
+
+    # the admin repo access was probably like this to start with:
+    repo gitolite-admin
+        RW+                                     = sitaram
+    # now add these lines to the config for the admin repo
+        RW                                      = alice bob mallory
+        RW  VREF/NAME/conf/subs/webbrowsers     = alice
+        RW  VREF/NAME/conf/subs/webservers      = bob
+        RW  VREF/NAME/conf/subs/malwares        = mallory
+        -   VREF/NAME/                          = alice bob mallory
+
+Finally, you tell gitolite to pull in these files using the "subconf" command
+
+    subconf "subs/*.conf"
+
+And that's it.
+
+## #subconf the subconf command
+
+Subconf is exactly like the include command in syntax:
+
+    subconf "foo/bar.conf"
+
+but while reading the included file, it sets a "subconf name" of "foo".
+
+When a subconf name is in effect, there are some restrictions on what repos
+can be managed.
+
+For example, in the include file in the above example, you can only have
+"repo" lines for:
+
+  * a repo called "foo"
+  * a group called "@foo" defined outside the include file
+  * a member of a group called "@foo" (again, defined outside)
+  * a repo that matches a member of a group called "@foo" if that member is a
+    regular expression pattern
+
+Here's an example.  If the main conf file contains
+
+    @foo    =   aa bb cc/..*
+
+then the subconf can only accept repo statements that refer to 'foo', '@foo',
+'aa', 'bb', or any repo whose name starts with 'cc/'.
+
+### how the "subconf name" is derived
+
+For subconf lines that look just like include statements, i.e.,
+
+    subconf "foo/bar.conf"
+    subconf "frob/*.conf"
+        # assume frob has files aa.conf, bb.conf
+
+the subconf name as each file is being processed is the base name of the file.
+This means it would be "bar" for the first line, "aa" when processing
+"frob/aa.conf", and "bb" when processing "frob/bb.conf".
+
+A variation of subconf exists that can explicitly state the subconf name:
+
+    subconf foo "frob/*.conf"
+
+In this variation, regardless of what file in "frob/" is being read, the
+subconf name in effect is "foo".
+
+## security notes
+
+### group names
+
+You can use "@group"s defined in the main config file but do not attempt to
+redefine or extend them in your own subconf file.  If you must extend a group
+(say `@foo`) defined in the main config file, do this:
+
+    @myfoo  =   @foo
+    # now do whatever you want with @myfoo
+
+Group names you define in your subconf will not clash even if the exact same
+name is used in another subconf file, so you need not worry about that.
+
+### delegating pubkeys
+
+Short answer: not gonna happen.
+
+The delegation feature is meant only for access control rules, not pubkeys.
+Adding/removing pubkeys is a much more significant event than changing branch
+level permissions for people already on staff, and only the main admin should
+be allowed to do it.
+
+Gitolite's "userids" all live in the same namespace.  This is unlikely to
+change, so please don't ask -- it gets real complicated to do otherwise.
+Allowing sub-admins to add users means username collisions, which also means
+security problems (admin-A creates a pubkey for Admin-B, thus gaining access
+to all of Admin-B's stuff).
+
+If you feel the need to delegate even that, please just go the whole hog and
+give them separate gitolite instances (i.e., running under different gitolite
+hosting users)!
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 9361fa4..3b1a6f1 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -3,9 +3,6 @@
 Gitolite has a huge bunch of existing features that gradually need to moved
 over.  Plus you may want to write your own programs to interact with it.
 
-Hints for developers wishing to help migrate features over from g2 are
-[here][dev-hints].
-
 Here are some random notes on developing hooks, commands, triggers, and sugar
 scripts.
 
@@ -20,15 +17,15 @@ In general, the following environment variables should always be available:
 Commands invoked by a remote client will also have `GL_USER` set.  Hooks will
 have `GL_REPO` also set.
 
-Finally, note that triggers get a lot of relevant information as arguments;
-see [here][triggers] for details.
+Finally, note that triggers get a lot of relevant information from gitolite as
+arguments; see [here][triggers] for details.
 
 ## APIs
 
 ### the shell API
 
 The following commands exist to help you write shell scripts that interact
-easily with gitolite.  Each of them responds to `h` so please run that for
+easily with gitolite.  Each of them responds to `-h` so please run that for
 more info.
 
   * `gitolite access` to check access rights given repo, user, type of access
@@ -38,7 +35,7 @@ more info.
     src/commands/desc
 
   * `gitolite git-config` to check gitolite options or git config variables
-    directly from gitolite's "compiled output, (i.e., without looking at the
+    directly from gitolite's "compiled" output, (i.e., without looking at the
     actual `repo.git/config` file or using the `git config` command).  Example
     use: none yet
 
@@ -53,16 +50,18 @@ perl API module) for ideas.
 ...is implemented by Gitolite::Easy; the comments in src/Gitolite/Easy.pm
 serve as documentation.
 
-## your own hooks
+## writing your own...
+
+### ...hooks
 
-### anything but the update hook
+#### anything but the update hook
 
 If you want to add your own hook, it's easy as long as it's not the 'update'
 hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
 
 The rest is between you and 'man githooks' :-)
 
-### update hook
+#### update hook
 
 If you want to add additional `update` hook functionality, do this:
 
@@ -83,7 +82,7 @@ Note: a normal update hook expects 3 arguments (ref, old SHA, new SHA).  A
 VREF will get those three, followed by at least 4 more.  Your VREF should just
 ignore the extra args.
 
-## your own commands
+### ...commands
 
 You can add your own commands.  You can run them on the server (example,
 `gitolite access`).  Then you can enable certain commands to be allowed to run
@@ -94,19 +93,15 @@ receive the arguments you append.  In addition, the env var `GL_USER` is
 available if it is being run remotely.  src/commands/desc is the best example
 at present.
 
-## your own trigger programs
+### ...trigger programs
 
-Trigger programs are just commands whose names have been added to the
-appropriate list in the [rc][] file.  Triggers get specific arguments
-depending on when they are called; see [here][triggers] for details.
+Trigger programs run at specific points in gitolite's execution, with specific
+arguments being passed to them.  See the [triggers][] page for details.
 
 You can write programs that are both manually runnable as well as callable by
 trigger events, especially if they don't *need* any arguments.
 
-Look in the distributed [rc][] file for example programs; at this point there
-aren't many.
-
-## your own "sugar"
+### ..."sugar"
 
 Syntactic sugar helpers are NOT complete, standalone, programs.  They must
 include a perl sub called `sugar_script` that takes in a listref, and returns
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
index 29ae450..08b84f9 100644
--- a/doc/dev-status.mkd
+++ b/doc/dev-status.mkd
@@ -2,20 +2,19 @@
 
 Not yet done (will be tackled in this order unless someone asks):
 
-  * detailed documentation for new features
-  * querying the outside world for group info (see gl-get-membership-program
-    in g2)
-  * mirroring
+  * svnserve (someone is testing it)
   * pulling in documentation for things that are the same in g2
-  * "unrestricted arguments" for some ADCs (like git-annexe)
   * smart http
-  * special features (no-create-repos, shell-access, gl-all-read-all, etc)
+  * special features (no-create-repos, gl-all-read-all, etc)
+  * mechanism for ADCs using unchecked arguments -- this is not just a matter
+    of writing it; I have to think about *how* it will be done.  (AFAIK the
+    only tool affected is git-annexe)
 
 Help needed:
 
   * I'd like distro packagers to play with it and help with migration advice
     for distro-upgrades
-  * [rsync][pw2], htpasswd
+  * rsync, htpasswd
   * git-annexe support (but this has a pre-requisite in the previous list)
 
 Won't be done unless someone asks (saw no evidence that anyone used them in g2
@@ -30,6 +29,7 @@ Done:
 
   * core code
   * test suite
-  * some documentation
+  * mirroring
+  * most documentation
   * distro packaging instructions
   * migration advice for common cases
diff --git a/doc/emergencies.mkd b/doc/emergencies.mkd
new file mode 100644
index 0000000..60b1cb0
--- /dev/null
+++ b/doc/emergencies.mkd
@@ -0,0 +1,190 @@
+# help for emergencies
+
+----
+
+"Don't Panic!"
+
+----
+
+## #lost-key lost admin key/access
+
+If you lost your gitolite **admin** key or access, here's what you do.  We'll
+assume your username is 'alice'.
+
+  * Make yourself a new keypair and copy the public key to the server as
+    'alice.pub'.
+
+  * Log on to the server, and run `gitolite setup -pk alice.pub`.
+
+That's it; the new alice.pub file replaces whatever existed in the repo
+before.
+
+## #bypass bypassing gitolite
+
+You may have lost access because of a conf file error, in which case the above
+trick won't help.  What you want is to make changes to the repo (or perhaps
+just rewind) and push that.  Here's how to do that:
+
+  * Log on to the server.
+
+  * Clone the admin repo using the full path: `git clone
+    $HOME/repositories/gitolite-admin.git temp`.
+
+  * Make whatever changes you want -- add/replace a key, 'git revert' or 'git
+    reset --hard' to an older commit, etc.  Anything you need to fix the
+    problem, really.
+
+  * Run `gitolite push` (or possibly `gitolite push -f`).  Note that's
+    'gitolite push', not 'git push'.
+
+<font color="red">
+**NOTE**: gitolite does **no access checking** when you do this!
+</font>
+
+## #clean cleaning out a botched install
+
+If you've read the [files involved in gitolite][files] page, you probably know
+the answer, but here's a list of files you should blow away.
+
+  * **gitolite sources** -- can be found by running `which gitolite`.  If it's
+    a symlink, go to its target directory.
+
+  * **gitolite admin directory** -- `$HOME/.gitolite`.  Save the 'logs'
+    directory if you want to preserve them for any reason.
+
+  * **the rc file** -- `$HOME/.gitolite.rc`.  If you made any changes to it
+    you can save it as some other name instead of deleting it.
+
+  * **the gitolite-admin repo** -- `$HOME/repositories/gitolite-admin.git`.
+    You can clone it somewhere to save it before blowing it away if you wish.
+
+  * **git repositories** -- `$HOME/repositories`.  The install process will
+    not touch any existing repos except 'gitolite-admin.git', so you do not
+    have to blow away (or move) your work repos to fix a botched install.
+
+    Only when you update the conf to include those repos and push the changes
+    will those repos be touched.  And even then all that happens is that the
+    update hook, if any, is replaced with gitolite's own hook.
+
+  * **ssh stuff** -- exercise caution when doing this, but in general it
+    should be safe to delete all lines between the "gitolite start" and
+    "gitolite end" markers in `$HOME/.ssh/authorized_keys`.
+
+    Gitolite does not touch any other files in the ssh directory.
+
+## #ce common errors
+
+  * `WARNING: keydir/<yourname>.pub duplicates a non-gitolite key, sshd will ignore it`
+
+    You used a key that is already set to give you shell access.  You cannot
+    use the same key to get shell access as well as access gitolite repos.
+
+    Solution: use a different keypair for gitolite.  There's a slightly longer
+    discussion in the [setup][] page.  Also see [why bypassing causes a
+    problem][ybpfail] and both the documents in [ssh][] for background.
+
+  * `Empty compile time value given to use lib at hooks/update line 6`
+
+    (followed by `Can't locate Gitolite/Hooks/Update.pm in @INC` a couple of
+    lines later).
+
+    You're bypassing gitolite.  You cloned the repo using the full path (i.e.,
+    including the `repositories/` prefix), either directly on the server, or
+    via ssh but with a key that gives you **shell** access.
+
+    Solution: same as for the previous bullet.
+
+    NOTE: If you really *must* do it, and this is a one-time thing, you can
+    try `gitolite push` instead of `git push`.  **BUT**... this defeats all
+    gitolite access control, so if you're going to do this often, maybe you
+    don't need gitolite!
+
+## #ue uncommon errors
+
+  * `WARNING: split conf not set, gl-conf present for <repo>`
+
+    (case 1) This can happen if you have a *bare* repo (i.e., some `repo.git`
+    directory) copied from g2 with `GL_BIG_CONFIG` on, and you pushed a change
+    to the conf or ran certain commands *before* adding the newly added repo
+    to the conf file.
+
+    (case 2) This can also happen if you changed something like this
+
+        repo foo
+            ...<some rules>...
+
+    to this
+
+        @grp = foo
+        repo @grp
+            ...<some rules>...
+
+    Also, even running `gitolite setup` will not fix this.
+
+    The root cause is an internal consistency check that I do not wish to
+    disable or subvert.  It is there for a reason, and I would prefer a
+    warning that a human can investigate.
+
+    If you're sure the reasons are one of the two above, you can either add
+    the repo to the conf file in case 1, or manually remove the gl-conf file
+    from the repo.git directory in case 2.
+
+    Either way, run `gitolite setup` afterwards to make sure things are in
+    good shape.
+
+    If you think neither of those is the cause, email me.
+
+## #ngp things that are not gitolite problems
+
+There are several things that appear to be gitolite problems but are not.  I
+cannot help with most of these (although the good folks on irc or the mailing
+list -- see [contact][] -- might be able to; they certainly appear to have a
+lot more patience than I do, bless 'em!)
+
+  * **client side software**
+
+      * putty/plink
+      * jgit/Eclipse
+      * Mac OS client **or** server
+      * putty/plink
+      * windows as a server
+      * ...probably some more I forgot; will update this list as I remember...
+      * did I mention putty/plink?
+
+  * **ssh**
+
+    The *superstar* of the "not a gitolite problem" category is actually ssh.
+
+    Surprised?  It is so common that it has [its own document][auth] to tell
+    you why it is *not* a gitolite problem, while [another one][ssh] tries to
+    help you anyway!
+
+    Everything I know is in that latter link.  Please email me about ssh ONLY
+    if you find something wrong or missing in those documents.
+
+  * **git**
+
+    I wish I had a dollar for each time someone did a *first push* on a new
+    repo, got an error because there were "no refs in common (etc.)", and
+    asked me why gitolite was not allowing the push.
+
+    Gitolite is designed to look like just another bare repo server to a
+    client (except requiring public keys -- no passwords allowed).  It is
+    *completely transparent* when there is no authorisation failure (i.e.,
+    when the access is allowed, the remote client has no way of knowing
+    gitolite was even installed!)
+
+    Even "on disk", apart from reserving the `update` hook for itself,
+    gitolite does nothing to your bare repos unless you tell it to (for
+    example, adding 'gitweb.owner' and such to the config file).
+
+    BEFORE you think gitolite is the problem, try the same thing with a normal
+    bare repo.  In most cases you can play with it just by doing something
+    like this:
+
+        mkdir /tmp/throwaway
+        cd    /tmp/throwaway
+        git clone --mirror <some repo you have a URL for> bare.git
+        git clone bare.git worktree
+        cd worktree
+        <...try stuff>
diff --git a/doc/external.mkd b/doc/external.mkd
new file mode 100644
index 0000000..45d19a7
--- /dev/null
+++ b/doc/external.mkd
@@ -0,0 +1,71 @@
+# interfacing with external tools
+
+>   ----
+
+>   **Note**: The old gitolite (v1.x, v2.x) used to tie itself into knots
+>   dealing with gitweb and daemon.  One of the goals of g3 was to get out of
+>   that game, which your author does not play anyway.  This means statements
+>   like "...special user called 'gitweb'..." really apply to the [non-core][]
+>   programs that gitolite ships with, not to "core" gitolite, and any or all
+>   of this functionality can be disabled by commenting out certain lines in
+>   the [rc][] file.
+
+>   ----
+
+>   Also, **note** that gitolite does **not** install or configure
+>   gitweb/git-daemon -- that is a one-time setup you must do separately.
+
+>   ----
+
+## gitweb
+
+The following repos are deemed to be readable by gitweb:
+
+  * any repos readable by the special user `gitweb`
+  * any repos containing one or more of the following types of lines:
+
+        config gitweb.owner         =   owner name
+        config gitweb.description   =   some description
+        config gitweb.category      =   some category
+
+    Side note: the following shorter forms are available as [syntactic
+    sugar][sugar] for the above longer forms:
+
+        owner       =   owner name
+        desc        =   some description
+        category    =   some category
+
+The list of gitweb-readable repos is written to a file whose name is given by
+the [rc][] file variable `GITWEB_PROJECTS_LIST`.  The default value of this
+variable, if it is not specified or empty, is `$HOME/projects.list`.
+
+In addition, each of the config variables described above is written to the
+repo to which it pertains, so that gitweb can use them.
+
+### #umask changing the UMASK
+
+Gitweb typically runs under a different userid, and the default permissions
+that gitolite sets make them unreadable.
+
+See the section on the `UMASK` variable in the documentation for the [rc
+file][rc].
+
+## git-daemon
+
+Any repo readable by the special user `daemon` is deemed to be readable by
+git-daemon.  For each of these repos, an empty file called
+`git-daemon-export-ok` is created in the repository (i.e., the `repo.git`
+directory inside `$HOME/repositories`).
+
+## tips
+
+Setting descriptions en-masse usually does not make sense, but you can
+certainly do things like
+
+    repo @all
+        R       =   gitweb daemon
+
+assuming you have other means of setting 'gitweb.description' and
+'gitweb.owner'.
+
+Also see [this][deny-rules] for a twist on that.
diff --git a/doc/extras/auth.mkd b/doc/extras/auth.mkd
index 0918f07..1b88a6a 100644
--- a/doc/extras/auth.mkd
+++ b/doc/extras/auth.mkd
@@ -3,7 +3,7 @@
 This document will explain why an "ssh issue" is almost never a "gitolite
 issue", and, indirectly, why I dont get too excited about the former.
 
-Note: for actual ssh troubleshooting see [this][ssh-troubleshooting].
+Note: for actual ssh troubleshooting see [this][sts].
 
 Here is a fundamental point: <font color="red">**Gitolite does not do
 authentication.  It only does authorisation**.</font>
@@ -18,41 +18,42 @@ So first, let's loosely define these words:
 >   **Authorisation** is the process of asking what you want to do and
 >   deciding if you're allowed to do it or not.
 
-Now, if you managed to read about [gitolite and ssh][gitolite-and-ssh], you
+Now, if you managed to read about [gitolite and ssh][glssh], you
 know that gitolite is meant to be invoked as:
 
-    /full/path/to/gl-auth-command some-authenticated-gitolite-username
+    /full/path/to/gitolite-shell some-authenticated-gitolite-username
 
 (where the "gitolite username" is a "virtual" username; it does not have to
 be, and usually *isn't*, an actual *unix* username).
 
 As you can see, authentication happens before gitolite is called.
 
-## but... but... you have all that ssh stuff in there!
+## but... but... you have all that ssh stuff in gitolite!
 
-The default mode of using gitolite does use ssh keys, but all it's doing is
-helping you **setup** ssh-based authentication **as a convenience to you**.
+No I don't.  Not in "core" gitolite from g3 onwards :-)
 
-You don't have to use it, though.  And many people don't.  The examples I know
-are [smart http][http], and ldap-backed sshd.  In both cases, gitolite has no
-role to play in creating users, setting up their passwords/keys, etc.  There's
-even a `GL_NO_SETUP_AUTHKEYS` option to make sure gitolite doesn't meddle with
-the authkeys file in such installations.
+The default setup does use ssh keys, but it's only helping you **setup**
+ssh-based authentication **as a convenience to you**.  But in fact it is a
+*completely* separate program that you can disable (in the rc file) or replace
+with something else of your choice.
+
+For example, in both [smart http][http] and ldap-backed sshd, gitolite has no
+role to play in creating users, setting up their passwords/keys, etc.
 
 ## so you're basically saying you won't support "X"
 
 (where "X" is some ssh related behaviour change or feature)
 
-Well, if it's not a security issue I *probably* won't.  I'm willing to change
-my mind if enough people convince me they need it.  (There's a mailing list if
-you want to find others who also need the same thing.)
+Well, if it's not a security issue I won't.  But since it's no longer part of
+"core" gitolite, I can be much more relaxed about taking patches, or even
+alternative implementations.
 
 While we're on the subject, locking someone out is *not* a security issue.
-Even if you locked yourself (the admin) out, the docs tell you how to recover
+Even if you [lost the admin key][lost-key], the docs tell you how to recover
 from such errors.  You do need some password based method to get a shell
 command line on the server, of course.
 
-## appendix: how to use other authentication systems with gitolite
+## #otherauth how to use other authentication systems with gitolite
 
 The bottom line in terms of how to invoke gitolite has been described above,
 and as long as you manage to do that gitolite won't even know how the
@@ -84,6 +85,22 @@ to generate keypairs and send them to the admin, but they can be more
 centrally stored and perhaps used by other programs or tools simultaneously,
 which can be useful.
 
-Finally, gitolite allows you to store *group* information externally too.  See
-[here][ldap] for more on this.
+## #ldap getting user group info from LDAP
+
+Gitolite's [groups][] are pretty convenient, but some organisations already
+have similar (or sufficient) information in their LDAP store.
+
+Gitolite can tap into that information, with a little help.  Write a program
+which, given a username, queries your LDAP store and returns a space-separated
+list of groups that the user is a member of.  Then put the full path to this
+program in an [rc][] variable called `GROUPLIST_PGM`, like so:
+
+    GROUPLIST_PGM           =>  '/home/git/bin/ldap-query-groups',
+
+Now you can use those groupnames in access rules in gitolite, just as if you
+had declared their memberships in the conf file.
 
+Caution: your program must do its own logging if you want the audit trail of
+"why/how did this user get access to this repo at this time?" to resolve
+properly.  Gitolite does not do any logging of the results of the queries
+because for people who don't need it that would be a huge waste.
diff --git a/doc/extras/gitolite-and-ssh.mkd b/doc/extras/gitolite-and-ssh.mkd
index 380b81c..1fa265e 100644
--- a/doc/extras/gitolite-and-ssh.mkd
+++ b/doc/extras/gitolite-and-ssh.mkd
@@ -99,13 +99,13 @@ The answer to the first question is the `command=` we talked about before.  If
 you look in the `authorized_keys` file, you'll see entries like this (I chopped
 off the ends of course; they're pretty long lines):
 
-    command="[path]/gl-auth-command sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
-    command="[path]/gl-auth-command usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
+    command="[path]/gitolite-shell sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
+    command="[path]/gitolite-shell usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
 
 First, it finds out which of the public keys in this file match the incoming
 login.  That's crypto stuff, and I won't go into it.  Once the match has been
 found, it will run the command given on that line; e.g., if I logged in, it
-would run `[path]/gl-auth-command sitaram`.  So the first thing to note is
+would run `[path]/gitolite-shell sitaram`.  So the first thing to note is
 that such users do not get "shell access", which is good!
 
 Before running the command, however, sshd sets up an environment variable
@@ -113,7 +113,7 @@ called `SSH_ORIGINAL_COMMAND` which contains the actual git command that your
 workstation sent out.  This is the command that *would have run* if you did
 not have the `command=` part in the authorised keys file.
 
-When `gl-auth-command` gets control, it looks at the first argument
+When `gitolite-shell` gets control, it looks at the first argument
 ("sitaram", "usertwo", etc) to determine who you are.  It then looks at the
 `SSH_ORIGINAL_COMMAND` variable to find out which repository you want to
 access, and whether you're reading or writing.
diff --git a/doc/extras/nagp.mkd b/doc/extras/nagp.mkd
deleted file mode 100644
index 7f783db..0000000
--- a/doc/extras/nagp.mkd
+++ /dev/null
@@ -1,64 +0,0 @@
-# not a gitolite problem
-
-These are issues I do not want to be emailed about.  That does not mean you
-cannot get help -- in all cases, you're welcome to ask on [irc or the mailing
-list][contact].  Irc especially has people with much more patience than I
-have, God bless them...
-
-## specific clients, or specific server OSs
-
-These are things I can not support.  That does not mean they will not work
-with gitolite -- on the contrary, lots of people are using them.
-
-But I personally don't use them, and I won't use them, and in my admittedly
-limited experience they have given me good reason to stay well away.
-
-Please ask for help on the [mailing list or IRC][contact].  Please do not
-email me directly.
-
-  * putty/plink
-  * jgit/Eclipse
-  * Mac OS client or server
-  * windows as a server
-  * ...probably some more I forgot; will update this list as I remember...
-
-## ssh
-
-The *superstar* of the "not a gitolite problem" category is actually ssh.
-
-Surprised?  It is so common that it has [its own document][auth] to tell
-you why it is *not* a gitolite problem, while [another one][ssh] tries to
-help you anyway!
-
-Everything I know is in that latter link.  Please email me about ssh ONLY if
-you find something wrong or missing in those documents.
-
-## git
-
-Example 1: when a first `git push` to a new repo fails, it is not because of
-gitolite, it is because you need to say `git push origin master` or something.
-This is a git issue.
-
-There are several such examples.  Gitolite is designed to look like just
-another bare repo server to a client (except requiring public keys -- no
-passwords allowed).  It is *completely transparent* when there is no
-authorisation failure (i.e., when the access is allowed, the remote client has
-no way of knowing gitolite was even installed!)
-
-Even "on disk", apart from reserving the `update` hook for itself, gitolite
-does nothing to your bare repos unless you tell it to (for example, adding
-'gitweb.owner' and such to the config file).
-
-BEFORE you think gitolite is a problem, try the same thing with a normal bare
-repo.  In most cases you can play with it just by doing something like this:
-
-    mkdir /tmp/throwaway
-    cd    /tmp/throwaway
-    git clone --mirror <some repo you have a URL for> bare.git
-    git clone bare.git worktree
-    cd worktree
-    <...try stuff>
-
-----
-
-In addition, the original nagp has more funny stuff...
diff --git a/doc/extras/putty.mkd b/doc/extras/putty.mkd
new file mode 100644
index 0000000..5a4ff56
--- /dev/null
+++ b/doc/extras/putty.mkd
@@ -0,0 +1,203 @@
+# putty and msysgit
+
+This document is intended for those who wish to use Putty/Plink with msysgit.
+
+If you need more help with putty or component programs I suggest looking at [the official putty documentation](http://the.earth.li/~sgtatham/putty/latest/htmldoc/).
+
+**If you are not already using Putty for SSH it is recommended you do _NOT_ use it with msysgit.**
+
+**Please note that this only covers the client side of things, and does not involve server side components to troubleshooting. For that, please see the [ssh-troubleshooting document][sts].**
+
+<a name="msysgit_setup"/>
+
+## msysgit setup
+
+Provided you have putty sessions msysgit should give you the option of specifying a location to plink. If it did not then you will need to add an environment variable named "GIT\_SSH" to point at plink.exe, wherever you have that sitting.
+
+How to do that on your version of windows will likely vary, and is not covered here. For purposes of example, on a 64 bit Windows Vista machine the GIT\_SSH value could be:
+
+    C:\Program Files (x86)\PuTTY\plink.exe
+
+Note the lack of quotes.
+
+Testing that msysgit is properly configured can be done from the git bash shell. Simply type (case sensitive, include the quotes):
+
+    "$GIT_SSH" -V
+
+You should get a response similar to this:
+
+    plink: Release 0.60
+
+If instead you get a "command not found" type error you likely have a typo in your environment variable.
+
+<a name="Going_back_to_OpenSSH"/>
+
+## Going back to OpenSSH
+
+If you wish to go back to OpenSSH all you need to do is delete the GIT\_SSH environment variable. This will vary by your version of windows and thus is not covered here.
+
+<a name="Putty_keys"/>
+
+## Putty keys
+
+If you do not already have putty private key files (.ppk) you will need to make at least one. You can either make a new one or convert an existing key to putty private key format.
+
+Either way, you will want to use puttygen. Note that you can go the other way if you want to stop using putty but keep the key by exporting the key to OpenSSH format.
+
+<a name="Creating_a_new_key"/>
+
+### Creating a new key
+
+To make it simple, I suggest SSH-2 RSA and a bit size of at least 1024. Larger keys will take longer to generate and will take longer to authenticate you on most systems. Making the key is as simple at hitting "Generate".
+
+It is recommended to give the key a meaningful comment.
+
+<a name="Importing_an_existing_key"/>
+
+### Importing an existing key
+
+If you already have an OpenSSH or ssh.com key you can import it using the "Import" option on the "Conversions" menu.
+
+If the key does not have a meaningful comment I would suggest adding one at this point.
+
+<a name="Loading_an_existing_key"/>
+
+### Loading an existing key
+
+If you need to load an existing key to edit or view it you can do so from the File menu.
+
+<a name="Public_key"/>
+
+### Public key
+
+To get your public key for use with gitolite, load (or generate, or import) your key into puttygen. There is a box labeled "Public key for pasting into OpenSSH `authorized_keys` file" there. Copy the text into your preferred text editor and save.
+
+<a name="Putty_ageant"/>
+
+### Putty ageant
+
+Though not required in all cases you may wish to use the putty ageant, pageant, to load your key(s). This will allow for your key(s) to be passphrase protected but not have to enter the passphrase when you go to use them, provided you have already loaded the key into the ageant.
+
+<a name="Sessionless_or_raw_hostname_usage"/>
+
+## Sessionless or raw hostname usage
+
+When using plink without a putty session you pretty much have to load your keys with putty ageant, if only so that plink can find them.
+
+<a name="Putty_sessions"/>
+
+## Putty sessions
+
+In addition to hostnames msysgit can, when using putty, use putty sessions. This works in a manner similar to definitions in OpenSSH's `ssh_config` file. All settings in the session that apply to plink usage will be loaded, including the key file to use and even the username to connect to. Thus, instead of:
+
+    ssh://user@host.example.ext:port/repo
+
+You can use:
+
+    ssh://session_name/repo
+
+<a name="Host_key_authentication"/>
+
+## Host key authentication
+
+Whether you are using hostnames or sessions you still run into one potential problem. Plink currently wants to validate the server's SSH host key before allowing you to connect, and when git calls plink there is no way to tell it yes. Thus, you may get something like this:
+
+    The server's host key is not cached in the registry. You
+    have no guarantee that the server is the computer you
+    think it is.
+    The server's rsa2 key fingerprint is:
+    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+    Connection abandoned.
+    fatal: The remote end hung up unexpectedly
+
+Or, in the case of the host key changing, something like this:
+
+    WARNING - POTENTIAL SECURITY BREACH!
+    The server's host key does not match the one PuTTY has
+    cached in the registry. This means that either the
+    server administrator has changed the host key, or you
+    have actually connected to another computer pretending
+    to be the server.
+    The new rsa2 key fingerprint is:
+    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+    Connection abandoned.
+    fatal: The remote end hung up unexpectedly
+
+The solution is to call plink directly, or start putty and connect with it first. To use plink, open the Git Bash shell and enter:
+
+    "$GIT_SSH" hostname_or_session_name
+
+When you do you will see something like this:
+
+    The server's host key is not cached in the registry. You
+    have no guarantee that the server is the computer you
+    think it is.
+    The server's rsa2 key fingerprint is:
+    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+    If you trust this host, enter "y" to add the key to
+    PuTTY's cache and carry on connecting.
+    If you want to carry on connecting just once, without
+    adding the key to the cache, enter "n".
+    If you do not trust this host, press Return to abandon the
+    connection.
+    Store key in cache? (y/n)
+
+Or, in the case of a changed key, a response like this:
+
+    WARNING - POTENTIAL SECURITY BREACH!
+    The server's host key does not match the one PuTTY has
+    cached in the registry. This means that either the
+    server administrator has changed the host key, or you
+    have actually connected to another computer pretending
+    to be the server.
+    The new rsa2 key fingerprint is:
+    ssh-rsa 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+    If you were expecting this change and trust the new key,
+    enter "y" to update PuTTY's cache and continue connecting.
+    If you want to carry on connecting but without updating
+    the cache, enter "n".
+    If you want to abandon the connection completely, press
+    Return to cancel. Pressing Return is the ONLY guaranteed
+    safe choice.
+    Update cached key? (y/n, Return cancels connection)
+
+In either case hit y and the key will be stored.
+
+<a name="Debugging_multiple_putty_ageant_keys"/>
+
+## Debugging multiple putty ageant keys
+
+In the event you are using putty ageant with multiple keys loaded you may see the wrong key being used. In general, pageant keys are tried in the order they were loaded into the ageant. If you have descriptive comment on each of your keys you can try connecting with plink in verbose mode to see what keys are being tried. Simply open the Git bash shell and run:
+
+    "$GIT_SSH" -v user at hostname
+
+Or, if using sessions with a pre-entered username:
+
+    "$GIT_SSH" -v session_name
+
+In either case, you should look for lines like:
+
+    Trying Pageant key #0
+	Authenticating with public key "My Key" from agent
+
+The first says which (numerical) key the ageant is trying. The second tells you the key comment for the authenticating key. To my knowledge the second line should only show up once, for the valid key.
+
+<a name="Setperms_and_other_commands"/>
+
+## Setperms and other commands
+
+When using wildcard repos the setperms command is very important, and other commands can come in handy as well. See their documentation for how to use them, but where they use:
+
+    ssh user at host command etc etc
+
+You will want to use:
+
+    "$GIT_SSH" user at host command etc etc
+
+Otherwise everything should be identical.
+
+<a name="About_this_document"/>
+
+## About this document
+
+This document was written by Thomas Berezansky (tsbere (at) mvlc (dot) org) in the hopes that it would be useful to those using putty on windows and wishing to use git/gitolite with their putty keys and sessions.
diff --git a/doc/extras/ssh-troubleshooting.mkd b/doc/extras/ssh-troubleshooting.mkd
index cdcc831..f4b8d28 100644
--- a/doc/extras/ssh-troubleshooting.mkd
+++ b/doc/extras/ssh-troubleshooting.mkd
@@ -14,7 +14,7 @@ and accessing gitolite.
   * Before reading this document, it is **mandatory** to read and **completely
     understand** [this][ssh], which is a very detailed look at how gitolite
     uses ssh's features on the server side.  Don't assume you know all that;
-    if you knew it, you wouldn't be needing *this* document either!
+    if you did, you wouldn't be needing *this* document either!
 
   * This document, and others linked from this, together comprise all the help
     I can give you in terms of the ssh aspect of using gitolite.  If you're
@@ -80,11 +80,6 @@ Here's how normal gitolite key handling works:
         between gitolite's "marker" lines (`# gitolite start` and `# gitolite
         end`).
 
-### (Other resources)
-
-People who think installing gitolite is too hard should take a look at this
-[tutorial][tut] to **see how simple it *actually* is**.
-
 ### common ssh problems
 
 Since I'm pretty sure at least some of you didn't bother to read the
@@ -94,15 +89,16 @@ you there again.  Especially the first bullet.
 Done?  OK, read on...
 
 The following problem(s) indicate that pubkey access is not working at all, so
-you should start with [appendix 1][stsapp1_].  If that doesn't fix the problem, continue
+you should start with [appendix 1][stsapp1].  If that doesn't fix the problem, continue
 with the other appendices in sequence.
 
   * running any git clone/fetch/ls-remote or just `ssh git at server info` asks
     you for a password.
 
 The following problem(s) indicate that your pubkey is bypassing gitolite and
-going straight to a shell.  You should start with [appendix 2][stsapp2_] and continue with
-the rest in sequence.  [Appendix 5][stsapp5_] has some background info.
+going straight to a shell.  You should start with [appendix 2][sshkeys-lint]
+and continue with the rest in sequence.  [Appendix 5][ybpfail] has some
+background info.
 
   * running `ssh git at server info` gets you the output of the GNU 'info'
     command instead of gitolite's version and access info.
@@ -114,8 +110,8 @@ the rest in sequence.  [Appendix 5][stsapp5_] has some background info.
     (note absence of `repositories/`)]
 
   * you are able to clone repositories but are unable to push changes back
-    (the error complains about the `GL_RC` environment variable not being set,
-    and the `hooks/update` failing in some way).
+    (the error complains about the `GL_BINDIR` environment variable not being
+    set, and the `hooks/update` failing in some way).
 
     [If you run `git remote -v` you will find that your clone URL included the
     `repositories/` described above!]
@@ -136,42 +132,39 @@ Done?  OK, now the general outline for ssh troubleshooting is this:
   * make sure the server's overall setup even *allows* pubkey based login.
     I.e., check that git fetch/clone/ls-remote commands or a plain `ssh
     git at server info` do NOT ask for a password.  If you do get asked for a
-    password, see [appendix 1][stsapp1_].
+    password, see [appendix 1][stsapp1].
 
   * match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
     file.  To do this, run `sshkeys-lint`, which tells you in detail what key
-    has what access.  See [appendix 2][stsapp2_].
+    has what access.  See [appendix 2][sshkeys-lint].
 
   * at this point, we know that we have the right key, and that if sshd
     receives that key, things will work.  But we're not done yet.  We still
     need to make sure that this specific key is being offered/sent by the
-    client, instead of the default key.  See [appendix 3][stsapp3_] and [appendix 4][sshhostaliases].
+    client, instead of the default key.  See [appendix 3][stsapp3] and
+    [appendix 4][ssh-ha].
 
 ### random tips, tricks, and notes
 
 #### giving shell access to gitolite users
 
-We've managed (thanks to an idea from Jesse Keating) to make it possible for a
-single key to allow both gitolite access *and* shell access.
+Thanks to an idea from Jesse Keating, a single key can allow both gitolite
+access *and* shell access.
 
-This is done by copying the pubkey (to which you want to give shell access) to
-the server and running
+This is done by manually prefixing the username with "-s" as an extra argument
+in the "command=" part of `~/.ssh/authorized_keys`.  For example
 
-    gl-tool add-shell-user ~/foo.pub
+    command="/home/g3/gitolite/src/gitolite-shell u1",no-port-[...etc...]
 
-**IMPORTANT UPGRADE NOTE**: previous implementations of this feature were
-crap.  There was no easy/elegant way to ensure that someone who had repo admin
-access would not manage to get himself shell access.
+should be edited to be
 
-Giving someone shell access requires that you should have shell access in the
-first place, so the simplest way is to enable it from the server side only.
+    command="/home/g3/gitolite/src/gitolite-shell -s u1",no-port-[...etc...]
 
-#### losing your admin key
+and moved out of the gitolite area of the authkeys file.
 
-If you lost the admin key, and need to re-establish ownership of the
-gitolite-admin repository with a fresh key, get a shell on the server and use
-the program called `gl-admin-push` that comes with gitolite.  See instructions
-[here][adminpush].
+It should be easy to make src/triggers/post-compile/ssh-authkeys read a list
+of shell capable users from some file on the server and put in the "-s" for
+those users.  Patches welcome.
 
 #### simulating ssh-copy-id
 
@@ -199,11 +192,12 @@ bigger problems than gitolite install not working!)]
 #### problems with using non-openssh public keys
 
 Gitolite accepts public keys only in openssh format.  Trying to use an "ssh2"
-key (used by proprietary SSH software) results in:
+key (used by proprietary SSH software) will not be a happy experience.
+src/triggers/post-compile/ssh-authkeys can be made to detect non-openssh
+formats and automatically convert them; patches welcome!
 
-    WARNING: a pubkey file can only have one line (key); ignoring YourName.pub
-
-To convert ssh2-compatible keys to openssh run:
+The actual conversion command, if you want to just do it manually for now and
+be done with it, is:
 
     ssh-keygen -i -f /tmp/ssh2/YourName.pub > /tmp/openssh/YourName.pub
 
@@ -218,9 +212,9 @@ used them for any kind of git access).  If you have unusual ssh problems that
 just don't seem to have any explanation, try removing all traces of
 putty/plink, including environment variables, etc., and then try again.
 
-Thankfully, someone contributed [contrib/putty.mkd][contrib_putty].
+Thankfully, someone contributed [this][putty].
 
-### #stsapp1_ appendix 1: ssh daemon asks for a password
+### #stsapp1 appendix 1: ssh daemon asks for a password
 
 >   **NOTE**: This section should be useful to anyone trying to get
 >   password-less access working.  It is not necessarily specific to gitolite,
@@ -231,7 +225,7 @@ You have generated a keypair on your workstation (`ssh-keygen`) and copied the
 public part of it (`~/.ssh/id_rsa.pub`, by default) to the server.
 
 On the server you have appended this file to `~/.ssh/authorized_keys`.  Or you
-ran something, like the `gl-setup` step during a gitolite install, which
+ran something, like the `gitolite setup` step during a gitolite install, which
 should have done that for you.
 
 You now expect to log in without having to type in a password, but when you
@@ -280,6 +274,10 @@ This is a quick checklist:
     `AllowUsers` config entry, then only users mentioned in that line are
     allowed to log in!
 
+  * while you're in there, check that file does NOT have a setting for
+    `AuthorizedKeysFile`.  See `man sshd_config` for details.  This setting is
+    a show stopper for gitolite to use ssh.
+
   * some OSs/distributions require that the "git" user should have a password
     and/or not be a locked account.  You may want to check that as well.
 
@@ -288,27 +286,27 @@ This is a quick checklist:
     this file for messages matching the approximate time of your last attempt
     to login, to see if they tell you what is the problem.
 
-### #stsapp2_ appendix 2: which key is which -- running sshkeys-lint
+### #sshkeys-lint appendix 2: which key is which -- running sshkeys-lint
 
-Follow these steps on the client:
+The sshkeys-lint program can be run on the server or the client.  Run it with
+'-h' to get a help message.
+
+On the server you can run `gitolite sshkeys-lint` and it will tell you, for
+each key in the admin directory's keydir, what access is available.  This is
+especially good at finding duplicate keys and such.
+
+To run it on the client you have to copy the file src/commands/sshkeys-lint
+from some gitolite clone, then follow these steps:
 
   * get a copy of `~/.ssh/authorized_keys` from the server and put it in
     `/tmp/foo` or something
 
   * cd to `~/.ssh`
 
-  * run `sshkeys-lint *.pub < /tmp/foo`
-
-This tells you, for each pubkey, what type of access (if any) it has to the
-server.
-
-Note that it is not trying to log in or anything -- it's just comparing bits
-of text (the contents of STDIN taken as an authkeys file, and the contents of
-each of the `*.pub` files one by one).
+  * run `/path/to/sshkeys-lint *.pub < /tmp/foo`
 
->   Note: It's also a stand-alone program, so even if your gitolite version is
->   old, you can safely bring over just this program from a more recent
->   gitolite and use it, without having to upgrade gitolite itself.
+Note that it is not trying to log in or anything -- it's just comparing
+fingerprints as computed by `ssh-keygen -l`.
 
 If the pubkey file you're interested in appears to have the correct access to
 the server, you're done with this step.
@@ -327,7 +325,7 @@ need.  Be careful:
 
 The admin often has passwordless shell access to `git at server` already, and
 then used that same key to get access to gitolite (i.e., copied that same
-pubkey as YourName.pub and ran `gl-setup` on it).
+pubkey as YourName.pub and ran `gitolite setup` on it).
 
 As a result, the same key appears twice in the authkeys file now, and since
 the ssh server will always use the first match, the second occurrence (which
@@ -335,11 +333,11 @@ invokes gitolite) is ignored.
 
 To fix this, you have to use a different keypair for gitolite access.  The
 best way to do this is to create a new keypair, copy the pubkey to the server
-as YourName.pub, then run `gl-setup YourName.pub` on the server.  Remember to
-adjust your agent identities using ssh-add -D and ssh-add if you're using
-ssh-agent, otherwise these new keys may not work.
+as YourName.pub, then run `gitolite setup -pk YourName.pub` on the server.
+Remember to adjust your agent identities using ssh-add -D and ssh-add if
+you're using ssh-agent, otherwise these new keys may not work.
 
-### #stsapp3_ appendix 3: ssh client may not be offering the right key
+### #stsapp3 appendix 3: ssh client may not be offering the right key
 
   * make sure the right private key is being offered.  Run ssh in very
     verbose mode and look for the word "Offering", like so:
@@ -348,7 +346,7 @@ ssh-agent, otherwise these new keys may not work.
 
     If some keys *are* being offered, but not the key that was supposed to be
     used, you may be using ssh-agent (next bullet).  You may also need to
-    create some host aliases in `~/.ssh/config` ([appendix 4][sshhostaliases]).
+    create some host aliases in `~/.ssh/config` ([appendix 4][ssh-ha]).
 
   * (ssh-agent issues) If `ssh-add -l` responds with either "The agent has no
     identities." or "Could not open a connection to your authentication
@@ -361,7 +359,7 @@ ssh-agent, otherwise these new keys may not work.
     In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try
     the access again.
 
-### F=sshhostaliases appendix 4: host aliases
+### #ssh-ha appendix 4: ssh host aliases
 
 (or "making git use the right options for ssh")
 
@@ -397,10 +395,10 @@ way to do this, as far as I know.
 
 [tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial
 
-### #stsapp5_ appendix 5: why bypassing gitolite causes a problem
+### #ybpfail appendix 5: why bypassing gitolite causes a problem
 
 When you bypass gitolite, you end up running your normal shell instead of the
-special gitolite entry point script `gl-auth-command`.
+special gitolite entry point script `gitolite-shell`.
 
 This means commands (like 'info') are interpreted by the shell instead of
 gitolite.
diff --git a/doc/extras/ssh.mkd b/doc/extras/ssh.mkd
index 4d04d57..1d7272f 100644
--- a/doc/extras/ssh.mkd
+++ b/doc/extras/ssh.mkd
@@ -2,10 +2,10 @@
 
 There are two documents you need to read, in order:
 
-  * [gitolite and ssh][glssh] -- this explains how gitolite uses openssh's
-    features to provide any number of virtual users over just one actual
-    (unix) user, and so on
+  * [Gitolite and ssh][glssh] explains how gitolite uses openssh features to
+    create any number of virtual users over just one actual (unix) user, and
+    distinguish between them by their public keys.
 
-  * [ssh troubleshooting][sts] -- this is a rather long document but as far
-    as I know almost every known ssh related issue is in here.  If you find
-    something missing, send me an email with details.
+  * [Ssh troubleshooting][sts] is a rather long document that, as far as I
+    know, covers almost every known ssh related issue.  If you find something
+    missing, send me an email with details so I can update it.
diff --git a/doc/extras/unique.mkd b/doc/extras/unique.mkd
deleted file mode 100644
index 3864769..0000000
--- a/doc/extras/unique.mkd
+++ /dev/null
@@ -1,6 +0,0 @@
-# unique setups
-
-This page is for unique setups that I support.  At present there is only one
--- Fedora.
-
-
diff --git a/doc/files.mkd b/doc/files.mkd
new file mode 100644
index 0000000..077078d
--- /dev/null
+++ b/doc/files.mkd
@@ -0,0 +1,87 @@
+# files involved in gitolite
+
+## install
+
+Let's say you start from a totally clean slate:
+
+    $ pwd
+    /home/gl-test
+
+    $ ls -a
+    .  ..  .bash_logout  .bash_profile  .bashrc
+
+You run `git clone -b g3 git://github.com/sitaramc/gitolite` (the '-b' option
+does the equivalent of 'git checkout g3' after the clone is done).
+
+Now you have
+
+    $ ls -aF
+    ./  ../  .bash_logout  .bash_profile  .bashrc  gitolite/
+
+    $ ls -aF gitolite
+    ./  ../  check-g2-compat*  doc/  dot.pl  .git/  install*  src/  t/
+
+If we were an existing user, we'd run the migration checker 'check-g2-compat'
+(see [here][g2migr]).  For now we're only interested in "gitolite/src", so:
+
+    $ ls -aF gitolite/src
+    ./  ../  commands/  gitolite*  Gitolite/  gitolite-shell*  syntactic-sugar/  triggers/  VREF/
+
+We check our PATH to make sure `$HOME/bin` is in it:
+
+    $ echo $PATH
+    /usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/gl-test/.local/bin:/home/gl-test/bin
+
+Since it is, we run `mkdir bin; gitolite/install -ln`, and get:
+
+    $ mkdir bin
+    $ gitolite/install -ln
+
+    $ ls -aF
+    ./  ../  .bash_logout  .bash_profile  .bashrc  bin/  gitolite/
+
+    $ ls -aF bin
+    ./  ../  gitolite@
+
+    $ file bin/gitolite
+    bin/gitolite: symbolic link to `/home/gl-test/gitolite/src/gitolite'
+
+That's the install done.  At the end of it, we have just *one* symlink in
+`$HOME/bin`; everything else is in a different directory.
+
+## setup
+
+Now you copy your pubkey (typically `id_rsa.pub`) from your workstation to the
+server, then run `gitolite setup -pk "yourname.pub"`.  Which gives you this:
+
+    $ ls -aF
+    ./   .bash_logout   .bashrc  gitolite/   .gitolite.rc   repositories/  .ssh/
+    ../  .bash_profile  bin/     .gitolite/  projects.list  sitaram.pub
+
+    $ ls -aF bin
+    ./  ../  gitolite@
+
+    $ ls -aF .gitolite
+    ./  ../  conf/  hooks/  keydir/  logs/
+
+    $ ls -aF repositories
+    ./  ../  gitolite-admin.git/  testing.git/
+
+    $ ls -aF .ssh
+    ./  ../  authorized_keys*
+
+And that's the setup done.  At the end of this step, you have
+
+  * `~/bin/gitolite` -- a symlink to `~/gitolite/src/gitolite`.  The target of
+    this symlink tells gitolite where the rest of the code is.
+  * `~/gitolite/src` -- the rest of the code.
+  * `~/.gitolite` -- the gitolite "admin" directory.  The only thing you
+    should directly touch here are the [log][] files and [hooks][].  (I.e., do
+    NOT change the conf or keydir contents here; see adding [users][] and
+    [repos][] for how to do that.
+  * `~/.gitolite.rc` -- the [rc][] file.
+  * `~/repositories` -- which contains all the repositories that gitolite will
+    be managing.
+  * `~/.ssh` -- which contains (at least) the `authorized_keys` file that
+    provides access to users.  You can look inside that file and correlate it
+    with what the [ssh][] docs tell you, if you wish.
diff --git a/doc/fm2mt.pl b/doc/fm2mt.pl
new file mode 100755
index 0000000..4d4d586
--- /dev/null
+++ b/doc/fm2mt.pl
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use 5.10.0;
+
+# usage: ./fm2mt.pl < g3-master-toc.mm > master-toc.mkd
+
+use HTML::Entities;
+
+sub out { my $out = shift; print $out; }
+
+# freemind to "dense" HTML
+my @in = fm2indent();
+
+out("# gitolite documentation");
+my $started = 0;
+for (@in) {
+    my($indent, $text) = split ' ', $_, 2;
+    $indent--;
+
+    if (not $indent) {
+        out "\n\n## $text\n";
+        $started = 0;
+        next;
+    }
+
+    if ($indent == 1) {
+        # (dense mode) $text = color("red", $text);
+    }
+
+    if ($indent <= 2) {
+        # (dense mode) $text = size(2 - $indent, $text);
+    } else {
+        # 3 or more
+        # (dense mode) $text = ("/" x ($indent-4)) . color("gray", $text);
+    }
+
+    # normal mode
+    $text = "\n" . ("    " x ($indent-1)) . "  * $text";
+    # (dense mode) out " -- " if $started++;
+    out $text;
+}
+
+sub size {
+    my ($s, $t) = @_;
+    return "<font size=\"+" . $s . "\">$t</font>" if $s;
+    return $t;
+}
+
+sub color {
+    my ($c, $t) = @_;
+    return "<font color=\"$c\">$t</font>";
+}
+
+sub get_indent {
+    my $_ = shift;
+    chomp;
+    return () unless /\S/;
+    if (/^(#+) (.*)/) {
+        return (length($1)-1, $2);
+    }
+    if (/^( +)  \* (.*)/) {
+        my $t = $2;
+        my $i = length($1);
+        die 1 if $i % 4;
+        $i = $i/4 + 3;
+
+        return ($i, $t);
+    }
+    return ();
+}
+
+sub fm2indent {
+    my @out = ();
+    my $indent=0;
+
+    while (<>)
+    {
+        next unless /^<node / or /^<\/node/;
+        if (/^<\/node>$/)
+        {
+            $indent--;
+            next;
+        }
+        next unless /TEXT="([^"]*)"/;
+        my $text = decode_entities($1);
+
+        push @out, "\n$indent $text" if $indent;
+
+        $indent++ unless (/\/>/);
+    }
+
+    return @out;
+}
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
index 2ae1750..50bb944 100644
--- a/doc/g2incompat.mkd
+++ b/doc/g2incompat.mkd
@@ -7,24 +7,27 @@ severity.  **The ones in the first section are IMPORTANT because they allow
 access that was previously not allowed -- please fix your config before using
 the new gitolite!**
 
-### fallthru in NAME rules
+### NAME rules
 
-Fallthru on all VREFs is "success" now, so any NAME/ rules you have **MUST**
-change the ruleset in some way to maintain the same restrictions.  The
-simplest is to add the following line to the end of each repo's rule list:
+1.  NAME/ rules must be changed to VREF/NAME/
 
-        -   NAME/       =   @all
+2.  Fallthru on all VREFs is "success" now, so any NAME/ rules you have
+    **MUST** change the ruleset in some way to maintain the same restrictions.
+    The simplest is to add the following line to the end of each repo's rule
+    list:
+
+        -   VREF/NAME/       =   @all
 
 ### subconf command in admin repo
 
-(This is also affected by the previous issue, 'fallthru in NAME rules'; please
-read that as well).
+(This is also affected by the previous issue, 'NAME rules'; please read that
+as well).
 
 If you're using delegation in your admin conf setup, please add the following
 lines to the end of the gitolite-admin rules in your conf/gitolite.conf file:
 
     repo gitolite-admin
-        -   NAME/       =   @all
+        -   VREF/NAME/       =   @all
 
     subconf "fragments/*.conf"
 
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 0bb3c36..de50079 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -53,7 +53,7 @@ put that contain the words "see docs":
 
     same as above
 
-  * `fallthru in NAME rules`
+  * `NAME rules`
 
     **This is a significant difference and affects access badly (gives access
     that would otherwise not be given)**.  Please see the [list of non-RC
diff --git a/doc/g3-master-toc.mm b/doc/g3-master-toc.mm
new file mode 100755
index 0000000..1821a1b
--- /dev/null
+++ b/doc/g3-master-toc.mm
@@ -0,0 +1,153 @@
+<map version="0.9.0">
+<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->
+<node CREATED="1331107648858" ID="ID_187992057" MODIFIED="1333299050498" TEXT="g3-master-index">
+<node CREATED="1333299038338" ID="ID_1574733847" MODIFIED="1333536460445" POSITION="right" TEXT="[Introduction][index]">
+<node CREATED="1333466035663" ID="ID_974293172" MODIFIED="1333536536267" TEXT="(for [current][g2] gitolite (v2) users)"/>
+<node CREATED="1333466047943" ID="ID_1283272433" MODIFIED="1333536530576" TEXT="[quick links][ql]"/>
+<node CREATED="1333326589821" ID="ID_195282410" MODIFIED="1333340829999" TEXT="[what][] is gitolite"/>
+<node CREATED="1333326593927" ID="ID_1110367572" MODIFIED="1333340841907" TEXT="[why][] might you need it"/>
+<node CREATED="1333536552704" ID="ID_1607033994" MODIFIED="1333536835577" TEXT="[contact][] info, mailing list, IRC channel"/>
+<node CREATED="1333536577819" ID="ID_1858469252" MODIFIED="1333536835576" TEXT="[license][] info"/>
+</node>
+<node CREATED="1333300010980" ID="ID_1728019284" MODIFIED="1333616327427" POSITION="right" TEXT="help for [emergencies][]">
+<node CREATED="1333300016176" ID="ID_30257493" MODIFIED="1333453799784" TEXT="[lost][lost-key] admin key/access"/>
+<node CREATED="1333327185236" ID="ID_910862972" MODIFIED="1333471145297" TEXT="[bypass][]ing gitolite"/>
+<node CREATED="1333328183948" ID="ID_564351456" MODIFIED="1333453834497" TEXT="[clean][]ing out a botched install"/>
+<node CREATED="1333326808789" ID="ID_54380370" MODIFIED="1333631901528" TEXT="[common][ce] errors (TBD)"/>
+<node CREATED="1333597279461" ID="ID_547537696" MODIFIED="1333631907824" TEXT="[uncommon][ue] errors"/>
+<node CREATED="1333328539885" ID="ID_162892604" MODIFIED="1333527642132" TEXT="things that are [not gitolite problems][ngp]"/>
+</node>
+<node CREATED="1333466070125" ID="ID_1329733023" MODIFIED="1333527838066" POSITION="right" TEXT="[WARNINGS][]"/>
+<node CREATED="1333326560775" ID="ID_1030406941" MODIFIED="1333631954561" POSITION="right" TEXT="[quick][qi] install, setup, and clone"/>
+<node CREATED="1333465293309" ID="ID_1405252383" MODIFIED="1333526188455" POSITION="right" TEXT="[install][]">
+<node CREATED="1333327826145" ID="ID_519306224" MODIFIED="1333467083667" TEXT="notes and naming conventions"/>
+<node CREATED="1333328069297" ID="ID_624460163" MODIFIED="1333465312762" TEXT="requirements">
+<node CREATED="1333328127550" ID="ID_368487912" MODIFIED="1333537067223" TEXT="your skills"/>
+<node CREATED="1333328123855" ID="ID_1146334636" MODIFIED="1333339551947" TEXT="server"/>
+<node CREATED="1333328126057" ID="ID_899541385" MODIFIED="1333328127275" TEXT="client"/>
+</node>
+<node CREATED="1333328151051" ID="ID_1577506173" MODIFIED="1333465323159" TEXT="getting the software"/>
+<node CREATED="1333328156575" ID="ID_208746322" MODIFIED="1333465333727" TEXT="the actual install"/>
+<node CREATED="1333467553366" ID="ID_926614739" MODIFIED="1333467556140" TEXT="upgrading"/>
+<node CREATED="1333328592876" ID="ID_237795046" MODIFIED="1333466108368" TEXT="packaging gitolite"/>
+<node CREATED="1333327776230" ID="ID_1431739921" MODIFIED="1333467569937" TEXT="migrating"/>
+</node>
+<node CREATED="1333301194129" ID="ID_746196740" MODIFIED="1333471816303" POSITION="right" TEXT="[setup][]"/>
+<node CREATED="1333299674481" ID="ID_714407264" MODIFIED="1333528325006" POSITION="right" TEXT="gitolite [admin][]istration">
+<node CREATED="1333299694885" ID="ID_1916098884" MODIFIED="1333537908466" TEXT="[server][]-side">
+<node CREATED="1333327917312" ID="ID_1266878047" MODIFIED="1333537539764" TEXT="([link][WARNINGS]: important cautions on server side activity)"/>
+<node CREATED="1333301196071" ID="ID_760433725" MODIFIED="1333537537075" TEXT="changing settings in the [rc][] file"/>
+<node CREATED="1333537546089" ID="ID_2500021" MODIFIED="1333537553267" TEXT="installing custom [hooks][]"/>
+<node CREATED="1333537563152" ID="ID_472486055" MODIFIED="1333537616205" TEXT="([link][existing]: moving existing repos into gitolite)"/>
+</node>
+<node CREATED="1333299702754" ID="ID_609351656" MODIFIED="1333537921248" TEXT="[access control][conf] (the gitolite.conf file)">
+<node CREATED="1333566502470" ID="ID_1931486515" MODIFIED="1333583575642" TEXT="[example][confex] of a conf file"/>
+<node CREATED="1333300715360" ID="ID_41940351" MODIFIED="1333547591053" TEXT="basic [syntax][]">
+<node CREATED="1333300732151" ID="ID_1494555007" MODIFIED="1333300733944" TEXT="include files"/>
+<node CREATED="1333300741424" ID="ID_416012685" MODIFIED="1333513004487" TEXT="([link][sugar]: syntactic sugar)"/>
+</node>
+<node CREATED="1333300461624" ID="ID_1848362678" MODIFIED="1333529103568" TEXT="[groups][] (of users and repos)">
+<node CREATED="1333300884900" ID="ID_1412135290" MODIFIED="1333300893755" TEXT="special: '@all'"/>
+<node CREATED="1333300437860" ID="ID_897670108" MODIFIED="1333300770020" TEXT="(link: storing user group info in LDAP)"/>
+</node>
+<node CREATED="1333299715546" ID="ID_1916374076" MODIFIED="1333529107922" TEXT="adding and removing [users][]">
+<node CREATED="1333326840977" ID="ID_1723439755" MODIFIED="1333517370375" TEXT="multiple keys per user"/>
+</node>
+<node CREATED="1333299719741" ID="ID_275665792" MODIFIED="1333529112764" TEXT="adding and removing [repos][]">
+<node CREATED="1333327426500" ID="ID_1964948889" MODIFIED="1333518450199" TEXT="renaming repos"/>
+</node>
+<node CREATED="1333300309235" ID="ID_1437714859" MODIFIED="1333529121211" TEXT="defining access [rules][]">
+<node CREATED="1333584760127" ID="ID_469376519" MODIFIED="1333584767343" TEXT="what does a rule look like?"/>
+<node CREATED="1333326976405" ID="ID_563906836" MODIFIED="1333584770230" TEXT="when are the rules checked?"/>
+<node CREATED="1333535584526" ID="ID_957313656" MODIFIED="1333584774497" TEXT="how are the rules matched?"/>
+<node CREATED="1333301045521" ID="ID_1228046254" MODIFIED="1333536127886" TEXT="summary of [permissions][perms]"/>
+<node CREATED="1333599586661" ID="ID_1785519868" MODIFIED="1333599590134" TEXT="additional topics">
+<node CREATED="1333599591055" ID="ID_1379421123" MODIFIED="1333599606966" TEXT="[rule accumulation][rule-accum]"/>
+<node CREATED="1333599613910" ID="ID_1760840175" MODIFIED="1333599634987" TEXT="applying [deny rules][deny-rules] during the pre-git check"/>
+</node>
+<node CREATED="1333535705100" ID="ID_1768473150" MODIFIED="1333535718347" TEXT="([link][refex]: refexes)"/>
+<node CREATED="1333535719180" ID="ID_431454064" MODIFIED="1333626093118" TEXT="([link][write-types]: different types of write operations)"/>
+<node CREATED="1333300541352" ID="ID_1407372791" MODIFIED="1333536011832" TEXT="([link][vref]: virtual refs)"/>
+</node>
+<node CREATED="1333521966550" ID="ID_462181887" MODIFIED="1333529126015" TEXT="gitolite [options][]"/>
+<node CREATED="1333521972558" ID="ID_1180232837" MODIFIED="1333536152124" TEXT=""[git config][git-config]" keys and values"/>
+<node CREATED="1333299726939" ID="ID_1229702100" MODIFIED="1333601090414" TEXT="["wild"][wild] repos (user created repos)">
+<node CREATED="1333329088398" ID="ID_533855844" MODIFIED="1333630553509" TEXT="quick introduction"/>
+<node CREATED="1333599529910" ID="ID_1393418948" MODIFIED="1333630403135" TEXT="(admin) declaring wild repos in the conf file"/>
+<node CREATED="1333629999807" ID="ID_139250181" MODIFIED="1333630432141" TEXT="(user) [creating][create] a specific repo"/>
+<node CREATED="1333599928516" ID="ID_439610891" MODIFIED="1333600901677" TEXT="repo patterns"/>
+<node CREATED="1333329137043" ID="ID_1876270574" MODIFIED="1333599539626" TEXT="roles"/>
+<node CREATED="1333329119800" ID="ID_1196899132" MODIFIED="1333600898568" TEXT="adding other roles">
+<node CREATED="1333630582435" ID="ID_792555451" MODIFIED="1333630596161" TEXT="[IMPORTANT WARNING ABOUT THIS FEATURE][rolenamewarn]"/>
+</node>
+<node CREATED="1333299760157" ID="ID_1787203633" MODIFIED="1333600934866" TEXT="listing wild repos"/>
+<node CREATED="1333299751513" ID="ID_1503072743" MODIFIED="1333600958915" TEXT="deleting wild repos"/>
+</node>
+</node>
+</node>
+<node CREATED="1333326619348" ID="ID_697668065" MODIFIED="1333537363837" POSITION="right" TEXT="what your [user][]s need to know"/>
+<node CREATED="1333300236173" ID="ID_1026631043" MODIFIED="1333607025161" POSITION="right" TEXT="[special][] features/setups">
+<node CREATED="1333327232490" ID="ID_849345095" MODIFIED="1333631333199" TEXT="[disabling pushes][writable] to take backups"/>
+<node CREATED="1333300243659" ID="ID_106425369" MODIFIED="1333604404937" TEXT="[personal][pers] branches"/>
+<node CREATED="1333300501314" ID="ID_1819387758" MODIFIED="1333606161557" TEXT="[delegating][deleg] access control responsibilities">
+<node CREATED="1333606177736" ID="ID_1153299625" MODIFIED="1333606191734" TEXT="([link][NAME]: the NAME VREF)"/>
+<node CREATED="1333300531135" ID="ID_206710372" MODIFIED="1333606536438" TEXT="the [subconf][] command"/>
+</node>
+<node CREATED="1333300925828" ID="ID_1848845001" MODIFIED="1333626115598" TEXT="([link][partial-copy]: faking selective READ control)"/>
+</node>
+<node CREATED="1333327469941" ID="ID_1230549116" MODIFIED="1333607053576" POSITION="right" TEXT="interfacing with [external][] tools">
+<node CREATED="1333327476469" ID="ID_1887257091" MODIFIED="1333327480072" TEXT="gitweb">
+<node CREATED="1333504877358" ID="ID_1448276401" MODIFIED="1333625357439" TEXT="changing the [UMASK][umask]"/>
+</node>
+<node CREATED="1333327480559" ID="ID_513431483" MODIFIED="1333327483014" TEXT="git-daemon"/>
+</node>
+<node CREATED="1333326753755" ID="ID_16865998" MODIFIED="1333625410973" POSITION="right" TEXT="[mirroring][]"/>
+<node CREATED="1333299976094" ID="ID_1948442779" MODIFIED="1333625437290" POSITION="right" TEXT="[rare][]/one-time activities">
+<node CREATED="1333299985915" ID="ID_530631392" MODIFIED="1333471109149" TEXT="moving [existing][] repos into gitolite"/>
+<node CREATED="1333300099453" ID="ID_1521618753" MODIFIED="1333625463686" TEXT="[moving][] servers"/>
+</node>
+<node CREATED="1333299141061" ID="ID_37630832" MODIFIED="1333625569805" POSITION="right" TEXT="[customisation][cust]">
+<node CREATED="1333299458493" ID="ID_992003929" MODIFIED="1333625583059" TEXT="types of non-core programs">
+<node CREATED="1333625624553" ID="ID_1205434644" MODIFIED="1333639834277" TEXT="([link][non-core]: non-core programs shipped with gitolite)"/>
+</node>
+<node CREATED="1333625674563" ID="ID_603892527" MODIFIED="1333625679247" TEXT="[commands][]"/>
+<node CREATED="1333625679584" ID="ID_230664255" MODIFIED="1333625682401" TEXT="[hooks][]"/>
+<node CREATED="1333625689987" ID="ID_112339937" MODIFIED="1333625696128" TEXT="syntactic [sugar][]"/>
+<node CREATED="1333625703698" ID="ID_1165978069" MODIFIED="1333625715099" TEXT="([link][triggers]: triggers)"/>
+<node CREATED="1333625717605" ID="ID_1483309372" MODIFIED="1333625734532" TEXT="([link][vref]: VREFs)"/>
+<node CREATED="1333625757836" ID="ID_1102952567" MODIFIED="1333625766871" TEXT="[developer notes][dev-notes]">
+<node CREATED="1333625797339" ID="ID_1760918724" MODIFIED="1333625812051" TEXT="environment variables and other inputs"/>
+<node CREATED="1333625816655" ID="ID_420128580" MODIFIED="1333625818681" TEXT="APIs">
+<node CREATED="1333625820871" ID="ID_672321981" MODIFIED="1333625823262" TEXT="the shell API"/>
+<node CREATED="1333625823632" ID="ID_206993094" MODIFIED="1333625825547" TEXT="the perl API"/>
+</node>
+<node CREATED="1333625864473" ID="ID_1009525945" MODIFIED="1333625888705" TEXT="writing your own...">
+<node CREATED="1333625889803" ID="ID_323248071" MODIFIED="1333625890797" TEXT="hooks"/>
+<node CREATED="1333625891081" ID="ID_774724756" MODIFIED="1333625895322" TEXT="commands"/>
+<node CREATED="1333625956866" ID="ID_1107908272" MODIFIED="1333625960910" TEXT="trigger programs"/>
+<node CREATED="1333625961452" ID="ID_159826002" MODIFIED="1333625967173" TEXT="sugar"/>
+</node>
+</node>
+</node>
+<node CREATED="1333300577608" ID="ID_583410113" MODIFIED="1333537235253" POSITION="right" TEXT="background info">
+<node CREATED="1333299088617" ID="ID_1919571562" MODIFIED="1333537208005" TEXT="[files and directories][files] involved in install+setup"/>
+<node CREATED="1333300378018" ID="ID_584192327" MODIFIED="1333536676126" TEXT="[auth][]entication versus authorisation">
+<node CREATED="1333300390847" ID="ID_384452374" MODIFIED="1333453722001" TEXT="interfacing with [other authentication][otherauth] systems"/>
+<node CREATED="1333300437860" ID="ID_99922512" MODIFIED="1333453750215" TEXT="getting user group info from [LDAP][ldap]"/>
+</node>
+<node CREATED="1333301124871" ID="ID_931083276" MODIFIED="1333453771459" TEXT="[ssh][]"/>
+<node CREATED="1333301127955" ID="ID_1527577153" MODIFIED="1333453780482" TEXT="[regular expressions][regex]"/>
+</node>
+<node CREATED="1333300587911" ID="ID_91544543" MODIFIED="1333641141649" POSITION="right" TEXT="contributed software, tools, and documentation">
+<node CREATED="1333300608550" ID="ID_1486318266" MODIFIED="1333626152668" TEXT="TBD"/>
+</node>
+<node CREATED="1333301296248" ID="ID_571518549" MODIFIED="1333526198202" POSITION="right" TEXT="TBD">
+<node CREATED="1333327082853" ID="ID_488765250" MODIFIED="1333327089444" TEXT="log file format, LOG_EXTRA"/>
+<node CREATED="1333301298504" ID="ID_60946303" MODIFIED="1333301300418" TEXT="smart http"/>
+<node CREATED="1333301308136" ID="ID_1900285587" MODIFIED="1333301311863" TEXT="hub"/>
+<node CREATED="1333301312124" ID="ID_843247306" MODIFIED="1333301313052" TEXT="sskm"/>
+<node CREATED="1333328274461" ID="ID_248606591" MODIFIED="1333328277083" TEXT="mob branches"/>
+<node CREATED="1333328277387" ID="ID_1027016949" MODIFIED="1333328280083" TEXT="password access"/>
+</node>
+</node>
+</map>
diff --git a/doc/git-config.mkd b/doc/git-config.mkd
new file mode 100644
index 0000000..e1c1c00
--- /dev/null
+++ b/doc/git-config.mkd
@@ -0,0 +1,66 @@
+# "git-config" keys and values
+
+Here's all you want to know about setting repo-specific git-config values.
+
+(Original version thanks to teemu dot matilainen at iki dot fi)
+
+>   ----
+
+>   **Note**: this won't work unless the rc file has the right settings;
+>   please see `$GIT_CONFIG_KEYS` in the [rc file doc][rc].
+
+>   ----
+
+The syntax is simple:
+
+    config sectionname.keyname = [optional value_string]
+
+For example:
+
+    repo gitolite
+        config hooks.mailinglist = gitolite-commits at example.tld
+        config hooks.emailprefix = "[gitolite] "
+        config foo.bar = ""
+        config foo.baz =
+
+This does either a plain "git config section.key value" (for the first 3
+examples above) or "git config --unset-all section.key" (for the last
+example).  Other forms of the `git config` command (`--add`, the
+`value_regex`, etc) are not supported.
+
+>   ----
+
+>   **WARNING**: simply deleting the config line from the `conf/gitolite.conf`
+>   file will *not* delete the variable from `repo.git/config`.  The syntax in
+>   the last example is the *only* way to make gitolite execute a
+>   `--unset-all` operation on the given key.
+
+>   ----
+
+You can also use the special value `%GL_REPO` in the string to save typing:
+
+    repo foo bar baz
+        config hooks.mailinglist = %GL_REPO-commits at example.tld
+        config hooks.emailprefix = "[%GL_REPO] "
+
+You can repeat the 'config' line as many times as you like, and the last
+occurrence will be the one in effect.  This allows you to override settings
+just for one project, as in this example:
+
+    repo @all
+        config hooks.mailinglist = %GL_REPO-commits at example.tld
+        config hooks.emailprefix = "[%GL_REPO] "
+
+    repo customer-project
+        # different mailing list
+        config hooks.mailinglist = announce at customer.tld
+
+The "delete config variable" syntax can also be used, if you wish:
+
+    repo secret     # no emails for this one please
+        config hooks.mailinglist =
+        config hooks.emailprefix =
+
+As you can see, the general idea is to place the most generic ones (`repo
+ at all`, or repo patterns like `repo foo.*`) first, and place more specific ones
+later to override the generic settings.
diff --git a/doc/group.mkd b/doc/groups.mkd
similarity index 91%
rename from doc/group.mkd
rename to doc/groups.mkd
index f9e14b0..970ea1d 100644
--- a/doc/group.mkd
+++ b/doc/groups.mkd
@@ -1,6 +1,4 @@
-# parts of the conf file
-
-## #group group definitions
+# group definitions
 
 You can group repos or users for convenience.  The syntax is the same for both
 and does not distinguish; until you *use* the group name it could really be
@@ -26,7 +24,7 @@ right there (meaning later additions will not appear in the second group).
 
     # wally is NOT part of @staff
 
-### special group `@all`
+## special group `@all`
 
 `@all` is a special group name that is often convenient to use if you really
 mean "all repos" or "all users".
diff --git a/doc/index.mkd b/doc/index.mkd
index 0756294..a2ec9c1 100644
--- a/doc/index.mkd
+++ b/doc/index.mkd
@@ -5,49 +5,47 @@ fine-grained access control and many more powerful features.
 
 Here's more on [what][] it is and [why][] you might need it.
 
+<font color="gray">
+
+## #g2 (for current gitolite (v2) users)
+
 For current gitolite (call it "g2" for convenience) users,
 
   * [Why][g3why] I rewrote gitolite.
-  * Development [status][dev-status] (**should change often for a while**)
+  * Development [status][dev-status].
   * Specific migration [issues and steps][g2migr].
 
-Quick links:
-
-  * [Minimum requirements][minreq].
-  * Here's how to [get started][qi] installing and setting it up
-  * Don't know ssh well enough?  [Learn][ssh].  It's **IMPORTANT**.
-  * [Add users and repos][add].
-  * Learn about fine-grained access control with the [conf][] file
-  * Explain gitolite to your [users][].
+</font>
 
-Not so "quick" links:
+## #ql quick links
 
-  * The "master table of contents" link at the top of each page is the
-    **first** place you should check when looking for anything.
-
-  * Want to do more than just add users and repos and control their access?
-    Here's how to [customise][cust] your server installation.
-
-Additional reading for Unix newbies:
+  * Minimum [requirements][req].
+  * Here's how to do a [quick install, setup, and clone][qi].
+  * Don't know ssh well enough?  [Learn][ssh].  It's **IMPORTANT**.
+  * Add [users][] and [repos][].
+  * Learn about fine-grained access control with the [conf][] file.
+  * Explain gitolite to your [users][user].
 
-  * [Regular Expressions][regex]
+The rest of the documentation is in the "master index" link at the top of each
+page on this website.  This is the first place you should search when looking
+for specific information.
 
 ## #what What is gitolite?
 
 Gitolite is an access control layer on top of git.  Here are the features that
 most people see:
 
-  * use a single unix user ("real" user) on the server
-  * provide access to many gitolite users
+  * Use a single unix user ("real" user) on the server.
+  * Provide access to many gitolite users:
       * they are not "real" users
       * they do not get shell access
-  * control access to many git repositories
+  * Control access to many git repositories:
       * read access controlled at the repo level
       * write access controlled at the branch/tag/file/directory level,
         including who can rewind, create, and delete branches/tags
-  * can be installed without root access, assuming git and perl are already
-    installed
-  * authentication is most commonly done using sshd, but you can also use
+  * Can be installed without root access, assuming git and perl are already
+    installed.
+  * Authentication is most commonly done using sshd, but you can also use
     httpd if you prefer (this may require root access).
 
 ## #contact contact
diff --git a/doc/install.mkd b/doc/install.mkd
index 51ca10f..1578161 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -1,37 +1,122 @@
-# different ways to install gitolite
-
-Gitolite has only one server side "command" now, much like git itself.  And
-it's been designed so that you don't even really have to *install* it, as you
-will see.
+# installing gitolite
 
 **NOTE**: if you're migrating from g2, there are some settings that MUST be
 dealt with **before** running `gitolite setup`; please read the [g2
 migration][g2migr] page and linked pages, and especially the one on
-'presetting the rc file][rc-preset].
+[presetting the rc file][rc-preset].
+
+## notes and naming conventions
+
+Gitolite uses a single "real" (i.e., unix) user to provide secure access to
+git repos to any number of "virtual" users, without giving them a shell.
+
+The real user used is called the **hosting user**.  Typically this user is
+*git*, and that is what we will use throughout the documentation.  However
+RPMs and DEBs create a user called *gitolite* for this, so adjust instructions
+and examples accordingly.
+
+Notes:
+
+  * Any unix user can be a hosting user.
+  * Which also means you can have several hosting users on the same machine.
+  * The URLs used will be of the form `git at host:reponame` (or its longer
+    equivalent starting with `ssh://`).  The `.git` at the end is optional.  I
+    recommend you leave it out, so your reponames are consistent with what the
+    conf file uses.
+
+## #req requirements
+
+### your skills
+
+  * If you're installing gitolite, you're a "system admin", like it or not.
+    Ssh is therefore a necessary skill.  Please take the time to learn at
+    least enough to get passwordless access working.
+
+  * You also need to be somewhat familiar with git itself.  You cannot
+    administer a whole bunch of git repositories if you don't know the basics
+    of git.
+
+  * Some familiarity with Unix and shells is probably required.
+
+  * Regular expressions are a big part of gitolite in many places but
+    familiarity is not necessary to do *basic* access control.
+
+### server
+
+  * any Unix system with a posix compatible "sh".
+  * git version 1.6.6 or later
+  * perl 5.8.8 or later
+  * openssh (almost any version).  Optional if you're using the http backend
+    (which is still a TODO item!)
+  * a dedicated Unix userid to be the hosting user, usually "git" but it can
+    be any user, even your own normal one.  (If you're using an RPM/DEB the
+    install probably created one called "gitolite").
+
+Also see the [WARNINGS][] page for more on what gitolite expects on the server
+side.
+
+### client
+
+  * openssh client
+  * git 1.6.6 or later.  Almost any git client will work, as long as it knows
+    how to use ssh keys and send the right one along.
+
+## getting the software
+
+    git clone -b g3 git://github.com/sitaramc/gitolite
+
+The -b g3' is needed until g3 becomes "master".  Current estimates put this
+around June 2012, when the old gitolite (upto v2.x) will be retired and
+supported only for security issues.
+
+## the actual install
+
+Gitolite has only one server side "command" now, much like git itself.  This
+command is `gitolite`.  You don't need to place it anywhere special; worst
+case you run it with the full path.
 
-## simplest
+"Installation" consists of the following options:
 
-1.  Put all of `src` in one place, doesn't matter where; let's call it
-    /foo/bar.
+1.  Keep the sources anywhere and use the full path to run the `gitolite`
+    command.
+2.  Keep the sources anywhere and symlink *just* the `gitolite` program to
+    some directory on your `$PATH`.
+3.  Copy the sources somewhere and use that path to run the `gitolite`
+    command.
 
-2.  Use the full path to run any gitolite commands, for example:
+Option 2 is the best for general use.
 
-        /foo/bar/gitolite setup -pk sitaram.pub
+There is a program called 'install' that helps you do these easily.  Assuming
+your cloned the repo like this:
 
-## almost as simple
+    git clone -b g3 git://github.com/sitaramc/gitolite
 
-1.  (same as above)
+you can run the 'install' command in 3 different ways:
 
-2.  Symlink /foo/bar/gitolite to some directory that is on your PATH.  For
-    example:
+    # option 1
+    gitolite/install
 
-        ln -sf /foo/bar/gitolite ~/bin
+    # option 2
+    gitolite/install -ln
+    # defaults to $HOME/bin, or use a specific directory:
+    gitolite/install -ln /usr/local/bin
 
-    Now you can just say
+    # option 3
+    gitolite/install -to /usr/local/gitolite/bin
 
-        gitolite setup -pk sitaram.pub
+Creating a symlink doesn't need a separate program but 'install' also runs
+`git describe` to create a VERSION file, which, trust me, is important!
 
-## packagers
+**Next step**: run [**setup**][setup].
+
+## upgrading
+
+Just put the new version on top of wherever you kept the old one.  That's it.
+
+If you feel it should require a little more effort, pretend I said "you have
+to then run `gitolite setup`".  Won't hurt...
+
+## packaging gitolite
 
 1.  Put src/Gitolite in `/usr/share/perl5/vendor_perl` or some such place.
 
@@ -39,25 +124,44 @@ migration][g2migr] page and linked pages, and especially the one on
     git's 150 executables in /usr/libexec/git-core, so maybe
     /usr/libexec/gitolite?)
 
-3.  Symlink 'gitolite' to /usr/bin or something, similar to step 2 above,
-
-    OR
-
-    Put it directly in /usr/bin, and hardcode `GL_BINDIR` into it to tell it
-    where the others are.  I'd prefer it if you did not do this but you can.
+3.  Symlink 'gitolite' to /usr/bin or something, similar to option 2.
 
-----
-
-Bottom line:
+**Bottom line**:
 
   * `GL_BINDIR` must point to a place that contains `commands`, `VREF`, and
     `syntactic-sugar` (so they must all be sibling directories).
   * The `Gitolite` directory can also be there, or it can be anywhere in
     perl's `@INC` path.
 
-## upgrading
+## migrating
 
-Just put the new version on top of wherever you kept the old one.  That's it.
+There are a lot of migration hints and notes; see links at the top of this
+document.
 
-If you feel it should require a little more effort, pretend I said "you have
-to then run `gitolite setup`".  Can't hurt...
+But here's the **bottom line** on migrating:
+
+Nothing in any of the gitolite install/setup/etc will ever touch the *data* in
+any repository except the gitolite-admin repo.  The only thing it will
+normally touch is the `update` hook.  So one fool-proof way of "migrating"
+from any older gitolite is this:
+
+1.  Wipe out the old gitolite, but **carefully**
+
+      * clone `~/repositories/gitolite-admin.git` to someplace, then delete it
+        (and only it, none of the other repos!) from `~/repositories`
+      * rename `~/.gitolite.rc` to something else
+      * move the ~/.gitolite/logs` directory somewhere else, then delete `~/.gitolite`
+
+2.  Install gitolite g3; see [quick install and setup][qi] or [install][]
+    followed by [setup][].
+
+3.  Make sure your gitolite-admin clone has the correct pubkey for the
+    administrator in its `keydir` directory, then `git push -f` to the server
+    to overwrite the "default" admin repo created by the install.
+
+4.  Handle any errors, look for migration issues, etc., as described in the
+    links at the top of this page.
+
+    This also includes building up your new `~/.gitolite.rc` file.
+
+You're done.
diff --git a/doc/list b/doc/list
deleted file mode 100644
index 46b0f34..0000000
--- a/doc/list
+++ /dev/null
@@ -1,45 +0,0 @@
-
-index.mkd
-why.mkd
-
-g3why.mkd
-dev-status.mkd
-g2migr.mkd
-g2rcdiff.mkd
-g2incompat.mkd
-g2dropped.mkd
-g2alt.mkd
-
-minreq.mkd
-qi.mkd
-install.mkd
-add.mkd
-users.mkd
-
-conf.mkd
-
-group.mkd
-repo.mkd
-rules.mkd
-refex.mkd
-write-types.mkd
-
-rc.mkd
-
-cust.mkd
-triggers.mkd
-vref.mkd
-
-non-core.mkd
-
-dev-notes.mkd
-
-misc.mkd
-pw.mkd
-testing.mkd
-extras/auth.mkd
-extras/nagp.mkd
-extras/regex.mkd
-extras/ssh.mkd
-extras/gitolite-and-ssh.mkd
-extras/ssh-troubleshooting.mkd
diff --git a/doc/minreq.mkd b/doc/minreq.mkd
deleted file mode 100644
index c3ab4f4..0000000
--- a/doc/minreq.mkd
+++ /dev/null
@@ -1,32 +0,0 @@
-# minimum requirements for gitolite
-
-**Client**:
-
-  * git 1.6.6 or greater
-  * an ssh client that can talk to an openssh server, and can generate keys in
-    openssh's default format (the pubkey is just one long line).  Gitolite
-    will [not currently][pw1] convert such keys.
-
-For people still using Windows, msysgit works fine.  If you're using
-[putty/plink][ens], God bless you.  (It'll work, but I still want him to bless
-you).
-
-TODO: when smart http support works, ssh will no longer be a *requirement*,
-merely a *strong* suggestion :-)
-
-**Server**
-
-  * git 1.6.6 or greater
-  * perl 5.8.8 or greater
-  * an ssh server compatible with openssh, especially it's authorized keys
-    file format and features.
-  * any Unix or Unix like OS.  That said, I've occasionally had some weird
-    reports from [Mac OSX servers][ens]; good luck.
-  * a single, dedicated, userid to host it (usually 'git' or 'gitolite').
-
-These version numbers are subject to fine-tuning as I get feedback and make
-fixes where possible and needed.
-
-Sshd must be configured so that each users authkeys file is in the user's
-`$HOME`, inside `.ssh/authorized_keys`, and not in some central
-/var/something.
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
index 056f080..959b085 100644
--- a/doc/mirroring.mkd
+++ b/doc/mirroring.mkd
@@ -1,18 +1,19 @@
 # mirroring using gitolite
 
-**WARNING** existing gitolite mirroring users please note: significant changes
-in syntax and usage compared to g2.  The most important change is that `config
-gitolite.mirror.master` changes to `option mirror.master`, and similarly for
-'slaves' and redirectOK.  In addition, the special value for 'redirectOK'
-changes from '1' to 'all'.  The second big change is the disappearance of the
-ambiguously named 'keys' (for example `gitolite.mirror.hourly` etc., in the
-old mirroring doc) -- all that complexity is yours to handle now :-)
+**WARNING** existing gitolite mirroring users please note: **there are
+significant changes** in syntax and usage compared to g2.  The most important
+change is that `config gitolite.mirror.master` changes to `option
+mirror.master`, and similarly for 'slaves' and redirectOK.  In addition, the
+special value for 'redirectOK' changes from '1' to 'all'.  The second big
+change is the disappearance of the ambiguously named 'keys' (for example
+`gitolite.mirror.hourly` etc., in the old mirroring doc) -- all that
+complexity is yours to handle now :-)
 
 ----
 
-Mirroring is simple: you have one "master" and one or more "slaves".  The
-slaves get updates only from the master; to the rest of the world they are at
-best read-only.
+Mirroring is simple: you have one "master" server and one or more "slave"
+servers.  The slaves get updates only from the master; to the rest of the
+world they are at best read-only.
 
 Gitolite extends this simple notion in the following ways:
 
@@ -52,12 +53,12 @@ file settings and syntax.
 
 ### the initial setup and the rc file
 
-On each server:
+On **each** server:
 
-  * install gitolite normally.  Make clones of each server's 'gitolite-admin'
+  * install gitolite normally.  Make clones of the server's 'gitolite-admin'
     repo on your workstation so you can admin them all from one place.
 
-  * give each server a short, simple, "hostname" and set the HOSTNAME in the
+  * give the server a short, simple, "hostname" and set the HOSTNAME in the
     rc file to this name, for example 'mars'.
 
   * run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
@@ -65,14 +66,14 @@ On each server:
     prefixed.  So the pubkey for server 'mars' would be stored as
     'server-mars.pub'.
 
-  * copy all keys to **each of** the admin repo clones on your workstation and
-    and add them as usual.
+  * copy all keys to all the admin repo clones on your workstation and and add
+    them as usual.  This is an `O(N^2)` operation ;-)
 
     You may have guessed that the prefix 'server-' is special, and
     distinguishes a human user from a mirroring peer.
 
-  * create "host" aliases on each machine to refer to all other machines.  See
-    [here][ssh-ha] for what/why/how.
+  * create "host" aliases to refer to all other machines.  See [here][ssh-ha]
+    for what/how.
 
     The host alias for a host (in all other machines' `~/.ssh/config` files)
     MUST be the same as the `HOSTNAME` in the referred host's
@@ -108,13 +109,12 @@ On each server:
     because on the first push to the master it will update all the slaves
     anyway.
 
-  * when that is all done and tested, **enable mirroring** on each server by
-    going through the rc file and uncommenting all the lines mentioning
-    `Mirroring`.
+  * when that is all done and tested, **enable mirroring** by going through
+    the rc file and uncommenting all the lines mentioning `Mirroring`.
 
 ### conf file settings and syntax
 
-Mirroring is defined by the following [option][]s.  You can have different
+Mirroring is defined by the following [options][].  You can have different
 settings for different repos, and of course some repos may not have any mirror
 options at all -- they are then purely local.
 
diff --git a/doc/misc.mkd b/doc/misc.mkd
deleted file mode 100644
index d598401..0000000
--- a/doc/misc.mkd
+++ /dev/null
@@ -1,132 +0,0 @@
-# odds and ends
-
-Most of these items don't fit anywhere or fit in more than one place or are of
-the nature of background information.
-
-## #include include files
-
-Gitolite allows you to break up the configuration into multiple files and
-include them in the main file for convenience.
-
-    include     "foo.conf"
-
-will include the contents of the file "foo.conf" from the "conf" directory.
-
-Details:
-
-  * You can also use a glob (`include "*.conf"`), or put your include files
-    into subdirectories of "conf" (`include "foo/bar.conf"`), or both
-    (`include "repos/*.conf"`).
-
-  * Included files are always searched relative to the gitolite-admin repo's
-    "conf/" directory.
-
-  * If you ended up recursing, files that have been already processed once are
-    skipped, with a warning.
-
-<font color="gray">Advanced users: `subconf`, a command that is very closely
-related to `include`, is documented [here][subconf].</font>
-
-## #deny-rules applying deny rules at the pre-git access check
-
-The access [rules][] rules section describes the problem.  To recap, you want
-this:
-
-    @staff          =   alice bob wally ashok
-
-    repo foo
-        RW+         =   alice       # line 1
-        RW+ dev     =   bob         # line 2
-        -           =   wally       # line 3
-        RW  temp/   =   @staff      # line 4
-
-to deny Wally even *read* access.
-
-The way to do this is to add this line to the repo:
-
-    option deny-rules = 1
-
-If you want this for all your repos, just add this somewhere at the top of
-your conf file
-
-    repo @all
-        option deny-rules = 1
-
-## #rule-accum rule accumulation
-
-Gitolite was meant to collect rules from multiple places and apply them all.
-For example, this:
-
-    repo foo
-        RW  =   u1
-
-    @gr1 = foo bar
-
-    repo @gr1
-        RW  =   u2
-        R   =   u3
-
-    repo @all
-        R   =   gitweb
-
-is effectively the same as this, for repo foo:
-
-    repo foo
-        RW  =   u1
-        RW  =   u2
-        R   =   u3
-        R   =   gitweb
-
-This extends to patterns also, but I'll leave an example for later.
-
-## #subconf the subconf command
-
-This is just like the include command:
-
-    subconf "foo/bar.conf"      # example 1
-    subconf "foo/*.conf"        # example 2
-
-with the difference that, for the duration of the file(s) being included, a
-subconf restriction is in effect.  This restrictions limits the repos that can
-be access controlled by the lines within the included file(s).
-
-Here's how it works.  First, a subconf *name* is derived from the filename
-being included, which is basically the basename of the file.  For example 1
-that is "bar".  For example 2, assuming foo contains "a.conf" and "b.conf",
-the subconf name is "a" while processing "a.conf", and "b" while processing
-"b.conf".
-
-A variation of the subconf command allows you to specify the subconf *name*
-explicitly, while including files as before:
-
-    subconf frob "foo/bar.conf  # example 3
-    subconf frob "foo/*.conf    # example 4
-
-In this case the subconf name is "frob" in both cases.
-
-A subconf restricts the repos that can be named in 'repo' lines while the
-subconf is in effect.  If the subconf name is "foo", the conf lines parsed
-while under the subconf restriction can only refer to
-
-  * a repo called 'foo'
-  * a repo group called '@foo'
-  * the members of the same repo group '@foo'
-  * match a regex that is a member of the repo group '@foo'
-
-In the last 3 cases, the repo group '@foo' must be defined in the main conf
-file (i.e., outside the subconf restriction).
-
-For example, if you have this in the main conf file:
-
-    @foo    =   bar baz frob/..*
-
-    subconf "foo.conf"
-
-then foo.conf can only refer to 'foo', '@foo', 'bar', 'baz', or any repo name
-matching the pattern `frob/..*`.
-
-## #HOSTNAME HOSTNAME substitution
-
-Wherever gitolite sees the word `%HOSTNAME`, it will replace it with the
-HOSTNAME supplied in the rc file, if one was supplied.  This is mainly useful
-in [mirroring][].
diff --git a/doc/mkdoc b/doc/mkdoc
index 998cf1d..863eebe 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -13,6 +13,8 @@ use Tsh;
 $ENV{TSH_ERREXIT} = 1;
 
 try "
+    ./fm2mt.pl < g3-master-toc.mm > master-toc.mkd
+
     mkdir ../html;                      ok
     git status -s -uno;                 !/./
     git log --oneline -1
@@ -36,8 +38,8 @@ try "
 " or die 2;
 
 sub main {
-    chomp(@ARGV = `cat list`) if not @ARGV;
-    @ARGV = grep { $_ ne 'master-toc.mkd' and /./ } @ARGV;
+    chomp(@ARGV = `find . -name "*.mkd" | cut -c3-`) if not @ARGV;
+    @ARGV = grep { /./ } @ARGV;
     my @save = @ARGV;
     my $css = join("", <DATA>);
 
@@ -68,16 +70,16 @@ sub main {
         }
     }
 
-    open($fh, ">", "master-toc.mkd")
-      and print $fh $mt
-      and close $fh;
+    # open($fh, ">", "master-toc.mk2")
+    #   and print $fh $mt
+    #   and close $fh;
 
     # after this, do this for every mkd (including the master-toc.mkd)
 
     #       cat $css_block > $base.html
     #       cat $base.mkd $mf | $MKD >> $base.html
 
-    for my $mkd ("master-toc.mkd", @save) {
+    for my $mkd (@save) {
         $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
         my $b = $1;
 
@@ -88,7 +90,7 @@ sub main {
         my $mkt = `cat $mkd`;
         $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
         open($fh, "|-", "$MKD >> ../html/$b.html")
-          and print $fh $mkt, $mf
+          and print $fh $mkt, "\n\n", $mf
           and close $fh;
     }
 }
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
index bc82515..7583b66 100644
--- a/doc/non-core.mkd
+++ b/doc/non-core.mkd
@@ -6,8 +6,8 @@ A list of these commands can be obtained by running `gitolite help` on the
 server.  A different (and probably much smaller) list can be obtained by a
 remote user running `ssh git at host help`.
 
-All the commands will respond to `-h`; please report a bug to me if they
-don't.
+All the commands that ship with gitolite will respond to `-h`; please report a
+bug to me if they don't.
 
 ## syntactic sugar
 
@@ -43,7 +43,7 @@ The `POST_GIT` triggers are:
 The `POST_COMPILE` triggers are:
 
   * post-compile/ssh-authkeys -- takes the pubkeys in keydir and populates
-    `~/.ssh/authorized_keys`
+    `~/.ssh/authorized_keys`.
 
   * post-compile/update-git-configs -- updates individual 'repo.git/config'
     files (using the 'git config ...' command) from settings supplied in the
@@ -52,7 +52,7 @@ The `POST_COMPILE` triggers are:
 
   * post-compile/update-git-daemon-access-list -- create/delete
     'git-daemon-export-ok' files in each repo based on whether the conf says
-    'daemon' can read the repo or not
+    'daemon' can read the repo or not.
 
   * post-compile/update-gitweb-access-list -- populates the file named in
     `GITWEB_PROJECTS_LIST` in the rc file (default: `$HOME/projects.list`)
@@ -60,7 +60,7 @@ The `POST_COMPILE` triggers are:
     more than just "R = gitweb"; any repo that has any config setting with the
     section name 'gitweb' (like 'gitweb.owner', 'gitweb.description', etc) is
     considered readable by gitweb, so the final list is a union of these two
-    methods
+    methods.
 
 The `POST_CREATE` triggers are:
 
@@ -69,56 +69,11 @@ The `POST_CREATE` triggers are:
 
 ## VREFs
 
-You should read about [vref][]s in detail first; this won't make sense
-otherwise.  For a brief recap, note that there are 2 kinds of VREFs: those
-that require arguments and those that behave just like any other `update`
-hook.
-
-COUNT is an example of the former (hence the long-ish description).  DUPKEYS
-and EMAIL-CHECK are both examples of the latter.
-
-  * COUNT
-
-    The COUNT VREF is used like this:
-
-        -   VREF/COUNT/9                    =   @junior-developers
-
-    In response, if anyone in the user list pushes a commit series that
-    changes more than 9 files, a vref of "VREF/COUNT/9" is returned.  Gitolite
-    uses that as a "ref" to match against all the rules, hit the same rule
-    that invoked it, and deny the request.
-
-    If the user did not push more than 9 files, the VREF code returns nothing,
-    and nothing happens.
-
-    COUNT can take one more argument:
-
-        -   VREF/COUNT/9/NEWFILES           =   @junior-developers
-
-    This is the same as before, but have to be more than 9 *new* files not
-    just changed files.
-
-  * DUPKEYS -- this checks keydir/ for duplicate keys and aborts the push if
-    it finds any.  You should use this only on the gitolite-admin repo.
-
-        repo gitolite-admin
-            -   VREF/DUPKEYS                =   @all
-
-  * EMAIL-CHECK -- read the comments in the code for this one.  Like DUPKEYS,
-    it does not take any arguments.
-
-  * FILETYPE -- this is sample code for a very site-specific purpose; you'll
-    have to read the code
-
-  * MERGE-CHECK -- this is sample code to illustrate how one of the gitolite
-    built-in functions *could* have been handled, although there are some
-    differences
-
-  * partial-copy -- this has its own section later in this page
+VREFs have their [own page][vref].
 
 ## special cases
 
-### partial-copy
+### #partial-copy partial-copy
 
 Git (and therefore gitolite) cannot do selective read control -- allowing
 someone to read branch A but not branch B.  It's the entire repo or nothing.
@@ -155,6 +110,6 @@ WARNINGS:
     you should delete the partial repo on the server and then run 'gitolite
     compile' to let it build again.  See t/partial-copy.t for details.
 
-  * not tested with smart http; probabl won't work
+  * not tested with smart http; probably won't work
 
   * also not tested with mirroring, or with wild card repos.
diff --git a/doc/options.mkd b/doc/options.mkd
new file mode 100644
index 0000000..22d12af
--- /dev/null
+++ b/doc/options.mkd
@@ -0,0 +1,20 @@
+# gitolite options
+
+Some gitolite features are enabled, or gitolite's behaviour changed, by
+setting "options".
+
+Options are set by repo.  The syntax is very simple:
+
+    option  foo.bar     =   baz
+
+Of course this is useless if some other part of gitolite, or some external
+command, is not querying for the option key 'foo.bar'!
+
+Options are therefore documented in the section/page they belong in, not here.
+Here are the currently recognised options:
+
+  * [deny-rules][] -- ask gitolite to honor deny rules during the pre-git
+    check also.
+
+  * [mirroring][] related options -- tell gitolite who is the master server,
+    and who are the slaves, for each repo.
diff --git a/doc/pw.mkd b/doc/pw.mkd
deleted file mode 100644
index ceaf995..0000000
--- a/doc/pw.mkd
+++ /dev/null
@@ -1,45 +0,0 @@
-# patches welcome :-)
-
-These are places where I could use some help, with hints about how you would
-go about it.  In addition, any of the items in [dev-status][] are up for
-grabs, if you wish to jump in.  But let me know before you start, and how you
-plan to go about it.
-
-## #pw2 rsync
-
-The crux of the old rsync ADC was an access check to a repo whose name starts
-with EXTCMD.  g2 would treat repos whose names start with EXTCMD as "don't
-create this repo".  g3 makes no such distinctions, but you can get the same
-effect by using a repo group name that does not exist:
-
-    repo @rsync-set1
-        ...rules...
-
-and check that in the script.  (It might mean adding one more function to
-Gitolite::Easy but that's... easy!)
-
-## #pw1 converting non-openssh pubkeys automatically
-
-Gitolite's [triggers][] has can be used to fix this.  Here's pseudo-code:
-
-    chdir $(gitolite query-rc -n GL_ADMIN_BASE)/keydir
-    for each *.pub file in this directory and its subdirectories
-        detect its format and convert it
-        # just overwrite it; the original is saved in git anyway
-
-Let's say the program is called "convert-non-openssh-keys".  You send it to me
-and I add it to src/commands/post-compile/.  Then I update the default rc file
-to look at least like this instead
-
-    POST_COMPILE                =>
-        [
-            # 'post-compile/convert-non-openssh-keys',
-            'post-compile/ssh-authkeys',
-                ...
-                ...
-        ],
-
-making sure to place it *before* ssh-authkeys, and anyone who needs it can
-just uncomment it.
-
-Done!
diff --git a/doc/qi.mkd b/doc/qi.mkd
index 3337070..9ddbb77 100644
--- a/doc/qi.mkd
+++ b/doc/qi.mkd
@@ -1,26 +1,45 @@
-# getting started
+# quick install, setup, and clone
 
-The quickest install, assuming your `$PATH` contains `$HOME/bin`, is:
+(Please do not ignore the "assumptions" list below).
 
-  * get the software
+On the server:
 
-        git clone git://github.com/sitaramc/gitolite
+    # get the software
+    git clone -b g3 git://github.com/sitaramc/gitolite
 
-        # (until this becomes "master")
-        cd gitolite
-        git checkout -f g3
+    # install it
+    gitolite/install -ln
 
-  * install it
+    # setup the initial repos with your key
+    gitolite setup -pk your-name.pub
 
-        ln -sf $PWD/src/gitolite $HOME/bin
+On your workstation:
 
-If you don't like that, there are [other install methods][install].
+    # clone the admin repo so you can start adding stuff
+    git clone git at host:gitolite-admin.git
 
-Once the install is done, setup:
+## ASSUMPTIONS
 
-    gitolite setup -pk your-name.pub
+  * on the server, your `$PATH` contains `$HOME/bin`.  If you don't like that,
+    there are [other install methods][install].
+
+  * "your-name.pub" is your public key from your workstation.
+      * also, this key does not already have shell access to this gitolite
+        hosting user
+
+  * the setup command does not generate any warnings.
+      * if it does, please see [common errors][ce] and fix things before
+        continuing, or read the more complete [setup][] page.
+
+## Notes
+
+Note that the clone path is NOT "repositories/gitolite-admin.git".  If you
+clone with a path that includes "repositories/", the clone should fail.  If
+the clone *does* succeed, a subsequent push should fail :-)  See
+[this][ybpfail] for some details.  If that doesn't make enough sense read all
+of [ssh][].
 
-And that's it.
+## next steps
 
-Next steps are usually [adding][add] users and repos and learning about
+Next steps are usually adding [users][] and [repos][] and learning about
 [access control][conf].
diff --git a/doc/rare.mkd b/doc/rare.mkd
new file mode 100644
index 0000000..9e7be4e
--- /dev/null
+++ b/doc/rare.mkd
@@ -0,0 +1,41 @@
+# rare or one-time activities
+
+## #existing moving existing repos into gitolite
+
+  * move the repos to `$HOME/repositories`.  Make sure they are all *bare*
+    repos, and the directory names end in ".git".
+
+  * [add them][repos] to conf/gitolite.conf in your clone of the admin repo,
+    then commit and push the change.
+
+    If the repos are already covered by some [wild][] pattern, this is
+    optional.
+
+  * run `gitolite setup` to fix up the hooks on all repos, just in case.
+
+## #moving moving servers
+
+This is adapted from the "migrating" section of the [install][] page; if
+you're actually *migrating* from g2 please go there!
+
+Nothing in any of the gitolite install/setup/etc will ever touch the *data* in
+any repository except the gitolite-admin repo.  The only thing it will
+normally touch is the `update` hook.  So one fool-proof way of "moving"
+servers is this (untested but should work; feedback appreciated):
+
+1.  Install gitolite on the new server, using the same key for the admin as
+    for the old server.
+
+2.  Copy the [rc][] file from the old server, overwriting this one.
+
+3.  [Disable][writable] the old server so people won't push to it.
+
+4.  Copy all the repos over from the old server, including gitolite-admin.
+    Make sure the files end up with right ownership and permissions; if not,
+    chown/chmod them.
+
+5.  On a clone of the old gitolite-admin, add a new remote (or change an
+    existing one) to point to the new server.  Then `git push -f` to this
+    remote.
+
+6.  On the server, run `gitolite setup`.
diff --git a/doc/rc.mkd b/doc/rc.mkd
index 03613d0..a582961 100644
--- a/doc/rc.mkd
+++ b/doc/rc.mkd
@@ -1,20 +1,82 @@
-# the "rc" file ($HOME/.gitolite.rc)
+# the "rc" file (`$HOME/.gitolite.rc`)
 
-The rc file for g3 is quite different from that of g2.  It has been designed
-to be (a) the only thing unique to your site, for most installations and (b)
-easy to extend when new needs show up, without having to touch core gitolite.
+**NOTE**: if you're migrating from g2, there are some settings that MUST be
+dealt with **before** running `gitolite setup`; please read the [g2
+migration][g2migr] page and linked pages, and especially the one on
+[presetting the rc file][rc-preset].
 
-g2 had a nasty rc file where every variable had to be declared.  As a result,
-ADCs that needed their own settings could not use it.
+----
 
-Now it's a perl hash, and you can add any keys you want.
+The rc file for g3 is *quite* different from that of g2.
 
-Please look at the rc file that gets installed when you setup gitolite.  As
-you can see there are 3 types of variables in it:
+As before, it is designed to be the only thing unique to your site for most
+setups.  What is new is that it is easy to extend it when new needs come up,
+without having to touch core gitolite.
+
+The rc file is perl code, but you do NOT need to know perl to edit it.  Just
+mind the commas, use single quotes unless you know what you're doing, and make
+sure the brackets and braces stay matched up!
+
+Please look at the `~/.gitolite.rc` file that gets installed when you setup
+gitolite.  As you can see there are 3 types of variables in it:
 
   * simple variables (like UMASK)
   * lists (like `POST_COMPILE`, `POST_CREATE`)
   * hashes (like `ROLES`, `COMMANDS`)
 
+While some of the variables are documented in this file, many of them are not.
 Their purposes are to be found in each of their individual documentation files
-around; start with [customising gitolite][cust].
+around; start with [customising gitolite][cust].  If a setting is used by an
+external command then running that command with '-h' may give you additional
+information.
+
+## specific variables
+
+  * `$UMASK`, octal, default `0077`
+
+    The default UMASK that gitolite uses makes all the repos and their
+    contents have `rwx------` permissions.  People who want to run gitweb
+    realise that this will not do.
+
+    The correct way to deal with this is to give this variable a value like
+    `0027` (note the syntax: the leading 0 is required), and then make the
+    user running the webserver (apache, www-data, whatever) a member of the
+    'git' group.
+
+    If you've already installed gitolite then existing files will have to be
+    fixed up manually (for a umask or 0027, that would be `chmod -R g+rX`).
+    This is because umask only affects permissions on newly created files, not
+    existing ones.
+
+  * `$GIT_CONFIG_KEYS`, string, default empty
+
+    This setting allows the repo admin to define acceptable gitconfig keys.
+
+    Gitolite allows you to set git config values using the "config" keyword;
+    see [here][git-config] for details and syntax.
+
+    However, if you are in an installation where the repo admin does not (and
+    should not) have shell access to the server, then allowing him to set
+    arbitrary repo config options *may* be a security risk -- some config
+    settings allow executing arbitrary commands!
+
+    You have 3 choices.  By default `$GIT_CONFIG_KEYS` is left empty, which
+    completely disables this feature (meaning you cannot set git configs via
+    the repo config).
+
+    The second choice is to give it a space separated list of settings you
+    consider safe.  (These are actually treated as a set of [regular
+    expression][regex] patterns, and any one of them must match).
+
+    For example:
+
+        $GIT_CONFIG_KEYS = 'core\.logAllRefUpdates core\..*compression';
+
+    Each pattern should match the *whole* key (in other words, there
+    is an implicit `^` at the start of each pattern, and a `$` at the
+    end).
+
+    The third choice (which you may have guessed already if you're familiar
+    with regular expressions) is to allow anything and everything:
+    `$GIT_CONFIG_KEYS = '.*';`
+
diff --git a/doc/refex.mkd b/doc/refex.mkd
index e7d8aad..348c3e7 100644
--- a/doc/refex.mkd
+++ b/doc/refex.mkd
@@ -3,11 +3,15 @@
 A refex is a word I made up to mean "a regex that matches a ref".  If you know
 [regular expressions][regex] you're halfway there.
 
-The only extra info you need is:
+In addition:
 
-  * for convenience, a refex not starting with `refs/` is assumed to start
-    with `refs/heads/`.  This means normal branches can be referred to like
-    this:
+  * if no refex is supplied, it defaults to `refs/.*`, for example in a rule
+    like this:
+
+        RW              =   alice
+
+  * a refex not starting with `refs/` is assumed to start with `refs/heads/`.
+    This means normal branches can be conveniently written like this:
 
         RW  master      =   alice
         # becomes 'refs/heads/master' internally
@@ -18,13 +22,13 @@ The only extra info you need is:
 
   * a refex is implicitly anchored at the start, but not at the end.  In
     regular expression lingo, a `^` is assumed at the start (but no `$` at the
-    end is assumed).  So a refex of `master` will allow all these:
+    end is assumed).  So a refex of `master` will match all these:
 
         refs/heads/master
         refs/heads/master1
         refs/heads/master2
         refs/heads/master/full
 
-    If you want to restrict to just the one specific ref, use
+    If you want to restrict the match to just the one specific ref, use
 
         RW  master$     =   alice
diff --git a/doc/repo.mkd b/doc/repo.mkd
deleted file mode 100644
index 2c3509b..0000000
--- a/doc/repo.mkd
+++ /dev/null
@@ -1,23 +0,0 @@
-## #repo repo definitions
-
-Example:
-
-    repo gitolite tsh gitpod
-        RW+     =   sitaram
-        RW  dev =   alice bob
-        R       =   @all
-
-The "repo" line can have any number of repo names or repo group names in it.
-However, it can only be one line; this will not work
-
-    repo foo
-    repo bar    # WRONG; 'foo' is now forgotten
-        RW      =   alice
-
-If you have too many, use a group name:
-
-    @myrepos    =   foo
-    @myrepos    =   bar
-
-    repo @myrepos
-        RW      =   alice
diff --git a/doc/repos.mkd b/doc/repos.mkd
new file mode 100644
index 0000000..624125e
--- /dev/null
+++ b/doc/repos.mkd
@@ -0,0 +1,53 @@
+# adding and removing repos
+
+>   ----
+
+>   *WARNING: Do NOT add repos directly on the server.  Clone the
+>   'gitolite-admin' repo to your workstation, make changes to it, then add,
+>   commit, and push.  When the push hits the server, the server "acts" upon
+>   your changes.*
+
+>   ----
+
+Just as for [users][], all operations are in a clone of the gitolite-admin
+repo.
+
+To **add** a new repo, edit `conf/gitolite.conf` and add it, along with at
+least one user with some permissions.  Or add it to an existing repo line:
+
+    repo gitolite tsh gitpod
+        RW+     =   sitaram
+        RW  dev =   alice bob
+        R       =   @all
+
+The "repo" line can have any number of repo names or repo group names in it.
+However, it can only be one line; this will not work
+
+    repo foo
+    repo bar    # WRONG; 'foo' is now forgotten
+        RW      =   alice
+
+If you have too many, use a group name:
+
+    @myrepos    =   foo
+    @myrepos    =   bar
+
+    repo @myrepos
+        RW      =   alice
+
+Finally, you add, commit, and push this change.  Gitolite will create a bare,
+empty, repo on the server that is ready to be cloned.
+
+**Removing** a repo is not so straightforward.  You certainly must remove the
+appropriate lines from the `conf/gitolite.conf` file, but gitolite will not
+automatically delete the repo from the server.  You have to log on to the
+server and do the dirty deed yourself :-)
+
+It is best to make the change in the conf file, push it, and *then* go to the
+server and do what you need to.
+
+**Renaming** a repo is also not automatic.  Here's what you do (and the order
+is important):
+
+  * go to the server and rename the repo at the Unix command line
+  * change the name in the conf/gitolite.conf file and add/commit/push.
diff --git a/doc/rules.mkd b/doc/rules.mkd
index c54b43f..b12bffc 100644
--- a/doc/rules.mkd
+++ b/doc/rules.mkd
@@ -15,7 +15,28 @@ We will use this as a running example:
         RW  temp/   =   @staff      # line 4
         R           =   ashok       # line 5
 
-### when does gitolite check access
+### what does a rule look like?
+
+A rule line has the structure
+
+    <permission> <zero or more refexes> = <one or more users/user groups>
+
+The most common permissions used are:
+
+  * R, for read only
+  * RW, for push existing ref or create new ref
+  * RW+, for  "push -f" or ref deletion allowed (i.e., destroy
+    information)
+  * `-` (the minus sign), to **deny** access.
+
+There are also other, less commonly used, [types of permissions][write-types].
+
+A refex is an expression that matches the ref (i.e., branch or tag) being
+pushed.  See [this][refex] for more info.
+
+### when are the rules checked?
+
+There are 2 places where access rules are checked.
 
 The "pre-git" check is before git is invoked.  Gitolite knows the repo name,
 user name, and attempted access (R or W), but no ref name.
@@ -23,7 +44,7 @@ user name, and attempted access (R or W), but no ref name.
 The "update" check is only for write operations, and it is just before git
 updates a ref.  This time gitolite knows the refname also.
 
-### how is a particular rule line matched
+### how are the rules matched?
 
 For the **pre-git check**, any permission that contains "R" matches a read
 operation, and any permission that contains "W" matches a write operation.
@@ -39,8 +60,8 @@ For the **update check**, git gives us all the information we need.  Then:
 
   * all the rules for a repo are [accumulated][rule-accum]
 
-  * then the rules pertaining to this repo *and* this user (or to a group to
-    which they belong, respectively) are kept; the rest are ignored
+  * the rules pertaining to this repo *and* this user (or to a group to which
+    they belong, respectively) are kept; the rest are ignored
 
   * these rules are examined *in the sequence they appeared in the conf file*.
     For each rule:
@@ -73,10 +94,58 @@ deny rules for pre-git, but having got past it, he can't actually do anything.
 That's by design, and as I said if you don't like it you can ask gitolite to
 [deny at pre-git][deny-rules].
 
-### summary of permissions
+### #perms summary of permissions
 
 The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`.  This expands
 to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or
-`RW+CD`, all but the first one optionally followed by an `M`.  And by now you
+`RW+CD`, all but the first two optionally followed by an `M`.  And by now you
 know what they all mean.
 
+## additional topics
+
+### #rule-accum rule accumulation
+
+Gitolite was meant to collect rules from multiple places and apply them all.
+For example, this:
+
+    repo foo
+        RW  =   u1
+
+    @gr1 = foo bar
+
+    repo @gr1
+        RW  =   u2
+        R   =   u3
+
+    repo @all
+        R   =   gitweb
+
+is effectively the same as this, for repo foo:
+
+    repo foo
+        RW  =   u1
+        RW  =   u2
+        R   =   u3
+        R   =   gitweb
+
+This extends to patterns also, but I'll leave an example for later.
+
+### #deny-rules applying deny rules during the pre-git check
+
+The access rules section above describes the problem in one scenario.  Here's
+another.  Let's say you have this at the end of your gitolite.conf file:
+
+    repo @all
+        R   =   gitweb daemon
+
+but you don't want the gitolite-admin repo showing up on gitweb.  How do you
+do that?  Here's how:
+
+    repo gitolite-admin
+        -   =   gitweb daemon
+        option deny-rules = 1
+
+    repo @all
+        R   =   gitweb daemon
+
+Note that the order matters; the `-` rule must come *before* the `R` rule.
diff --git a/doc/setup.mkd b/doc/setup.mkd
new file mode 100644
index 0000000..2b3921a
--- /dev/null
+++ b/doc/setup.mkd
@@ -0,0 +1,41 @@
+# setting up gitolite
+
+Installing the software gets you ready to use it, but the first "use" of it is
+always the "setup" command.
+
+The first time you run it, you need to have a public key file ready.  If the
+main gitolite admin's username is "alice", this file should be named
+"alice.pub".  Then run
+
+    gitolite setup -pk alice.pub
+
+If that command completes without any warnings, you should be done.  If it had
+a warning, you probably supplied a key which already has shell access to the
+server.  That won't work.
+
+>   ----
+
+>   Normally, gitolite is hosted on a user that no one accesses directly --
+>   you log on to the server using some other userid, and then `su - git`.  In
+>   this scenario, there *is* no key being used for shell access, so there is
+>   no conflict.
+
+>   An alternative method is to use two different keys, and a [host
+>   alias][ssh-ha] to distinguish the two.
+
+>   [common errors][ce] has some links to background information on this
+>   issue.
+
+>   ----
+
+The 'setup' command has other uses, so you will be running it at other times
+after the install as well:
+
+  * to setup the update hook when you move [existing][] repos to gitolite.
+    This also applies if someone has been fiddling with the hooks on some
+    repos and you want to put them all right quickly.
+
+  * to replace a [lost admin key][lost-key].
+
+When in doubt, run 'gitolite setup' anyway; it doesn't do any harm, though it
+may take a minute or so if you have more than a few thousand repos!
diff --git a/doc/special.mkd b/doc/special.mkd
new file mode 100644
index 0000000..9500ce0
--- /dev/null
+++ b/doc/special.mkd
@@ -0,0 +1,42 @@
+# special features and setups
+
+## #writable disabling pushes to take backups
+
+The `writable` command allows you to disable pushes to all repos or just the
+named repo, in order to do file-system level things to the repo directory that
+require it not to change, like using normal backup software.
+
+Run `gitolite writable -h` for more info.
+
+## #pers "personal" branches
+
+"personal" branches are great for environments where developers need to share
+work but can't directly pull from each other (usually due to either a
+networking or authentication related reason, both common in corporate setups).
+
+Personal branches exist **in a namespace** of their own.  The syntax is
+
+        RW+ personal/USER/  =   @userlist
+
+where the "personal" can be anything you like (but cannot be empty), and the
+"/USER/" part is **necessary (including both slashes)**.
+
+A user "alice" (if she's in the userlist) can then push any branches inside
+`personal/alice/`.  Which means she can push `personal/alice/foo` and
+`personal/alice/bar`, but NOT `personal/alice`.
+
+(Background: at runtime the "USER" component will be replaced by the name of
+the invoking user.  Access is determined by the right hand side, as usual).
+
+Compared to using arbitrary branch names on the same server, this
+
+  * reduces namespace pollution by corralling all these ad hoc branches into
+    the "personal/" namespace.
+  * reduces branch name collision by giving each developer her own
+    sub-hierarchy within that.
+  * removes the need to think about access control, because a user can push
+    only to his own sub-hierarchy.
+
+## delegating access control responsibilities
+
+See [this][deleg].
diff --git a/doc/syntax.mkd b/doc/syntax.mkd
new file mode 100644
index 0000000..5d3b6b5
--- /dev/null
+++ b/doc/syntax.mkd
@@ -0,0 +1,46 @@
+# #syntax basic syntax
+
+In general, everything is **space separated**; there are no commas,
+semicolons, etc., in the syntax.
+
+**Comments** are in the usual shell-ish style.
+
+**User names** and **repo names** are as simple as possible; they must start
+with an alphanumeric, but after that they can also contain `.`, `_`, or `-`.
+
+Usernames can optionally be followed by an `@` and a domainname containing at
+least one `.` (this allows you to use an email address as someone's username).
+Reponames can contain `/` characters (this allows you to put your repos in a
+tree-structure for convenience)
+
+There are no continuation lines by default.  You do not need them; the section
+on "groups" will tell you how you can break up large lists of names in a group
+definition into multiple lines.
+
+<font color="gray">If you *must* have them, you can optionally enable them;
+see the syntactic [sugar][] section.</font>
+
+## #include include files
+
+Gitolite allows you to break up the configuration into multiple files and
+include them in the main file for convenience.
+
+    include     "foo.conf"
+
+will include the contents of the file "foo.conf".
+
+Details:
+
+  * You can also use a glob (`include "*.conf"`), or put your include files
+    into subdirectories of "conf" (`include "foo/bar.conf"`), or both
+    (`include "repos/*.conf"`).
+
+  * Included files are always searched from the gitolite-admin repo's "conf/"
+    directory, unless you supplied an absolute path.  (Note: in the interests
+    of cloning the admin-repo sanely you should avoid absolute paths!)
+
+  * If you ended up recursing, files that have been already processed once are
+    skipped, with a warning.
+
+<font color="gray">Advanced users: `subconf`, a command that is very closely
+related to `include`, is documented [here][subconf].</font>
diff --git a/doc/testing.mkd b/doc/testing.mkd
index a973851..c8b72ba 100644
--- a/doc/testing.mkd
+++ b/doc/testing.mkd
@@ -1,7 +1,9 @@
 # testing gitolite
 
-Here's how to *run* the tests.  **WARNING: they will clobber lots of things in
-your `$HOME`, so be sure to use a throwaway userid**.
+Here's how to *run* the tests.
+
+<font color="red">**WARNING: they will clobber lots of things in your `$HOME`,
+so be sure to use a throwaway userid**.</font>
 
     git clone git://github.com/sitaramc/gitolite
     cd gitolite
@@ -21,7 +23,3 @@ would otherwise take.
 
 If you think that defeats the purpose of the testing, you haven't read
 [this][auth] yet.
-
-There are 2 specific tests that deal with ssh though, which are run only on
-request, as you can see above, because they clobber your `~/.ssh`.  You have
-been warned.
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 13bc735..4517564 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -50,15 +50,16 @@ Triggers receive the following arguments:
     command in the PRE_GIT trigger sequence),
 
 2.  the name of the trigger as a string (example, `"POST_COMPILE"`), so you
-    can call the same program from multiple triggers and know where it was
-    called from,
+    can call the same program from multiple triggers and it can know where it
+    was called from,
 
 3.  followed by zero or more arguments specific to the trigger, as given in
     the next section.
 
-## trigger-specific details
+## trigger-specific arguments and other details
 
-Here's a brief "when" and "with what arguments" for each trigger.
+Here are the **rest of** the arguments for each trigger, plus a brief
+description of when the trigger runs:
 
   * `ACCESS_1` runs after the first access check.  Arguments:
       * repo
diff --git a/doc/users.mkd b/doc/user.mkd
similarity index 97%
copy from doc/users.mkd
copy to doc/user.mkd
index f9064b6..dda660c 100644
--- a/doc/users.mkd
+++ b/doc/user.mkd
@@ -32,8 +32,7 @@ new repo that you know was setup.
 Gitolite has two kinds of repos.  Normal repos are specified by their full
 names in the config file.  "Wildcard" repos are specified by a regex in the
 config file.  Try the [`info` command][info] and see if it shows any lines
-that look like regex patterns, (with a "C" permission in addition to the "R"
-and the "W").
+that look like regex patterns, (with a "C" permission).
 
 If you see any, it means you are allowed to create brand new repos whose names
 fit that pattern.  When you create such a repo, your "ownership" of it (as far
diff --git a/doc/users.mkd b/doc/users.mkd
index f9064b6..520451f 100644
--- a/doc/users.mkd
+++ b/doc/users.mkd
@@ -1,96 +1,67 @@
-# what users (not admins) need to know about gitolite
+# adding and removing users
 
-...written for the one guy in the world no one will think of as "just a normal
-user" ;-)
+Strictly speaking, gitolite doesn't know where users come from.  If that
+surprises you, read [this][auth].  However, gitolite does help with ssh-based
+authentication, so here's some info on adding and removing users.
 
-This document has some text, and a lot of links.  Most of this info *is*
-available in the rest of the documentation, but it's scattered and sparse.
-Collecting all of it, or at least links to it, in one place sounds useful.
+>   ----
 
-## accessing gitolite
+>   *WARNING: Do NOT add users directly on the server.  Clone the
+>   'gitolite-admin' repo to your workstation, make changes to it, then add,
+>   commit, and push.  When the push hits the server, the server "acts" upon
+>   your changes.*
 
-The most common setup is based on ssh, where your admin asks you to send him
-your public key, and uses that to setup your access.
+>   ----
 
-Your actual access is either a git command (like `git clone
-git at server:reponame`, and we won't be discussing these any more in this
-document), or an ssh command (like `ssh git at server info`).
+All operations are in a clone of the gitolite-admin repo.
 
-Note that you do *not* get a shell on the server -- the whole point of
-gitolite is to prevent that!
+To **add** a user, say Alice, obtain her public key (typically
+`$HOME/.ssh/id_rsa.pub` on her workstation), copy it to `keydir` with the user
+name as the basename (e.g., 'alice.pub' for user alice), then `git add
+keydir/alice.pub`.  (All keys files must have names ending in ".pub", and must
+be in openssh's default format).
 
-## #info the info command
+To **remove** a user, `git rm keydir/alice.pub`.
 
-The only command that is *always* available to every user is the `info`
-command (run `ssh git at host info -h` for help), which tells you what version of
-gitolite and git are on the server, and what repositories you have access to.
-The list of repos is very useful if you have doubts about the spelling of some
-new repo that you know was setup.
+In both cases, you must commit and push.  On receiving the push, gitolite will
+carry out the changes specified.
 
-## digression: two kinds of repos
+The user name is simply the base name of the public key file name.  So
+'alice.pub', 'foo/alice.pub' and 'bar/alice.pub', all resolve to user "alice".
 
-Gitolite has two kinds of repos.  Normal repos are specified by their full
-names in the config file.  "Wildcard" repos are specified by a regex in the
-config file.  Try the [`info` command][info] and see if it shows any lines
-that look like regex patterns, (with a "C" permission in addition to the "R"
-and the "W").
+## #multi-key multiple keys per user
 
-If you see any, it means you are allowed to create brand new repos whose names
-fit that pattern.  When you create such a repo, your "ownership" of it (as far
-as gitolite is concerned) is *automatically* recorded by gitolite.
+The simplest and most understandable is to put their keys in different
+subdirectories, (alice,pub, home/alice.pub, laptop/alice.pub, etc).
 
-## other commands
+### old style multi-keys
 
-### #perms set/get additional permissions for repos you created
+There is another way that involves creating key files like `alice at home.pub`
+and `alice at laptop.pub`, but there is a complication because gitolite also
+allows *full email addresses* as user names.  (I.e., `sitaramc at gmail.com.pub`
+denotes the user called `sitaramc at gmail.com`).
 
-The gitolite config may have several permissions lines for your repo, like so:
+This older method of enabling multi-keys was developed to deal with that.  It
+will continue to work and be supported in *code*, simply because I prefer it.
+But I will not accept questions or doc patches for it, because it seems it is
+too difficult to understand for a lot of people.  This table of sample pubkey
+filenames and the corresponding derived usernames is all you get:
 
-    repo pub/CREATOR/..*
-        RW+     =   CREATOR
-        RW      =   user1 user2
-        R       =   user3
+  * plain username, no multikey
 
-If that's all it had, you really can't do much.  Any changes to access must be
-done by the administrator.  (Note that "CREATOR" is a reserved word that gets
-expanded to your userid in some way, so the admin can literally add just the
-first two lines, and *every* authenticated user now has his own personal repo
-namespace, starting with `pub/<username>/`).
+        sitaramc.pub                            sitaramc
 
-To give some flexibility to users, the admin could add rules like this:
+  * plain username, with multikeys
 
-        RW      =   WRITERS
-        R       =   READERS
+        sitaramc at laptop.pub                     sitaramc
+        sitaramc at desktop.pub                    sitaramc
 
-(he could also add other roles but then he needs to read the documentation).
+  * email address as username, no multikey
 
-Once he does this, you can then use the `perms` command (run `ssh git at host
-perms -h` for help) to set permissions for other users by specifying which
-users are in the list of "READERS", and which in "WRITERS".
+        sitaramc at gmail.com.pub                  sitaramc at gmail.com
 
-If you think of READERS and WRITERS as "roles", it will help.  You can't
-change what access a role has, but you *can* say which users have that role.
+  * email address as username, with multikeys
 
-**Note**: there isn't a way for you to see the actual rule set unless you're
-given read access to the special 'gitolite-admin' repo.  Sorry.  The idea is
-that your admin will tell you what "roles" he added into rules for your repos,
-and what permissions those roles have.
+        sitaramc at gmail.com@laptop.pub           sitaramc at gmail.com
+        sitaramc at gmail.com@desktop.pub          sitaramc at gmail.com
 
-### #desc adding a description to repos you created
-
-The `desc` command is extremely simple.  Run `ssh git at host desc -h` for help.
-
-## "site-local" commands
-
-The main purpose of gitolite is to prevent you from getting a shell.  But
-there are commands that you often need to run on the server (i.e., cannot be
-done by pushing something to a repo).
-
-To enable this, gitolite allows the admin to setup scripts in a special
-directory that users can then run.  Gitolite comes with a set of working
-scripts that your admin may install, or may use as a starting point for his
-own, if he chooses.
-
-Think of these commands as equivalent to those in `COMMAND_DIR` in `man
-git-shell`.
-
-You can get a list of available commands by running `ssh git at host help`.
diff --git a/doc/vref.mkd b/doc/vref.mkd
index c5263df..97b6a66 100644
--- a/doc/vref.mkd
+++ b/doc/vref.mkd
@@ -1,5 +1,11 @@
 # virtual refs
 
+**IMPORTANT**: fallthru is success in VREFs, unlike the normal refs.  That
+won't make sense until you read further, but I had to put it up here for folks
+who stop reading halfway!
+
+----
+
 Here's an example to start you off.
 
     repo    r1
@@ -39,7 +45,7 @@ is therefore a *virtual* ref.  This could be a property that git knows about,
 like in the example above, or comes from outside git like, say, the current
 time; see examples section later for some ideas.
 
-## fallthru is success here
+## #vref-fallthru fallthru is success here
 
 Notice that you didn't need to add an `RW+ VREF/...` rule for user `lead_dev`
 in our example.  This section explains why.
@@ -53,7 +59,7 @@ ref involves calling an external program, many of these calls may be wasted.
 
 There's another advantage to doing it this way: a VREF can choose to simply
 die if things look bad, and it will have the same effect, assuming you used
-the VREF only in [deny][] rules.
+the VREF only in "deny" rules.
 
 This in turn means any existing update hook can be used as a VREF *as-is*, as
 long as it (a) prints nothing on success and (b) dies on failure.  See the
@@ -169,26 +175,65 @@ first rule, it might pass back (to gitolite) a virtual ref saying
 
 ## VREFs shipped with gitolite
 
+### #NAME restricting pushes by dir/file name
+
+The "NAME" VREF allows you to restrict pushes by the names of dirs and files
+changed.
+
+Here's an example.  Say you don't want junior developers pushing changes to
+the Makefile, because it's quite complex:
+
+    repo foo
+            RW+                             =   @senior_devs
+            RW                              =   @junior_devs
+
+            -   VREF/NAME/Makefile          =   @junior_devs
+
+When a senior dev pushes, the VREF is not invoked at all.  But when a junior
+dev pushes, the VREF is invoked, and it returns a list of files changed **as
+refs**, looking like this:
+
+    VREF/NAME/file-1
+    VREF/NAME/dir-2/file-3
+    ...etc...
+
+Each of these refs is matched against the access rules.  If one of them
+happens to be the Makefile, then the ref returned (VREF/NAME/Makefile) will
+match the deny rule and kill the push.
+
+Another way to use this is when you know what is allowed instead of what is
+not allowed.  Let's say the QA person is only allowed to touch a file called
+CHANGELOG and any files in a directory called ReleaseNotes:
+
+    repo foo
+            RW+                             =   @senior_devs
+            RW                              =   @junior_devs
+            RW+                             =   QA-guy
+
+            RW+ VREF/NAME/CHANGELOG         =   QA-guy
+            RW+ VREF/NAME/ReleaseNotes/     =   QA-guy
+            -   VREF/NAME/                  =   QA-guy
+
 ### number of new files
 
-If a dev pushes more than 2 *new* files, the top commit needs to have a
-signed-off by line in its commit message.  For example if he has 4 new files
-this text should be:
+The COUNT VREF is used like this:
 
-    4 new files signed-off by: <top commit author's email>
+    -   VREF/COUNT/9                    =   @junior-developers
 
-The config entry for this is below (`NO_SIGNOFF` applies only to, and thus
-implies, `NEWFILES`):
+In response, if anyone in the user list pushes a commit series that
+changes more than 9 files, a vref of "VREF/COUNT/9" is returned.  Gitolite
+uses that as a "ref" to match against all the rules, hits the same rule
+that invoked it, and denies the request.
 
-     RW+ VREF/COUNT/2/NO_SIGNOFF         =   sitaram
-     -   VREF/COUNT/2/NO_SIGNOFF         =   @all
+If the user did not push more than 9 files, the VREF code returns nothing,
+and nothing happens.
 
-Notice how the refex in both cases is *exactly* the same.  If you make it
-different (even change the number on my access line), things won't work.
+COUNT can take one more argument:
 
-Junior devs can't push more than 10 new files, even with a signed-off by line:
+    -   VREF/COUNT/9/NEWFILES           =   @junior-developers
 
-     -   VREF/COUNT/10/NEWFILES          =   @junior_devs
+This is the same as before, but have to be more than 9 *new* files not
+just changed files.
 
 ### advanced filetype detection
 
@@ -220,6 +265,9 @@ have to change one subroutine in that script if you want to use it)
 
 ### catching duplicate pubkeys
 
+This checks keydir/ for duplicate keys and aborts the push if it finds any.
+You should use this only on the gitolite-admin repo.
+
 We covered this as a teaser example at the start.
 
 ## other ideas -- code welcome!
@@ -231,9 +279,10 @@ something like this (untested)
 
     [ -z "$(git rev-list --first-parent --no-merges $2..$3)" ]
 
-This can be implemented using `src/VREF/MERGE-CHECK` as a model.
-That script does what the 'in core' feature called [merge check][mergecheck]
-does, although the syntax to be used in conf/gitolite will be quite different.
+This can be implemented using `src/VREF/MERGE-CHECK` as a model.  That script
+does what the 'M' qualifier does in access rules (see last part of
+[this][write-types]), although the syntax to be used in conf/gitolite will be
+quite different.
 
 ### other ideas for VREFs
 
diff --git a/doc/why.mkd b/doc/why.mkd
index 3d05124..2112a0b 100644
--- a/doc/why.mkd
+++ b/doc/why.mkd
@@ -1,4 +1,4 @@
-# Why is gitolite needed?
+# why might you need gitolite
 
 Gitolite is separate from git, and needs to be installed and configured.  So...
 why do we bother?
@@ -14,33 +14,34 @@ allow/disallow users access to repos.
 
 But there are several disadvantages here:
 
-  * every user needs a userid and password on the server.  This is usually a
-    killer, especially in tightly controlled environments
-  * adding/removing access rights involves complex `usermod -G ...` mumblings
-    which most admins would rather not deal with
-  * *viewing* (aka auditing) the current set of permissions requires running
+  * Every user needs a userid and password on the server.  This is usually a
+    killer, especially in tightly controlled environments.
+  * Adding/removing access rights involves complex `usermod -G ...` mumblings
+    which most admins would rather not deal with.
+  * *Viewing* (aka auditing) the current set of permissions requires running
     multiple commands to list directories and their permissions/ownerships,
-    users and their group memberships, and then correlating all these manually
-  * auditing historical permissions or permission changes is pretty much
-    impossible without extraneous tools
-  * errors or omissions in setting the permissions exactly can cause problems
-    of either kind: false accepts or false rejects
-  * without going into ACLs it is not possible to give some people read-only
+    users and their group memberships, and then correlating all these
+    manually.
+  * Auditing historical permissions or permission changes is pretty much
+    impossible without extraneous tools.
+  * Errors or omissions in setting the permissions exactly can cause problems
+    of either kind: false accepts or false rejects.
+  * Without going into ACLs it is not possible to give some people read-only
     access while some others have read-write access to a repo (unless you make
-    it world-readable).  Group access just doesn't have enough granularity
-  * it is absolutely impossible to restrict pushing by branch name or tag
+    it world-readable).  Group access just doesn't have enough granularity.
+  * It is absolutely impossible to restrict pushing by branch name or tag
     name.
 
 Gitolite does away with all this:
 
-  * it uses ssh magic to remove the need to give actual unix userids to
-    developers
-  * it uses a simple but powerful config file format to specify access rights
-  * access control changes are affected by modifying this file, adding or
-    removing user's public keys, and "compiling" the configuration
-  * this also makes auditing trivial -- all the data is in one place, and
+  * It uses ssh magic to remove the need to give actual unix userids to
+    developers.
+  * It uses a simple but powerful config file format to specify access rights.
+  * Access control changes are affected by modifying this file, adding or
+    removing user's public keys, and "compiling" the configuration.
+  * This also makes auditing trivial -- all the data is in one place, and
     changes to the configuration are also logged, so you can audit them.
-  * finally, the config file allows distinguishing between read-only and
+  * Finally, the config file allows distinguishing between read-only and
     read-write access, not only at the repository level, but at the branch
     level within repositories.
 
diff --git a/doc/wild.mkd b/doc/wild.mkd
new file mode 100644
index 0000000..9b26e0e
--- /dev/null
+++ b/doc/wild.mkd
@@ -0,0 +1,148 @@
+# "wild" repos (user created repos)
+
+## quick introduction
+
+The wildrepos feature allows you to specify access control rules using regular
+expression patterns, so you can have many actual repos being served by a
+single set of rules in the config file.  The regex pattern can also include
+the word `CREATOR` in it, allowing you to parametrise the name of the user
+creating the repo.
+
+See the section on "repo patterns" later for additional information on what
+counts as a "wild" repo pattern and how it is matched.
+
+## (admin) declaring wild repos in the conf file
+
+Here's an example:
+
+    @prof       =   u1
+    @TAs        =   u2 u3
+    @students   =   u4 u5 u6
+
+    repo    assignments/CREATOR/a[0-9][0-9]
+        C   =   @students
+        RW+ =   CREATOR
+        RW  =   WRITERS @TAs
+        R   =   READERS @prof
+
+Note the "C" permission.  This is a standalone "C", which gives the named
+users the right to *create a repo*.  <font color="gray">This is not to be
+confused with the "RWC" or its variants described elsewhere, which are about
+*branches*, not *repos*.</font>
+
+## #create (user) creating a specific repo
+
+For now, ignore the special usernames READERS and WRITERS, and just create a
+new repo, as user "u4" (a student):
+
+    $ git clone git at server:assignments/u4/a12
+    Initialized empty Git repository in /home/sitaram/a12/.git/
+    Initialized empty Git repository in /home/git/repositories/assignments/u4/a12.git/
+    warning: You appear to have cloned an empty repository.
+
+Notice the *two* empty repo inits, and the order in which they occur ;-)
+
+## a slightly different example
+
+Here's how the same example would look if you did not want the CREATOR's name
+to be part of the actual repo name.
+
+    repo    assignments/a[0-9][0-9]
+        C   =   @students
+        RW+ =   CREATOR
+        RW  =   WRITERS @TAs
+        R   =   READERS @prof
+
+We haven't changed anything except the repo name pattern.  This means that the
+first student that creates, say, `assignments/a12` becomes the owner.
+Mistakes (such as claiming a12 instead of a13) need to be rectified by an
+admin logging on to the back end, though it's not too difficult.
+
+You could also repace the C line like this:
+
+        C   =   @TAs
+
+and have a TA create the repos in advance.
+
+## repo patterns
+
+### pattern versus normal repo
+
+Due to projects like `gtk+`, the `+` character is now considered a valid
+character for an *ordinary* repo.  Therefore, a pattern like `foo/.+` does not
+look like a regex to gitolite.  Use `foo/..*` if you want that.
+
+Also, `..*` by itself is not considered a valid repo pattern.  Try
+`[a-zA-Z0-9].*`.
+
+### line-anchored regexes
+
+A regex like
+
+    repo assignments/S[0-9]+/A[0-9]+
+
+would match `assignments/S02/A37`.  It will not match `assignments/S02/ABC`,
+or `assignments/S02/a37`, obviously.
+
+But you may be surprised to find that it does not match even
+`assignments/S02/A37/B99`.  This is because internally, gitolite
+*line-anchors* the given regex; so that regex actually becomes
+`^assignments/S[0-9]+/A[0-9]+$` -- notice the line beginning and ending
+metacharacters.
+
+Side-note: contrast with refexes
+
+>   Just for interest, note that this is in contrast to the refexes for the >
+>   normal "branch" permissions, as described in `doc/gitolite.conf.mkd` and
+>   elsewhere.  These "refexes" are only anchored at the start; a pattern like
+>   `refs/heads/master` actually can match `refs/heads/master01/bar` as well,
+>   even if no one will actually push such a branch!  You can anchor both
+>   sides if you really care, by using `master$` instead of `master`, but that
+>   is *not* the default for refexes.
+
+## roles
+
+The tokens READERS and WRITERS are called "role" names.  The access rules that
+the admin specifies say what permissions these roles have, but they don't say
+what users are in these roles.
+
+That needs to be done by the creator of the repo, using the `perms` command.
+You can run `ssh git at host perms -h` for detailed help, but in brief, that
+command lets you give and take away roles to users.
+
+## adding other roles
+
+If you want to have more than just the 2 default roles, say something like:
+
+    repo foo/..*
+      C                 =   u1
+      RW    refs/tags/  =   TESTERS
+      -     refs/tags/  =   @all
+      RW+               =   WRITERS
+      RW                =   INTERNS
+      R                 =   READERS
+      RW+D              =   MANAGERS
+
+You can add the new names to the ROLES hash in the [rc][] file.  Be sure to
+run the 2 commands mentioned there after you have added the roles.
+file.  The rc file documentation (`doc/gitolite.rc.mkd`) explains how.
+
+#### #rolenamewarn **IMPORTANT WARNING ABOUT THIS FEATURE**
+
+Please make sure that none of the role names conflict with any of the user
+names or group names in the system.  For example, if you have a user called
+"foo" or a group called "@foo", make sure you do not include "foo" as a valid
+role in the ROLES hash.
+
+You can keep things sane by using UPPERCASE names for roles, while keeping all
+your user and group names lowercase; then you don't have to worry about this
+problem.
+
+## listing wild repos
+
+In order to see what repositories were created from a wildcard, use the 'info'
+command.  Try `ssh git at host info -h` to get help on the info command.
+
+## deleting a wild repo
+
+TBD

commit 7c6728af8979e67f520681f557827c7f81d0a96e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 15:14:25 2012 +0530

    (some minor changes)
    
      - whitespace change to t/reset
      - remove dbg line accidentally left in in Load.pm
      - add a bit more explanation to 'writable'

diff --git a/check-g2-compat b/check-g2-compat
index 9001a7b..58c6b2f 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -70,7 +70,7 @@ sub conf {
     chdir($GL_ADMINDIR);
 
     my $conf = `find . -name "*.conf" | xargs cat`;
-    msg( "SEVERE", "fallthru in NAME rules; see docs" )        if $conf =~ m(NAME/);
+    msg( "SEVERE", "NAME rules; see docs" )                    if $conf =~ m(NAME/);
     msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
     msg( "SEVERE", "mirroring used; see docs" )                if $conf =~ m(config +gitolite\.mirror\.);
 }
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 071b3d0..5100a61 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -232,7 +232,6 @@ sub load_1 {
 
         my @repos = memberships( 'repo', $repo );
         my @users = memberships( 'user', $user, $repo );
-        dbg(\@users);
         trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
diff --git a/src/commands/writable b/src/commands/writable
index f87e370..2b46fa2 100755
--- a/src/commands/writable
+++ b/src/commands/writable
@@ -8,6 +8,9 @@ use Gitolite::Easy;
 =for usage
 Usage: gitolite writable <reponame>|@all on|off
 
+Disable/re-enable pushes to all repos or named repo.  Useful to run
+non-git-aware backups and so on.
+
 'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
 
 With 'off', any subsequent text is taken to be the message to be shown to
diff --git a/t/reset b/t/reset
index fa9bbcf..7805940 100755
--- a/t/reset
+++ b/t/reset
@@ -14,11 +14,11 @@ use Cwd;
 my $workdir = getcwd();
 
 confreset;confadd '
-    repo foo/..*
-        C   =   u1 u2 u3
-        RW+ =   CREATOR
-        RW  =   WRITERS
-        R   =   READERS
+repo foo/..*
+    C   =   u1 u2 u3
+    RW+ =   CREATOR
+    RW  =   WRITERS
+    R   =   READERS
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();

commit 9bbc5703e359a69d66135a159c28dea65278f44d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 5 18:42:40 2012 +0530

    CSS text-color specified...
    
    ...so it is readable for people who use black bg browsers

diff --git a/doc/mkdoc b/doc/mkdoc
index b1ed932..998cf1d 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -96,14 +96,14 @@ sub main {
 __DATA__
 
 <head><style>
-    body        { background: #fff; margin-left:  40px;   font-size:  0.9em;  font-family: sans-serif; max-width: 800px; }
-    h1          { background: #ffb; margin-left: -30px;   border-top:    5px  solid #ccc; }
-    h2          { background: #ffb; margin-left: -20px;   border-top:    3px  solid #ddd; }
-    h3          { background: #ffb; margin-left: -10px; }
-    h4          { background: #ffb; }
-    code        { font-size:    1.1em;  background:  #ddf; }
-    pre         { margin-left:  2em;    background:  #ddf; }
-    pre code    { font-size:    1.1em;  background:  #ddf; }
+    body        { background: #fff; text-color: #000; margin-left:  40px;   font-size:  0.9em;  font-family: sans-serif; max-width: 800px; }
+    h1          { background: #ffb; text-color: #000; margin-left: -30px;   border-top:    5px  solid #ccc; }
+    h2          { background: #ffb; text-color: #000; margin-left: -20px;   border-top:    3px  solid #ddd; }
+    h3          { background: #ffb; text-color: #000; margin-left: -10px; }
+    h4          { background: #ffb; text-color: #000; }
+    code        { font-size:    1.1em;  background:  #ddf; text-color: #000; }
+    pre         { margin-left:  2em;    background:  #ddf; text-color: #000; }
+    pre code    { font-size:    1.1em;  background:  #ddf; text-color: #000; }
 </style></head>
 
 <p style="text-align:center">

commit ecb172b785d5989e8b22a48855d8e2f5dd27eb4a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Apr 5 19:18:53 2012 +0530

    new README, in preparation for rolling the new release into "master"

diff --git a/README.mkd b/README.mkd
new file mode 100644
index 0000000..afbcf3f
--- /dev/null
+++ b/README.mkd
@@ -0,0 +1,32 @@
+# Gitolite README
+
+**Github users: please read the "wiki" link at the top of the page before
+submitting issues or pull requests**.
+
+----
+
+If you're reading this on the main gitolite page on github, several
+**IMPORTANT CHANGES** have happened to gitolite:
+
+1.  A competely re-written version of gitolite has been pushed to the "master"
+    branch, and is now the actively maintained and supported software.  Do NOT
+    try to merge this with your old "master" branch!
+
+2.  Versions v2.x is on branch "g2".  It will be supported for security issues
+    and serious bugs in core functionality, but not for anything less
+    critical.  Versions v1.x are completely unsupported now.
+
+If you're an existing (v1.x, v2.x) gitolite user please spend some time with
+the documentation for the new version before upgrading.  The [main page][h-mp]
+leads to quick install as well as several other useful starting points.  The
+[table of contents][h-mt] is a much more meaningfully ordered/structured list
+of links (instead of putting them in alphabetical order of the filename, like
+in g2!)
+
+[h-mp]: http://sitaramc.github.com/gitolite/
+[h-mt]: http://sitaramc.github.com/gitolite/master-toc.html
+
+----
+
+License information for code and documentation is at the end of doc/index.mkd
+(or you can read it online [here][license]).

commit 8df28a02dbf80c2a920305ba9904e707f5ca1d77
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Apr 4 04:58:50 2012 +0530

    (minor) comments and clarifications in default rc

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 5654af1..c7390eb 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -313,7 +313,7 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence at the start, before a git operation has started
+    # these will run in sequence just before the actual git command is invoked
     PRE_GIT                     =>
         [
             # if you use this, make this the first item in the list
@@ -332,7 +332,7 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence at the end, after a git operation has ended
+    # these will run in sequence after the git command returns
     POST_GIT                    =>
         [
             # if you use this, make this the last item in the list
@@ -341,6 +341,12 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence before a new wild repo is created
+    PRE_CREATE                  =>
+        [
+        ],
+
+    # comment out or uncomment as needed
     # these will run in sequence after a new wild repo is created
     POST_CREATE                 =>
         [

commit 495390926d773982d1609af122c13c13fe3d8c4d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 3 11:58:39 2012 +0530

    added sshkeys-lint as a command

diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
new file mode 100755
index 0000000..2ff8e66
--- /dev/null
+++ b/src/commands/sshkeys-lint
@@ -0,0 +1,189 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# complete rewrite of the sshkeys-lint program.  Usage has changed, see
+# usage() function or run without arguments.
+
+use Getopt::Long;
+my $admin = 0;
+my $quiet = 0;
+my $help  = 0;
+GetOptions( 'admin|a=s' => \$admin, 'quiet|q' => \$quiet, 'help|h' => \$help );
+
+use Data::Dumper;
+$Data::Dumper::Deepcopy = 1;
+$|++;
+
+my $in_gl_section = 0;
+my $warnings      = 0;
+
+sub dbg {
+    use Data::Dumper;
+    for my $i (@_) {
+        print STDERR "DBG: " . Dumper($i);
+    }
+}
+
+sub msg {
+    my $warning = shift;
+    return if $quiet and not $warning;
+    $warnings++ if $warning;
+    print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_;
+}
+
+usage() if $help;
+
+our @pubkeyfiles = @ARGV; @ARGV = ();
+my $kd = "$ENV{HOME}/.gitolite/keydir";
+if ( not @pubkeyfiles ) {
+    chomp( @pubkeyfiles = `find $kd -type f -name "*.pub" | sort` );
+}
+
+if ( -t STDIN ) {
+    @ARGV = ("$ENV{HOME}/.ssh/authorized_keys");
+}
+
+# ------------------------------------------------------------------------
+
+my @authkeys;
+my %seen_fprints;
+my %pkf_by_fp;
+msg 0, "==== checking authkeys file:\n";
+fill_authkeys();    # uses up STDIN
+
+if ($admin) {
+    my $fp = fprint("$admin.pub");
+    my $fpu = ( $fp && $seen_fprints{$fp}{user} || 'no access' );
+    # dbg("fpu = $fpu, admin=$admin");
+    #<<<
+    die "\t\t*** FATAL ***\n" .
+        "$admin.pub maps to $fpu, not $admin.\n" .
+        "You will not be able to access gitolite with this key.\n" .
+        "Look for the 'ssh troubleshooting' link in http://sitaramc.github.com/gitolite/.\n"
+    if $fpu ne "user $admin";
+    #>>>
+}
+
+msg 0, "==== checking pubkeys:\n" if @pubkeyfiles;
+for my $pkf (@pubkeyfiles) {
+    # get the short name for the pubkey file
+    ( my $pkfsn = $pkf ) =~ s(^$kd/)();
+
+    my $fp = fprint($pkf);
+    next unless $fp;
+    msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
+    $pkf_by_fp{$fp} ||= $pkf;
+    my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
+    msg 0, "$pkfsn maps to $fpu\n";
+}
+
+if ($warnings) {
+    print "\n$warnings warnings found\n";
+}
+
+exit $warnings;
+
+# ------------------------------------------------------------------------
+sub fill_authkeys {
+    while (<>) {
+        my $seq = $.;
+        next if ak_comment($_);    # also sets/clears $in_gl_section global
+        my $fp   = fprint($_);
+        my $user = user($_);
+
+        check( $seq, $fp, $user );
+
+        $authkeys[$seq]{fprint}  = $fp;
+        $authkeys[$seq]{ustatus} = $user;
+    }
+}
+
+sub check {
+    my ( $seq, $fp, $user ) = @_;
+
+    msg 1, "line $seq, $user key found *outside* gitolite section!\n"
+      if $user =~ /^user / and not $in_gl_section;
+
+    msg 1, "line $seq, $user key found *inside* gitolite section!\n"
+      if $user !~ /^user / and $in_gl_section;
+
+    if ( $seen_fprints{$fp} ) {
+        #<<<
+        msg 1, "authkeys line $seq ($user) will be ignored by sshd; " .
+              "same key found on line " .
+              $seen_fprints{$fp}{seq} . " (" .
+              $seen_fprints{$fp}{user} . ")\n";
+        return;
+        #>>>
+    }
+
+    $seen_fprints{$fp}{seq}  = $seq;
+    $seen_fprints{$fp}{user} = $user;
+}
+
+sub user {
+    my $user = '';
+    $user ||= "user $1"         if /^command=.*gitolite-shell (.*?)"/;
+    $user ||= "unknown command" if /^command/;
+    $user ||= "shell access"    if /^ssh-(rsa|dss)/;
+
+    return $user;
+}
+
+sub ak_comment {
+    local $_ = shift;
+    $in_gl_section = 1 if /^# gitolite start/;
+    $in_gl_section = 0 if /^# gitolite end/;
+    die "gitosis?  what's that?\n" if /^#.*gitosis/;
+    return /^\s*(#|$)/;
+}
+
+sub fprint {
+    local $_ = shift;
+    my ( $fh, $tempfn, $in );
+    if (/ssh-(dss|rsa) /) {
+        # an actual key was passed.  Since ssh-keygen requires an actual file,
+        # make a temp file to take the data and pass on to ssh-keygen
+        s/^.* (ssh-dss|ssh-rsa)/$1/;
+        use File::Temp qw(tempfile);
+        ( $fh, $tempfn ) = tempfile();
+        $in = $tempfn;
+        print $fh $_;
+        close $fh;
+    } else {
+        # a filename was passed
+        $in = $_;
+    }
+    # dbg("in = $in");
+    -f $in or die "file not found: $in\n";
+    open( $fh, "ssh-keygen -l -f $in |" ) or die "could not fork: $!\n";
+    my $fp = <$fh>;
+    # dbg("fp = $fp");
+    close $fh;
+    unlink $tempfn if $tempfn;
+    warn "$fp\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
+    return $1;
+}
+
+# ------------------------------------------------------------------------
+sub usage {
+    print <<EOF;
+
+sshkeys-lint expects
+  - the contents of an authorized_keys file via STDIN
+  - one or more pubkey filenames as arguments
+
+sample use to check all keys on gitolite server:
+    cd ~/.gitolite/keydir
+    cat ~/.ssh/authorized_keys | sshkeys-lint `find . -name "*.pub"`
+    # or supply only one pubkey file to check only that:
+    cat ~/.ssh/authorized_keys | sshkeys-lint YourName.pub
+
+Note that it runs ssh-keygen -l for each line in the authkeys file and each
+pubkey in the argument list, so be wary of running it on something huge.  This
+is meant for troubleshooting.
+
+EOF
+    exit 1;
+}

commit 35953a5bd34f7a2bbfbc6793ad90b6644ce9b083
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 3 13:27:19 2012 +0530

    added 'gitolite push' to make server side pushes easier...
    
    also force the update hook to log SHAs of bypassed pushes

diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 262f566..7cb17b1 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -22,6 +22,8 @@ use warnings;
 sub update {
     # this is the *real* update hook for gitolite
 
+    bypass() if $ENV{GL_BYPASS_ACCESS_CHECKS};
+
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
 
     trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
@@ -37,6 +39,13 @@ sub update {
     exit 0;
 }
 
+sub bypass {
+    require Cwd;
+    Cwd->import;
+    gl_log( 'update', getcwd(), '(' . ($ENV{USER} || '?') . ')', 'bypass', @ARGV );
+    exit 0;
+}
+
 sub check_vrefs {
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
     my $name_seen = 0;
@@ -143,10 +152,6 @@ __DATA__
 use strict;
 use warnings;
 
-BEGIN {
-    exit 0 if $ENV{GL_BYPASS_ACCESS_CHECKS};
-    die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
-}
 use lib $ENV{GL_BINDIR};
 use Gitolite::Hooks::Update;
 
diff --git a/src/commands/push b/src/commands/push
new file mode 100755
index 0000000..f97f730
--- /dev/null
+++ b/src/commands/push
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export GL_BYPASS_ACCESS_CHECKS=1
+
+git push "$@"

commit ad77cef7deab0c7108a0ccc79e3fe080a2d3d8ec
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Apr 3 09:44:43 2012 +0530

    (mainly for fedora) '-s' gets a shell.  Manual spot-testing only
    
    also includes "use $USER if username not passed"

diff --git a/src/Gitolite/Triggers/Shell.pm b/src/Gitolite/Triggers/Shell.pm
new file mode 100644
index 0000000..0e6f0a1
--- /dev/null
+++ b/src/Gitolite/Triggers/Shell.pm
@@ -0,0 +1,64 @@
+package Gitolite::Triggers::Shell;
+
+# usage notes: this module must be loaded first in the INPUT trigger list.  Or
+# at least before Mirroring::input anyway.
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+# fedora likes to do things that are a little off the beaten track, compared
+# to typical gitolite usage:
+# - every user has their own login
+# - the forced command may not get the username as an argument.  If it does
+#   not, the gitolite user name is $USER (the unix user name)
+# - and finally, if the first argument to the forced command is '-s', and
+#   $SSH_ORIGINAL_COMMAND is empty or runs a non-git/gitolite command, then
+#   the user gets a shell
+
+sub input {
+    my $shell_allowed = 0;
+    if ( @ARGV and $ARGV[0] eq '-s' ) {
+        shift @ARGV;
+        $shell_allowed++;
+    }
+
+    @ARGV = ( $ENV{USER} ) unless @ARGV;
+
+    return unless $shell_allowed;
+
+    # now determine if this was intended as a shell command or git/gitolite
+    # command
+
+    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+
+    # no command, just 'ssh alice at host'; doesn't return ('exec's out)
+    shell_out() if $shell_allowed and not $soc;
+
+    return if git_gitolite_command($soc);
+
+    gl_log( 'shell', $ENV{SHELL}, "-c", $soc );
+    exec $ENV{SHELL}, "-c", $soc;
+}
+
+sub shell_out {
+    my $shell = $ENV{SHELL};
+    $shell =~ s/.*\//-/;    # change "/bin/bash" to "-bash"
+    gl_log( 'shell', $shell );
+    exec { $ENV{SHELL} } $shell;
+}
+
+# some duplication with gitolite-shell, factor it out later, if it works fine
+# for fedora and they like it.
+sub git_gitolite_command {
+    my $soc = shift;
+
+    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+    return 1 if $soc =~ /^($git_commands) /;
+
+    my @words = split ' ', $soc;
+    return 1 if $rc{COMMANDS}{ $words[0] };
+
+    return 0;
+}
+
+1;
diff --git a/src/gitolite-shell b/src/gitolite-shell
index ce98368..4d201c7 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -54,7 +54,7 @@ sub in_ssh {
     my $ip;
     ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
 
-    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}", "FROM=$ip" );
+    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=" . ( $ENV{SSH_ORIGINAL_COMMAND} || ''), "FROM=$ip" );
 
     $ENV{SSH_ORIGINAL_COMMAND} ||= '';
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};

commit 81b503d2bde8a1bef4156f8e7e96fa44864e87af
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Apr 2 21:25:06 2012 +0530

    GROUPLIST_PGM; manually spot-tested, no test script.  PW.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 72db4de..071b3d0 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -232,6 +232,7 @@ sub load_1 {
 
         my @repos = memberships( 'repo', $repo );
         my @users = memberships( 'user', $user, $repo );
+        dbg(\@users);
         trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
@@ -296,6 +297,8 @@ sub memberships {
         push @ret, user_roles( $base, $repo, @ret );
     }
 
+    push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
+
     @ret = @{ sort_u( \@ret ) };
     trace( 3, sort @ret );
     return @ret;
@@ -385,6 +388,20 @@ sub creator {
     return $creator;
 }
 
+{
+    my %cache = ();
+
+    sub ext_grouplist {
+        my $user = shift;
+        my $pgm = $rc{GROUPLIST_PGM};
+        return [] if not $pgm;
+
+        return $cache{$user} if $cache{$user};
+        my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
+        return ($cache{$user} = \@extgroups);
+    }
+}
+
 # ----------------------------------------------------------------------
 # api functions
 # ----------------------------------------------------------------------

commit 9b3efb908404a72db809b1910f16c603ce366afe
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Apr 1 13:25:07 2012 +0530

    mirroring: docs and check-g2-compat

diff --git a/check-g2-compat b/check-g2-compat
index 1f2bfe3..9001a7b 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -38,7 +38,7 @@ sub intro {
     msg( ''   => "or that might end up giving *more* access to someone if migrated as-is." );
     msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
     msg( '', '' );
-    msg( INFO => "'see docs' usually means doc/g2migr.mkd");
+    msg( INFO => "'see docs' usually means doc/g2migr.mkd" );
     msg( '',  => "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)" );
     msg( '', '' );
 }
@@ -52,16 +52,16 @@ sub rc_basic {
 }
 
 sub rest_of_rc {
-    msg( SEVERE  => "GIT_PATH found; see docs" )                                              if $GIT_PATH;
-    msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )                               if $GL_ALL_INCLUDES_SPECIAL;
-    msg( SEVERE  => "GL_GET_MEMBERSHIPS_PGM not yet implemented" )                            if $GL_GET_MEMBERSHIPS_PGM;
-    msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )                                if $GL_NO_CREATE_REPOS;
-    msg( SEVERE  => 'htpasswd, rsync, and svnserve not yet implemented' )                     if $HTPASSWD_FILE or $RSYNC_BASE or $SVNSERVE;
-    msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )                           if $ADMIN_POST_UPDATE_CHAINS_TO;
-    msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )                                if $GL_NO_DAEMON_NO_GITWEB;
-    msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )                                  if $GL_NO_SETUP_AUTHKEYS;
-    msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                                      if $UPDATE_CHAINS_TO;
-    msg( WARNING => "GL_ADC_PATH found; many ADCs not yet implemented")                       if $GL_ADC_PATH;
+    msg( SEVERE  => "GIT_PATH found; see docs" )                          if $GIT_PATH;
+    msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )           if $GL_ALL_INCLUDES_SPECIAL;
+    msg( SEVERE  => "GL_GET_MEMBERSHIPS_PGM not yet implemented" )        if $GL_GET_MEMBERSHIPS_PGM;
+    msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )            if $GL_NO_CREATE_REPOS;
+    msg( SEVERE  => 'htpasswd, rsync, and svnserve not yet implemented' ) if $HTPASSWD_FILE or $RSYNC_BASE or $SVNSERVE;
+    msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )       if $ADMIN_POST_UPDATE_CHAINS_TO;
+    msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )            if $GL_NO_DAEMON_NO_GITWEB;
+    msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )              if $GL_NO_SETUP_AUTHKEYS;
+    msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                  if $UPDATE_CHAINS_TO;
+    msg( WARNING => "GL_ADC_PATH found; many ADCs not yet implemented" )  if $GL_ADC_PATH;
     msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
 }
 
@@ -70,8 +70,9 @@ sub conf {
     chdir($GL_ADMINDIR);
 
     my $conf = `find . -name "*.conf" | xargs cat`;
-    msg( "SEVERE",  "fallthru in NAME rules; see docs" )        if $conf =~ m(NAME/);
+    msg( "SEVERE", "fallthru in NAME rules; see docs" )        if $conf =~ m(NAME/);
     msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
+    msg( "SEVERE", "mirroring used; see docs" )                if $conf =~ m(config +gitolite\.mirror\.);
 }
 
 sub repo {
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 3f065c7..0bb3c36 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -65,6 +65,11 @@ put that contain the words "see docs":
     processing files you intended to.  Again, see the same link as in the
     previous bullet.
 
+  * `mirroring used`
+
+    There have been quite a few changes to mirroring.  Please see the new
+    [mirroring][mirroring] doc for details.
+
   * `found N gl-creater files`
 
     These need to be renamed to `gl-creator` (the correct spelling at last,
diff --git a/doc/mirroring.mkd b/doc/mirroring.mkd
new file mode 100644
index 0000000..056f080
--- /dev/null
+++ b/doc/mirroring.mkd
@@ -0,0 +1,188 @@
+# mirroring using gitolite
+
+**WARNING** existing gitolite mirroring users please note: significant changes
+in syntax and usage compared to g2.  The most important change is that `config
+gitolite.mirror.master` changes to `option mirror.master`, and similarly for
+'slaves' and redirectOK.  In addition, the special value for 'redirectOK'
+changes from '1' to 'all'.  The second big change is the disappearance of the
+ambiguously named 'keys' (for example `gitolite.mirror.hourly` etc., in the
+old mirroring doc) -- all that complexity is yours to handle now :-)
+
+----
+
+Mirroring is simple: you have one "master" and one or more "slaves".  The
+slaves get updates only from the master; to the rest of the world they are at
+best read-only.
+
+Gitolite extends this simple notion in the following ways:
+
+  * different masters and sets of slaves for different repos
+
+    This lets you do things like
+
+      * use the server closest to *most* of its developers as the master for
+        that repo
+      * not mirror a repo at all to some servers
+      * have repos that are purely local to a server (not mirrored at all)
+      * negotiate mirroring with servers that are not even under your control
+      * push to a slave on demand or via cron (helps deal with bandwidth or
+        connectivity constraints).
+
+  * pushes to a slave can be transparently forwarded to the real master
+
+    Your developers need not worry about where a repo's master is -- they just
+    write to their local mirror for *all* repos, even if their local mirror is
+    only a slave for some.
+
+## caveats
+
+  * mirroring will never *create* a repo on a slave; it has to exist and be
+    prepared to receive updates from the master.  (For example, [wild][] repos
+    must be created on the slave as well, otherwise they will not propagate).
+
+  * mirroring is only for git repos.  Ancillary files like gl-creator and
+    gl-perms in the repo directory are not mirrored; you must do that
+    separately.  Files in the admin directory (like log files) are also not
+    mirrored.
+
+## setting up mirroring
+
+This is in two parts: the initial setup and the rc file, followed by the conf
+file settings and syntax.
+
+### the initial setup and the rc file
+
+On each server:
+
+  * install gitolite normally.  Make clones of each server's 'gitolite-admin'
+    repo on your workstation so you can admin them all from one place.
+
+  * give each server a short, simple, "hostname" and set the HOSTNAME in the
+    rc file to this name, for example 'mars'.
+
+  * run ssh-keygen if needed and get an ssh key pair for the server.  Copy the
+    public key to a common area and name it after the host, but with 'server-'
+    prefixed.  So the pubkey for server 'mars' would be stored as
+    'server-mars.pub'.
+
+  * copy all keys to **each of** the admin repo clones on your workstation and
+    and add them as usual.
+
+    You may have guessed that the prefix 'server-' is special, and
+    distinguishes a human user from a mirroring peer.
+
+  * create "host" aliases on each machine to refer to all other machines.  See
+    [here][ssh-ha] for what/why/how.
+
+    The host alias for a host (in all other machines' `~/.ssh/config` files)
+    MUST be the same as the `HOSTNAME` in the referred host's
+    `~/.gitolite.rc`.  Gitolite mirroring **requires** this consistency in
+    naming; things will NOT work otherwise.
+
+    Normally you should be able to build one common file and append it to all
+    the servers' `~/.ssh/config` files.
+
+  * the following **MUST** work for **each pair** of servers that must talk to
+    each other:
+
+        # on server mars
+        ssh phobos info
+        # the response MUST start with "hello, server-mars..."
+
+    Note the exact syntax used; variations like "ssh git at phobos.example.com
+    info" are NOT sufficient.  That is why you need the ssh host aliases.
+
+    Check this command from *everywhere to everywhere else*, and make sure you
+    get expected results.  **Do NOT proceed otherwise.**
+
+  * setup the gitolite.conf file on all the servers.  If the slaves are to be
+    exact copies of the master, you need to do the complete configuration only
+    on the master; the slaves can have just this:
+
+        repo gitolite-admin
+            RW+     =   <some local admin>
+
+            option mirror.master    =   mars
+            option mirror.slaves    =   phobos
+
+    because on the first push to the master it will update all the slaves
+    anyway.
+
+  * when that is all done and tested, **enable mirroring** on each server by
+    going through the rc file and uncommenting all the lines mentioning
+    `Mirroring`.
+
+### conf file settings and syntax
+
+Mirroring is defined by the following [option][]s.  You can have different
+settings for different repos, and of course some repos may not have any mirror
+options at all -- they are then purely local.
+
+    repo foo
+        ...access rules...
+
+        option mirror.master        =   mars
+        option mirror.slaves        =   phobos deimos
+        option mirror.redirectOK    =   all
+
+The first line is easy, since a repo can have only one master.
+
+The second is a space separated list of hosts that are all slaves.  You can
+have several slave lists, as long as the config key starts with
+'mirror.slaves' and is unique.  For example.
+
+        option mirror.slaves-1   =   phobos deimos
+        option mirror.slaves-2   =   io europa
+        option mirror.slaves-3   =   ganymede callisto
+
+Do not repeat a key; then only the last line for that key will be effective.
+
+## redirected pushes
+
+**Please read carefully; there are security implications if you enable this
+for mirrors NOT under your control**.
+
+Normally, a master, (and *only* a master), pushes to a slave, and the slaves
+are "read-only" to the users.  Gitolite allows a *slave* to receive pushes
+from a user and transparently redirect them to the *master*.
+
+This simplifies things for users in complex setups, letting them use their
+local mirror for both fetch and push access to all repos.
+
+Just remember that if you do this, **authentication** happens on the slave,
+but **authorisation** is on the master.  The master is trusting the slave to
+authenticate the user correctly, *and* use the same authentication data (i.e.,
+user alice on the slave should be guaranteed to be the same as user alice on
+the master).
+
+The syntax for enabling this is one of these:
+
+    option mirror.redirectOK    =   all
+    option mirror.redirectOK    =   phobos deimos
+
+The first syntax trusts all valid slaves to redirect user pushes, while the
+second one trusts only some slaves.
+
+Note that you cannot redirect gitolite commands (like perms, etc).
+
+## manually synchronising a slave repo
+
+You can use the `gitolite mirror push` command on a master to manually
+synchronise any of its slaves.  Try it with `-h` to get usage info.
+
+Tip: if you want to do this to all the slaves, try this:
+
+    for s in `gitolite git-config -r reponame mirror.slave | cut -f3`
+    do
+        gitolite mirror push $s reponame
+    done
+
+This command can also be run remotely; run `ssh git at host mirror -h` for
+details.
+
+## appendix A: efficiency versus paranoia
+
+If you're paranoid enough to use mirrors, you should be paranoid enough to
+set this on each server, despite the possible CPU overhead:
+
+    git config --global receive.fsckObjects true

commit 8e8ccb50f411878bf4f7131dfc48e244cf6fa870
Author: gitolite tester <tester at example.com>
Date:   Sat Mar 31 21:42:16 2012 +0530

    additions to default rc (commented out) for mirroring

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 57cedb2..5654af1 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -248,6 +248,9 @@ __DATA__
 # (Tip: perl allows a comma after the last item in a list also!)
 
 %RC = (
+    # if you're using mirroring, you need a hostname.  This is *one* simple
+    # word, not a full domain name.  See documentation if in doubt
+    # HOSTNAME                  =>  'darkstar',
     UMASK                       =>  0077,
     GIT_CONFIG_KEYS             =>  '',
 
@@ -285,6 +288,7 @@ __DATA__
             'info'              =>  1,
             'desc'              =>  1,
             'perms'             =>  1,
+            # 'mirror'          =>  1,
             'writable'          =>  1,
         },
 
@@ -296,6 +300,13 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence to modify the input (arguments and environment)
+    INPUT                       =>
+        [
+            # 'Mirroring::input',
+        ],
+
+    # comment out or uncomment as needed
     # these will run in sequence just after the first access check is done
     ACCESS_1                    =>
         [
@@ -308,6 +319,8 @@ __DATA__
             # if you use this, make this the first item in the list
             # 'renice 10',
 
+            # 'Mirroring::pre_git',
+
             # see docs ("list of non-core programs shipped") for details
             # 'partial-copy',
         ],
@@ -324,6 +337,7 @@ __DATA__
         [
             # if you use this, make this the last item in the list
             # 'cpu-time',
+            # 'Mirroring::post_git',
         ],
 
     # comment out or uncomment as needed

commit 25bb1c00db047a451e932071fb796de967f60113
Author: gitolite tester <tester at example.com>
Date:   Tue Mar 27 22:06:43 2012 +0530

    mirroring without sausages
    
    (or at least without showing the making of said sausages)

diff --git a/src/Gitolite/Triggers/Mirroring.pm b/src/Gitolite/Triggers/Mirroring.pm
new file mode 100644
index 0000000..430f662
--- /dev/null
+++ b/src/Gitolite/Triggers/Mirroring.pm
@@ -0,0 +1,212 @@
+package Gitolite::Triggers::Mirroring;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+my $hn = $rc{HOSTNAME};
+
+# ----------------------------------------------------------------------
+
+sub input {
+    return unless $ARGV[0] =~ /^server-(\S+)$/;
+
+    # note: we treat %rc as our own internal "poor man's %ENV"
+    $rc{FROM_SERVER} = $1;
+    trace(3, "from_server: $1");
+
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
+        # my ($user, $newsoc, $repo) = ($1, $2, $3);
+        $ENV{SSH_ORIGINAL_COMMAND} = $2;
+        @ARGV = ($1);
+        $rc{REDIRECTED_PUSH} = 1;
+        trace(3, "redirected_push for user $1");
+    } else {
+        # master -> slave push, no access checks needed
+        $ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
+    }
+}
+
+# ----------------------------------------------------------------------
+
+my ($mode, $master, %slaves, %trusted_slaves);
+
+sub pre_git {
+    return unless $hn;
+    # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+    trace(1, "pre_git() on $hn");
+
+    my ($repo, $user, $aa) = @_[1, 2, 3];
+
+    my $sender = $rc{FROM_SERVER} || '';
+    $user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
+
+    # ------------------------------------------------------------------
+    # now you know the repo, get its mirroring details
+    details($repo);
+
+    # we don't deal with any reads.  Note that for pre-git this check must
+    # happen *after* getting details, to give mode() a chance to die on "known
+    # unknown" repos (repos that are in the config, but mirror settings
+    # exclude this host from both the master and slave lists)
+    return if $aa eq 'R';
+
+    trace(1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
+      ($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
+
+    # ------------------------------------------------------------------
+    # case 1: we're master or slave, normal user pushing to us
+    if ($user and not $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 1, user push");
+        return if $mode eq 'local' or $mode eq 'master';
+        if ($trusted_slaves{$hn}) {
+            trace(3, "redirecting to $master");
+            trace(1, "redirect to $master");
+            exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}");
+        } else {
+            _die "$hn: pushing '$repo' to slave '$hn' not allowed";
+        }
+    }
+
+    # ------------------------------------------------------------------
+    # case 2: we're slave, master pushing to us
+    if ($sender and not $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 2, master push");
+        _die "$hn: '$repo' is local" if $mode eq 'local';
+        _die "$hn: '$repo' is native" if $mode eq 'master';
+        _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
+        return;
+    }
+
+    # ------------------------------------------------------------------
+    # case 3: we're master, slave sending a redirected push to us
+    if ($sender and $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 2, slave redirect");
+        _die "$hn: '$repo' is local"  if $mode eq 'local';
+        _die "$hn: '$repo' is not native" if $mode eq 'slave';
+        _die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
+        _die "$hn: redirection not allowed from '$sender'" if not $trusted_slaves{$sender};
+        return;
+    }
+
+    _die "$hn: should not reach this line";
+
+}
+
+# ----------------------------------------------------------------------
+
+sub post_git {
+    return unless $hn;
+    # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+    trace(1, "post_git() on $hn");
+
+    my ($repo, $user, $aa) = @_[1, 2, 3];
+    # we don't deal with any reads
+    return if $aa eq 'R';
+
+    my $sender = $rc{FROM_SERVER} || '';
+    $user = '' if $sender;
+
+    # ------------------------------------------------------------------
+    # now you know the repo, get its mirroring details
+    details($repo);
+
+    trace(1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
+      ($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
+
+    # ------------------------------------------------------------------
+    # case 1: we're master or slave, normal user pushing to us
+    if ($user and not $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 1, user push");
+        return if $mode eq 'local';
+        # slave was eliminated earlier anyway, so that leaves 'master'
+
+        # find all slaves and push to each of them
+        push_to_slaves($repo);
+
+        return;
+    }
+
+    # ------------------------------------------------------------------
+    # case 2: we're slave, master pushing to us
+    if ($sender and not $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 2, master push");
+        # nothing to do
+        return;
+    }
+
+    # ------------------------------------------------------------------
+    # case 3: we're master, slave sending a redirected push to us
+    if ($sender and $rc{REDIRECTED_PUSH}) {
+        trace(3, "case 2, slave redirect");
+
+        # find all slaves and push to each of them
+        push_to_slaves($repo);
+
+        return;
+    }
+}
+
+{
+    my $lastrepo = '';
+
+    sub details {
+        my $repo = shift;
+        return if $lastrepo eq $repo;
+
+        $master = master($repo);
+        %slaves = slaves($repo);
+        $mode = mode($repo);
+        %trusted_slaves = trusted_slaves($repo);
+        trace(3, $master, $mode, join(",", sort keys %slaves), join(",", sort keys %trusted_slaves) );
+    }
+
+    sub master {
+        return option(+shift, 'mirror.master');
+    }
+
+    sub slaves {
+        my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.slaves.*");
+        my %out = map { $_ => 1 } map { split } values %$ref;
+        return %out;
+    }
+
+    sub trusted_slaves {
+        my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.redirectOK.*");
+        # the list of trusted slaves (where we accept redirected pushes from)
+        # is either explicitly given...
+        my @out = map { split } values %$ref;
+        my %out = map { $_ => 1 } @out;
+        # ...or it's all the slaves mentioned if the list is just a "all"
+        %out = %slaves if (@out == 1 and $out[0] eq 'all');
+        return %out;
+    }
+
+    sub mode {
+        my $repo = shift;
+        return 'local' if not $hn;
+        return 'master' if $master eq $hn;
+        return 'slave' if $slaves{$hn};
+        return 'local' if not $master and not %slaves;
+        _die "$hn: '$repo' is mirrored but not here";
+    }
+}
+
+sub push_to_slaves {
+    my $repo = shift;
+
+    my $u = $ENV{GL_USER};
+    delete  $ENV{GL_USER};  # why?  see src/commands/mirror
+
+    for my $s (sort keys %slaves) {
+        system("gitolite mirror push $s $repo &");
+    }
+
+    $ENV{GL_USER} = $u;
+}
+
+1;
diff --git a/src/commands/mirror b/src/commands/mirror
new file mode 100755
index 0000000..004ee38
--- /dev/null
+++ b/src/commands/mirror
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+my $tid;
+BEGIN {
+    $tid = $ENV{GL_TID} || 0;
+    delete $ENV{GL_TID};
+}
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage 1:    gitolite mirror push <slave> <repo>
+Usage 2:    ssh git at master-server mirror push <slave> <repo>
+
+Forces a push of one repo to one slave.
+
+Usage 1 is directly on the master server.  Nothing is checked; if the slave
+accepts it, the push happens, even if the slave is not in any slaves
+option.  This is how you do delayed or lagged pushes to servers that do not
+need real-time updates or have bandwidth/connectivity issues.
+
+Usage 2 can be initiated by *any* user who has *any* gitolite access to the
+master server, but it checks that the slave is in one of the slaves options
+before doing the push.
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+_die "HOSTNAME not set" if not $rc{HOSTNAME};
+
+my ($cmd, $host, $repo) = @ARGV;
+usage() if not $repo;
+
+if ($cmd eq 'push') {
+    valid_slave($host, $repo) if exists $ENV{GL_USER};
+    # will die if host not in slaves for repo
+
+    trace(1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started");
+    _chdir($rc{GL_REPO_BASE});
+    _chdir("$repo.git");
+
+    my $errors = 0;
+    for (`git push --mirror $host:$repo 2>&1`) {
+        print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
+        chomp;
+        if (/FATAL/) {
+            $errors++;
+            gl_log('mirror', $_);
+        } else {
+            trace(1, "mirror: $_");
+        }
+    }
+    exit $errors;
+}
+
+sub valid_slave {
+    my ($host, $repo) = @_;
+    _die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
+
+    my $ref = git_config($repo, "^gitolite-options\\.mirror\\.slaves.*");
+    my %list = map { $_ => 1 } map { split } values %$ref;
+
+    _die "'$host' not a valid slave for '$repo'" unless $list{$host};
+}
diff --git a/t/mirror-test b/t/mirror-test
new file mode 100755
index 0000000..8793083
--- /dev/null
+++ b/t/mirror-test
@@ -0,0 +1,435 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# you need 3 disposable userids: sam, frodo, gollum.  Then the test user (say
+# "g3") needs to be able to sudo into them.  Put this in /etc/sudoers:
+
+#       g3 ALL = (sam,frodo,gollum) NOPASSWD: ALL
+
+$ENV{TSH_ERREXIT} = 1;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+use Cwd;
+my $workdir = getcwd();
+my $h = $ENV{HOME};
+my ($t, $t2);   # temp vars
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 152";
+##  try "DEF POK = !/DENIED/; !/failed to push/";
+
+##  confreset;confadd '
+
+##  ';
+
+##  try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+# ----------------------------------------------------------------------
+
+# switch keys
+sub swk {
+    my $h = $ENV{HOME};
+    my $k = shift;
+    system("cp $h/.ssh/$k $h/.ssh/id_rsa");
+    system("cp $h/.ssh/$k.pub $h/.ssh/id_rsa.pub");
+}
+
+sub all {
+    try "F " . join(" ", @_);
+    try "S " . join(" ", @_);
+    try "G " . join(" ", @_);
+}
+
+try "
+    DEF F   =   sudo -u frodo -i
+    DEF S   =   sudo -u sam -i
+    DEF G   =   sudo -u gollum -i
+";
+
+my $bd = `gitolite query-rc -n GL_BINDIR`;
+
+try "
+    $bd/../t/mirror-test-setup.sh;   ok or die mirror setup shell script failed
+        /hello server-frodo, this is frodo/
+        /hello server-sam, this is frodo/
+        /hello server-gollum, this is frodo/
+        /hello server-frodo, this is sam/
+        /hello server-sam, this is sam/
+        /hello server-gollum, this is sam/
+        /hello server-frodo, this is gollum/
+        /hello server-sam, this is gollum/
+        /hello server-gollum, this is gollum/
+        /hello admin, this is frodo/
+        /Initialized empty .*/gitolite-admin.git/
+        /Initialized empty .*/r1.git/
+        /Initialized empty .*/r2.git/
+        /Initialized empty .*/testing.git/
+        /Initialized empty .*/gitolite-admin.git/
+        /Initialized empty .*/r1.git/
+        /Initialized empty .*/r2.git/
+        /Initialized empty .*/testing.git/
+        /Initialized empty .*/gitolite-admin.git/
+        /Initialized empty .*/r1.git/
+        /Initialized empty .*/r2.git/
+        /Initialized empty .*/testing.git/
+";
+
+# ----------------------------------------------------------------------
+# SECTION 1: gitolite-admin shenanigans
+
+# push to frodo and see sam and gollum change
+try "
+    git clone frodo\@localhost:gitolite-admin fga
+        ok; /Cloning into 'fga'.../
+    cd fga;                             ok
+    cp $h/.ssh/u?.pub keydir;           ok
+    git add keydir;                     ok
+    git commit -m 6keys;                ok
+    git push;                           ok
+        /To frodo\@localhost:gitolite-admin/
+        /master -> master/
+    git rev-parse HEAD
+";
+
+chomp($t = text());
+
+try "
+    git ls-remote sam\@localhost:gitolite-admin
+        ok; /$t/
+    git ls-remote gollum\@localhost:gitolite-admin
+        ok; /$t/
+";
+
+try "
+    cd ..
+
+";
+
+# push to sam and see frodo and gollum change
+try "
+    git clone sam\@localhost:gitolite-admin sga
+        ok; /Cloning into 'sga'.../
+    cd sga;                             ok
+    empty;                              ok
+    git push;                           ok
+        /To sam\@localhost:gitolite-admin/
+        /master -> master/
+    git rev-parse HEAD
+";
+
+chomp($t = text());
+
+try "
+    git ls-remote frodo\@localhost:gitolite-admin
+        ok; /$t/
+    git ls-remote gollum\@localhost:gitolite-admin
+        ok; /$t/
+";
+
+try "
+    cd ..
+
+";
+
+# push to gollum and fail at gollum
+try "
+    git clone gollum\@localhost:gitolite-admin gga
+        ok; /Cloning into 'gga'.../
+    cd gga;                             ok
+    empty;                              ok
+    git push;                           !ok
+        !/To gollum\@localhost:gitolite-admin/
+        !/master -> master/
+        /gollum: pushing 'gitolite-admin' to slave 'gollum' not allowed/
+    git rev-parse HEAD
+";
+
+chomp($t2 = text());
+
+try "
+    git ls-remote frodo\@localhost:gitolite-admin
+        ok; /$t/; !/$t2/
+    git ls-remote sam\@localhost:gitolite-admin
+        ok; /$t/; !/$t2/
+    git ls-remote gollum\@localhost:gitolite-admin
+        ok; /$t/; !/$t2/
+";
+
+# fake out the gollum failure to continue the redirected push and fail at frodo
+try "
+    sudo -u gollum -i gitolite git-config -r gitolite-admin .
+        ok
+        /redirectOK.*sam/
+        !/redirectOK.*gollum/
+
+    sudo -u gollum -i bash -c 'echo repo gitolite-admin > junk'
+    sudo -u gollum -i bash -c 'echo option mirror.redirectOK-1 = gollum   >> junk'
+    sudo -u gollum -i bash -c 'cat junk >> .gitolite/conf/gitolite.conf'
+    sudo -u gollum -i gitolite compile
+    sudo -u gollum -i gitolite git-config -r gitolite-admin .
+        ok
+        /redirectOK.*sam/
+        /redirectOK.*gollum/
+
+    git push;                           !ok
+        /frodo: redirection not allowed from 'gollum'/
+        !/To gollum\@localhost:gitolite-admin/
+        !/master -> master/
+";
+
+# reset gollum via frodo
+try "
+    cd ..
+    rm -rf fga
+    git clone frodo\@localhost:gitolite-admin fga
+        ok; /Cloning into 'fga'.../
+    cd fga;                             ok
+    empty;                              ok
+    git push;                           ok
+        /To frodo\@localhost:gitolite-admin/
+        /master -> master/
+
+    sudo -u gollum -i gitolite git-config -r gitolite-admin .
+        ok
+        /redirectOK.*sam/
+        !/redirectOK.*gollum/
+
+    git rev-parse HEAD
+";
+
+chomp($t = text());
+
+try "
+    git ls-remote sam\@localhost:gitolite-admin
+        ok; /$t/
+    git ls-remote gollum\@localhost:gitolite-admin
+        ok; /$t/
+";
+
+# ----------------------------------------------------------------------
+# user repo shenanigans
+
+# for a recap of the perms see t/mirror-test-setup.sh
+
+try "
+    cd ..
+    pwd
+    /tmp/tsh_tempdir/ or die not in the right place
+" or die;
+
+swk('u1');
+
+# u1 sam r1, R ok, W ok
+try "
+    rm -rf fga sga gga
+
+    git clone sam\@localhost:r1 sr1
+        /Cloning into 'sr1'.../
+        /warning: You appear to have cloned an empty repository/
+    cd sr1
+    empty
+    git push origin master
+        /new branch/;   /master -> master/
+    git rev-parse HEAD
+";
+chomp($t = text());
+
+# u1 sam r1, W mirrors to frodo but not gollum
+try "
+    git ls-remote sam\@localhost:r1
+        /$t/
+    git ls-remote frodo\@localhost:r1
+        /$t/
+    git ls-remote gollum\@localhost:r1
+        /gollum: 'r1' is mirrored but not here/
+";
+
+swk("u2");
+try "
+    empty
+    git rev-parse HEAD
+";
+chomp($t2 = text());
+
+# u2 sam r2 W ok, mirrors to all
+try "
+    git push sam\@localhost:r2 master
+        /new branch/;   /master -> master/
+        /master -> master/
+    git ls-remote frodo\@localhost:r2
+        !/$t/
+        /$t2/
+    git ls-remote gollum\@localhost:r2
+        !/$t/
+        /$t2/
+";
+
+swk("u1");
+
+# u1 gollum r1 -- "known unknown" :-)
+# u1 frodo r1 R ok, W not ok
+# u1 sam r1 R ok, W ok
+try "
+    cd ..
+    rm -rf sr1
+
+    git clone gollum\@localhost:r1 fr1
+        /gollum: 'r1' is mirrored but not here/
+
+    git clone frodo\@localhost:r1 fr1;       ok
+    cd fr1
+    empty
+    git push
+        /frodo: pushing 'r1' to slave 'frodo' not allowed/
+    cd ..
+    git clone sam\@localhost:r1 sr1;         ok
+    cd sr1
+    empty
+    git push;                               ok
+        /master -> master/
+    git rev-parse HEAD
+";
+chomp($t = text());
+
+# u1 sam r1 W mirrored to frodo but not gollum
+try "
+    git ls-remote sam\@localhost:r1
+        /$t/
+    git ls-remote frodo\@localhost:r1
+        /$t/
+
+    git ls-remote gollum\@localhost:r1
+        /gollum: 'r1' is mirrored but not here/
+
+    git reset --hard HEAD^;                 ok
+    tc a
+    git push;                               !ok
+        /rejected/
+        /failed to push/
+
+    git push -f
+        /\\+ .......\\.\\.\\........ master -> master .forced update/
+";
+
+swk("u2");
+
+# u2 frodo r1 R ok, W not allowed (no redirectOK)
+# u2 frodo r2 W ok
+try "
+    cd ..
+    rm -rf fr1 sr1
+
+    git clone frodo\@localhost:r1 fr1;       ok
+    cd fr1
+    tc b
+    git push
+        /frodo: pushing 'r1' to slave 'frodo' not allowed/
+    cd ..
+    git clone frodo\@localhost:r2 fr2;       ok
+    cd fr2
+    tc c
+    git push
+        /master -> master/
+    git rev-parse HEAD
+";
+chomp($t = text());
+
+# u2 frodo r2 W mirrors to sam and gollum
+try "
+    git ls-remote sam\@localhost:r2
+        /$t/
+    git ls-remote gollum\@localhost:r2
+        /$t/
+
+    git reset --hard HEAD^;                 ok
+    tc d
+    git push
+        /rejected/
+        /failed to push/
+
+    git push -f
+        /\\+ .......\\.\\.\\........ master -> master .forced update/
+
+    cd ..
+    rm -rf fr1 fr2
+";
+
+swk("u3");
+
+# u3 frodo r2 R ok W ok
+try "
+    git clone frodo\@localhost:r2 fr2;       ok
+    cd fr2
+    tc e
+    git push;                               ok
+
+    git rev-parse HEAD
+";
+chomp($t = text());
+
+# u3 frodo r2 W mirrors to sam and gollum
+try "
+    git ls-remote sam\@localhost:r2
+        /$t/
+    git ls-remote gollum\@localhost:r2
+        /$t/
+
+    git reset --hard HEAD^;                 ok
+    tc f
+    git push
+        /rejected/
+        /failed to push/
+
+    sleep 10
+    git push -f
+        /\\+ refs/heads/master r2 u3 DENIED by fallthru/
+        /hook declined/
+        /rejected/
+";
+
+# ----------------------------------------------------------------------
+# all those vague edge cases where the two servers have totally wrong ideas
+# about each other
+
+swk('u1');
+
+try "sudo -u frodo -i ls .gitolite/logs";
+chomp($t = text());
+my $lfn = ".gitolite/logs/$t";
+
+try "
+    ssh sam\@localhost mirror push frodo lfrodo;  !ok
+    /FATAL: frodo: 'lfrodo' is local/
+
+    ssh sam\@localhost mirror push frodo mboth;  !ok
+    /FATAL: frodo: 'mboth' is native/
+
+    ssh sam\@localhost mirror push frodo mnotsam;  !ok
+    /FATAL: frodo: 'sam' is not the master for 'mnotsam'/
+
+    cd ..
+    git clone sam\@localhost:lfrodo2 lfrodo2;   ok
+    cd lfrodo2
+    empty
+    git push origin master;                     !ok
+    /FATAL: frodo: 'lfrodo2' is local/
+
+    cd ..
+    git clone sam\@localhost:nnfrodo nnfrodo;   ok
+    cd nnfrodo
+    empty
+    git push origin master;                     !ok
+    /FATAL: frodo: 'nnfrodo' is not native/
+
+    cd ..
+    git clone sam\@localhost:nvsfrodo nvsfrodo; ok
+    cd nvsfrodo
+    empty
+    git push origin master;                     !ok
+    /FATAL: frodo: 'sam' is not a valid slave for 'nvsfrodo'/
+";
diff --git a/t/mirror-test-rc b/t/mirror-test-rc
new file mode 100644
index 0000000..c5752bb
--- /dev/null
+++ b/t/mirror-test-rc
@@ -0,0 +1,127 @@
+# configuration variables for gitolite
+
+# This file is in perl syntax.  But you do NOT need to know perl to edit it --
+# just mind the commas, use single quotes unless you know what you're doing,
+# and make sure the brackets and braces stay matched up!
+
+# (Tip: perl allows a comma after the last item in a list also!)
+
+%RC = (
+    HOSTNAME                    =>  '%HOSTNAME',
+    UMASK                       =>  0077,
+    GIT_CONFIG_KEYS             =>  '',
+
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                   =>  1,
+
+    # settings used by external programs; uncomment and change as needed.  You
+    # can add your own variables for use in your own external programs; take a
+    # look at the cpu-time and desc commands for perl and shell samples.
+
+    # used by the cpu-time command
+    # DISPLAY_CPU_TIME          =>  1,
+    # CPU_TIME_WARN_LIMIT       =>  0.1,
+    # used by the desc command
+    # WRITER_CAN_UPDATE_DESC    =>  1,
+    # used by the info command
+    # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
+    # add more roles (like MANAGER, TESTER, ...) here.
+    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
+    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
+    ROLES                       =>
+        {
+            READERS             =>  1,
+            WRITERS             =>  1,
+        },
+    # uncomment (and change) this if you wish
+    # DEFAULT_ROLE_PERMS          =>  'READERS @all',
+
+    # comment out or uncomment as needed
+    # these are available to remote users
+    COMMANDS                    =>
+        {
+            'help'              =>  1,
+            'info'              =>  1,
+            'desc'              =>  1,
+            'perms'             =>  1,
+            'mirror'            =>  1,
+            'writable'          =>  1,
+        },
+
+    # comment out or uncomment as needed
+    # these will run in sequence during the conf file parse
+    SYNTACTIC_SUGAR             =>
+        [
+            # 'continuation-lines',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence to modify the input (arguments and environment)
+    INPUT                       =>
+        [
+            'Mirroring::input',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence just after the first access check is done
+    ACCESS_1                    =>
+        [
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence at the start, before a git operation has started
+    PRE_GIT                     =>
+        [
+            # if you use this, make this the first item in the list
+            # 'renice 10',
+
+            'Mirroring::pre_git',
+
+            # see docs ("list of non-core programs shipped") for details
+            # 'partial-copy',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence just after the second access check is done
+    ACCESS_2                    =>
+        [
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence at the end, after a git operation has ended
+    POST_GIT                    =>
+        [
+            # if you use this, make this the last item in the list
+            # 'cpu-time',
+            'Mirroring::post_git',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence after a new wild repo is created
+    POST_CREATE                 =>
+        [
+            'post-compile/update-git-configs',
+            'post-compile/update-gitweb-access-list',
+            'post-compile/update-git-daemon-access-list',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence after post-update
+    POST_COMPILE                =>
+        [
+            'post-compile/ssh-authkeys',
+            'post-compile/update-git-configs',
+            'post-compile/update-gitweb-access-list',
+            'post-compile/update-git-daemon-access-list',
+        ],
+);
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:
diff --git a/t/mirror-test-setup.sh b/t/mirror-test-setup.sh
new file mode 100755
index 0000000..23f382d
--- /dev/null
+++ b/t/mirror-test-setup.sh
@@ -0,0 +1,193 @@
+#!/bin/bash
+
+set -e
+hosts="frodo sam gollum"
+mainhost=frodo
+
+# setup software
+bd=`gitolite query-rc -n GL_BINDIR`
+rm -rf /tmp/g3/src
+cp -a $bd /tmp/g3/src
+chmod -R go+rX /tmp/g3
+
+# setup symlinks in frodo, sam, and gollum's accounts
+for h in $hosts
+do
+    sudo -u $h -i bash -c "rm -rf *.pub bin .ssh projects.list repositories .gitolite .gitolite.rc"
+done
+
+[ "$1" = "clear" ] && exit
+
+cd /tmp/g3
+[ -d keys ] || {
+    mkdir keys
+    cd keys
+    for h in $hosts
+    do
+        ssh-keygen -N '' -q -f server-$h  -C $h
+        chmod go+r /tmp/g3/keys/server-$h
+    done
+    cp $bd/../t/mirror-test-ssh-config ssh-config
+}
+
+for h in $hosts
+do
+    sudo -u $h -i bash -c "mkdir -p bin; ln -sf /tmp/g3/src/gitolite bin; mkdir -p .ssh; chmod 0700 .ssh"
+
+    sudo -u $h -i cp /tmp/g3/keys/ssh-config    .ssh/config
+    sudo -u $h -i cp /tmp/g3/keys/server-$h     .ssh/id_rsa
+    sudo -u $h -i cp /tmp/g3/keys/server-$h.pub .ssh/id_rsa.pub
+    sudo -u $h -i chmod go-rwx                  .ssh/id_rsa .ssh/config
+
+done
+
+# add all pubkeys to all servers
+for h in $hosts
+do
+    sudo -u $h -i gitolite setup -a admin
+    for j in $hosts
+    do
+        sudo -u $h -i gitolite setup -pk /tmp/g3/keys/server-$j.pub
+        echo sudo _u $j _i ssh $h at localhost info
+        sudo -u $j -i ssh -o StrictHostKeyChecking=no $h at localhost info
+    done
+    echo ----
+done
+
+# now copy our admin key to the main host
+cd;cd .ssh
+cp admin id_rsa; cp admin.pub id_rsa.pub
+cp admin.pub /tmp/g3/keys; chmod go+r /tmp/g3/keys/admin.pub
+sudo -u $mainhost -i gitolite setup -pk /tmp/g3/keys/admin.pub
+ssh $mainhost at localhost info
+
+lines="
+repo gitolite-admin
+    option mirror.master = frodo
+    option mirror.slaves-1 = sam gollum
+    option mirror.redirectOK = sam
+
+repo r1
+    RW+     =   u1
+    RW      =   u2
+    R       =   u3
+    option mirror.master = sam
+    option mirror.slaves-1 = frodo
+
+repo r2
+    RW+     =   u2
+    RW      =   u3
+    R       =   u4
+    option mirror.master = sam
+    option mirror.slaves-1 = frodo gollum
+    option mirror.redirectOK = all
+
+include \"%HOSTNAME.conf\"
+"
+
+lines2="
+repo l-%HOSTNAME
+RW  =   u1
+"
+
+# for each server, set the HOSTNAME to the rc, add the mirror options to the
+# conf file, and compile
+for h in $hosts
+do
+    cat $bd/../t/mirror-test-rc | perl -pe "s/%HOSTNAME/$h/" > /tmp/g3/temp
+    sudo -u $h -i cp /tmp/g3/temp .gitolite.rc
+    echo "$lines"  | sudo -u $h -i sh -c 'cat >> .gitolite/conf/gitolite.conf'
+    echo "$lines2" | sudo -u $h -i sh -c "cat >> .gitolite/conf/$h.conf"
+    sudo -u $h -i gitolite setup
+done
+
+# goes on frodo
+lines="
+# local to frodo but sam thinks frodo is a slave
+repo lfrodo
+RW  =   u1
+
+# both think they're master
+repo mboth
+RW  =   u1
+option mirror.master = frodo
+option mirror.slaves = sam
+
+# frodo thinks someone else is the master but sam thinks he is
+repo mnotsam
+RW  =   u1
+option mirror.master = merry
+option mirror.slaves = frodo
+
+# local to frodo but sam thinks frodo is a master and redirect is OK
+repo lfrodo2
+RW  =   u1
+
+# non-native to frodo but sam thinks frodo is master
+repo nnfrodo
+RW  =   u1
+option mirror.master = gollum
+option mirror.slaves = frodo
+option mirror.redirectOK = all
+
+# sam is not a valid slave to send stuff to frodo
+repo nvsfrodo
+RW  =   u1
+option mirror.master = frodo
+option mirror.slaves = gollum
+option mirror.redirectOK = all
+"
+
+echo "$lines" | sudo -u frodo -i sh -c "cat >> .gitolite/conf/frodo.conf"
+
+# goes on sam
+lines="
+# local to frodo but sam thinks frodo is a slave
+repo lfrodo
+RW  =   u1
+option mirror.master = sam
+option mirror.slaves = frodo
+
+# both think they're master
+repo mboth
+RW  =   u1
+option mirror.master = sam
+option mirror.slaves = frodo
+
+# frodo thinks someone else is the master but sam thinks he is
+repo mnotsam
+RW  =   u1
+option mirror.master = sam
+option mirror.slaves = frodo
+
+# local to frodo but sam thinks frodo is a master and redirect is OK
+repo lfrodo2
+RW  =   u1
+option mirror.master = frodo
+option mirror.slaves = sam
+option mirror.redirectOK = all
+
+# non-native to frodo but sam thinks frodo is master
+repo nnfrodo
+RW  =   u1
+option mirror.master = frodo
+option mirror.slaves = sam
+option mirror.redirectOK = all
+
+# sam is not a valid slave to send stuff to frodo
+repo nvsfrodo
+RW  =   u1
+option mirror.master = frodo
+option mirror.slaves = sam
+option mirror.redirectOK = all
+"
+
+echo "$lines" | sudo -u sam -i sh -c "cat >> .gitolite/conf/sam.conf"
+
+for h in $hosts
+do
+    sudo -u $h -i gitolite setup
+done
+
+# that ends the setup phase
+echo ======================================================================
diff --git a/t/mirror-test-ssh-config b/t/mirror-test-ssh-config
new file mode 100644
index 0000000..40de6d7
--- /dev/null
+++ b/t/mirror-test-ssh-config
@@ -0,0 +1,11 @@
+host frodo
+    user frodo
+    hostname localhost
+
+host sam
+    user sam
+    hostname localhost
+
+host gollum
+    user gollum
+    hostname localhost

commit b78466b1642ca4d685c26360d2882cca73614c4a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 31 19:45:59 2012 +0530

    put the VERSION file in the right place
    
    I don't know why I had put VERSION in GL_ADMIN_BASE, which is pretty
    stupid.  It should be in GL_BINDIR.
    
    It also has nothing to do with setup -- the file needs to be generated
    at 'install' time.

diff --git a/INSTALL b/INSTALL
deleted file mode 100644
index b1c634d..0000000
--- a/INSTALL
+++ /dev/null
@@ -1,17 +0,0 @@
-1.  Clone the repo and copy src somewhere (or leave it where it is, if you're
-    sure no one will 'git pull' on a running system!)
-
-        cp -a src /some/full/path
-
-2.  (Optional) Make a symlink for the single executable 'gitolite' to
-    somewhere in `$PATH`
-
-        ln -sf /full/path/to/some/damn/place/gitolite $HOME/bin
-
-3.  Run setup.  That is, either run:
-
-        gitolite setup -a YourName -pk /tmp/YourName.pub
-
-    or, if you did not do step 2, run:
-
-        /some/full/path/src/gitolite -a YourName -pk /tmp/YourName.pub
diff --git a/install b/install
new file mode 100755
index 0000000..f9df125
--- /dev/null
+++ b/install
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# Clearly you don't need a program to make one measly symlink, but the git
+# describe command involved in generating the VERSION string is a bit fiddly.
+
+use Getopt::Long;
+use FindBin;
+
+# meant to be run from the root of the gitolite tree, one level above 'src'
+BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin . "/src"; }
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+
+=for usage
+Usage (from gitolite clone directory):
+
+    ./install
+        to run gitolite using an absolute or relative path, for example
+        'src/gitolite' or '/full/path/to/this/dir/src/gitolite'
+    ./install -ln [<dir>]
+        to symlink just the gitolite executable to some <dir> that is in
+        $PATH.  <dir> defauls to $HOME/bin if <dir> not specified.
+    ./install -to <dir>
+        to copy the entire 'src' directory to <dir>.  If <dir> is not in
+        $PATH, use the full path to run gitolite commands.
+
+Simplest use, if you have $HOME/bin in $PATH, is:
+
+    git clone -b g3 git://github.com/sitaramc/gitolite
+    gitolite/install -ln
+
+    # now run setup
+    gitolite setup -pk /path/to/YourName.pub
+=cut
+
+my ( $to, $ln, $help, $quiet );
+
+GetOptions(
+    'to=s' => \$to,
+    'ln:s' => \$ln,
+    'help|h'    => \$help,
+    'quiet|q'    => \$quiet,
+);
+usage() if $to and $ln or $help;
+$ln = "$ENV{HOME}/bin" if defined($ln) and not $ln;
+
+chdir($ENV{GL_BINDIR});
+my $version = `git describe --tags --long --dirty=-dt`;
+
+if ($to) {
+    _mkdir($to);
+    system("cp -a * $to");
+    _print( "$to/VERSION", $version );
+} elsif ($ln) {
+    ln_sf( $ENV{GL_BINDIR}, "gitolite", $ln );
+    _print( "VERSION", $version );
+} else {
+    say "use the following full path for gitolite:";
+    say "\t$ENV{GL_BINDIR}/gitolite";
+    _print( "VERSION", $version );
+}
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index e8def9a..57cedb2 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -161,7 +161,7 @@ sub query_rc {
 sub version {
     my $version = '';
     $version = '(unknown)';
-    for ("$rc{GL_ADMIN_BASE}/VERSION") {
+    for ("$ENV{GL_BINDIR}/VERSION") {
         $version = slurp($_) if -r $_;
     }
     chomp($version);
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index e57b8d1..e876a50 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -102,9 +102,6 @@ sub setup_gladmin {
     _mkdir( $rc{GL_ADMIN_BASE} );
     _chdir( $rc{GL_ADMIN_BASE} );
 
-    tsh_try("cd \$GL_BINDIR; git describe --tags --long --dirty=-dt 2>/dev/null")
-      and _print( "VERSION", tsh_text() );
-
     _mkdir("conf");
     _mkdir("logs");
     my $conf;

commit 4e093d5909eec9447f04ee97b115a070ae3a0d92
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 23:57:54 2012 +0530

    migration bug: admin repo hooks not updated by 'gitolite setup'
    
    because the loop in the code below was sending out $repo =
    './gitolite-admin.git' to hook_1(), inside which there is an explicit
    check for 'gitolite-admin', which of course doesn't match
    './gitolite-admin'!

diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 0c917ac..a568a3b 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -197,6 +197,7 @@ sub hook_repos {
     for my $repo (`find . -name "*.git" -prune`) {
         chomp($repo);
         $repo =~ s/\.git$//;
+        $repo =~ s(^\./)();
         hook_1($repo);
     }
 }

commit 2c1f46c0559ff49be5738735eea2b4b05134005e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 18:00:02 2012 +0530

    %HOSTNAME and new subconf enhancement

diff --git a/doc/misc.mkd b/doc/misc.mkd
index 0b8c9e7..d598401 100644
--- a/doc/misc.mkd
+++ b/doc/misc.mkd
@@ -78,3 +78,55 @@ is effectively the same as this, for repo foo:
         R   =   gitweb
 
 This extends to patterns also, but I'll leave an example for later.
+
+## #subconf the subconf command
+
+This is just like the include command:
+
+    subconf "foo/bar.conf"      # example 1
+    subconf "foo/*.conf"        # example 2
+
+with the difference that, for the duration of the file(s) being included, a
+subconf restriction is in effect.  This restrictions limits the repos that can
+be access controlled by the lines within the included file(s).
+
+Here's how it works.  First, a subconf *name* is derived from the filename
+being included, which is basically the basename of the file.  For example 1
+that is "bar".  For example 2, assuming foo contains "a.conf" and "b.conf",
+the subconf name is "a" while processing "a.conf", and "b" while processing
+"b.conf".
+
+A variation of the subconf command allows you to specify the subconf *name*
+explicitly, while including files as before:
+
+    subconf frob "foo/bar.conf  # example 3
+    subconf frob "foo/*.conf    # example 4
+
+In this case the subconf name is "frob" in both cases.
+
+A subconf restricts the repos that can be named in 'repo' lines while the
+subconf is in effect.  If the subconf name is "foo", the conf lines parsed
+while under the subconf restriction can only refer to
+
+  * a repo called 'foo'
+  * a repo group called '@foo'
+  * the members of the same repo group '@foo'
+  * match a regex that is a member of the repo group '@foo'
+
+In the last 3 cases, the repo group '@foo' must be defined in the main conf
+file (i.e., outside the subconf restriction).
+
+For example, if you have this in the main conf file:
+
+    @foo    =   bar baz frob/..*
+
+    subconf "foo.conf"
+
+then foo.conf can only refer to 'foo', '@foo', 'bar', 'baz', or any repo name
+matching the pattern `frob/..*`.
+
+## #HOSTNAME HOSTNAME substitution
+
+Wherever gitolite sees the word `%HOSTNAME`, it will replace it with the
+HOSTNAME supplied in the rc file, if one was supplied.  This is mainly useful
+in [mirroring][].
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index c19975b..f72878b 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -9,6 +9,7 @@ package Gitolite::Conf::Explode;
 
 use Exporter 'import';
 
+use Gitolite::Rc;
 use Gitolite::Common;
 
 use strict;
@@ -33,10 +34,13 @@ sub explode {
         my $line = cleanup_conf_line($_);
         next unless $line =~ /\S/;
 
+        # subst %HOSTNAME word if rc defines a hostname, else leave as is
+        $line =~ s/%HOSTNAME\b/$rc{HOSTNAME}/g if $rc{HOSTNAME};
+
         $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
 
-        if ( $line =~ /^(include|subconf) (\S.+)$/ ) {
-            incsub( $1, $2, $subconf, $out );
+        if ( $line =~ /^(include|subconf) (?:(\S+) )?(\S.+)$/ ) {
+            incsub( $1, $2, $3, $subconf, $out );
         } else {
             # normal line, send it to the callback function
             push @{$out}, $line;
@@ -46,9 +50,9 @@ sub explode {
 
 sub incsub {
     my $is_subconf = ( +shift eq 'subconf' );
-    my ( $include_glob, $subconf, $out ) = @_;
+    my ( $new_subconf, $include_glob, $current_subconf, $out ) = @_;
 
-    _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master';
+    _die "subconf $current_subconf attempting to run 'subconf'\n" if $is_subconf and $current_subconf ne 'master';
 
     _die "invalid include/subconf file/glob '$include_glob'"
       unless $include_glob =~ /^"(.+)"$/
@@ -65,11 +69,11 @@ sub incsub {
         next if already_included($file);
 
         if ($is_subconf) {
-            push @{$out}, "subconf $basename";
-            explode( $file, $basename, $out );
-            push @{$out}, "subconf $subconf";
+            push @{$out}, "subconf " . ( $new_subconf || $basename );
+            explode( $file, ( $new_subconf || $basename ), $out );
+            push @{$out}, "subconf $current_subconf";
         } else {
-            explode( $file, $subconf, $out );
+            explode( $file, $current_subconf, $out );
         }
     }
 }
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index eef34d0..0c917ac 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -218,7 +218,7 @@ sub store {
 
 sub parse_done {
     for my $ig ( sort keys %ignored ) {
-        _warn "$ig.conf attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
+        _warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
     }
 }
 
diff --git a/t/deleg-1.t b/t/deleg-1.t
index b969543..5f4d6e5 100755
--- a/t/deleg-1.t
+++ b/t/deleg-1.t
@@ -81,7 +81,7 @@ put   "conf/fragments/u3r.conf", '
         RW+     =   u6
 ';
 try "SUBCONF_PUSH u3 u3;
-        /WARNING: u3r.conf attempting to set access for r2a/
+        /WARNING: subconf 'u3r' attempting to set access for r2a/
 ";
 
 try "git reset --hard origin/master; ok";
@@ -95,5 +95,5 @@ put   "conf/fragments/u3r.conf", '
 ';
 
 try "SUBCONF_PUSH u3 u3
-        /WARNING: u3r.conf attempting to set access for locally modified \@u3r/
+        /WARNING: subconf 'u3r' attempting to set access for locally modified \@u3r/
 ";
diff --git a/t/deleg-2.t b/t/deleg-2.t
index 54f5156..cf55972 100755
--- a/t/deleg-2.t
+++ b/t/deleg-2.t
@@ -103,7 +103,7 @@ put   "conf/fragments/u3r.conf", '
         RW+     =   u6
 ';
 try "SUBCONF_PUSH u3 u3;
-        /WARNING: u3r.conf attempting to set access for r2a/
+        /WARNING: subconf 'u3r' attempting to set access for r2a/
 ";
 
 try "git reset --hard origin/master; ok";
@@ -117,5 +117,5 @@ put   "conf/fragments/u3r.conf", '
 ';
 
 try "SUBCONF_PUSH u3 u3
-        /WARNING: u3r.conf attempting to set access for locally modified \@u3r/
+        /WARNING: subconf 'u3r' attempting to set access for locally modified \@u3r/
 ";
diff --git a/t/hostname.t b/t/hostname.t
new file mode 100755
index 0000000..a6cbde2
--- /dev/null
+++ b/t/hostname.t
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# %HOSTNAME tests
+# ----------------------------------------------------------------------
+
+try "plan 60";
+
+try "pwd";
+my $od = text();
+chomp($od);
+
+# without setting HOSTNAME in rc
+confreset;confadd '
+
+    repo foo
+        RW  dev/%HOSTNAME   =   u1
+';
+
+try "ADMIN_PUSH set1; /FATAL/";
+try "/bad ref 'refs/heads/dev/%HOSTNAME'/";
+
+# make a hostname entry
+$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
+put "$ENV{G3T_RC}", "\$rc{HOSTNAME} = 'frodo';\n";
+
+confreset;confadd '
+
+    repo bar
+        RW  %HOSTNAME_baz   =   u1
+';
+
+try "ADMIN_PUSH set1; /FATAL/";
+try "/bad ref 'refs/heads/%HOSTNAME_baz'/";
+
+confreset;confadd '
+
+    repo bar
+        RW  %HOSTNAME/      =   u1
+        RW  %HOSTNAME-baz   =   u1
+';
+
+try "ADMIN_PUSH set1; !/FATAL/";
+try "
+    gitolite access bar u2 R any;                   /R any bar u2 DENIED by fallthru/
+    gitolite access bar u2 W any;                   /W any bar u2 DENIED by fallthru/
+    gitolite access bar u1 W any;                   !/DENIED/; /refs/heads/frodo/; !/baz/
+    gitolite access bar u1 R any;                   !/DENIED/; /refs/heads/frodo/; !/baz/
+    gitolite access bar u1 R refs/heads/frodo;      /R refs/heads/frodo bar u1 DENIED by fallthru/
+    gitolite access bar u1 W refs/heads/frodo;      /W refs/heads/frodo bar u1 DENIED by fallthru/
+    gitolite access bar u1 R refs/heads/frodo/1;    !/DENIED/; /refs/heads/frodo/; !/baz/
+    gitolite access bar u1 W refs/heads/frodo/1;    !/DENIED/; /refs/heads/frodo/; !/baz/
+    gitolite access bar u1 R refs/heads/sam;        /R refs/heads/sam bar u1 DENIED by fallthru/
+    gitolite access bar u1 W refs/heads/sam;        /W refs/heads/sam bar u1 DENIED by fallthru/
+    gitolite access bar u1 R refs/heads/master;     /R refs/heads/master bar u1 DENIED by fallthru/
+    gitolite access bar u1 W refs/heads/master;     /W refs/heads/master bar u1 DENIED by fallthru/
+
+    gitolite access bar u1 R refs/heads/frodo-baz;  !/DENIED/; /refs/heads/frodo-baz/
+    gitolite access bar u1 W refs/heads/frodo-baz;  !/DENIED/; /refs/heads/frodo-baz/
+";
+
+confreset;confadd '
+
+    repo foo-%HOSTNAME
+        RW  =   u1
+';
+
+try "ADMIN_PUSH set1; !/FATAL/";
+try "
+    gitolite list-repos;            /foo-frodo/
+    gitolite list-phy-repos;        /foo-frodo/
+";
diff --git a/t/include-subconf.t b/t/include-subconf.t
index a5fc908..a3d2a57 100755
--- a/t/include-subconf.t
+++ b/t/include-subconf.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # include and subconf
 # ----------------------------------------------------------------------
 
-try 'plan 37';
+try 'plan 55';
 
 confreset; confadd '
     include "i1.conf"
@@ -39,7 +39,7 @@ try "ADMIN_PUSH set2; !/FATAL/" or die text();
 
 try "
                                         /i1.conf already included/
-	                                /i2.conf attempting to set access for \@i1, b2, bar, i1, locally modified \@g2/
+	                                /subconf 'i2' attempting to set access for \@i1, b2, bar, i1, locally modified \@g2/
                                         !/attempting to set access.*i2/
                                         /Initialized.*empty.*baz.git/
                                         /Initialized.*empty.*foo.git/
@@ -62,7 +62,7 @@ confadd 'g2.conf', '
 
 try "ADMIN_PUSH set3; !/FATAL/" or die text();
 try "
-                                        /g2.conf attempting to set access for locally modified \@g2/
+                                        /subconf 'g2' attempting to set access for locally modified \@g2/
                                         !/Initialized.*empty/
 ";
 
@@ -80,3 +80,37 @@ confadd 'g2.conf', '
 try "
     ADMIN_PUSH set3;           ok;     /FATAL: subconf g2 attempting to run 'subconf'/
 ";
+
+# ----------------------------------------------------------------------
+
+confreset; confadd '
+    include "i1.conf"
+    @i2 = b1
+    subconf i2 "eye2.conf"
+';
+confadd 'eye2.conf', '
+    repo @eye2
+        RW = u2
+';
+
+try "ADMIN_PUSH set2; !/FATAL/" or die text();
+
+try "
+    /subconf 'i2' attempting to set access for \@eye2/
+";
+
+confreset; confadd '
+    include "i1.conf"
+    @i2 = b1
+    subconf i2 "eye2.conf"
+';
+confadd 'eye2.conf', '
+    repo @i2
+        RW = u2
+';
+
+try "ADMIN_PUSH set2; !/FATAL/" or die text();
+
+try "
+    !/subconf 'i2' attempting to set access for \@eye2/
+";

commit 0dfabe9f45bf56f4cec4a241f475829a93033599
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 09:30:13 2012 +0530

    added "INPUT" trigger (not yet documented; internal use only for now)

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 9407883..ce98368 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -26,6 +26,8 @@ if ( exists $ENV{G3T_USER} ) {
     _die "who the *heck* are you?";
 }
 
+trigger('INPUT');
+
 main($id);
 
 gl_log('END') if $$ == $ENV{GL_TID};

commit 906ed4cbe2a7ddc338b6238cc075d307c82cb882
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 06:11:06 2012 +0530

    logging, tracing, and perltidy, ...
    
    ...plus renamed a couple of log events for consistency

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 0dc503f..79c905d 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -39,6 +39,8 @@ sub say2 {
 }
 
 sub trace {
+    gl_log( "\t" . join( ",", @_[ 1 .. $#_ ] ) ) if $_[0] <= 1 and defined $Gitolite::Rc::rc{LOG_EXTRA};
+
     return unless defined( $ENV{D} );
 
     my $level = shift; return if $ENV{D} < $level;
@@ -111,8 +113,7 @@ sub _chdir {
 sub _system {
     # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
     # exit with <rc of system()> if it applies, else just "exit 1".
-    trace( 2, @_ );
-    gl_log( 'system', @_ );
+    trace( 1, 'system', @_ );
     if ( system(@_) != 0 ) {
         trace( 1, "system() failed", @_, "-> $?" );
         if ( $? == -1 ) {
@@ -238,10 +239,12 @@ sub gl_log {
     # the log filename and the timestamp come from the environment.  If we get
     # called even before they are set, we have no choice but to dump to STDERR
     # (and probably call "logger").
+
+    # tab sep if there's more than one field
     my $msg = join( "\t", @_ );
     $msg =~ s/[\n\r]+/<<newline>>/g;
 
-    my $ts  = gen_ts();
+    my $ts = gen_ts();
     my $tid = $ENV{GL_TID} ||= $$;
 
     my $fh;
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index 44f534a..c19975b 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -86,7 +86,7 @@ sub prefix_groupnames {
     if ($lhs) {
         $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
         $prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs";
-        trace( 2, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
+        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
     }
 
     return $line;
@@ -105,7 +105,7 @@ sub already_included {
 
 sub device_inode {
     my $file = shift;
-    trace( 2, $file, ( stat $file )[ 0, 1 ] );
+    trace( 3, $file, ( stat $file )[ 0, 1 ] );
     return join( "/", ( stat $file )[ 0, 1 ] );
 }
 
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 28819ff..72db4de 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -67,7 +67,7 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
-    my $deny_rules = option($repo, 'deny-rules');
+    my $deny_rules = option( $repo, 'deny-rules' );
     load($repo);
 
     # sanity check the only piece the user can control
@@ -192,7 +192,7 @@ sub load_1 {
     trace( 3, $repo );
 
     if ( repo_missing($repo) ) {
-        trace( 2, "repo '$repo' missing" );
+        trace( 1, "repo '$repo' missing" );
         return;
     }
     _chdir("$rc{GL_REPO_BASE}/$repo.git");
@@ -289,7 +289,7 @@ sub memberships {
         }
     }
 
-    if ( $type eq 'user' and $repo and not repo_missing($repo)  ) {
+    if ( $type eq 'user' and $repo and not repo_missing($repo) ) {
         # find the roles this user has when accessing this repo and add those
         # in as groupnames he is a member of.  You need the already existing
         # memberships for this; see below this function for an example
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index a33931e..eef34d0 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -277,7 +277,7 @@ sub store_common {
     my $compiled_fh = _open( ">", "$cc.new" );
 
     my $data_version = glrc('current-data-version');
-    trace( 1, "data_version = $data_version" );
+    trace( 3, "data_version = $data_version" );
     print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
 
     my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] );
diff --git a/src/Gitolite/Easy.pm b/src/Gitolite/Easy.pm
index 4d97ff8..60574a0 100644
--- a/src/Gitolite/Easy.pm
+++ b/src/Gitolite/Easy.pm
@@ -130,11 +130,11 @@ sub can_write {
 #   foo=$(gitolite git-config -r $REPONAME foo\\.bar)
 sub config {
     my $repo = shift;
-    my $key = shift;
+    my $key  = shift;
 
     return () if repo_missing($repo);
 
-    my $ret = git_config($repo, $key);
+    my $ret = git_config( $repo, $key );
     return %$ret;
 }
 
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index d9fa47a..6effeda 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -19,8 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub post_update {
-    trace( 2, @ARGV );
-    gl_log( 'post-up', @ARGV );
+    trace( 1, 'post-up', @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index d772c2a..262f566 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -20,27 +20,27 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub update {
-    trace( 2, @ARGV );
-    gl_log( 'update', @ARGV );
     # this is the *real* update hook for gitolite
 
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
 
+    trace( 1, 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
+
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
 
-    gl_log( 'check2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, '->', $ret );
+    trace( 1, "-> $ret" );
+    gl_log( 'update', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
     exit 0;
 }
 
 sub check_vrefs {
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
     my $name_seen = 0;
-    my $n_vrefs = 0;
+    my $n_vrefs   = 0;
     for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
         $n_vrefs++;
         if ( $vref =~ m(^VREF/NAME/) ) {
@@ -72,10 +72,10 @@ sub check_vref {
     my ( $aa, $ref, $deny_message ) = @_;
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+    trace( 2, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
       if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
-    trace( 1, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
+    trace( 2, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
 }
 
 {
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 25d5cb8..e8def9a 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -58,8 +58,7 @@ my $rc = glrc('filename');
 do $rc if -r $rc;
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
-    say2 "FATAL: $rc seems to be for older gitolite; please see doc/g2migr.mkd\n" .
-    "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)";
+    say2 "FATAL: $rc seems to be for older gitolite; please see doc/g2migr.mkd\n" . "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)";
 
     exit 1;
 }
@@ -177,12 +176,13 @@ sub trigger {
             _die "$rc_section section in rc file is not a perl list";
         } else {
             for my $s ( @{ $rc{$rc_section} } ) {
-                my ($pgm, @args) = split ' ', $s;
+                my ( $pgm, @args ) = split ' ', $s;
 
-                if ( my($module, $sub) = ($pgm =~ /^(.*)::(\w+)$/ ) ) {
+                if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
 
                     require Gitolite::Triggers;
-                    Gitolite::Triggers::run($module, $sub, @args, $rc_section, @_);
+                    trace(1, 'trigger', $module, $sub, @args, $rc_section, @_ );
+                    Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
 
                 } else {
                     $pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
@@ -251,6 +251,9 @@ __DATA__
     UMASK                       =>  0077,
     GIT_CONFIG_KEYS             =>  '',
 
+    # comment out if you don't need all the extra detail in the logfile
+    LOG_EXTRA                   =>  1,
+
     # settings used by external programs; uncomment and change as needed.  You
     # can add your own variables for use in your own external programs; take a
     # look at the cpu-time and desc commands for perl and shell samples.
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 227dfe4..e57b8d1 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -64,11 +64,11 @@ sub args {
         'help|h'      => \$help,
     ) or usage();
 
-    usage() if $help or ($pubkey and $admin);
+    usage() if $help or ( $pubkey and $admin );
 
     if ($pubkey) {
         $pubkey =~ /\.pub$/ or _die "$pubkey name does not end in .pub";
-        $pubkey =~ /\@/    and _die "$pubkey name contains '\@'";
+        $pubkey =~ /\@/ and _die "$pubkey name contains '\@'";
         tsh_try("cat $pubkey")              or _die "$pubkey not a readable file";
         tsh_lines() == 1                    or _die "$pubkey must have exactly one line";
         tsh_try("ssh-keygen -l -f $pubkey") or _die "$pubkey does not seem to be a valid ssh pubkey file";
diff --git a/src/Gitolite/Triggers.pm b/src/Gitolite/Triggers.pm
index e4d09ec..16e8aa6 100644
--- a/src/Gitolite/Triggers.pm
+++ b/src/Gitolite/Triggers.pm
@@ -18,7 +18,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub run {
-    my ($module, $sub, @args) = @_;
+    my ( $module, $sub, @args ) = @_;
     $module = "Gitolite::Triggers::$module" if $module !~ /^Gitolite::/;
 
     eval "require $module";
diff --git a/src/Gitolite/Triggers/Writable.pm b/src/Gitolite/Triggers/Writable.pm
index b6a1e6f..837145c 100644
--- a/src/Gitolite/Triggers/Writable.pm
+++ b/src/Gitolite/Triggers/Writable.pm
@@ -4,10 +4,10 @@ use Gitolite::Rc;
 use Gitolite::Common;
 
 sub writable {
-    my ($repo, $aa, $result) = @_[1, 3, 5];
+    my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ];
     return if $aa eq 'R' or $result =~ /DENIED/;
 
-    for my $f ("$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down") {
+    for my $f ( "$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down" ) {
         next unless -f $f;
         _die slurp($f) if -s $f;
         _die "sorry, writes are currently disabled (no more info available)\n";
diff --git a/src/commands/creator b/src/commands/creator
index b50aae5..94b8cf9 100755
--- a/src/commands/creator
+++ b/src/commands/creator
@@ -23,7 +23,7 @@ printing anything, which makes it possible to do this in shell:
 
 usage() if not @ARGV or $ARGV[0] eq '-h';
 my $nl = "\n";
-if ($ARGV[0] eq '-n') {
+if ( $ARGV[0] eq '-n' ) {
     $nl = '';
     shift;
 }
@@ -36,5 +36,5 @@ if ($user) {
     exit 0 if $creator eq $user;
     exit 1;
 }
-return ($creator eq $user) if $user;
+return ( $creator eq $user ) if $user;
 print "$creator$nl";
diff --git a/src/commands/info b/src/commands/info
index 7b8b845..f68f867 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -26,8 +26,8 @@ my ( $lc, $patt ) = args();
 
 print_version();
 
-print_patterns();   # repos he can create for himself
-print_phy_repos();  # repos already created
+print_patterns();     # repos he can create for himself
+print_phy_repos();    # repos already created
 print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
 
 # ----------------------------------------------------------------------
diff --git a/src/commands/perms b/src/commands/perms
index a476241..bbc0e5f 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -35,7 +35,7 @@ if ( $ARGV[0] eq '-l' ) {
 }
 
 setperms(@ARGV);
-_system("gitolite", "trigger", "POST_CREATE");
+_system( "gitolite", "trigger", "POST_CREATE" );
 
 # ----------------------------------------------------------------------
 
diff --git a/src/gitolite b/src/gitolite
index 341657f..b96c2ee 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -46,7 +46,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 my ( $command, @args ) = @ARGV;
-gl_log( 'command', @ARGV ) if -d $rc{GL_ADMIN_BASE};
+gl_log( 'cli', 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE} and $$ == ( $ENV{GL_TID} || 0 );
 args();
 
 # the first two commands need options via @ARGV, as they have their own
@@ -60,7 +60,7 @@ if ( $command eq 'setup' ) {
 
 } elsif ( $command eq 'query-rc' ) {
     shift @ARGV;
-    query_rc();     # doesn't return
+    query_rc();    # doesn't return
 
 # the rest don't need @ARGV per se
 
@@ -91,7 +91,7 @@ if ( $command eq 'setup' ) {
     _die "unknown gitolite sub-command";
 }
 
-gl_log( '==end==' ) if $$ == $ENV{GL_TID};
+gl_log('END') if $$ == $ENV{GL_TID};
 
 sub args {
     usage() if not $command or $command eq '-h';
diff --git a/src/gitolite-shell b/src/gitolite-shell
index c3381da..9407883 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -17,7 +17,7 @@ use warnings;
 # the main() sub expects ssh-ish things; set them up...
 my $id = '';
 if ( exists $ENV{G3T_USER} ) {
-    $id = in_local();    # file:// masquerading as ssh:// for easy testing
+    $id = in_file();    # file:// masquerading as ssh:// for easy testing
 } elsif ( exists $ENV{SSH_CONNECTION} ) {
     $id = in_ssh();
 } elsif ( exists $ENV{REQUEST_URI} ) {
@@ -28,18 +28,20 @@ if ( exists $ENV{G3T_USER} ) {
 
 main($id);
 
-gl_log( '==end==' ) if $$ == $ENV{GL_TID};
+gl_log('END') if $$ == $ENV{GL_TID};
 
 exit 0;
 
 # ----------------------------------------------------------------------
 
-sub in_local {
+sub in_file {
+    gl_log( 'file', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
+
     if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) {
         print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
         print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
     }
-    return 'local';
+    return 'file';
 }
 
 sub in_http {
@@ -47,13 +49,16 @@ sub in_http {
 }
 
 sub in_ssh {
+    my $ip;
+    ( $ip = $ENV{SSH_CONNECTION} || '(no-IP)' ) =~ s/ .*//;
+
+    gl_log( 'ssh', "ARGV=" . join( ",", @ARGV ), "SOC=$ENV{SSH_ORIGINAL_COMMAND}", "FROM=$ip" );
+
     $ENV{SSH_ORIGINAL_COMMAND} ||= '';
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
     $soc =~ s/[\n\r]+/<<newline>>/g;
     _die "I don't like newlines in the command: $soc\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
 
-    my $ip;
-    ($ip = $ENV{SSH_CONNECTION} || '(no-IP)') =~ s/ .*//;
     return $ip;
 }
 
@@ -64,7 +69,6 @@ sub in_ssh {
 sub main {
     my $id = shift;
 
-    gl_log( 'remote', $id, @ARGV, $ENV{SSH_ORIGINAL_COMMAND} );
     umask $rc{UMASK};
 
     # set up the user
@@ -88,12 +92,13 @@ sub main {
     # yet know the ref that will be eventually pushed (and even that won't
     # apply if it's a read operation).  See the matching code in access() for
     # more information.
-    unless ($ENV{GL_BYPASS_ACCESS_CHECKS}) {
+    unless ( $ENV{GL_BYPASS_ACCESS_CHECKS} ) {
         my $ret = access( $repo, $user, $aa, 'any' );
         trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
-        gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret );
         trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
         _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
+
+        gl_log( "pre_git", $repo, $user, $aa, 'any', "-> $ret" );
     }
 
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
diff --git a/src/triggers/cpu-time b/src/triggers/cpu-time
index d363328..c7ca8f7 100755
--- a/src/triggers/cpu-time
+++ b/src/triggers/cpu-time
@@ -5,7 +5,7 @@ use warnings;
 use lib $ENV{GL_BINDIR};
 use Gitolite::Easy;
 
-my ($trigger, $repo, $user, $aa, $ref, $verb, $utime, $stime, $cutime, $cstime) = @ARGV;
+my ( $trigger, $repo, $user, $aa, $ref, $verb, $utime, $stime, $cutime, $cstime ) = @ARGV;
 
 # now do whatever you want with this data; the following is just an example.
 
@@ -14,13 +14,13 @@ my ($trigger, $repo, $user, $aa, $ref, $verb, $utime, $stime, $cutime, $cstime)
 # to the rc file, and (c) change your rc file to call your program at the end
 # of the POST_GIT list.
 
-if (my $limit = $rc{CPU_TIME_WARN_LIMIT}) {
+if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
     my $total = $utime + $cutime + $stime + $cstime;
     # some code to send an email or whatever...
     say2 "limit = $limit, actual = $total" if $total > $limit;
 }
 
-if ($rc{DISPLAY_CPU_TIME}) {
+if ( $rc{DISPLAY_CPU_TIME} ) {
     say2 "perf stats for $verb on repo '$repo':";
     say2 "  user CPU time: " . ( $utime + $cutime );
     say2 "  sys  CPU time: " . ( $stime + $cstime );
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index ec1bff6..5dc395e 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -14,13 +14,13 @@ $|++;
 # arguments anyway, it hardly matters.
 
 tsh_try("sestatus");
-my $selinux = (tsh_text() =~ /enabled/);
+my $selinux = ( tsh_text() =~ /enabled/ );
 
 my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
 trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
-my $akdir   = "$ENV{HOME}/.ssh";
-my $akfile  = "$ENV{HOME}/.ssh/authorized_keys";
-my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
+my $akdir        = "$ENV{HOME}/.ssh";
+my $akfile       = "$ENV{HOME}/.ssh/authorized_keys";
+my $glshell      = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
 my $auth_options = auth_options();
 
 sanity();
@@ -65,10 +65,10 @@ sub sanity {
     _die "$glshell found but not readable; this should NOT happen..."   if not -r $glshell;
     _die "$glshell found but not executable; this should NOT happen..." if not -x $glshell;
 
-    _warn "$akdir missing; creating a new one"                          if not -d $akdir;
-    _warn "$akfile missing; creating a new one"                         if not -f $akfile;
+    _warn "$akdir missing; creating a new one"  if not -d $akdir;
+    _warn "$akfile missing; creating a new one" if not -f $akfile;
 
-    _mkdir($akdir, 0700) if not -d $akfile;
+    _mkdir( $akdir, 0700 ) if not -d $akfile;
     if ( not -f $akfile ) {
         _print( $akfile, "" );
         chmod 0700, $akfile;
@@ -101,7 +101,7 @@ sub fp {
 }
 
 sub fp_file {
-    return $selinux++ if $selinux;  # return a unique "fingerprint" to prevent noise
+    return $selinux++ if $selinux;    # return a unique "fingerprint" to prevent noise
     my $f  = shift;
     my $fp = `ssh-keygen -l -f '$f'`;
     chomp($fp);
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
index df85f35..96e97db 100755
--- a/src/triggers/post-compile/update-git-configs
+++ b/src/triggers/post-compile/update-git-configs
@@ -16,19 +16,19 @@ use warnings;
 # ----------------------------------------------------------------------
 
 my $RB = $rc{GL_REPO_BASE};
-_chdir ($RB);
+_chdir($RB);
 my $lpr = list_phy_repos();
 
 for my $pr (@$lpr) {
-    my $gc = git_config($pr, '.');
-    while ( my ($key, $value) = each(%{ $gc }) ) {
+    my $gc = git_config( $pr, '.' );
+    while ( my ( $key, $value ) = each( %{$gc} ) ) {
         next if $key =~ /^gitolite-options\./;
-        if ($value ne "") {
+        if ( $value ne "" ) {
             $value =~ s/^['"](.*)["']$/$1/;
             $value =~ s/%GL_REPO/$pr/g;
-            system("git", "config", "--file", "$RB/$pr.git/config", $key, $value);
+            system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
         } else {
-            system("git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key);
+            system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );
         }
     }
 }
diff --git a/t/0-me-first.t b/t/0-me-first.t
index 0661a13..f131a51 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -53,7 +53,7 @@ try "
 
     # log file
     cat \$(gitolite query-rc GL_LOGFILE);
-                                    ok;     /check2/
+                                    ok;     /\tupdate\t/
                                             /aa\tu1\t\\+\trefs/heads/master/
                                             /2d066fb4860c29cf321170c17695c6883f3d50e8/
                                             /284951dfa11d58f99ab76b9f4e4c1ad2f2461236/

commit a439f47a6723a1d13abc2bcd262b0fa62b4728a9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 28 15:25:32 2012 +0530

    GL_BYPASS_UPDATE_HOOK -> GL_BYPASS_ACCESS_CHECKS, also added to gitolite-shell

diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 9d4b44e..d772c2a 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -144,7 +144,7 @@ use strict;
 use warnings;
 
 BEGIN {
-    exit 0 if $ENV{GL_BYPASS_UPDATE_HOOK};
+    exit 0 if $ENV{GL_BYPASS_ACCESS_CHECKS};
     die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
 }
 use lib $ENV{GL_BINDIR};
diff --git a/src/VREF/partial-copy b/src/VREF/partial-copy
index 4cb4440..efb73bf 100755
--- a/src/VREF/partial-copy
+++ b/src/VREF/partial-copy
@@ -21,7 +21,7 @@ main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCop
 [ -z "$main" ] && exit 0
 
 rand=$RANDOM
-export GL_BYPASS_UPDATE_HOOK=1
+export GL_BYPASS_ACCESS_CHECKS=1
 
 git push -f $GL_REPO_BASE/$main.git $new:refs/heads/br-$rand || die "FATAL: failed to send $new"
 
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 1706c48..c3381da 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -88,11 +88,13 @@ sub main {
     # yet know the ref that will be eventually pushed (and even that won't
     # apply if it's a read operation).  See the matching code in access() for
     # more information.
-    my $ret = access( $repo, $user, $aa, 'any' );
-    trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
-    gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret );
-    trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
-    _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
+    unless ($ENV{GL_BYPASS_ACCESS_CHECKS}) {
+        my $ret = access( $repo, $user, $aa, 'any' );
+        trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
+        gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret );
+        trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
+        _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
+    }
 
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";

commit c2991d974df90472b3d0aeaf2d9151d434a06835
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 03:10:55 2012 +0530

    the 'eval' in run() needed to die on error

diff --git a/src/Gitolite/Triggers.pm b/src/Gitolite/Triggers.pm
index 15c2cdf..e4d09ec 100644
--- a/src/Gitolite/Triggers.pm
+++ b/src/Gitolite/Triggers.pm
@@ -22,6 +22,7 @@ sub run {
     $module = "Gitolite::Triggers::$module" if $module !~ /^Gitolite::/;
 
     eval "require $module";
+    _die "$@" if $@;
     my $subref;
     eval "\$subref = \\\&$module" . "::" . "$sub";
     _die "module '$module' does not exist or does not have sub '$sub'" unless ref($subref) eq 'CODE';

commit dfb9844dfb08eba25febea69f7fa31236d195a6f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 29 07:04:46 2012 +0530

    info command simplified
    
    (thanks to Eli for the back-and-forth that led to this)

diff --git a/src/commands/info b/src/commands/info
index 2dc433b..7b8b845 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -9,78 +9,88 @@ use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
 
-=for usage
-Usage:  gitolite info [-p [-lc] [<repo name pattern>]
+=for args
+Usage:  gitolite info [-lc] [<repo name pattern>]
 
-List all repos/repo groups you can access.  By default, it shows you what the
-conf file specified, which means group names and wild card patterns may show
-up.  Example, if the conf file looked like this:
+List all repos/repo groups you can access.
 
-    @oss = git gitolite linux
-    repo @oss
-        RW+ =   YourName
-
-then running 'ssh git at host info' will only show you '@oss'.
-
-'-p' looks at actual (physical) repos instead; in our example this will show
-you git, gitolite, and linux.
-
-'-lc' lists creators as an additional field at the end; this option is only
-available with '-p'.
+    '-lc'       lists creators as an additional field at the end.
 
 The optional pattern is an unanchored regex that will limit the repos
 searched, in both cases.  It might speed up things a little if you have more
 than a few thousand repos.
 =cut
 
-my ( $help, $phy, $lc, $patt ) = ('') x 4;
-GetOptions(
-    'lc' => \$lc,
-    'p'  => \$phy,
-    'h'  => \$help,
-) or usage();
+# these two are globals
+my ( $lc, $patt ) = args();
 
-usage("'-lc' requires '-p'") if $lc and not $phy;
-usage() if @ARGV > 1 or $help;
-$patt = shift || '.';
+print_version();
 
-my $user = $ENV{GL_USER} or _die "GL_USER not set";
-my $ref = 'any';
+print_patterns();   # repos he can create for himself
+print_phy_repos();  # repos already created
+print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
 
-chomp(my $hn = `hostname -s`);
-my $gv = substr( `git --version`, 12 );
-print "hello $user, this is $ENV{USER}\@$hn running gitolite3 " . version() . " on git $gv\n";
+# ----------------------------------------------------------------------
 
-my $lr = lister_dispatch('list-repos');
+sub args {
+    my ( $lc, $patt ) = ( '', '' );
+    my $help = '';
 
-my $perm;
-my $repos;
-my @aa;
+    GetOptions(
+        'lc' => \$lc,
+        'h'  => \$help,
+    ) or usage();
 
-if ($phy) {
-    _chdir( $rc{GL_REPO_BASE} );
-    $repos = list_phy_repos(1);
-    @aa = qw(R W);
-} else {
-    $repos = $lr->();
+    usage() if @ARGV > 1 or $help;
+    $patt = shift @ARGV || '.';
+
+    return ( $lc, $patt );
+}
+
+sub print_version {
+    chomp( my $hn = `hostname -s` );
+    my $gv = substr( `git --version`, 12 );
+    $ENV{GL_USER} or _die "GL_USER not set";
+    print "hello $ENV{GL_USER}, this is $ENV{USER}\@$hn running gitolite3 " . version() . " on git $gv\n";
+}
+
+sub print_patterns {
+    my ( $repos, @aa );
+
+    # find repo patterns only, call them with ^C flag included
+    @$repos = grep { !/$REPONAME_PATT/ } @{ lister_dispatch('list-repos')->() };
     @aa = qw(R W ^C);
+    listem( $repos, '', @aa );
+    # but squelch the 'lc' flag for these
 }
 
-my $creator = '';
-for my $repo (@$repos) {
-    next unless $repo =~ /$patt/;
-    my $perm = '';
-    $creator = creator($repo) if $lc;
+sub print_phy_repos {
+    my ( $repos, @aa );
+
+    # now get the actual repos and get R or W only
+    _chdir( $rc{GL_REPO_BASE} );
+    $repos = list_phy_repos(1);
+    @aa    = qw(R W);
+    listem( $repos, $lc, @aa );
+}
 
-    for my $aa (@aa) {
-        my $ret = access( $repo, $user, $aa, $ref );
-        $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
+sub listem {
+    my ( $repos, $lc, @aa ) = @_;
+    my $creator = '';
+    for my $repo (@$repos) {
+        next unless $repo =~ /$patt/;
+        my $perm = '';
+        $creator = creator($repo) if $lc;
+
+        for my $aa (@aa) {
+            my $ret = access( $repo, $ENV{GL_USER}, $aa, 'any' );
+            $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
+        }
+        $perm =~ s/\^//;
+        next unless $perm =~ /\S/;
+        print "$perm\t$repo";
+        print "\t$creator" if $lc;
+        print "\n";
     }
-    $perm =~ s/\^//;
-    next unless $perm =~ /\S/;
-    print "$perm\t$repo";
-    print "\t$creator" if $lc;
-    print "\n";
 }
 
-print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};
diff --git a/t/info.t b/t/info.t
index ccb25b3..ce54027 100755
--- a/t/info.t
+++ b/t/info.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # the info command
 # ----------------------------------------------------------------------
 
-try 'plan 83';
+try 'plan 78';
 
 try "## info";
 
@@ -22,8 +22,12 @@ confreset;confadd '
         RW  =               u2
         R   =               u1
     repo    t3
-                    RW  =   u3
-                    R   =   u4
+        RW  =   u3
+        R   =   u4
+
+    repo foo/..*
+        C   =   u1
+        RW  =   CREATOR u3
 ';
 
 try "ADMIN_PUSH info; !/FATAL/" or die text();
@@ -38,66 +42,67 @@ try "DEF GS = /hello %1, this is $ENV{USER}\\@.* running gitolite/";
 
 try "
     glt info u1; ok; GS u1
-        /R W  \t\@t1/
-        /R    \tt2/
-        /R W  \ttesting/
-        !/R W  \tt3/
-    glt info u2; ok; GS u2
-        /R    \t\@t1/
-        /R W  \tt2/
-        /R W  \ttesting/
-        !/R W  \tt3/
-    glt info u3; ok; GS u3
-        /R W  \tt3/
-        /R W  \ttesting/
-        !/R    \t\@t1/
-        !/R W  \tt2/
-    glt info u4; ok; GS u4
-        /R    \tt3/
-        /R W  \ttesting/
-        !/R    \t\@t1/
-        !/R W  \tt2/
-    glt info u5; ok; GS u5
-        /R W  \ttesting/
-        !/R    \t\@t1/
-        !/R W  \tt2/
-        !/R W  \tt3/
-    glt info u6; ok; GS u6
-        /R W  \ttesting/
-        !/R    \t\@t1/
-        !/R W  \tt2/
-        !/R W  \tt3/
-";
-
-try "
-    glt info u1 -p; ok; GS u1
+        /C\tfoo/\\.\\.\\*/
         /R W *\tt1/
         /R   *\tt2/
         /R W *\ttesting/
         !/R W *\tt3/
-    glt info u2 -p; ok; GS u2
+    glt info u2; ok; GS u2
+        !/C\tfoo/
         /R   *\tt1/
         /R W *\tt2/
         /R W *\ttesting/
         !/R W *\tt3/
-    glt info u3 -p; ok; GS u3
+    glt info u3; ok; GS u3
         /R W *\tt3/
         /R W *\ttesting/
         !/R   *\tt1/
         !/R W *\tt2/
-    glt info u4 -p; ok; GS u4
+    glt info u4; ok; GS u4
         /R   *\tt3/
         /R W *\ttesting/
         !/R   *\tt1/
         !/R W *\tt2/
-    glt info u5 -p; ok; GS u5
+    glt info u5; ok; GS u5
         /R W *\ttesting/
         !/R   *\tt1/
         !/R W *\tt2/
         !/R W *\tt3/
-    glt info u6 -p; ok; GS u6
+    glt info u6; ok; GS u6
         /R W *\ttesting/
         !/R   *\tt1/
         !/R W *\tt2/
         !/R W *\tt3/
 ";
+
+try "
+    glt ls-remote u1 file:///foo/one;   ok
+    glt info u1; ok; GS u1
+        /C\tfoo/\\.\\.\\*/
+        /R W *\tfoo/one/
+        !/R W *\tfoo/one\tu1/
+    glt info u2; ok; GS u2
+        !/C\tfoo/
+        !/R W *\tfoo/one/
+    glt info u3; ok; GS u3
+        !/C\tfoo/
+        /R W *\tfoo/one/
+        !/R W *\tfoo/one\tu1/
+";
+
+try "
+    glt ls-remote u1 file:///foo/one;   ok
+    glt info u1 -lc; ok; GS u1
+    put
+        /C\tfoo/\\.\\.\\*/
+        !/C\tfoo.*u1/
+        /R W *\tfoo/one\tu1/
+    glt info u2 -lc; ok; GS u2
+    put
+        !/C\tfoo/
+        !/R W *\tfoo/one/
+    glt info u3 -lc; ok; GS u3
+    put
+        !/C\tfoo/
+        /R W *\tfoo/one\tu1/
+";
diff --git a/t/perms-groups.t b/t/perms-groups.t
index 1681d33..59c5fb5 100755
--- a/t/perms-groups.t
+++ b/t/perms-groups.t
@@ -33,20 +33,20 @@ try "
     glt ls-remote u1 file:///bar/u1/try1
         /Initialized empty Git repository in .*/bar/u1/try1.git//
     # default permissions for u2 and u4
-    glt info u1 -p -lc
+    glt info u1 -lc
         /R W *\tbar/u1/try1\tu1/
-    glt info u2 -p -lc
+    glt info u2 -lc
         !/R W *\tbar/u1/try1\tu1/
-    glt info u4 -p -lc
+    glt info u4 -lc
         !/R W *\tbar/u1/try1\tu1/
 
     # \@leads can RW try1
     echo WRITERS \@leads | glt perms u1 bar/u1/try1; ok
-    glt info u1 -p -lc
+    glt info u1 -lc
         /R W *\tbar/u1/try1\tu1/
-    glt info u2 -p -lc
+    glt info u2 -lc
         /R W *\tbar/u1/try1\tu1/
-    glt info u4 -p -lc
+    glt info u4 -lc
         !/R W *\tbar/u1/try1\tu1/
 
     # \@devs can R try1
@@ -55,14 +55,14 @@ try "
         /READERS \@devs/
         !/WRITERS \@leads/
 
-    glt info u1 -p -lc
+    glt info u1 -lc
         /R W *\tbar/u1/try1\tu1/
 
-    glt info u2 -p -lc
+    glt info u2 -lc
         !/R W *\tbar/u1/try1\tu1/
         /R *\tbar/u1/try1\tu1/
 
-    glt info u4 -p -lc
+    glt info u4 -lc
         !/R W *\tbar/u1/try1\tu1/
         /R *\tbar/u1/try1\tu1/
 
@@ -71,11 +71,11 @@ try "
     glt perms u1 -l bar/u1/try1
         /READERS \@devs/
         /WRITERS \@leads/
-    glt info u1 -p -lc
+    glt info u1 -lc
         /R W *\tbar/u1/try1\tu1/
-    glt info u2 -p -lc
+    glt info u2 -lc
         /R W *\tbar/u1/try1\tu1/
-    glt info u4 -p -lc
+    glt info u4 -lc
         !/R W *\tbar/u1/try1\tu1/
         /R *\tbar/u1/try1\tu1/
 ";
diff --git a/t/sequence.t b/t/sequence.t
index f88348a..d85f420 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -37,7 +37,7 @@ try "
     glt perms u1 -l foo/u1/bar
         /WRITERS u2/
     # expand
-    glt info u2 -p
+    glt info u2
         /R W *\tfoo/u1/bar/
         /R W *\ttesting/
 
@@ -80,7 +80,7 @@ try "
     glt perms u1 -l foo/u1/bar
         /WRITERS u2/
     # expand
-    glt info u2 -p
+    glt info u2
         /R W *\tfoo/u1/bar/
         /R W *\ttesting/
 
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 9051fa0..2ace6ca 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -48,10 +48,10 @@ confreset; confadd '
 try "ADMIN_PUSH set3; !/FATAL/" or die text();
 
 try "
-    ssh u1 info;                ok;     /R W  \tfoo/
-    ssh u2 info;                ok;     /R    \tfoo/
-    ssh u3 info;                ok;     /R W  \tfoo/
-    ssh u4 info;                ok;     /R    \tfoo/
+    ssh u1 info;                ok;     /R W\tfoo/
+    ssh u2 info;                ok;     /R  \tfoo/
+    ssh u3 info;                ok;     /R W\tfoo/
+    ssh u4 info;                ok;     /R  \tfoo/
     ssh u5 info;                ok;     !/foo/
     ssh u6 info;                ok;     !/foo/
 "

commit ae75f9c93842af752ac78a5a27e3dc5a3d93432f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 30 22:55:54 2012 +0530

    (minor) check-g2-compat needed an "all done" message at the end for clarity

diff --git a/check-g2-compat b/check-g2-compat
index 8fd1e3b..1f2bfe3 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -29,6 +29,8 @@ print "checking repos...\n";
 repo();
 print "\n";
 
+print "...all done...\n";
+
 # ----------------------------------------------------------------------
 
 sub intro {

commit 5db29062f5708ecff368442da0121ffbe265fa17
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 22:18:20 2012 +0530

    try: info command header enhanced

diff --git a/src/commands/info b/src/commands/info
index 66b7e68..2dc433b 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -47,7 +47,9 @@ $patt = shift || '.';
 my $user = $ENV{GL_USER} or _die "GL_USER not set";
 my $ref = 'any';
 
-print "hello $user, this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
+chomp(my $hn = `hostname -s`);
+my $gv = substr( `git --version`, 12 );
+print "hello $user, this is $ENV{USER}\@$hn running gitolite3 " . version() . " on git $gv\n";
 
 my $lr = lister_dispatch('list-repos');
 
diff --git a/t/info.t b/t/info.t
index 7844fa5..ccb25b3 100755
--- a/t/info.t
+++ b/t/info.t
@@ -34,7 +34,7 @@ try "
 ";
 
 # GS == greeting string
-try "DEF GS = /hello %1, this is gitolite/";
+try "DEF GS = /hello %1, this is $ENV{USER}\\@.* running gitolite/";
 
 try "
     glt info u1; ok; GS u1

commit 9c460a4b9ae89280e2c52f68b2042b00b530b2b5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 10:31:32 2012 +0530

    patch to make things work under selinux...
    
    ...at the cost of some nice warnings
    
    (thanks to Seth Robertson for reporting the issue and then verifying the
    fix, since I don't run selinux)

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 55e06aa..ec1bff6 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -13,6 +13,9 @@ $|++;
 # can be called directly, or as a post-update hook.  Since it ignores
 # arguments anyway, it hardly matters.
 
+tsh_try("sestatus");
+my $selinux = (tsh_text() =~ /enabled/);
+
 my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
 trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
 my $akdir   = "$ENV{HOME}/.ssh";
@@ -98,6 +101,7 @@ sub fp {
 }
 
 sub fp_file {
+    return $selinux++ if $selinux;  # return a unique "fingerprint" to prevent noise
     my $f  = shift;
     my $fp = `ssh-keygen -l -f '$f'`;
     chomp($fp);

commit 2bec5510095678e907962431a011c49cae6fe986
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 19:28:56 2012 +0530

    'writes' -> 'writable', order of arguments changed

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 06420d9..25d5cb8 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -94,7 +94,7 @@ $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 }
 
 # these two are meant to help externally written commands (see
-# src/commands/writes for an example)
+# src/commands/writable for an example)
 $ENV{GL_REPO_BASE}  = $rc{GL_REPO_BASE};
 $ENV{GL_ADMIN_BASE} = $rc{GL_ADMIN_BASE};
 
@@ -282,7 +282,7 @@ __DATA__
             'info'              =>  1,
             'desc'              =>  1,
             'perms'             =>  1,
-            'writes'            =>  1,
+            'writable'          =>  1,
         },
 
     # comment out or uncomment as needed
diff --git a/src/commands/writes b/src/commands/writable
similarity index 92%
rename from src/commands/writes
rename to src/commands/writable
index 0dc5d6a..f87e370 100755
--- a/src/commands/writes
+++ b/src/commands/writable
@@ -6,7 +6,7 @@ use lib $ENV{GL_BINDIR};
 use Gitolite::Easy;
 
 =for usage
-Usage: gitolite writes on|off <reponame>|@all
+Usage: gitolite writable <reponame>|@all on|off
 
 'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
 
@@ -16,11 +16,10 @@ from STDIN; this allows longer messages.
 =cut
 
 usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
-
-usage() if $ARGV[0] ne 'on' and $ARGV[0] ne 'off';
-my $on = ( shift eq 'on' );
+usage() if $ARGV[1] ne 'on' and $ARGV[1] ne 'off';
 
 my $repo = shift;
+my $on = ( shift eq 'on' );
 
 if ( $repo eq '@all' ) {
     die "you are not authorized\n" if $ENV{GL_USER} and not is_admin();
diff --git a/t/glt b/t/glt
index 3b3daf8..b335cc0 100755
--- a/t/glt
+++ b/t/glt
@@ -14,7 +14,7 @@ my %extcmds = (
     info        => 1,
     desc        => 1,
     perms       => 1,
-    writes      => 1,
+    writable    => 1,
 );
 
 $ENV{G3T_USER} = $user;
diff --git a/t/writes.t b/t/writable.t
similarity index 92%
rename from t/writes.t
rename to t/writable.t
index 08a8143..25d93ec 100755
--- a/t/writes.t
+++ b/t/writable.t
@@ -8,7 +8,7 @@ use Gitolite::Test;
 use Cwd;
 my $workdir = getcwd();
 
-# 'gitolite writes' command
+# 'gitolite writable' command
 # ----------------------------------------------------------------------
 
 my $sf = ".gitolite.down";
@@ -48,7 +48,7 @@ try "
     PUSH u2 master;             ok;
 
     # disable site with some message
-    gitolite writes off \@all testing site-wide disable; ok
+    gitolite writable \@all off testing site-wide disable; ok
 
     # try push foo and see fail + message
     cd ../foo;                  ok
@@ -67,7 +67,7 @@ try "
     cd u4;                      ok
 
     # enable site
-    gitolite writes on \@all; ok
+    gitolite writable \@all on; ok
 
     # try same 3 again
 
@@ -88,7 +88,7 @@ try "
     cd u6;                      ok
 
     # disable just foo
-    gitolite writes off foo foo down
+    gitolite writable foo off foo down
 
     # try push foo and see the message
     cd ../foo;                  ok
@@ -101,8 +101,8 @@ try "
     PUSH u2;                    ok;     /master -> master/
 
     # enable foo, disable bar/u2
-    gitolite writes on foo
-    gitolite writes off bar/u2 the bar is closed
+    gitolite writable foo on
+    gitolite writable bar/u2 off the bar is closed
 
     # try both
     cd ../foo;                  ok

commit cc8b10483b266f58561dff95c4047c398dced6f7
Author: gitolite tester <tester at example.com>
Date:   Sun Mar 25 21:29:14 2012 +0530

    allow perl modules as triggers also...
    
    ...and move "check_repo_write_enabled" to that mode ("writable")

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index f89919f..28819ff 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -11,7 +11,6 @@ package Gitolite::Conf::Load;
 
   option
   repo_missing
-  check_repo_write_enabled
   creator
 
   vrefs
@@ -162,15 +161,6 @@ sub repo_missing {
     return not -d "$rc{GL_REPO_BASE}/$repo.git";
 }
 
-sub check_repo_write_enabled {
-    my ($repo) = shift;
-    for my $f ("$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down") {
-        next unless -f $f;
-        _die slurp($f) if -s $f;
-        _die "sorry, writes are currently disabled (no more info available)";
-    }
-}
-
 # ----------------------------------------------------------------------
 
 sub load_common {
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 9f563cb..06420d9 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -71,6 +71,9 @@ if ( defined($GL_ADMINDIR) ) {
 # add internal triggers
 # ----------------------------------------------------------------------
 
+# is the server/repo in a writable state (i.e., not down for maintenance etc)
+unshift @{ $rc{ACCESS_1} }, 'Writable::writable';
+
 # (testing only) override the rc file silently
 # ----------------------------------------------------------------------
 # use an env var that is highly unlikely to appear in real life :)
@@ -174,13 +177,20 @@ sub trigger {
             _die "$rc_section section in rc file is not a perl list";
         } else {
             for my $s ( @{ $rc{$rc_section} } ) {
-
                 my ($pgm, @args) = split ' ', $s;
-                $pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
 
-                _warn("skipped command '$s'"), next if not -x $pgm;
-                trace( 2, "command: $s" );
-                _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
+                if ( my($module, $sub) = ($pgm =~ /^(.*)::(\w+)$/ ) ) {
+
+                    require Gitolite::Triggers;
+                    Gitolite::Triggers::run($module, $sub, @args, $rc_section, @_);
+
+                } else {
+                    $pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
+
+                    _warn("skipped command '$s'"), next if not -x $pgm;
+                    trace( 2, "command: $s" );
+                    _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
+                }
             }
         }
         return;
diff --git a/src/Gitolite/Triggers.pm b/src/Gitolite/Triggers.pm
new file mode 100644
index 0000000..15c2cdf
--- /dev/null
+++ b/src/Gitolite/Triggers.pm
@@ -0,0 +1,32 @@
+package Gitolite::Triggers;
+
+# load and run triggered modules
+# ----------------------------------------------------------------------
+
+#<<<
+ at EXPORT = qw(
+);
+#>>>
+use Exporter 'import';
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub run {
+    my ($module, $sub, @args) = @_;
+    $module = "Gitolite::Triggers::$module" if $module !~ /^Gitolite::/;
+
+    eval "require $module";
+    my $subref;
+    eval "\$subref = \\\&$module" . "::" . "$sub";
+    _die "module '$module' does not exist or does not have sub '$sub'" unless ref($subref) eq 'CODE';
+
+    $subref->(@args);
+}
+
+1;
diff --git a/src/Gitolite/Triggers/Writable.pm b/src/Gitolite/Triggers/Writable.pm
new file mode 100644
index 0000000..b6a1e6f
--- /dev/null
+++ b/src/Gitolite/Triggers/Writable.pm
@@ -0,0 +1,17 @@
+package Gitolite::Triggers::Writable;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+sub writable {
+    my ($repo, $aa, $result) = @_[1, 3, 5];
+    return if $aa eq 'R' or $result =~ /DENIED/;
+
+    for my $f ("$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down") {
+        next unless -f $f;
+        _die slurp($f) if -s $f;
+        _die "sorry, writes are currently disabled (no more info available)\n";
+    }
+}
+
+1;
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 2a57e2d..1706c48 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -94,7 +94,6 @@ sub main {
     trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
     _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
 
-    check_repo_write_enabled($repo) if $aa eq 'W';
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
     _system( "git", "shell", "-c", "$verb $repodir" );

commit 1cf062fad589bb15b47818d5d13673a2bd1f466e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 16:18:25 2012 +0530

    ACCESS_CHECK split into ACCESS_1 and ACCESS_2; docs updated

diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 637a04b..13bc735 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -2,7 +2,7 @@
 
 ## intro and sample rc excerpt
 
-Gitolite fires off external commands at six different times.  The [rc][] file
+Gitolite fires off external commands at 7 different times.  The [rc][] file
 specifies what commands to run at each trigger point, but for illustration,
 here's an excerpt:
 
@@ -58,43 +58,51 @@ Triggers receive the following arguments:
 
 ## trigger-specific details
 
-Here's all you need to know about each specific trigger.
-
-  * `ACCESS_CHECK`: this fires once after each access check.  The first is
-    just before invoking git-receive-pack or git-upload-pack.  The second,
-    which only applies to "write" operations, is from git's own 'update' hook.
-
-    Arguments: repo name, user name, [attempted access][perm], the ref being
-    updated, and the result of the access check.
-
-    The 'ref' is `any` for the first check, because at that point we don't
-    know what the actual ref is.  For the second check it could be, say,
-    `refs/heads/master` or some such.
-
-    The result is a text field that the `access()` function returned.
-    Programmatically, the only thing you should rely on is that if it contains
-    the upper case word "DENIED" then access was denied, otherwise it was
-    allowed.
-
-  * `PRE_GIT`: before running the git command.
-
-    Arguments: repo name, user name, [attempted access][perm], the string
-    `any`, and the git command ('git-receive-pack', 'git-upload-pack', or
-    'git-upload-archive') being invoked.
-
-  * `POST_GIT`: after the git command returns.
-
-    Arguments: same as for `PRE_GIT`, followed by the output of the perl
-    function "times" (i.e., 4 CPU times: user, system, cumulative user,
-    cumulative system)
-
-  * `POST_COMPILE`: after an admin push has successfully "compiled" the config
-    file.  By default, the next thing is to update the ssh authkeys file, then
-    all the 'git-config's, gitweb access, and daemon access.
-
-    Programs run by this trigger receive no extra arguments.
-
-  * `PRE_CREATE` and `POST_CREATE`: before and after a new "[wild][]" repo is
-    created by user action.
-
-    Arguments: repo name, user name.
+Here's a brief "when" and "with what arguments" for each trigger.
+
+  * `ACCESS_1` runs after the first access check.  Arguments:
+      * repo
+      * user
+      * 'R' or 'W'
+      * 'any'
+      * result: this is the result of the access() function.  If it contains
+        the uppercase word "DENIED", the access was rejected.  Otherwise
+        result contains the refex that caused the access to succeed.
+
+  * `ACCESS_2` runs after the second access check, in the update hook.
+    Arguments:
+      * repo
+      * user
+      * any of W, +, C, D, WM, +M, CM, DM
+      * the ref being updated (e.g., 'refs/heads/master')
+      * result (see above)
+
+  * `PRE_GIT` runs just before running the git command.  Arguments:
+      * repo
+      * user
+      * 'R' or 'W'
+      * 'any'
+      * the git command ('git-receive-pack', 'git-upload-pack', or
+        'git-upload-archive') being invoked.
+
+  * `POST_GIT` runs after the git command returns.  Arguments:
+      * repo
+      * user
+      * 'R' or 'W'
+      * 'any'
+      * the git command ('git-receive-pack', 'git-upload-pack', or
+
+    These are followed by the output of the perl function "times" (i.e., 4 CPU
+    times: user, system, cumulative user, cumulative system) so that's 9
+    arguments in total
+
+  * `PRE_CREATE` and `POST_CREATE` run just before and after a new "[wild][]"
+    repo is created by user action.  Arguments:
+      * repo
+      * user
+
+  * `POST_COMPILE` runs after an admin push has successfully "compiled" the
+    config file.  By default, the next thing is to update the ssh authkeys
+    file, then all the 'git-config's, gitweb access, and daemon access.
+
+    No arguments.
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 53be05a..9d4b44e 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -28,7 +28,7 @@ sub update {
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
-    trigger( 'ACCESS_CHECK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
+    trigger( 'ACCESS_2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index fa4b5fc..9f563cb 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -52,7 +52,6 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 
 # find the rc file and 'do' it
 # ----------------------------------------------------------------------
-
 my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
@@ -64,11 +63,17 @@ if ( defined($GL_ADMINDIR) ) {
 
     exit 1;
 }
+
 # let values specified in rc file override our internal ones
+# ----------------------------------------------------------------------
 @rc{ keys %RC } = values %RC;
 
-# (testing only) testing sometimes requires all of it to be overridden
-# silently; use an env var that is highly unlikely to appear in real life :)
+# add internal triggers
+# ----------------------------------------------------------------------
+
+# (testing only) override the rc file silently
+# ----------------------------------------------------------------------
+# use an env var that is highly unlikely to appear in real life :)
 do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
 # fix some env vars, setup gitolite internal "env" vars (aka rc vars)
@@ -278,6 +283,12 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence just after the first access check is done
+    ACCESS_1                    =>
+        [
+        ],
+
+    # comment out or uncomment as needed
     # these will run in sequence at the start, before a git operation has started
     PRE_GIT                     =>
         [
@@ -289,6 +300,12 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence just after the second access check is done
+    ACCESS_2                    =>
+        [
+        ],
+
+    # comment out or uncomment as needed
     # these will run in sequence at the end, after a git operation has ended
     POST_GIT                    =>
         [
@@ -297,19 +314,19 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence after post-update
-    POST_COMPILE                =>
+    # these will run in sequence after a new wild repo is created
+    POST_CREATE                 =>
         [
-            'post-compile/ssh-authkeys',
             'post-compile/update-git-configs',
             'post-compile/update-gitweb-access-list',
             'post-compile/update-git-daemon-access-list',
         ],
 
     # comment out or uncomment as needed
-    # these will run in sequence after a new wild repo is created
-    POST_CREATE                 =>
+    # these will run in sequence after post-update
+    POST_COMPILE                =>
         [
+            'post-compile/ssh-authkeys',
             'post-compile/update-git-configs',
             'post-compile/update-gitweb-access-list',
             'post-compile/update-git-daemon-access-list',
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 14136eb..2a57e2d 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -91,7 +91,7 @@ sub main {
     my $ret = access( $repo, $user, $aa, 'any' );
     trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
     gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret );
-    trigger( 'ACCESS_CHECK', $repo, $user, $aa, 'any', $ret );
+    trigger( 'ACCESS_1', $repo, $user, $aa, 'any', $ret );
     _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
 
     check_repo_write_enabled($repo) if $aa eq 'W';

commit fd98b21bf2cc9b7eeea2b3ba685ba8bb8435df62
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 11:31:02 2012 +0530

    (doc updates)

diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index f652d93..9361fa4 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -50,9 +50,8 @@ perl API module) for ideas.
 
 ### the perl API
 
-...is implemented by Gitolite::Easy; see src/Gitolite/Easy.pm.  This is a work
-in progress; for example it does not yet have the equivalent of `gitolite
-git-config`.  I'll add it when I or someone else needs it.
+...is implemented by Gitolite::Easy; the comments in src/Gitolite/Easy.pm
+serve as documentation.
 
 ## your own hooks
 
diff --git a/doc/g2rcdiff.mkd b/doc/g2rcdiff.mkd
index 53ebd55..5fa2605 100644
--- a/doc/g2rcdiff.mkd
+++ b/doc/g2rcdiff.mkd
@@ -11,11 +11,20 @@ when you're ready to migrate.  Instead, you need to run
     # (assuming you saved your g2 rc file somewhere)
     gitolite print-default-rc > $HOME/.gitolite.rc
     $EDITOR $HOME/.gitolite.rc
-    # make appropriate changes, save
+    # make appropriate changes (see below), save
     gitolite setup
 
-One example of this is `GL_NO_SETUP_AUTHKEYS`.  If you don't jump in and fix
-the rc first, the first run will clobber your authkeys file.
+The most serious example of this is `GL_NO_SETUP_AUTHKEYS`.  If you don't
+preset the rc (in this case, by commenting out the 'ssh-authkeys' line)
+**before** running `gitolite setup`, your `~/.ssh/authorized_keys` file will
+get clobbered.
+
+There are several other settings that you may want to look at, with varying
+degrees of severity; **please go through the list below before blindly running
+`gitolite setup`**.  (For example, if you had `GL_NO_DAEMON_NO_GITWEB` in g2
+but forgot to comment out all the gitweb and daemon update lines in the rc
+file, your projects.list and git-daemon-export-ok files might get clobbered.
+Of course this is less severe than ssh authkeys but still...).
 
 ### rc file differences
 
diff --git a/doc/install.mkd b/doc/install.mkd
index bc08162..51ca10f 100644
--- a/doc/install.mkd
+++ b/doc/install.mkd
@@ -4,6 +4,11 @@ Gitolite has only one server side "command" now, much like git itself.  And
 it's been designed so that you don't even really have to *install* it, as you
 will see.
 
+**NOTE**: if you're migrating from g2, there are some settings that MUST be
+dealt with **before** running `gitolite setup`; please read the [g2
+migration][g2migr] page and linked pages, and especially the one on
+'presetting the rc file][rc-preset].
+
 ## simplest
 
 1.  Put all of `src` in one place, doesn't matter where; let's call it

commit 93fb9552d261abd1e3c1df2e6faa108f205f1af4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 11:24:24 2012 +0530

    perl API docs, plus one minor change to the code...
    
    in_group() accepts group name with or without leading '@'

diff --git a/src/Gitolite/Easy.pm b/src/Gitolite/Easy.pm
index fb749f0..4d97ff8 100644
--- a/src/Gitolite/Easy.pm
+++ b/src/Gitolite/Easy.pm
@@ -5,6 +5,11 @@ package Gitolite::Easy;
 # most/all functions in this module test $ENV{GL_USER}'s rights and
 # permissions so it needs to be set.
 
+# documentation for each function is at the top of the function.
+# Documentation is NOT in pod format; just read the source with a nice syntax
+# coloring text editor and you'll be happy enough.  (I do not like POD; please
+# don't send me patches for this aspect of the module).
+
 #<<<
 @EXPORT = qw(
   is_admin
@@ -37,13 +42,25 @@ my $user;
 
 # ----------------------------------------------------------------------
 
+# is_admin()
+
+# return true if $ENV{GL_USER} is set and has W perms to the admin repo
+
 # shell equivalent
 #   if gitolite access -q gitolite-admin $GL_USER W; then ...
+
 sub is_admin {
     valid_user();
     return not( access( 'gitolite-admin', $user, 'W', 'any' ) =~ /DENIED/ );
 }
 
+# is_super_admin()
+
+# (useful only if you are using delegation)
+
+# return true if $ENV{GL_USER} is set and has W perms to any file in the admin
+# repo
+
 # shell equivalent
 #   if gitolite access -q gitolite-admin $GL_USER W VREF/NAME/; then ...
 sub is_super_admin {
@@ -51,15 +68,24 @@ sub is_super_admin {
     return not( access( 'gitolite-admin', $user, 'W', 'VREF/NAME/' ) =~ /DENIED/ );
 }
 
+# in_group()
+
+# return true if $ENV{GL_USER} is set and is in the given group
+
 # shell equivalent
 #   if gitolite list-memberships $GL_USER | grep -x $GROUPNAME >/dev/null; then ...
 sub in_group {
     valid_user();
-    my $g = "@" . +shift;
+    my $g = shift;
+    $g =~ s/^\@?/@/;
 
     return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships($user) };
 }
 
+# owns()
+
+# return true if $ENV{GL_USER} is set and is the creator of the given repo
+
 # shell equivalent
 #   if gitolite creator $REPONAME $GL_USER; then ...
 sub owns {
@@ -72,6 +98,9 @@ sub owns {
     return ( creator($r) eq $user );
 }
 
+# can_read()
+# return true if $ENV{GL_USER} is set and can read the given repo
+
 # shell equivalent
 #   if gitolite access -q $REPONAME $GL_USER R; then ...
 sub can_read {
@@ -80,6 +109,9 @@ sub can_read {
     return not( access( $r, $user, 'R', 'any' ) =~ /DENIED/ );
 }
 
+# can_write()
+# return true if $ENV{GL_USER} is set and can write to the given repo
+
 # shell equivalent
 #   if gitolite access -q $REPONAME $GL_USER W; then ...
 sub can_write {
@@ -88,6 +120,12 @@ sub can_write {
     return not( access( $r, $user, 'W', 'any' ) =~ /DENIED/ );
 }
 
+# config()
+# given a repo and a key, return a hash containing all the git config
+# variables for that repo where the section+key match the regex.  If none are
+# found, return an empty hash.  If you don't want it as a regex, use \Q
+# appropriately
+
 # shell equivalent
 #   foo=$(gitolite git-config -r $REPONAME foo\\.bar)
 sub config {

commit 5e11d104c7db3b1a68445c24d6ec8b2feb91c2c8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 27 06:22:07 2012 +0530

    ssh-authkeys: remove needless map
    
    someone reported an error on "my $_" (presumably old perl) but I now
    realise the whole map is useless; both the lists concerned have already
    been chomped.

diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
index 5bfb4a8..55e06aa 100755
--- a/src/triggers/post-compile/ssh-authkeys
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -48,7 +48,7 @@ for my $f (@pubkeys) {
 
 # dump it out
 if (@gl_keys) {
-    my $out = join( "\n", map { my $_ = $_; chomp($_); $_ } @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
+    my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
 
     my $ak = slurp($akfile);
     _die "$akfile changed between start and end of this program!" if $ak ne $old_ak;

commit 276900edbbd279915147ea6a377c2b00fded865a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 21:35:23 2012 +0530

    fixup new check-g2-compat, lots of migration related changes...
    
    aaargh!  I forgot to 'git rm' something despite documenting it to be
    gone and not using it

diff --git a/src/Gitolite/Compat.pm b/src/Gitolite/Compat.pm
deleted file mode 100644
index cd69768..0000000
--- a/src/Gitolite/Compat.pm
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/perl
-
-# a quick and dirty program to warn about compatibilities issues in your
-# current gitolite 2 setup, with the new one.
-
- at EXPORT = qw(
-);
-
-use Exporter 'import';
-
-use lib $ENV{GL_BINDIR};
-use Gitolite::Common;
-
-my $header_printed = 0;
-
-my $glrc = "$ENV{HOME}/.gitolite.rc";
-do "$glrc";
-if (defined($GL_ADMINDIR)) {
-    check_compat();
-    if ($header_printed) {
-        say2 "Please read the documentation for additional details on migrating.\n\n"
-    } else {
-        say2 "
-It looks like there were no real issues found, but you should still read the
-documentation for additional details on migrating.
-
-";
-    }
-}
-
-sub check_compat {
-    chdir($GL_ADMINDIR) or die "FATAL: could not chdir to $GL_ADMINDIR\n";
-
-    my $conf = `find . -name "*.conf" | xargs cat`;
-
-    g2warn("MUST fix", "fallthru in NAME rules; this affects user's push rights")
-      if $conf =~ m(NAME/);
-
-    g2warn("MUST fix", "subconf command in admin repo; this affects conf compilation")
-      if $conf =~ m(NAME/conf/fragments);
-}
-
-sub header {
-    return if $header_printed;
-    $header_printed++;
-
-    say2 "
-    The following is a list of compat issues found, if any.  Please see the
-    compatibility with gitolite 2' section of the documentation for additional
-    details on each issue found.\n";
-}
-
-sub g2warn {
-    my ($cat, $msg) = @_;
-    header();
-    say2 "$cat: $msg\n";
-}
-
-1;
-

commit 490636b832f53260d089ba282202c25fbc842057
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 21:30:12 2012 +0530

    aded non-core documentation

diff --git a/doc/cust.mkd b/doc/cust.mkd
index 0967dc2..e57f0a7 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -17,6 +17,9 @@ There are 5 basic types of non-core programs.
     different name to avoid confusion and constant disambiguation in the docs.
   * **VREFs** are extensions to the access control check part of gitolite.
 
+[Here][non-core] is a list of non-core programs shipped with gitolite, with
+some description of each.
+
 ## #commands gitolite "commands"
 
 Gitolite comes with several commands that users can run.  Remote user run the
diff --git a/doc/list b/doc/list
index 994e5a6..46b0f34 100644
--- a/doc/list
+++ b/doc/list
@@ -30,6 +30,8 @@ cust.mkd
 triggers.mkd
 vref.mkd
 
+non-core.mkd
+
 dev-notes.mkd
 
 misc.mkd
diff --git a/doc/non-core.mkd b/doc/non-core.mkd
new file mode 100644
index 0000000..bc82515
--- /dev/null
+++ b/doc/non-core.mkd
@@ -0,0 +1,160 @@
+# non-core programs shipped with gitolite
+
+## commands
+
+A list of these commands can be obtained by running `gitolite help` on the
+server.  A different (and probably much smaller) list can be obtained by a
+remote user running `ssh git at host help`.
+
+All the commands will respond to `-h`; please report a bug to me if they
+don't.
+
+## syntactic sugar
+
+The following "sugar" programs are available:
+
+  * continuation-lines -- allow the use of C-style backslash escaped
+    continuation lines in the conf file.  I don't like it but some people do,
+    and now I can support them without bulking up the "core" conf parser!
+
+  * keysubdirs-as-groups -- someone wanted the sub-directory name (of
+    "keydir/") in which the pubkey was placed to be a group to which the user
+    automatically belonged.  A very unusual requirement, and one which would
+    *never* have seen the light of day in g2, but in g3 it's easy, and doesn't
+    affect anyone else!
+
+    (Note: the last component of the directory path is used if there are more
+    than one level between "keydir/" and the actual file).
+
+## triggers
+
+The `PRE_GIT` triggers are:
+
+  * partial-copy -- this has its own section later in this page
+
+  * renice -- this renices the entire job to whatever value you specify
+
+The `POST_GIT` triggers are:
+
+  * cpu-time -- post-git triggers, if you check the [triggers][] doc, receive
+    4 CPU time numbers from the main shell program.  Treat this code as sample
+    and do do with them as you please to do with as you please.
+
+The `POST_COMPILE` triggers are:
+
+  * post-compile/ssh-authkeys -- takes the pubkeys in keydir and populates
+    `~/.ssh/authorized_keys`
+
+  * post-compile/update-git-configs -- updates individual 'repo.git/config'
+    files (using the 'git config ...' command) from settings supplied in the
+    conf file.  All sections except 'gitolite-options' are processed.  (The
+    'gitolite-options' section is considered internal to gitolite).
+
+  * post-compile/update-git-daemon-access-list -- create/delete
+    'git-daemon-export-ok' files in each repo based on whether the conf says
+    'daemon' can read the repo or not
+
+  * post-compile/update-gitweb-access-list -- populates the file named in
+    `GITWEB_PROJECTS_LIST` in the rc file (default: `$HOME/projects.list`)
+    with the list of repos that gitweb is allowed to access.  This could be
+    more than just "R = gitweb"; any repo that has any config setting with the
+    section name 'gitweb' (like 'gitweb.owner', 'gitweb.description', etc) is
+    considered readable by gitweb, so the final list is a union of these two
+    methods
+
+The `POST_CREATE` triggers are:
+
+  * the last 3 in the `POST_COMPILE` list also run from `POST_CREATE`, for
+    obvious reasons.
+
+## VREFs
+
+You should read about [vref][]s in detail first; this won't make sense
+otherwise.  For a brief recap, note that there are 2 kinds of VREFs: those
+that require arguments and those that behave just like any other `update`
+hook.
+
+COUNT is an example of the former (hence the long-ish description).  DUPKEYS
+and EMAIL-CHECK are both examples of the latter.
+
+  * COUNT
+
+    The COUNT VREF is used like this:
+
+        -   VREF/COUNT/9                    =   @junior-developers
+
+    In response, if anyone in the user list pushes a commit series that
+    changes more than 9 files, a vref of "VREF/COUNT/9" is returned.  Gitolite
+    uses that as a "ref" to match against all the rules, hit the same rule
+    that invoked it, and deny the request.
+
+    If the user did not push more than 9 files, the VREF code returns nothing,
+    and nothing happens.
+
+    COUNT can take one more argument:
+
+        -   VREF/COUNT/9/NEWFILES           =   @junior-developers
+
+    This is the same as before, but have to be more than 9 *new* files not
+    just changed files.
+
+  * DUPKEYS -- this checks keydir/ for duplicate keys and aborts the push if
+    it finds any.  You should use this only on the gitolite-admin repo.
+
+        repo gitolite-admin
+            -   VREF/DUPKEYS                =   @all
+
+  * EMAIL-CHECK -- read the comments in the code for this one.  Like DUPKEYS,
+    it does not take any arguments.
+
+  * FILETYPE -- this is sample code for a very site-specific purpose; you'll
+    have to read the code
+
+  * MERGE-CHECK -- this is sample code to illustrate how one of the gitolite
+    built-in functions *could* have been handled, although there are some
+    differences
+
+  * partial-copy -- this has its own section later in this page
+
+## special cases
+
+### partial-copy
+
+Git (and therefore gitolite) cannot do selective read control -- allowing
+someone to read branch A but not branch B.  It's the entire repo or nothing.
+
+<font color="gray"> [Side note: Gerrit Code Review can do that, but that is
+because they have their own git stack (and their own sshd, and so on) all in
+one big Java program.  Gerrit is *really* useful if you want code review to be
+part of the access control decision] </font>
+
+Gitolite can now help you do this, as follows:
+
+1.  enable 'partial-copy' in the `PRE_GIT` section in the rc file.
+
+2.  for each repo "foo" which has secret branches that a certain set of
+    developers (we'll use a group called `@temp-emp` as an example) are not
+    supposed to see, do this:
+
+        repo foo
+            # rules should allow @temp-emp NO ACCESS
+
+        repo foo-partialcopy-1
+            -   secret-branch               =   @temp-emp
+            # other rules should ensure ONLY @temp-emp has ANY ACCESS
+            # NO other user should have access
+
+            -   VREF/partial-copy           =   @all
+            config gitolite.partialCopyOf   =   foo
+
+And that should be it.  **Please test it and let me know if it doesn't work!**
+
+WARNINGS:
+
+  * if you change the config to disallow something that used to be allowed,
+    you should delete the partial repo on the server and then run 'gitolite
+    compile' to let it build again.  See t/partial-copy.t for details.
+
+  * not tested with smart http; probabl won't work
+
+  * also not tested with mirroring, or with wild card repos.

commit 61f6967f67fcfbc83a62b1f757e468d034131019
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 21:27:49 2012 +0530

    partial-copy fixed...
    
    ...there was one real bug, plus I had forgotten to put a comented out
    line in the rc file, but most of the rest of the effort was moving the
    test script over.
    
    oh and I'd also forgotten to move this from 'commands' to 'triggers' :-)

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index b0752b0..fa4b5fc 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -283,6 +283,9 @@ __DATA__
         [
             # if you use this, make this the first item in the list
             # 'renice 10',
+
+            # see docs ("list of non-core programs shipped") for details
+            # 'partial-copy',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/commands/partial-copy b/src/triggers/partial-copy
similarity index 100%
rename from src/commands/partial-copy
rename to src/triggers/partial-copy
index 19aa8d6..119c9f1 100755
--- a/src/commands/partial-copy
+++ b/src/triggers/partial-copy
@@ -26,9 +26,9 @@ cd $GL_REPO_BASE/$main.git
 
 for ref in `git for-each-ref refs/heads '--format=%(refname)'`
 do
-    gitolite access -q $repo $user R $ref &&
     cd $GL_REPO_BASE/$repo.git
 
+    gitolite access -q $repo $user R $ref &&
     git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
 done
 
diff --git a/t/partial-copy.t b/t/partial-copy.t
new file mode 100755
index 0000000..20a2cfe
--- /dev/null
+++ b/t/partial-copy.t
@@ -0,0 +1,181 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# test script for partial copy feature
+# ----------------------------------------------------------------------
+
+try "plan 82";
+try "DEF POK = !/DENIED/; !/failed to push/";
+my $h = $ENV{HOME};
+
+try "
+    cat $h/.gitolite.rc
+    perl s/GIT_CONFIG_KEYS.*/GIT_CONFIG_KEYS => '.*',/
+    perl s/# 'partial-copy'/'partial-copy'/
+    put $h/.gitolite.rc
+";
+
+confreset;confadd '
+    repo foo
+            RW+                 =   u1 u2
+
+    repo foo-pc
+            -   secret-1$       =   u4
+            R                   =   u4  # marker 01
+            RW  next            =   u4
+            RW+ dev/USER/       =   u4
+            RW  refs/tags/USER/ =   u4
+
+            -   VREF/partial-copy   =   @all
+            config gitolite.partialCopyOf = foo
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+        /Init.*empty.*foo\\.git/
+        /Init.*empty.*foo-pc\\.git/
+";
+
+try "
+    cd ..
+
+    ## populate repo foo, by user u1
+    # create foo with a bunch of branches and tags
+    CLONE u1 foo
+        /appear.*cloned/
+    cd foo
+    tc a1 a2
+    checkout -b dev/u1/foo; tc f1 f2
+    checkout master; tc m1 m2
+    checkout master; checkout -b next; tc n1 n2; tag nt1
+    checkout -b secret-1; tc s11 s12; tag s1t1
+    checkout next; checkout -b secret-2; tc s21 s22; tag s2t1
+    glt push u1 --all
+        /new branch/; /secret-1/; /secret-2/
+    glt push u1 --tags
+        /new tag/; /s1t1/; /s2t1/
+
+    ## user u4 tries foo, fails, tries foo-pc
+    cd ..
+    CLONE u4 foo foo4; !ok
+        /R any foo u4 DENIED by fallthru/
+    CLONE u4 foo-pc ; ok;
+        /Cloning into 'foo-pc'/
+        /new branch.* dev/u1/foo .* dev/u1/foo/
+        /new branch.* master .* master/
+        /new branch.* next .* next/
+        /new branch.* secret-2 .* secret-2/
+        !/new branch.* secret-1 .* secret-1/
+        /new tag.* nt1 .* nt1/
+        /new tag.* s2t1 .* s2t1/
+        !/new tag.* s1t1 .* s1t1/
+
+    ## user u4 pushes to foo-pc
+    cd foo-pc
+    checkout master
+    tc u4m1 u4m2; PUSH u4; !ok
+        /W refs/heads/master foo-pc u4 DENIED by fallthru/
+        /hook declined to update refs/heads/master/
+        /To file:///foo-pc/
+        /remote rejected/
+        /failed to push some refs to 'file:///foo-pc'/
+
+    checkout next
+    tc u4n1 u4n2
+    PUSH u4 next; ok
+        /To .*/foo.git/
+        /new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\\d+/
+        /file:///foo-pc/
+        /52c7716..ca37871  next -> next/
+    tag u4/nexttag; glt push u4 --tags
+        /To file:///foo-pc/
+        /\\[new tag\\]         u4/nexttag -> u4/nexttag/
+        /\\[new branch\\]      ca3787119b7e8b9914bc22c939cefc443bc308da -> br-\\d+/
+
+    checkout master
+    checkout -b dev/u4/u4master
+    tc devu4m1 devu4m2
+    PUSH u4 HEAD; ok
+        /To .*/foo.git/
+        /new branch\\]      228353950557ed1eb13679c1fce4d2b4718a2060 -> br-\\d+/
+        /file:///foo-pc/
+        /new branch.* HEAD -> dev/u4/u4master/
+
+    ## user u1 gets u4's updates, makes some more
+    cd ../foo
+    glt fetch u1
+        /From file:///foo/
+        /new branch\\]      dev/u4/u4master -> origin/dev/u4/u4master/
+        /new tag\\]         u4/nexttag -> u4/nexttag/
+        /52c7716..ca37871  next       -> origin/next/
+    checkout master; tc u1ma1 u1ma2;
+        /\\[master 8ab1ff5\\] u1ma2 at Thu Jul  7 06:23:20 2011/
+    tag mt2; PUSH u1 master; ok
+    checkout secret-1; tc u1s1b1 u1s1b2
+        /\\[secret-1 5f96cb5\\] u1s1b2 at Thu Jul  7 06:23:20 2011/
+    tag s1t2; PUSH u1 HEAD; ok
+    checkout secret-2; tc u1s2b1 u1s2b2
+        /\\[secret-2 1ede682\\] u1s2b2 at Thu Jul  7 06:23:20 2011/
+    tag s2t2; PUSH u1 HEAD; ok
+    glt push u1 --tags; ok
+
+    glt ls-remote u1 origin
+        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/
+        /5f96cb5ff73c730fb040eb2d01981f7677ca6dba\trefs/tags/s1t2/
+        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/
+
+    ## u4 gets updates but without the tag in secret-1
+    cd ../foo-pc
+    glt ls-remote u4 origin
+        !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/
+        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\tHEAD/
+        /8ced4a374b3935bac1a5ba27ef8dd950bd867d47\trefs/heads/dev/u1/foo/
+        /228353950557ed1eb13679c1fce4d2b4718a2060\trefs/heads/dev/u4/u4master/
+        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/heads/master/
+        /ca3787119b7e8b9914bc22c939cefc443bc308da\trefs/heads/next/
+        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/heads/secret-2/
+        /8ab1ff512faf5935dc0fbff357b6f453b66bb98b\trefs/tags/mt2/
+        /52c7716c6b029963dd167c647c1ff6222a366499\trefs/tags/nt1/
+        /01f04ece6519e7c0e6aea3d26c7e75e9c4e4b06d\trefs/tags/s2t1/
+        /1ede6829ec7b75a53cd6acb7da64e5a8011e6050\trefs/tags/s2t2/
+
+    glt fetch u4
+        /3ea704d..8ab1ff5  master     -> origin/master/
+        /01f04ec..1ede682  secret-2   -> origin/secret-2/
+        /\\[new tag\\]         mt2        -> mt2/
+        /\\[new tag\\]         s2t2       -> s2t2/
+        !/ refs/heads/secret-1/; !/s1t1/; !/s1t2/
+";
+__END__
+
+# last words...
+glt ls-remote u4 file:///foo-pc
+
+cd ../gitolite-admin
+cat conf/gitolite.conf
+perl s/.*marker 01.*//;
+put conf/gitolite.conf
+add conf; commit -m erdel; ok; PUSH admin; ok
+
+glt ls-remote u4 file:///foo-pc
+# see rant below at this point
+
+cd $h/repositories/foo-pc.git
+git branch -D secret-2
+git tag -d s2t1 s2t2
+git gc --prune=now
+glt ls-remote u4 file:///foo-pc
+# only *now* does the rant get addressed
+
+__END__
+
+RANT...
+
+This is where things go all screwy.  Because we still have the *objects*
+pointed to by tags s2t1 and s2t2, we still get them back from the main repo.

commit 9764b39b0d2c61776c155cd178a8ad23b6d0783e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 11:53:00 2012 +0530

    documentation fixups

diff --git a/doc/commands.mkd b/doc/commands.mkd
deleted file mode 100644
index da2baa4..0000000
--- a/doc/commands.mkd
+++ /dev/null
@@ -1,26 +0,0 @@
-# gitolite "commands"
-
-Gitolite comes with several commands that users can run.  Remote user run the
-commands by saying:
-
-    ssh git at host command-name [args...]
-
-while on the server you can run
-
-    gitolite command [args...]
-
-Very few commands are designed to be run both ways, but it can be done, by
-checking for the presence of env var `GL_USER`.
-
-You can get a **list of available commands** by using the `help` command.
-Naturally, a remote user will see only a subset of what the server user will
-see.
-
-You add commands to the "allowed from remote" list by adding its name (or
-uncommenting it if it's already added but commented out) to the COMMANDS hash
-in the [rc][] file.
-
-If you write your own commands, put them in src/commands.
-
-**Note that this is also the place that all triggered programs go**.  In fact,
-all standalone programs related to gitolite go here.
diff --git a/doc/cust.mkd b/doc/cust.mkd
index fb79b21..0967dc2 100644
--- a/doc/cust.mkd
+++ b/doc/cust.mkd
@@ -1,23 +1,71 @@
 # customising gitolite
 
-Here are the ways you can customise gitolite on the server.
+Much of gitolite (g3)'s functionality comes from programs and scripts that are
+not considered "core".  This keeps the core simpler, and allows you to enhance
+gitolite for your own purposes without too much fuss.
 
-First, learn about:
+## types of non-core programs
 
-  * [git hooks][hooks] and [virtual refs][vref]
+There are 5 basic types of non-core programs.
 
-  * [commands][] for your [users][] to run own][dev-notes]
+  * *commands* can be run from the shell command line.  Those listed in the
+    COMMANDS hash of the rc file can also be run remotely.
+  * *hooks* are standard git hooks; see below.
+  * *sugar scripts* change the conf language for your convenience.  The word
+    sugar comes from "syntactics sugar".
+  * *triggers* are to gitolite what hooks are to git.  I just chose a
+    different name to avoid confusion and constant disambiguation in the docs.
+  * **VREFs** are extensions to the access control check part of gitolite.
 
-  * [triggers][] to be run by gitolite as various points in its execution
+## #commands gitolite "commands"
 
-  * [syntactic sugar][sugar] to change the conf language for your convenience
+Gitolite comes with several commands that users can run.  Remote user run the
+commands by saying:
 
-For all of the above:
+    ssh git at host command-name [args...]
 
-  * [edit the rc file][rc] to enable optional features that are shipped in a
-    disabled state
+while on the server you can run
 
-  * [write your own][dev-notes]
+    gitolite command [args...]
 
-(Note: "trigger" is the same concept as "hook", applied to gitolite; I just
-chose a different name to avoid constant ambiguity in documentation).
+Very few commands are designed to be run both ways, but it can be done, by
+checking for the presence of env var `GL_USER`.
+
+You can get a **list of available commands** by using the `help` command.
+Naturally, a remote user will see a much smaller list than the server user.
+
+You add commands to the "allowed from remote" list by adding its name (or
+uncommenting it if it's already added but commented out) to the COMMANDS hash
+in the [rc][] file.
+
+If you write your own commands, put them in src/commands.
+
+## #hooks hooks and gitolite
+
+Gitolite uses the `update` hook for all repos.  In addition, it uses the
+`post-update` hook for the gitolite-admin repo.
+
+If you want to add your own hook, it's easy as long as it's not the 'update'
+hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
+
+The rest is between you and 'man githooks' :-)
+
+## #sugar syntactic sugar
+
+Sugar scripts help you change the perceived syntax of the conf language.  The
+base syntax of the language is as described [here][conf], so sugar scripts
+take something *else* and convert it into that.
+
+That way, the admin sees additional features (like allowing continuation
+lines), while the parser in the core gitolite engine does not change.
+
+If you want to write your own sugar scripts, please read the "your own sugar"
+section in [dev-notes][] first then email me.
+
+## triggers
+
+Triggers have their own [document][triggers].
+
+## VREFs
+
+VREFs also have their own [document][vref].
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
index 854c547..f652d93 100644
--- a/doc/dev-notes.mkd
+++ b/doc/dev-notes.mkd
@@ -9,7 +9,7 @@ Hints for developers wishing to help migrate features over from g2 are
 Here are some random notes on developing hooks, commands, triggers, and sugar
 scripts.
 
-## environment variables
+## environment variables and other inputs
 
 In general, the following environment variables should always be available:
 
@@ -20,6 +20,9 @@ In general, the following environment variables should always be available:
 Commands invoked by a remote client will also have `GL_USER` set.  Hooks will
 have `GL_REPO` also set.
 
+Finally, note that triggers get a lot of relevant information as arguments;
+see [here][triggers] for details.
+
 ## APIs
 
 ### the shell API
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 81ce6c9..3f065c7 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -73,7 +73,7 @@ put that contain the words "see docs":
         cd $HOME/repositories
         find . -type d -name "*.git" -prune | while read r
         do
-            mv \$r/gl-creater \$r/gl-creator
+            mv $r/gl-creater $r/gl-creator
         done 2>/dev/null
 
     Once you do this, the g2 will not work completely unless you change them
diff --git a/doc/hooks.mkd b/doc/hooks.mkd
deleted file mode 100644
index 499714d..0000000
--- a/doc/hooks.mkd
+++ /dev/null
@@ -1,9 +0,0 @@
-# hooks and gitolite
-
-Gitolite uses the `update` hook for all repos.  In addition, it uses the
-`post-update` hook for the gitolite-admin repo.
-
-If you want to add your own hook, it's easy as long as it's not the 'update'
-hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
-
-The rest is between you and 'man githooks' :-)
diff --git a/doc/list b/doc/list
index a844832..994e5a6 100644
--- a/doc/list
+++ b/doc/list
@@ -14,11 +14,9 @@ minreq.mkd
 qi.mkd
 install.mkd
 add.mkd
-conf.mkd
 users.mkd
 
-rc.mkd
-cust.mkd
+conf.mkd
 
 group.mkd
 repo.mkd
@@ -26,13 +24,14 @@ rules.mkd
 refex.mkd
 write-types.mkd
 
-dev-notes.mkd
-commands.mkd
-hooks.mkd
+rc.mkd
+
+cust.mkd
 triggers.mkd
-sugar.mkd
 vref.mkd
 
+dev-notes.mkd
+
 misc.mkd
 pw.mkd
 testing.mkd
diff --git a/doc/sugar.mkd b/doc/sugar.mkd
deleted file mode 100644
index 016de55..0000000
--- a/doc/sugar.mkd
+++ /dev/null
@@ -1,11 +0,0 @@
-# syntactic sugar
-
-Sugar scripts help you change the perceived syntax of the conf language.  The
-base syntax of the language is as described [here][conf], so sugar scripts
-take something *else* and convert it into that.
-
-That way, the admin sees additional features (like allowing continuation
-lines), while the parser in the core gitolite engine does not change.
-
-If you want to write your own sugar scripts, please read the "your own sugar"
-section in [dev-notes][] first then email me.

commit 07cf7fedfe5c9f80b51124d9cbb479d6078a7e9b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 11:02:57 2012 +0530

    move triggers into their own subdir...
    
    ...otherwise 'gitolite help' was getting too confusing, mixing up stuff
    that users should not be running directly (even on the server)
    
    ----
    
    implementation notes:
    
    those who are worried about the '../triggers/' in various parts of the
    code here, remember you can only do that from a command line on the
    server.  Remote users can only use commands that have been explicitly
    listed in the COMMANDS hash in the rc file.  This means they can't even
    access other commands in the same directory as, say, the 'info' command,
    so a '../' is definitely not going to work.

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index bdd6755..b0752b0 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -171,7 +171,7 @@ sub trigger {
             for my $s ( @{ $rc{$rc_section} } ) {
 
                 my ($pgm, @args) = split ' ', $s;
-                $pgm = "$ENV{GL_BINDIR}/commands/$pgm";
+                $pgm = "$ENV{GL_BINDIR}/triggers/$pgm";
 
                 _warn("skipped command '$s'"), next if not -x $pgm;
                 trace( 2, "command: $s" );
diff --git a/src/commands/access b/src/commands/access
index 6da81c4..726a346 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -31,7 +31,7 @@ For each case where access is not denied, one line is printed like this:
 This is orders of magnitude faster than running the command multiple times;
 you'll notice if you have more than a hundred or so repos.
 
-Advanced uses: see src/commands/post-compile/update-git-daemon-access-list for
+Advanced uses: see src/triggers/post-compile/update-git-daemon-access-list for
 a good example.
 =cut
 
diff --git a/src/commands/cpu-time b/src/triggers/cpu-time
similarity index 100%
rename from src/commands/cpu-time
rename to src/triggers/cpu-time
diff --git a/src/commands/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
similarity index 100%
rename from src/commands/post-compile/ssh-authkeys
rename to src/triggers/post-compile/ssh-authkeys
diff --git a/src/commands/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
similarity index 100%
rename from src/commands/post-compile/update-git-configs
rename to src/triggers/post-compile/update-git-configs
diff --git a/src/commands/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
similarity index 100%
rename from src/commands/post-compile/update-git-daemon-access-list
rename to src/triggers/post-compile/update-git-daemon-access-list
diff --git a/src/commands/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
similarity index 100%
rename from src/commands/post-compile/update-gitweb-access-list
rename to src/triggers/post-compile/update-gitweb-access-list
diff --git a/src/commands/renice b/src/triggers/renice
similarity index 100%
rename from src/commands/renice
rename to src/triggers/renice
diff --git a/t/all-yall.t b/t/all-yall.t
index 947c753..5691cf2 100755
--- a/t/all-yall.t
+++ b/t/all-yall.t
@@ -60,8 +60,8 @@ try "
 ";
 
 try "
-    gitolite post-compile/update-git-daemon-access-list;    ok
-    gitolite post-compile/update-gitweb-access-list;        ok
+    gitolite ../triggers/post-compile/update-git-daemon-access-list;    ok
+    gitolite ../triggers/post-compile/update-gitweb-access-list;        ok
     cat $ENV{HOME}/projects.list;                           ok
 ";
 cmp 'bar.git
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index 98f2d79..d941922 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -17,7 +17,7 @@ my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir";
 
 try "plan 49";
 
-my $pgm = "gitolite post-compile/ssh-authkeys";
+my $pgm = "gitolite ../triggers/post-compile/ssh-authkeys";
 
 try "
     # prep
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 14a1cf4..9051fa0 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -32,7 +32,7 @@ try "
     cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6
 ";
 
-system("gitolite post-compile/ssh-authkeys");
+system("gitolite ../triggers/post-compile/ssh-authkeys");
 
 # basic tests
 # ----------------------------------------------------------------------

commit 96ccbf0c1c38631896d3f9299474e616ec7fa603
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 05:40:49 2012 +0530

    make standalone config entries work
    
    For example, in
    
        repo foo/..*
            C   =   u1 u2 u3
            RW+ =   CREATOR
            RW  =   WRITERS
            R   =   READERS
    
            config hooks.emailprefix = '[%GL_REPO] '
            config foo.bar  = bar one
    
        repo foo/u1/..*
            config bar.baz  = frob nitz
    
    make that last config also work!

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 99256d7..f89919f 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -286,7 +286,7 @@ sub memberships {
         $base2 = generic_name($base);
 
         # second, you need to check in %repos also
-        for my $i ( keys %repos ) {
+        for my $i ( keys %repos, keys %configs ) {
             if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
                 push @ret, $i;
             }
diff --git a/t/easy.t b/t/easy.t
index d8ca753..9e4bfb4 100755
--- a/t/easy.t
+++ b/t/easy.t
@@ -18,7 +18,7 @@ sub ok { (+shift) ? print "ok\n" : print "not ok\n"; }
 sub nok { (+shift) ? print "not ok\n" : print "ok\n"; }
 sub msg { return unless $ENV{D}; print STDERR "#" . +shift . "\n"; }
 
-try "plan 88";
+try "plan 90";
 
 try "
     cat $ENV{HOME}/.gitolite.rc
@@ -49,7 +49,6 @@ confreset;confadd '
     @oddguys = u1 u3 u5
     @evensout = u2 u4 u6
 
-    # TODO
     repo cc/sub/..*
         config sub.cc   =   1
 ';
@@ -166,8 +165,7 @@ my @a;
 @a = config("cc/sub/one", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
 @a = config("cc/sub/one", "fo\\..cc"); ok(scalar(@a) == 0);
 
-# TODO
-# @a = config("cc/sub/one", "su..cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
-# @a = config("cc/sub/one", "sub.cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
+ at a = config("cc/sub/one", "su..cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
+ at a = config("cc/sub/one", "sub.cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
 @a = config("cc/sub/one", "su\\..cc"); ok(scalar(@a) == 0);
 

commit 5d1adc63c27a6e5db73919241faa73820ef5ec4c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 05:24:21 2012 +0530

    (mkdoc) make it do a little more post-mkdoc work

diff --git a/doc/mkdoc b/doc/mkdoc
index e794fb7..b1ed932 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -7,61 +7,90 @@ my $MKD = "./Markdown.pl";
 use 5.10.0;
 use strict;
 use warnings;
-
-chomp(@ARGV = `cat list`) if not @ARGV;
- at ARGV = grep { $_ ne 'master-toc.mkd' and /./ } @ARGV;
-my @save = @ARGV;
-my $css = join("", <DATA>);
-
-my $mt = "# gitolite master table of contents/index\n";
-my $mf = '';
-my $fh;
-
-while (<>) {
-    $ARGV =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
-    my $b = $1;
-
-    if (/^(#+) (?:#(\S+) )?(.*)/) {
-        if ( length($1) == 1 ) {
-            $mt .= "\n";
-            $mt .= "  * [$3][$b]\n";
-            $mf .= "[$b]: $b.html\n";
-        } else {
-            $mt .= " " x ( 4 * ( length($1) - 1 ) );
-            $mt .= "  * ";
-            $mt .= (
-                $2
-                ? "[$3][$2]"
-                : "$3"
-            );
-            $mt .= "\n";
-            $mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
+use lib '../src/Gitolite/Test';
+use Tsh;
+
+$ENV{TSH_ERREXIT} = 1;
+
+try "
+    mkdir ../html;                      ok
+    git status -s -uno;                 !/./
+    git log --oneline -1
+" or die 1;
+
+my $head = (lines())[0];
+
+main();
+
+try "
+    git checkout gh-pages;              ok
+    git reset --hard github/gh-pages;   ok
+    cd ..;                              ok
+    git rm g3/*.html;                   ok
+    mkdir g3;                           ok
+    mv html/*.html g3;                  ok
+    git add g3;                         ok
+    git commit -m '$head';              ok
+    git checkout g3;                    ok
+    rmdir html;                         ok
+" or die 2;
+
+sub main {
+    chomp(@ARGV = `cat list`) if not @ARGV;
+    @ARGV = grep { $_ ne 'master-toc.mkd' and /./ } @ARGV;
+    my @save = @ARGV;
+    my $css = join("", <DATA>);
+
+    my $mt = "# gitolite master table of contents/index\n";
+    my $mf = '';
+    my $fh;
+
+    while (<>) {
+        $ARGV =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
+        my $b = $1;
+
+        if (/^(#+) (?:#(\S+) )?(.*)/) {
+            if ( length($1) == 1 ) {
+                $mt .= "\n";
+                $mt .= "  * [$3][$b]\n";
+                $mf .= "[$b]: $b.html\n";
+            } else {
+                $mt .= " " x ( 4 * ( length($1) - 1 ) );
+                $mt .= "  * ";
+                $mt .= (
+                    $2
+                    ? "[$3][$2]"
+                    : "$3"
+                );
+                $mt .= "\n";
+                $mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
+            }
         }
     }
-}
 
-open($fh, ">", "master-toc.mkd")
-  and print $fh $mt
-  and close $fh;
+    open($fh, ">", "master-toc.mkd")
+      and print $fh $mt
+      and close $fh;
 
-# after this, do this for every mkd (including the master-toc.mkd)
+    # after this, do this for every mkd (including the master-toc.mkd)
 
-#       cat $css_block > $base.html
-#       cat $base.mkd $mf | $MKD >> $base.html
+    #       cat $css_block > $base.html
+    #       cat $base.mkd $mf | $MKD >> $base.html
 
-for my $mkd ("master-toc.mkd", @save) {
-    $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
-    my $b = $1;
+    for my $mkd ("master-toc.mkd", @save) {
+        $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
+        my $b = $1;
 
-    open($fh, ">", "../html/$b.html")
-      and print $fh $css
-      and close $fh;
+        open($fh, ">", "../html/$b.html")
+          and print $fh $css
+          and close $fh;
 
-    my $mkt = `cat $mkd`;
-    $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
-    open($fh, "|-", "$MKD >> ../html/$b.html")
-      and print $fh $mkt, $mf
-      and close $fh;
+        my $mkt = `cat $mkd`;
+        $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
+        open($fh, "|-", "$MKD >> ../html/$b.html")
+          and print $fh $mkt, $mf
+          and close $fh;
+    }
 }
 
 __DATA__

commit cf10f7933c75ba42726746dd3cb13e9e2199b6c3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 26 05:23:23 2012 +0530

    (minor) use 'sts' as the url for the ssh-troubleshooting page
    
    when g3 becomes "master", the URL in the git info bot won't have to
    change

diff --git a/doc/extras/ssh-troubleshooting.mkd b/doc/extras/ssh-troubleshooting.mkd
index aeee3d9..cdcc831 100644
--- a/doc/extras/ssh-troubleshooting.mkd
+++ b/doc/extras/ssh-troubleshooting.mkd
@@ -1,4 +1,4 @@
-## #sshts ssh troubleshooting
+## #sts ssh troubleshooting
 
 **This document must be read in full the first time.  If you start from some
 nice looking section in the middle it may not help you unless you're already
diff --git a/doc/extras/ssh.mkd b/doc/extras/ssh.mkd
index a5ee5d4..4d04d57 100644
--- a/doc/extras/ssh.mkd
+++ b/doc/extras/ssh.mkd
@@ -6,6 +6,6 @@ There are two documents you need to read, in order:
     features to provide any number of virtual users over just one actual
     (unix) user, and so on
 
-  * [ssh troubleshooting][sshts] -- this is a rather long document but as far
+  * [ssh troubleshooting][sts] -- this is a rather long document but as far
     as I know almost every known ssh related issue is in here.  If you find
     something missing, send me an email with details.

commit 2845de74ea8b0ef39f7d157ccb2f5d44743db7bf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 16:49:02 2012 +0530

    Easy.pm learns config(), acquires a test script

diff --git a/src/Gitolite/Easy.pm b/src/Gitolite/Easy.pm
index 81ff02f..fb749f0 100644
--- a/src/Gitolite/Easy.pm
+++ b/src/Gitolite/Easy.pm
@@ -10,10 +10,13 @@ package Gitolite::Easy;
   is_admin
   is_super_admin
   in_group
+
   owns
   can_read
   can_write
 
+  config
+
   %rc
   say
   say2
@@ -52,9 +55,9 @@ sub is_super_admin {
 #   if gitolite list-memberships $GL_USER | grep -x $GROUPNAME >/dev/null; then ...
 sub in_group {
     valid_user();
-    my $g = shift;
+    my $g = "@" . +shift;
 
-    return grep { $_ eq $g } @{ list_memberships($user) };
+    return grep { $_ eq $g } @{ Gitolite::Conf::Load::list_memberships($user) };
 }
 
 # shell equivalent
@@ -85,6 +88,18 @@ sub can_write {
     return not( access( $r, $user, 'W', 'any' ) =~ /DENIED/ );
 }
 
+# shell equivalent
+#   foo=$(gitolite git-config -r $REPONAME foo\\.bar)
+sub config {
+    my $repo = shift;
+    my $key = shift;
+
+    return () if repo_missing($repo);
+
+    my $ret = git_config($repo, $key);
+    return %$ret;
+}
+
 # ----------------------------------------------------------------------
 
 sub valid_user {
diff --git a/t/easy.t b/t/easy.t
new file mode 100755
index 0000000..d8ca753
--- /dev/null
+++ b/t/easy.t
@@ -0,0 +1,173 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Easy;
+use Gitolite::Test;
+# put this after ::Easy because it chdirs away from where you were and the
+# 'use lib "src"', not being absolute, fails
+
+# smoke tests for Easy.pm
+# ----------------------------------------------------------------------
+# for a change these are actual perl tests, so not much call for tsh here,
+# although I still need the basic infrastructure for setting up the repos and
+# I still can't intermix this with perl's Test.pm or Test::More etc
+sub ok { (+shift) ? print "ok\n" : print "not ok\n"; }
+sub nok { (+shift) ? print "not ok\n" : print "ok\n"; }
+sub msg { return unless $ENV{D}; print STDERR "#" . +shift . "\n"; }
+
+try "plan 88";
+
+try "
+    cat $ENV{HOME}/.gitolite.rc
+    perl s/GIT_CONFIG_KEYS.*/GIT_CONFIG_KEYS => '.*',/
+    put $ENV{HOME}/.gitolite.rc
+";
+
+# basic push admin repo
+confreset;confadd '
+    repo gitolite-admin
+        RW+     VREF/NAME/      =   admin
+        RW+     VREF/NAME/u5/   =   u5
+
+    repo aa
+        RW+     =   u1
+        RW      =   u2
+        R       =   u4
+
+        config for.aa   =   1
+
+    repo cc/..*
+        C       =   u4
+        RW+     =   CREATOR u5
+        R       =   u6
+
+        config for.cc   =   1
+
+    @oddguys = u1 u3 u5
+    @evensout = u2 u4 u6
+
+    # TODO
+    repo cc/sub/..*
+        config sub.cc   =   1
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+# valid_user() -- an internal function but still worth testing by itself first
+eval { Gitolite::Easy::valid_user(); };
+ok($@ =~ /FATAL.*GL_USER not set/);
+$ENV{GL_USER} = "u2";
+eval { Gitolite::Easy::valid_user(); };
+nok($@ =~ /FATAL.*GL_USER not set/);
+
+# is_admin
+msg('is_admin');
+$ENV{GL_USER} = "admin"; ok(is_admin());
+$ENV{GL_USER} = "u5"; ok(is_admin());
+$ENV{GL_USER} = "u2"; nok(is_admin());
+
+# is_super_admin -- not sure how useful it is right now
+msg('is_super_admin');
+$ENV{GL_USER} = "admin"; ok( is_super_admin() );
+$ENV{GL_USER} = "u5";    nok( is_super_admin() );
+$ENV{GL_USER} = "u2";    nok( is_super_admin() );
+
+# in_group
+msg('in_group');
+$ENV{GL_USER} = "u1"; ok( in_group('oddguys') );  nok( in_group('evensout') );
+$ENV{GL_USER} = "u3"; ok( in_group('oddguys') );  nok( in_group('evensout') );
+$ENV{GL_USER} = "u4"; nok( in_group('oddguys') ); ok( in_group('evensout') );
+$ENV{GL_USER} = "u2"; nok( in_group('oddguys') ); ok( in_group('evensout') );
+
+# owns
+msg('owns');
+try("glt ls-remote u4 cc/u4; /Initialized empty.*cc/u4/");
+$ENV{GL_USER} = "u3"; nok( owns("cc/u3") ); nok( owns("cc/u4") );
+$ENV{GL_USER} = "u4"; nok( owns("cc/u3") ); ok( owns("cc/u4") );
+$ENV{GL_USER} = "u5"; nok( owns("cc/u3") ); nok( owns("cc/u4") );
+
+# can_read
+msg('can_read');
+$ENV{GL_USER} = "u1"; ok(can_read("aa"));
+$ENV{GL_USER} = "u2"; ok(can_read("aa"));
+$ENV{GL_USER} = "u3"; nok(can_read("aa"));
+$ENV{GL_USER} = "u4"; ok(can_read("aa"));
+
+$ENV{GL_USER} = "u1"; nok(can_read("bb"));
+$ENV{GL_USER} = "u2"; nok(can_read("bb"));
+$ENV{GL_USER} = "u3"; nok(can_read("bb"));
+$ENV{GL_USER} = "u4"; nok(can_read("bb"));
+
+$ENV{GL_USER} = "u3"; nok(can_read("cc/u3"));
+$ENV{GL_USER} = "u4"; nok(can_read("cc/u3"));
+$ENV{GL_USER} = "u5"; nok(can_read("cc/u3"));
+$ENV{GL_USER} = "u6"; nok(can_read("cc/u3"));
+
+$ENV{GL_USER} = "u3"; nok(can_read("cc/u4"));
+$ENV{GL_USER} = "u4"; ok(can_read("cc/u4"));
+$ENV{GL_USER} = "u5"; ok(can_read("cc/u4"));
+$ENV{GL_USER} = "u6"; ok(can_read("cc/u4"));
+
+# can_write
+msg('can_write');
+$ENV{GL_USER} = "u1"; ok(can_write("aa"));
+$ENV{GL_USER} = "u2"; ok(can_write("aa"));
+$ENV{GL_USER} = "u3"; nok(can_write("aa"));
+$ENV{GL_USER} = "u4"; nok(can_write("aa"));
+
+$ENV{GL_USER} = "u1"; nok(can_write("bb"));
+$ENV{GL_USER} = "u2"; nok(can_write("bb"));
+$ENV{GL_USER} = "u3"; nok(can_write("bb"));
+$ENV{GL_USER} = "u4"; nok(can_write("bb"));
+
+$ENV{GL_USER} = "u3"; nok(can_write("cc/u3"));
+$ENV{GL_USER} = "u4"; nok(can_write("cc/u3"));
+$ENV{GL_USER} = "u5"; nok(can_write("cc/u3"));
+$ENV{GL_USER} = "u6"; nok(can_write("cc/u3"));
+
+$ENV{GL_USER} = "u3"; nok(can_write("cc/u4"));
+$ENV{GL_USER} = "u4"; ok(can_write("cc/u4"));
+$ENV{GL_USER} = "u5"; ok(can_write("cc/u4"));
+$ENV{GL_USER} = "u6"; nok(can_write("cc/u4"));
+
+# config
+try("glt ls-remote u4 cc/sub/one; /Initialized empty.*cc/sub/one/");
+try("glt ls-remote u4 cc/two; /Initialized empty.*cc/two/");
+ok(1);
+my @a;
+ at a = config("aa", "fo..aa");   ok($a[0] eq 'for.aa' and $a[1] eq '1');
+ at a = config("aa", "for.aa");   ok($a[0] eq 'for.aa' and $a[1] eq '1');
+ at a = config("aa", "fo\\..aa"); ok(scalar(@a) == 0);
+
+ at a = config("aa", "fo..cc");   ok(scalar(@a) == 0);
+ at a = config("aa", "for.cc");   ok(scalar(@a) == 0);
+ at a = config("aa", "fo\\..cc"); ok(scalar(@a) == 0);
+
+ at a = config("bb", "fo..aa");   ok(scalar(@a) == 0);
+ at a = config("bb", "for.aa");   ok(scalar(@a) == 0);
+ at a = config("bb", "fo\\..aa"); ok(scalar(@a) == 0);
+
+ at a = config("cc/u4", "fo..aa");   ok(scalar(@a) == 0);
+ at a = config("cc/u4", "for.aa");   ok(scalar(@a) == 0);
+ at a = config("cc/u4", "fo\\..aa"); ok(scalar(@a) == 0);
+
+ at a = config("cc/u4", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/u4", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/u4", "fo\\..cc"); ok(scalar(@a) == 0);
+
+ at a = config("cc/two", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/two", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/two", "fo\\..cc"); ok(scalar(@a) == 0);
+
+ at a = config("cc/sub/one", "fo..cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/sub/one", "for.cc");   ok($a[0] eq 'for.cc' and $a[1] eq '1');
+ at a = config("cc/sub/one", "fo\\..cc"); ok(scalar(@a) == 0);
+
+# TODO
+# @a = config("cc/sub/one", "su..cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
+# @a = config("cc/sub/one", "sub.cc");   ok($a[0] eq 'sub.cc' and $a[1] eq '1');
+ at a = config("cc/sub/one", "su\\..cc"); ok(scalar(@a) == 0);
+

commit 213909970611adbb25112e8b23adc4c0ad5cc69b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 10:43:36 2012 +0530

    arguments in rc for triggered programs...
    
    ...using 'renice' as example and first user
    
    (also had to re-arrange rc file to a more sensible order)

diff --git a/doc/g2rcdiff.mkd b/doc/g2rcdiff.mkd
index 81af122..53ebd55 100644
--- a/doc/g2rcdiff.mkd
+++ b/doc/g2rcdiff.mkd
@@ -64,12 +64,8 @@ or mechanisms, but you have to do some setup work.
   * `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
     file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
 
-  * `NICE_VALUE` -- use the `PRE_GIT` trigger to attach a program that renices
-    the pid given by $GL_TID (that's the pid of the initial gitolite entry
-    point, usually gitolite-shell, and propagates from there once set).
-
-    You may have to add this list to the rc file; if you don't know perl use
-    one of the others as a model or ask me.
+  * `NICE_VALUE` -- uncomment the 'renice 10' line in the rc file.  You can
+    also change the 10 to something else if you wish.
 
   * `GIT_PATH` -- gone, not needed.  Just add these lines to the end of the rc
     file:
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
index 50e74bd..637a04b 100644
--- a/doc/triggers.mkd
+++ b/doc/triggers.mkd
@@ -42,13 +42,23 @@ However if the triggered code depends on arguments (see next section) this
 won't work.  (The `POST_COMPILE` trigger programs all just happen to not
 require any arguments, so it works).
 
-## triggers and arguments
+## common arguments
 
-All triggers receive the name of the trigger as a string (example,
-`"POST_COMPILE"`) as the first argument, so they can know who invoked them.
-(This allows you to write the same program and fire it from more than one
-trigger, as above).  In addition, they may receive other arguments pertaining
-to the event that happened.
+Triggers receive the following arguments:
+
+1.  any arguments mentioned in the rc file (for an example, see the renice
+    command in the PRE_GIT trigger sequence),
+
+2.  the name of the trigger as a string (example, `"POST_COMPILE"`), so you
+    can call the same program from multiple triggers and know where it was
+    called from,
+
+3.  followed by zero or more arguments specific to the trigger, as given in
+    the next section.
+
+## trigger-specific details
+
+Here's all you need to know about each specific trigger.
 
   * `ACCESS_CHECK`: this fires once after each access check.  The first is
     just before invoking git-receive-pack or git-upload-pack.  The second,
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index c4c3bcd..bdd6755 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -170,14 +170,12 @@ sub trigger {
         } else {
             for my $s ( @{ $rc{$rc_section} } ) {
 
-                # perl-ism; apart from keeping the full path separate from the
-                # simple name, this also protects %rc from change by implicit
-                # aliasing, which would happen if you touched $s itself
-                my $sfp = "$ENV{GL_BINDIR}/commands/$s";
+                my ($pgm, @args) = split ' ', $s;
+                $pgm = "$ENV{GL_BINDIR}/commands/$pgm";
 
-                _warn("skipped command '$s'"), next if not -x $sfp;
+                _warn("skipped command '$s'"), next if not -x $pgm;
                 trace( 2, "command: $s" );
-                _system( $sfp, $rc_section, @_ );    # they better all return with 0 exit codes!
+                _system( $pgm, @args, $rc_section, @_ );    # they better all return with 0 exit codes!
             }
         }
         return;
@@ -262,6 +260,17 @@ __DATA__
     # DEFAULT_ROLE_PERMS          =>  'READERS @all',
 
     # comment out or uncomment as needed
+    # these are available to remote users
+    COMMANDS                    =>
+        {
+            'help'              =>  1,
+            'info'              =>  1,
+            'desc'              =>  1,
+            'perms'             =>  1,
+            'writes'            =>  1,
+        },
+
+    # comment out or uncomment as needed
     # these will run in sequence during the conf file parse
     SYNTACTIC_SUGAR             =>
         [
@@ -269,6 +278,22 @@ __DATA__
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence at the start, before a git operation has started
+    PRE_GIT                     =>
+        [
+            # if you use this, make this the first item in the list
+            # 'renice 10',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence at the end, after a git operation has ended
+    POST_GIT                    =>
+        [
+            # if you use this, make this the last item in the list
+            # 'cpu-time',
+        ],
+
+    # comment out or uncomment as needed
     # these will run in sequence after post-update
     POST_COMPILE                =>
         [
@@ -286,25 +311,6 @@ __DATA__
             'post-compile/update-gitweb-access-list',
             'post-compile/update-git-daemon-access-list',
         ],
-
-    # comment out or uncomment as needed
-    # these are available to remote users
-    COMMANDS                    =>
-        {
-            'help'              =>  1,
-            'info'              =>  1,
-            'desc'              =>  1,
-            'perms'             =>  1,
-            'writes'            =>  1,
-        },
-
-    # comment out or uncomment as needed
-    # these will run in sequence at the end, after a git operation has ended
-    POST_GIT                    =>
-        [
-            # if you use this, make this the last item in the list
-            # 'cpu-time',
-        ],
 );
 
 # ------------------------------------------------------------------------------
diff --git a/src/commands/renice b/src/commands/renice
new file mode 100755
index 0000000..ba0b726
--- /dev/null
+++ b/src/commands/renice
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+n=$1
+[ "$n" = "PRE_GIT" ] && n=10
+renice -n $n $GL_TID >/dev/null

commit cb9794d55b70399f16bc1e84df74639afad239cd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 09:57:02 2012 +0530

    warn about test suite clobbering lots of stuff
    
    (not just ~/.ssh!)

diff --git a/doc/testing.mkd b/doc/testing.mkd
index feb2ad7..a973851 100644
--- a/doc/testing.mkd
+++ b/doc/testing.mkd
@@ -1,17 +1,13 @@
 # testing gitolite
 
-Here's how to *run* the tests:
+Here's how to *run* the tests.  **WARNING: they will clobber lots of things in
+your `$HOME`, so be sure to use a throwaway userid**.
 
     git clone git://github.com/sitaramc/gitolite
     cd gitolite
     git checkout -f g3
-
-    # if you're not ok with your ~/.ssh getting clobbered
     prove
 
-    # if you're ok with your ~/.ssh getting clobbered
-    # prove t/*.t t/ssh*
-
 Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
 Take a look at some of the scripts and you will see what it looks like.  It
 has a few quirks and nuances; if you really care, email me.
diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 11d1c9d..5f7fa6c 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -51,7 +51,7 @@ try "
     # clean install
     mkdir -p $ENV{HOME}/bin
     ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin
-    cd; rm -vrf .gito* gito* repositories
+    cd; rm -vrf .gito* repositories
     git config --global user.name \"gitolite tester\"
     git config --global user.email \"tester\@example.com\"
 
diff --git a/t/ssh-authkeys b/t/ssh-authkeys.t
similarity index 100%
rename from t/ssh-authkeys
rename to t/ssh-authkeys.t
diff --git a/t/ssh-basic b/t/ssh-basic.t
similarity index 100%
rename from t/ssh-basic
rename to t/ssh-basic.t

commit 8de959476f3344426174d3d70df09590ce39b736
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 11:06:28 2012 +0530

    fixup: new check-g2-compat, lots of migration related changes

diff --git a/check-g2-compat b/check-g2-compat
index 48472c1..8fd1e3b 100755
--- a/check-g2-compat
+++ b/check-g2-compat
@@ -85,3 +85,12 @@ sub repo {
         msg( WARNING => "found " . scalar(@perms) . " gl-perms files with R or RW; see docs" );
     }
 }
+
+sub msg {
+    my ( $type, $text ) = @_;
+    print "$type" if $type;
+    print "\t$text\n";
+    exit 1 if $type eq 'FATAL';
+
+    $count{$type}++ if $type;
+}

commit 3ed923f503f7cd16289779f31005f8a60e9b276e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 09:15:39 2012 +0530

    new check-g2-compat, lots of migration related changes
    
      - rc differences moved to their own file
      - main g2migr now helps interpret output of check-g2-compat
      - Gitolite::Compat gone; no point...

diff --git a/check-g2-compat b/check-g2-compat
new file mode 100755
index 0000000..48472c1
--- /dev/null
+++ b/check-g2-compat
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+
+use Cwd;
+
+my $h  = $ENV{HOME};
+my $rc = "$h/.gitolite.rc";
+my %count;
+
+intro();
+
+msg( FATAL => "no rc file found; do you even *have* g2 running?" ) if not -f $rc;
+do $rc;
+unless ( $return = do $rc ) {
+    msg( FATAL => "couldn't parse $rc: $@" ) if $@;
+    msg( FATAL   => "couldn't do $rc: $!" ) unless defined $return;
+    msg( WARNING => "couldn't run $rc" )    unless $return;
+}
+
+print "checking rc file...\n";
+rc_basic();
+rest_of_rc();
+print "\n";
+
+print "checking conf file(s)...\n";
+conf();
+print "\n";
+
+print "checking repos...\n";
+repo();
+print "\n";
+
+# ----------------------------------------------------------------------
+
+sub intro {
+    msg( INFO => "This program only checks for uses that make the new g3 completely unusable" );
+    msg( ''   => "or that might end up giving *more* access to someone if migrated as-is." );
+    msg( ''   => "It does NOT attempt to catch all the differences described in the docs." );
+    msg( '', '' );
+    msg( INFO => "'see docs' usually means doc/g2migr.mkd");
+    msg( '',  => "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)" );
+    msg( '', '' );
+}
+
+sub rc_basic {
+    msg( FATAL => "GL_ADMINDIR in the wrong place -- aborting; see docs" ) if $GL_ADMINDIR ne "$h/.gitolite";
+    msg( NOTE => "GL_ADMINDIR is in the right place; assuming you did not mess with" );
+    msg( '', "GL_CONF, GL_LOGT, GL_KEYDIR, and GL_CONF_COMPILED" );
+    msg( FATAL => "REPO_BASE in the wrong place -- aborting; see docs" ) if $REPO_BASE ne "$h/repositories" and $REPO_BASE ne "repositories";
+# ( abs or rel both ok)
+}
+
+sub rest_of_rc {
+    msg( SEVERE  => "GIT_PATH found; see docs" )                                              if $GIT_PATH;
+    msg( SEVERE  => "GL_ALL_INCLUDES_SPECIAL found; see docs" )                               if $GL_ALL_INCLUDES_SPECIAL;
+    msg( SEVERE  => "GL_GET_MEMBERSHIPS_PGM not yet implemented" )                            if $GL_GET_MEMBERSHIPS_PGM;
+    msg( SEVERE  => "GL_NO_CREATE_REPOS not yet implemented" )                                if $GL_NO_CREATE_REPOS;
+    msg( SEVERE  => 'htpasswd, rsync, and svnserve not yet implemented' )                     if $HTPASSWD_FILE or $RSYNC_BASE or $SVNSERVE;
+    msg( WARNING => "ADMIN_POST_UPDATE_CHAINS_TO found; see docs" )                           if $ADMIN_POST_UPDATE_CHAINS_TO;
+    msg( WARNING => "GL_NO_DAEMON_NO_GITWEB found; see docs" )                                if $GL_NO_DAEMON_NO_GITWEB;
+    msg( WARNING => "GL_NO_SETUP_AUTHKEYS found; see docs" )                                  if $GL_NO_SETUP_AUTHKEYS;
+    msg( WARNING => "UPDATE_CHAINS_TO found; see docs" )                                      if $UPDATE_CHAINS_TO;
+    msg( WARNING => "GL_ADC_PATH found; many ADCs not yet implemented")                       if $GL_ADC_PATH;
+    msg( WARNING => "non-default GL_WILDREPOS_PERM_CATS found" ) if $GL_WILDREPOS_PERM_CATS ne 'READERS WRITERS';
+}
+
+sub conf {
+    chdir($h);
+    chdir($GL_ADMINDIR);
+
+    my $conf = `find . -name "*.conf" | xargs cat`;
+    msg( "SEVERE",  "fallthru in NAME rules; see docs" )        if $conf =~ m(NAME/);
+    msg( "SEVERE", "subconf command in admin repo; see docs" ) if $conf =~ m(NAME/conf/fragments);
+}
+
+sub repo {
+    chdir($h);
+    chdir($REPO_BASE);
+    my @creater = `find . -name gl-creater`;
+    if (@creater) {
+        msg( WARNING => "found " . scalar(@creater) . " gl-creater files; see docs" );
+    }
+
+    my @perms = `find . -name gl-perms | xargs egrep -l -w R\\|RW`;
+    if (@perms) {
+        msg( WARNING => "found " . scalar(@perms) . " gl-perms files with R or RW; see docs" );
+    }
+}
diff --git a/doc/g2dropped.mkd b/doc/g2dropped.mkd
index ab5f63c..429cdce 100644
--- a/doc/g2dropped.mkd
+++ b/doc/g2dropped.mkd
@@ -1,2 +1,4 @@
 ## #g2dropped g2 features dropped
 
+(none yet that are not already covered in the rc section in [this][g2migr]
+page).
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
index c425ec2..2ae1750 100644
--- a/doc/g2incompat.mkd
+++ b/doc/g2incompat.mkd
@@ -1,4 +1,6 @@
-## #g2incompat compatibility with g2
+## #g2incompat incompatibility with g2
+
+(other than in the rc file, which is dealt with [elsewhere][g2rcdiff])
 
 The following incompatibilities exist, in vaguely decreasing order of
 severity.  **The ones in the first section are IMPORTANT because they allow
@@ -8,8 +10,8 @@ the new gitolite!**
 ### fallthru in NAME rules
 
 Fallthru on all VREFs is "success" now, so any NAME/ rules you have **MUST**
-change the ruleset in some way.  The simplest is to add the following line to
-the end of each repo's rule list:
+change the ruleset in some way to maintain the same restrictions.  The
+simplest is to add the following line to the end of each repo's rule list:
 
         -   NAME/       =   @all
 
@@ -33,3 +35,7 @@ line.  As the [vref documentation][vref] says:
 
 >   **Virtual refs are best used as additional "deny" rules**, performing
 >   extra checks that core gitolite cannot.
+
+The second part explicitly says when and where to include the subconf files.
+(Before subconf was invented, this used to happen implicitly at the end of the
+main conf file, and was hardcoded to that specific glob.)
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
index 21cc4b2..81ce6c9 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2migr.mkd
@@ -10,113 +10,98 @@ move to g3.**
 First things first: g2 will be supported for a good long time.  My current
 *expert* users do not cause me any load anyway.
 
-Migration should be straightforward, but it is not automatic.  When you first
-run "gitolite setup [...]", gitolite3 will try to detect at least the big
-problems.  However, converting the RC file and the conf files is (as of now)
-still a manual exercise, though not very difficult.
+Migration should be straightforward, but it is not automatic.  You should run
+the "check-g2-compat" program first, to see any *major* differences that
+affect you.  The bulk of the changes are in the RC file, which must be
+manually handled (links below).  The conf files have very few changes -- they
+apply only if you use "NAME/" or delegation.
 
 You must first read about [incompatible][g2incompat] features and
 [dropped][g2dropped] features.  Some features have been replaced with
 [alternatives][g2alt].
 
-Since the majority of changes are in the rc file, we list them all here.
+Since the majority of changes are in the rc file, they're all listed
+[here][g2rcdiff].
 
-### rc file differences
+The rest of this page describes the completely standalone "check-g2-compat"
+script that you can find in the repo root (i.e., not in "src/").
 
-**DROPPED** variables (possible high impact): these could be show-stoppers for
-migration, at least for now.
+### the "check-g2-compat" program
 
-  * `BIG_INFO_CAP` -- if you think you must have this, try it without and see
-    if there's a difference.  If you *know* you need this, convince me.
+This program checks a few things only, not everything.  In particular, it
+looks for settings and status that might:
 
-  * `GL_ALL_READ_ALL` -- same
+  * make g3 unusable for lots of users
+  * make g3 give *more* access than g2 under some conditions.
 
-  * `GL_NO_CREATE_REPOS` -- if you think you need this, email me.  I know one
-    group who does need this so I will be putting it in eventually but not
-    right away.
+It does NOT look for or warn about anything else; you're expected to read (and
+act upon, if needed) the rest of the migration guide links given a few paras
+above to cover everything else.
 
-  * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
-    are using any of these.
+Here's an explanation of those messages that the check-g2-compat program may
+put that contain the words "see docs":
 
-  * `GL_GET_MEMBERSHIPS_PGM` -- is on my todo list
+  * `GL_ADMINDIR in the wrong place -- aborting`
 
-  * `GL_LOGT` -- is now fixed; you can't change it.  Email me if this is a
-    problem.
+    It expects to find `GL_ADMINDIR` and `REPO_BASE` pointing to the right
+    places.  It aborts if these conditions are not met and does not scan
+    further since that sort of guesswork is not good.  If you are in that
+    position, make a symlink from the real location to the expected location,
+    change the RC accordingly, and re-try.
 
-**DROPPED** variables (medium impact): these have alternative implementations
-or mechanisms, but you have to do some setup work.
+  * `REPO_BASE in the wrong place -- aborting`
 
-  * `GL_ADMINDIR` -- this is now at a fixed location: `~/.gitolite`.  If you
-    want it somewhere else go ahead and move it, then place a symlink from the
-    assumed location to the real one.
+    same as above
 
-  * `REPO_BASE` -- this is now at a fixed location: `~/repositories`.  If you
-    want it somewhere else go ahead and move it, then place a symlink from the
-    assumed location to the real one.
+  * `fallthru in NAME rules`
 
-  * `PROJECTS_LIST` -- it's called `GITWEB_PROJECTS_LIST` now, but more
-    importantly, it is only used by update-gitweb-access-list in
-    src/commands/post-compile.  This variable now has nothing to do with
-    gitolite core, and the rc is just helping to store settings for external
-    programs like that one.
+    **This is a significant difference and affects access badly (gives access
+    that would otherwise not be given)**.  Please see the [list of non-RC
+    incompatibilities][g2incompat].
 
-    `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` are also gone; patches to the
-    update program to directly do those things are welcome.
+  * `subconf command in admin repo`
 
-  * `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
-    file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
+    This is not so bad security wise but it might *reduce* access by not
+    processing files you intended to.  Again, see the same link as in the
+    previous bullet.
 
-  * `NICE_VALUE` -- use the `PRE_GIT` trigger to attach a program that renices
-    the pid given by $GL_TID (that's the pid of the initial gitolite entry
-    point, usually gitolite-shell, and propagates from there once set).
+  * `found N gl-creater files`
 
-    You may have to add this list to the rc file; if you don't know perl use
-    one of the others as a model or ask me.
+    These need to be renamed to `gl-creator` (the correct spelling at last,
+    hooray!).  Suggested command sequence:
 
-  * `GIT_PATH` -- gone, not needed.  Just add these lines to the end of the rc
-    file:
+        cd $HOME/repositories
+        find . -type d -name "*.git" -prune | while read r
+        do
+            mv \$r/gl-creater \$r/gl-creator
+        done 2>/dev/null
 
-        $ENV{PATH}="...whatever you want...";
-        1;
+    Once you do this, the g2 will not work completely unless you change them
+    back.
 
-  * `GL_NO_SETUP_AUTHKEYS` -- comment out the lines that call ssh-authkeys, in
-    the rc file.
+  * `found N gl-perms files with R or RW`
 
-  * `GL_WILDREPOS_DEFPERMS` -- if you need this, add a `POST_CREATE` script
-    that does it.  Or email me and I will write it for you.
+    Setting perms of R and RW will no longer work; you have to say READERS and
+    WRITERS now.  Suggested command:
 
-  * `UPDATE_CHAINS_TO` -- use a [vref][] instead.  You can directly use the
-    chained-to script as a VREF; it'll work.
+The following variables need to be [preset][rc-preset] in the rc file
+**before** running `gitolite setup`.  Otherwise the default actions will
+clobber something and require some recovery.
 
-  * `ADMIN_POST_UPDATE_CHAINS_TO` -- add your script to the `POST_COMPILE`
-    trigger chain.  You won't be getting any arguments but for the admin repo
-    the only argument that ever comes in is "refs/heads/master" anyway.
+  * `GL_NO_SETUP_AUTHKEYS` (default will clobber your authkeys file)
 
-  * `GL_ADC_PATH` -- obsolete; use [commands][] or add [your own][dev-notes].
+  * `GL_NO_DAEMON_NO_GITWEB` (default will clobber your projects.list file and
+    git-daemon-export-ok files)
 
-  * `GL_ALL_INCLUDES_SPECIAL` -- obsolete; @all always includes gitweb and
-    daemon now.  Use [deny-rules][] if you want to say `R = @all` but not have
-    it be visible to gitweb or daemon.
+  * `UPDATE_CHAINS_TO` (default will fail to run this extra check when users
+    push)
 
-  * `GL_PERFLOGT` -- see the entry for "gl-time" in the [alternative
-    implementations][g2alt] page.
+  * `ADMIN_POST_UPDATE_CHAINS_TO` (severity depends on what your code is
+    doing; see [g2rcdiff][] for how to fix this)
 
-**DROPPED** variables (no impact/low impact): these variables should not
-actually affect anything anyway, so even if you had them set you should not
-feel their loss.
+  * `GL_ALL_INCLUDES_SPECIAL` (default will allow gitweb and daemon to be able
+    to read any repos that have `R = @all`)
 
-  * `GL_CONF`, `GL_KEYDIR`, and `GL_CONF_COMPILED` -- you had no business
-    touching these anyway; if you did, move them into the expected default
-    locations before attempting to run `gitolite setup`
-  * `GL_PACKAGE_HOOKS` -- not needed anymore, but check if you had any custom
-    hooks set there and copy them across.
-  * `GL_WILDREPOS` -- dropped; this feature is default now.
-  * `GL_BIG_CONFIG` -- dropped; this feature is default now.
-
-**RENAMED** variables (no impact): these are functionally the same but are
-renamed.
-
-  * `REPO_UMASK` is now `UMASK`
-  * `GL_GITCONFIG_KEYS` is now `GITCONFIG_KEYS`
-  * `GL_WILDREPOS_PERM_CATS` is now the ROLES hash in the rc file
-  * `GL_SITE_INFO` is not `SITE_INFO`
+  * `GIT_PATH` (presumably your git is in some non-std path so unless you
+    preset `$ENV{PATH}` per instructions in the [rc file
+    differences][g2rcdiff] doc, nothing will work).
diff --git a/doc/g2migr.mkd b/doc/g2rcdiff.mkd
similarity index 80%
copy from doc/g2migr.mkd
copy to doc/g2rcdiff.mkd
index 21cc4b2..81af122 100644
--- a/doc/g2migr.mkd
+++ b/doc/g2rcdiff.mkd
@@ -1,25 +1,21 @@
-## #g2migr migrating from g2
+## #g2rcdiff rc file differences between g2 and g3
 
-<font color="red">
+The new rc file has far fewer variables; many have been dropped.  You should
+not see much ill effect though, but please read below.
 
-**This document is a *MUST* read if you are currently using g2 and want to
-move to g3.**
+### #rc-preset pre-setting the rc file
 
-</font>
+Some of these settings are such that you cannot directly run `gitolite setup`
+when you're ready to migrate.  Instead, you need to run
 
-First things first: g2 will be supported for a good long time.  My current
-*expert* users do not cause me any load anyway.
+    # (assuming you saved your g2 rc file somewhere)
+    gitolite print-default-rc > $HOME/.gitolite.rc
+    $EDITOR $HOME/.gitolite.rc
+    # make appropriate changes, save
+    gitolite setup
 
-Migration should be straightforward, but it is not automatic.  When you first
-run "gitolite setup [...]", gitolite3 will try to detect at least the big
-problems.  However, converting the RC file and the conf files is (as of now)
-still a manual exercise, though not very difficult.
-
-You must first read about [incompatible][g2incompat] features and
-[dropped][g2dropped] features.  Some features have been replaced with
-[alternatives][g2alt].
-
-Since the majority of changes are in the rc file, we list them all here.
+One example of this is `GL_NO_SETUP_AUTHKEYS`.  If you don't jump in and fix
+the rc first, the first run will clobber your authkeys file.
 
 ### rc file differences
 
@@ -38,7 +34,7 @@ migration, at least for now.
   * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
     are using any of these.
 
-  * `GL_GET_MEMBERSHIPS_PGM` -- is on my todo list
+  * `GL_GET_MEMBERSHIPS_PGM` -- is high on my todo list
 
   * `GL_LOGT` -- is now fixed; you can't change it.  Email me if this is a
     problem.
@@ -61,7 +57,9 @@ or mechanisms, but you have to do some setup work.
     programs like that one.
 
     `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` are also gone; patches to the
-    update program to directly do those things are welcome.
+    update program to directly do those things are welcome.  Personally, I
+    think people who use spaces and other funky characters in dir/file names
+    should be shot but luckily no one listens to me :-)
 
   * `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
     file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
@@ -119,4 +117,4 @@ renamed.
   * `REPO_UMASK` is now `UMASK`
   * `GL_GITCONFIG_KEYS` is now `GITCONFIG_KEYS`
   * `GL_WILDREPOS_PERM_CATS` is now the ROLES hash in the rc file
-  * `GL_SITE_INFO` is not `SITE_INFO`
+  * `GL_SITE_INFO` is now `SITE_INFO`
diff --git a/doc/list b/doc/list
index b9f7411..a844832 100644
--- a/doc/list
+++ b/doc/list
@@ -5,6 +5,7 @@ why.mkd
 g3why.mkd
 dev-status.mkd
 g2migr.mkd
+g2rcdiff.mkd
 g2incompat.mkd
 g2dropped.mkd
 g2alt.mkd
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 4d9299c..c4c3bcd 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -59,8 +59,8 @@ my $rc = glrc('filename');
 do $rc if -r $rc;
 if ( defined($GL_ADMINDIR) ) {
     say2 "";
-    say2 "FATAL: $rc seems to be for older gitolite; checking compat";
-    require Gitolite::Compat;
+    say2 "FATAL: $rc seems to be for older gitolite; please see doc/g2migr.mkd\n" .
+    "(online at http://sitaramc.github.com/gitolite/g3/g2migr.html)";
 
     exit 1;
 }
@@ -250,7 +250,9 @@ __DATA__
     # used by the info command
     # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
 
-    # add more roles (like MANAGER, TESTER, ...) here
+    # add more roles (like MANAGER, TESTER, ...) here.
+    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
+    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
     ROLES                       =>
         {
             READERS             =>  1,

commit c14e01d6c01a18c5ac59f32a19b55efdbe914cd9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 09:19:10 2012 +0530

    new 'gitolite print-default-rc' command

diff --git a/src/commands/print-default-rc b/src/commands/print-default-rc
new file mode 100755
index 0000000..d877462
--- /dev/null
+++ b/src/commands/print-default-rc
@@ -0,0 +1,8 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+
+print glrc('default-text');

commit 863a732080d0943e066efaf7697cdc5589095550
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 25 05:09:58 2012 +0530

    minor fixup to mkdoc; also pulled in Markdown.pl

diff --git a/doc/Markdown.pl b/doc/Markdown.pl
new file mode 100755
index 0000000..47d82e8
--- /dev/null
+++ b/doc/Markdown.pl
@@ -0,0 +1,1450 @@
+#!/usr/bin/perl
+
+#
+# Markdown -- A text-to-HTML conversion tool for web writers
+#
+# Copyright (c) 2004 John Gruber
+# <http://daringfireball.net/projects/markdown/>
+#
+
+
+package Markdown;
+require 5.006_000;
+use strict;
+use warnings;
+
+use Digest::MD5 qw(md5_hex);
+use vars qw($VERSION);
+$VERSION = '1.0.1';
+# Tue 14 Dec 2004
+
+## Disabled; causes problems under Perl 5.6.1:
+# use utf8;
+# binmode( STDOUT, ":utf8" );  # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html
+
+
+#
+# Global default settings:
+#
+my $g_empty_element_suffix = " />";     # Change to ">" for HTML output
+my $g_tab_width = 4;
+
+
+#
+# Globals:
+#
+
+# Regex to match balanced [brackets]. See Friedl's
+# "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
+my $g_nested_brackets;
+$g_nested_brackets = qr{
+	(?> 								# Atomic matching
+	   [^\[\]]+							# Anything other than brackets
+	 | 
+	   \[
+		 (??{ $g_nested_brackets })		# Recursive set of nested brackets
+	   \]
+	)*
+}x;
+
+
+# Table of hash values for escaped characters:
+my %g_escape_table;
+foreach my $char (split //, '\\`*_{}[]()>#+-.!') {
+	$g_escape_table{$char} = md5_hex($char);
+}
+
+
+# Global hashes, used by various utility routines
+my %g_urls;
+my %g_titles;
+my %g_html_blocks;
+
+# Used to track when we're inside an ordered or unordered list
+# (see _ProcessListItems() for details):
+my $g_list_level = 0;
+
+
+#### Blosxom plug-in interface ##########################################
+
+# Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine
+# which posts Markdown should process, using a "meta-markup: markdown"
+# header. If it's set to 0 (the default), Markdown will process all
+# entries.
+my $g_blosxom_use_meta = 0;
+
+sub start { 1; }
+sub story {
+	my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
+
+	if ( (! $g_blosxom_use_meta) or
+	     (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i))
+	     ){
+			$$body_ref  = Markdown($$body_ref);
+     }
+     1;
+}
+
+
+#### Movable Type plug-in interface #####################################
+eval {require MT};  # Test to see if we're running in MT.
+unless ($@) {
+    require MT;
+    import  MT;
+    require MT::Template::Context;
+    import  MT::Template::Context;
+
+	eval {require MT::Plugin};  # Test to see if we're running >= MT 3.0.
+	unless ($@) {
+		require MT::Plugin;
+		import  MT::Plugin;
+		my $plugin = new MT::Plugin({
+			name => "Markdown",
+			description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)",
+			doc_link => 'http://daringfireball.net/projects/markdown/'
+		});
+		MT->add_plugin( $plugin );
+	}
+
+	MT::Template::Context->add_container_tag(MarkdownOptions => sub {
+		my $ctx	 = shift;
+		my $args = shift;
+		my $builder = $ctx->stash('builder');
+		my $tokens = $ctx->stash('tokens');
+
+		if (defined ($args->{'output'}) ) {
+			$ctx->stash('markdown_output', lc $args->{'output'});
+		}
+
+		defined (my $str = $builder->build($ctx, $tokens) )
+			or return $ctx->error($builder->errstr);
+		$str;		# return value
+	});
+
+	MT->add_text_filter('markdown' => {
+		label     => 'Markdown',
+		docs      => 'http://daringfireball.net/projects/markdown/',
+		on_format => sub {
+			my $text = shift;
+			my $ctx  = shift;
+			my $raw  = 0;
+		    if (defined $ctx) {
+		    	my $output = $ctx->stash('markdown_output'); 
+				if (defined $output  &&  $output =~ m/^html/i) {
+					$g_empty_element_suffix = ">";
+					$ctx->stash('markdown_output', '');
+				}
+				elsif (defined $output  &&  $output eq 'raw') {
+					$raw = 1;
+					$ctx->stash('markdown_output', '');
+				}
+				else {
+					$raw = 0;
+					$g_empty_element_suffix = " />";
+				}
+			}
+			$text = $raw ? $text : Markdown($text);
+			$text;
+		},
+	});
+
+	# If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter:
+	my $smartypants;
+
+	{
+		no warnings "once";
+		$smartypants = $MT::Template::Context::Global_filters{'smarty_pants'};
+	}
+
+	if ($smartypants) {
+		MT->add_text_filter('markdown_with_smartypants' => {
+			label     => 'Markdown With SmartyPants',
+			docs      => 'http://daringfireball.net/projects/markdown/',
+			on_format => sub {
+				my $text = shift;
+				my $ctx  = shift;
+				if (defined $ctx) {
+					my $output = $ctx->stash('markdown_output'); 
+					if (defined $output  &&  $output eq 'html') {
+						$g_empty_element_suffix = ">";
+					}
+					else {
+						$g_empty_element_suffix = " />";
+					}
+				}
+				$text = Markdown($text);
+				$text = $smartypants->($text, '1');
+			},
+		});
+	}
+}
+else {
+#### BBEdit/command-line text filter interface ##########################
+# Needs to be hidden from MT (and Blosxom when running in static mode).
+
+    # We're only using $blosxom::version once; tell Perl not to warn us:
+	no warnings 'once';
+    unless ( defined($blosxom::version) ) {
+		use warnings;
+
+		#### Check for command-line switches: #################
+		my %cli_opts;
+		use Getopt::Long;
+		Getopt::Long::Configure('pass_through');
+		GetOptions(\%cli_opts,
+			'version',
+			'shortversion',
+			'html4tags',
+		);
+		if ($cli_opts{'version'}) {		# Version info
+			print "\nThis is Markdown, version $VERSION.\n";
+			print "Copyright 2004 John Gruber\n";
+			print "http://daringfireball.net/projects/markdown/\n\n";
+			exit 0;
+		}
+		if ($cli_opts{'shortversion'}) {		# Just the version number string.
+			print $VERSION;
+			exit 0;
+		}
+		if ($cli_opts{'html4tags'}) {			# Use HTML tag style instead of XHTML
+			$g_empty_element_suffix = ">";
+		}
+
+
+		#### Process incoming text: ###########################
+		my $text;
+		{
+			local $/;               # Slurp the whole file
+			$text = <>;
+		}
+        print Markdown($text);
+    }
+}
+
+
+
+sub Markdown {
+#
+# Main function. The order in which other subs are called here is
+# essential. Link and image substitutions need to happen before
+# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
+# and <img> tags get encoded.
+#
+	my $text = shift;
+
+	# Clear the global hashes. If we don't clear these, you get conflicts
+	# from other articles when generating a page which contains more than
+	# one article (e.g. an index page that shows the N most recent
+	# articles):
+	%g_urls = ();
+	%g_titles = ();
+	%g_html_blocks = ();
+
+
+	# Standardize line endings:
+	$text =~ s{\r\n}{\n}g; 	# DOS to Unix
+	$text =~ s{\r}{\n}g; 	# Mac to Unix
+
+	# Make sure $text ends with a couple of newlines:
+	$text .= "\n\n";
+
+	# Convert all tabs to spaces.
+	$text = _Detab($text);
+
+	# Strip any lines consisting only of spaces and tabs.
+	# This makes subsequent regexen easier to write, because we can
+	# match consecutive blank lines with /\n+/ instead of something
+	# contorted like /[ \t]*\n+/ .
+	$text =~ s/^[ \t]+$//mg;
+
+	# Turn block-level HTML blocks into hash entries
+	$text = _HashHTMLBlocks($text);
+
+	# Strip link definitions, store in hashes.
+	$text = _StripLinkDefinitions($text);
+
+	$text = _RunBlockGamut($text);
+
+	$text = _UnescapeSpecialChars($text);
+
+	return $text . "\n";
+}
+
+
+sub _StripLinkDefinitions {
+#
+# Strips link definitions from text, stores the URLs and titles in
+# hash references.
+#
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Link defs are in the form: ^[id]: url "optional title"
+	while ($text =~ s{
+						^[ ]{0,$less_than_tab}\[(.+)\]:	# id = $1
+						  [ \t]*
+						  \n?				# maybe *one* newline
+						  [ \t]*
+						<?(\S+?)>?			# url = $2
+						  [ \t]*
+						  \n?				# maybe one newline
+						  [ \t]*
+						(?:
+							(?<=\s)			# lookbehind for whitespace
+							["(]
+							(.+?)			# title = $3
+							[")]
+							[ \t]*
+						)?	# title is optional
+						(?:\n+|\Z)
+					}
+					{}mx) {
+		$g_urls{lc $1} = _EncodeAmpsAndAngles( $2 );	# Link IDs are case-insensitive
+		if ($3) {
+			$g_titles{lc $1} = $3;
+			$g_titles{lc $1} =~ s/"/"/g;
+		}
+	}
+
+	return $text;
+}
+
+
+sub _HashHTMLBlocks {
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Hashify HTML blocks:
+	# We only want to do this for block-level HTML tags, such as headers,
+	# lists, and tables. That's because we still want to wrap <p>s around
+	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+	# phrase emphasis, and spans. The list of tags we're looking for is
+	# hard-coded:
+	my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/;
+	my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/;
+
+	# First, look for nested blocks, e.g.:
+	# 	<div>
+	# 		<div>
+	# 		tags for inner block must be indented.
+	# 		</div>
+	# 	</div>
+	#
+	# The outermost tags must start at the left margin for this to match, and
+	# the inner nested divs must be indented.
+	# We need to do this before the next, more liberal match, because the next
+	# match will start at the first `<div>` and stop at the first `</div>`.
+	$text =~ s{
+				(						# save in $1
+					^					# start of line  (with /m)
+					<($block_tags_a)	# start tag = $2
+					\b					# word break
+					(.*\n)*?			# any number of lines, minimally matching
+					</\2>				# the matching end tag
+					[ \t]*				# trailing spaces/tabs
+					(?=\n+|\Z)	# followed by a newline or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egmx;
+
+
+	#
+	# Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+	#
+	$text =~ s{
+				(						# save in $1
+					^					# start of line  (with /m)
+					<($block_tags_b)	# start tag = $2
+					\b					# word break
+					(.*\n)*?			# any number of lines, minimally matching
+					.*</\2>				# the matching end tag
+					[ \t]*				# trailing spaces/tabs
+					(?=\n+|\Z)	# followed by a newline or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egmx;
+	# Special case just for <hr />. It was easier to make a special case than
+	# to make the other regex more complicated.	
+	$text =~ s{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,$less_than_tab}
+					<(hr)				# start tag = $2
+					\b					# word break
+					([^<>])*?			# 
+					/?>					# the matching end tag/
+					[ \t]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egx;
+
+	# Special case for standalone HTML comments:
+	$text =~ s{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,$less_than_tab}
+					(?s:
+						<!
+						(--.*?--\s*)+
+						>
+					)
+					[ \t]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}{
+				my $key = md5_hex($1);
+				$g_html_blocks{$key} = $1;
+				"\n\n" . $key . "\n\n";
+			}egx;
+
+
+	return $text;
+}
+
+
+sub _RunBlockGamut {
+#
+# These are all the transformations that form block-level
+# tags like paragraphs, headers, and list items.
+#
+	my $text = shift;
+
+	$text = _DoHeaders($text);
+
+	# Do Horizontal Rules:
+	$text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+	$text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+	$text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx;
+
+	$text = _DoLists($text);
+
+	$text = _DoCodeBlocks($text);
+
+	$text = _DoBlockQuotes($text);
+
+	# We already ran _HashHTMLBlocks() before, in Markdown(), but that
+	# was to escape raw HTML in the original Markdown source. This time,
+	# we're escaping the markup we've just created, so that we don't wrap
+	# <p> tags around block-level tags.
+	$text = _HashHTMLBlocks($text);
+
+	$text = _FormParagraphs($text);
+
+	return $text;
+}
+
+
+sub _RunSpanGamut {
+#
+# These are all the transformations that occur *within* block-level
+# tags like paragraphs, headers, and list items.
+#
+	my $text = shift;
+
+	$text = _DoCodeSpans($text);
+
+	$text = _EscapeSpecialChars($text);
+
+	# Process anchor and image tags. Images must come first,
+	# because ![foo][f] looks like an anchor.
+	$text = _DoImages($text);
+	$text = _DoAnchors($text);
+
+	# Make links out of things like `<http://example.com/>`
+	# Must come after _DoAnchors(), because you can use < and >
+	# delimiters in inline links like [this](<url>).
+	$text = _DoAutoLinks($text);
+
+	$text = _EncodeAmpsAndAngles($text);
+
+	$text = _DoItalicsAndBold($text);
+
+	# Do hard breaks:
+	$text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g;
+
+	return $text;
+}
+
+
+sub _EscapeSpecialChars {
+	my $text = shift;
+	my $tokens ||= _TokenizeHTML($text);
+
+	$text = '';   # rebuild $text from the tokens
+# 	my $in_pre = 0;	 # Keep track of when we're inside <pre> or <code> tags.
+# 	my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!;
+
+	foreach my $cur_token (@$tokens) {
+		if ($cur_token->[0] eq "tag") {
+			# Within tags, encode * and _ so they don't conflict
+			# with their use in Markdown for italics and strong.
+			# We're replacing each such character with its
+			# corresponding MD5 checksum value; this is likely
+			# overkill, but it should prevent us from colliding
+			# with the escape values by accident.
+			$cur_token->[1] =~  s! \* !$g_escape_table{'*'}!gx;
+			$cur_token->[1] =~  s! _  !$g_escape_table{'_'}!gx;
+			$text .= $cur_token->[1];
+		} else {
+			my $t = $cur_token->[1];
+			$t = _EncodeBackslashEscapes($t);
+			$text .= $t;
+		}
+	}
+	return $text;
+}
+
+
+sub _DoAnchors {
+#
+# Turn Markdown link shortcuts into XHTML <a> tags.
+#
+	my $text = shift;
+
+	#
+	# First, handle reference-style links: [link text] [id]
+	#
+	$text =~ s{
+		(					# wrap whole match in $1
+		  \[
+		    ($g_nested_brackets)	# link text = $2
+		  \]
+
+		  [ ]?				# one optional space
+		  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+		  \[
+		    (.*?)		# id = $3
+		  \]
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $link_text   = $2;
+		my $link_id     = lc $3;
+
+		if ($link_id eq "") {
+			$link_id = lc $link_text;     # for shortcut links like [this][].
+		}
+
+		if (defined $g_urls{$link_id}) {
+			my $url = $g_urls{$link_id};
+			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+			$result = "<a href=\"$url\"";
+			if ( defined $g_titles{$link_id} ) {
+				my $title = $g_titles{$link_id};
+				$title =~ s! \* !$g_escape_table{'*'}!gx;
+				$title =~ s!  _ !$g_escape_table{'_'}!gx;
+				$result .=  " title=\"$title\"";
+			}
+			$result .= ">$link_text</a>";
+		}
+		else {
+			$result = $whole_match;
+		}
+		$result;
+	}xsge;
+
+	#
+	# Next, inline-style links: [link text](url "optional title")
+	#
+	$text =~ s{
+		(				# wrap whole match in $1
+		  \[
+		    ($g_nested_brackets)	# link text = $2
+		  \]
+		  \(			# literal paren
+		  	[ \t]*
+			<?(.*?)>?	# href = $3
+		  	[ \t]*
+			(			# $4
+			  (['"])	# quote char = $5
+			  (.*?)		# Title = $6
+			  \5		# matching quote
+			)?			# title is optional
+		  \)
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $link_text   = $2;
+		my $url	  		= $3;
+		my $title		= $6;
+
+		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+		$result = "<a href=\"$url\"";
+
+		if (defined $title) {
+			$title =~ s/"/"/g;
+			$title =~ s! \* !$g_escape_table{'*'}!gx;
+			$title =~ s!  _ !$g_escape_table{'_'}!gx;
+			$result .=  " title=\"$title\"";
+		}
+
+		$result .= ">$link_text</a>";
+
+		$result;
+	}xsge;
+
+	return $text;
+}
+
+
+sub _DoImages {
+#
+# Turn Markdown image shortcuts into <img> tags.
+#
+	my $text = shift;
+
+	#
+	# First, handle reference-style labeled images: ![alt text][id]
+	#
+	$text =~ s{
+		(				# wrap whole match in $1
+		  !\[
+		    (.*?)		# alt text = $2
+		  \]
+
+		  [ ]?				# one optional space
+		  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+		  \[
+		    (.*?)		# id = $3
+		  \]
+
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $alt_text    = $2;
+		my $link_id     = lc $3;
+
+		if ($link_id eq "") {
+			$link_id = lc $alt_text;     # for shortcut links like ![this][].
+		}
+
+		$alt_text =~ s/"/"/g;
+		if (defined $g_urls{$link_id}) {
+			my $url = $g_urls{$link_id};
+			$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+			$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
+			if (defined $g_titles{$link_id}) {
+				my $title = $g_titles{$link_id};
+				$title =~ s! \* !$g_escape_table{'*'}!gx;
+				$title =~ s!  _ !$g_escape_table{'_'}!gx;
+				$result .=  " title=\"$title\"";
+			}
+			$result .= $g_empty_element_suffix;
+		}
+		else {
+			# If there's no such link ID, leave intact:
+			$result = $whole_match;
+		}
+
+		$result;
+	}xsge;
+
+	#
+	# Next, handle inline images:  ![alt text](url "optional title")
+	# Don't forget: encode * and _
+
+	$text =~ s{
+		(				# wrap whole match in $1
+		  !\[
+		    (.*?)		# alt text = $2
+		  \]
+		  \(			# literal paren
+		  	[ \t]*
+			<?(\S+?)>?	# src url = $3
+		  	[ \t]*
+			(			# $4
+			  (['"])	# quote char = $5
+			  (.*?)		# title = $6
+			  \5		# matching quote
+			  [ \t]*
+			)?			# title is optional
+		  \)
+		)
+	}{
+		my $result;
+		my $whole_match = $1;
+		my $alt_text    = $2;
+		my $url	  		= $3;
+		my $title		= '';
+		if (defined($6)) {
+			$title		= $6;
+		}
+
+		$alt_text =~ s/"/"/g;
+		$title    =~ s/"/"/g;
+		$url =~ s! \* !$g_escape_table{'*'}!gx;		# We've got to encode these to avoid
+		$url =~ s!  _ !$g_escape_table{'_'}!gx;		# conflicting with italics/bold.
+		$result = "<img src=\"$url\" alt=\"$alt_text\"";
+		if (defined $title) {
+			$title =~ s! \* !$g_escape_table{'*'}!gx;
+			$title =~ s!  _ !$g_escape_table{'_'}!gx;
+			$result .=  " title=\"$title\"";
+		}
+		$result .= $g_empty_element_suffix;
+
+		$result;
+	}xsge;
+
+	return $text;
+}
+
+
+sub _DoHeaders {
+	my $text = shift;
+
+	# Setext-style headers:
+	#	  Header 1
+	#	  ========
+	#  
+	#	  Header 2
+	#	  --------
+	#
+	$text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{
+		"<h1>"  .  _RunSpanGamut($1)  .  "</h1>\n\n";
+	}egmx;
+
+	$text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{
+		"<h2>"  .  _RunSpanGamut($1)  .  "</h2>\n\n";
+	}egmx;
+
+
+	# atx-style headers:
+	#	# Header 1
+	#	## Header 2
+	#	## Header 2 with closing hashes ##
+	#	...
+	#	###### Header 6
+	#
+	$text =~ s{
+			^(\#{1,6})	# $1 = string of #'s
+			[ \t]*
+			(.+?)		# $2 = Header text
+			[ \t]*
+			\#*			# optional closing #'s (not counted)
+			\n+
+		}{
+			my $h_level = length($1);
+			"<h$h_level>"  .  _RunSpanGamut($2)  .  "</h$h_level>\n\n";
+		}egmx;
+
+	return $text;
+}
+
+
+sub _DoLists {
+#
+# Form HTML ordered (numbered) and unordered (bulleted) lists.
+#
+	my $text = shift;
+	my $less_than_tab = $g_tab_width - 1;
+
+	# Re-usable patterns to match list item bullets and number markers:
+	my $marker_ul  = qr/[*+-]/;
+	my $marker_ol  = qr/\d+[.]/;
+	my $marker_any = qr/(?:$marker_ul|$marker_ol)/;
+
+	# Re-usable pattern to match any entirel ul or ol list:
+	my $whole_list = qr{
+		(								# $1 = whole list
+		  (								# $2
+			[ ]{0,$less_than_tab}
+			(${marker_any})				# $3 = first list item marker
+			[ \t]+
+		  )
+		  (?s:.+?)
+		  (								# $4
+			  \z
+			|
+			  \n{2,}
+			  (?=\S)
+			  (?!						# Negative lookahead for another list item marker
+				[ \t]*
+				${marker_any}[ \t]+
+			  )
+		  )
+		)
+	}mx;
+
+	# We use a different prefix before nested lists than top-level lists.
+	# See extended comment in _ProcessListItems().
+	#
+	# Note: There's a bit of duplication here. My original implementation
+	# created a scalar regex pattern as the conditional result of the test on
+	# $g_list_level, and then only ran the $text =~ s{...}{...}egmx
+	# substitution once, using the scalar as the pattern. This worked,
+	# everywhere except when running under MT on my hosting account at Pair
+	# Networks. There, this caused all rebuilds to be killed by the reaper (or
+	# perhaps they crashed, but that seems incredibly unlikely given that the
+	# same script on the same server ran fine *except* under MT. I've spent
+	# more time trying to figure out why this is happening than I'd like to
+	# admit. My only guess, backed up by the fact that this workaround works,
+	# is that Perl optimizes the substition when it can figure out that the
+	# pattern will never change, and when this optimization isn't on, we run
+	# afoul of the reaper. Thus, the slightly redundant code to that uses two
+	# static s/// patterns rather than one conditional pattern.
+
+	if ($g_list_level) {
+		$text =~ s{
+				^
+				$whole_list
+			}{
+				my $list = $1;
+				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
+				# Turn double returns into triple returns, so that we can make a
+				# paragraph for the last item in a list, if necessary:
+				$list =~ s/\n{2,}(?! {8,})/\n\n\n/g;
+				my $result = _ProcessListItems($list, $marker_any);
+				$result = "<$list_type>\n" . $result . "</$list_type>\n";
+				$result;
+			}egmx;
+	}
+	else {
+		$text =~ s{
+				(?:(?<=\n\n)|\A\n?)
+				$whole_list
+			}{
+				my $list = $1;
+				my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol";
+				# Turn double returns into triple returns, so that we can make a
+				# paragraph for the last item in a list, if necessary:
+				$list =~ s/\n{2,}(?! {8,})/\n\n\n/g;
+				my $result = _ProcessListItems($list, $marker_any);
+				$result = "<$list_type>\n" . $result . "</$list_type>\n";
+				$result;
+			}egmx;
+	}
+
+
+	return $text;
+}
+
+
+sub _ProcessListItems {
+#
+#	Process the contents of a single ordered or unordered list, splitting it
+#	into individual list items.
+#
+
+	my $list_str = shift;
+	my $marker_any = shift;
+
+
+	# The $g_list_level global keeps track of when we're inside a list.
+	# Each time we enter a list, we increment it; when we leave a list,
+	# we decrement. If it's zero, we're not in a list anymore.
+	#
+	# We do this because when we're not inside a list, we want to treat
+	# something like this:
+	#
+	#		I recommend upgrading to version
+	#		8. Oops, now this line is treated
+	#		as a sub-list.
+	#
+	# As a single paragraph, despite the fact that the second line starts
+	# with a digit-period-space sequence.
+	#
+	# Whereas when we're inside a list (or sub-list), that line will be
+	# treated as the start of a sub-list. What a kludge, huh? This is
+	# an aspect of Markdown's syntax that's hard to parse perfectly
+	# without resorting to mind-reading. Perhaps the solution is to
+	# change the syntax rules such that sub-lists must start with a
+	# starting cardinal number; e.g. "1." or "a.".
+
+	$g_list_level++;
+
+	# trim trailing blank lines:
+	$list_str =~ s/\n{2,}\z/\n/;
+
+
+	$list_str =~ s{
+		(\n)?							# leading line = $1
+		(^[ \t]*)						# leading whitespace = $2
+		($marker_any) [ \t]+			# list marker = $3
+		((?s:.+?)						# list item text   = $4
+		(\n{1,2}))
+		(?= \n* (\z | \2 ($marker_any) [ \t]+))
+	}{
+		my $item = $4;
+		my $leading_line = $1;
+		my $leading_space = $2;
+
+		if ($leading_line or ($item =~ m/\n{2,}/)) {
+			$item = _RunBlockGamut(_Outdent($item));
+		}
+		else {
+			# Recursion for sub-lists:
+			$item = _DoLists(_Outdent($item));
+			chomp $item;
+			$item = _RunSpanGamut($item);
+		}
+
+		"<li>" . $item . "</li>\n";
+	}egmx;
+
+	$g_list_level--;
+	return $list_str;
+}
+
+
+
+sub _DoCodeBlocks {
+#
+#	Process Markdown `<pre><code>` blocks.
+#	
+
+	my $text = shift;
+
+	$text =~ s{
+			(?:\n\n|\A)
+			(	            # $1 = the code block -- one or more lines, starting with a space/tab
+			  (?:
+			    (?:[ ]{$g_tab_width} | \t)  # Lines must start with a tab or a tab-width of spaces
+			    .*\n+
+			  )+
+			)
+			((?=^[ ]{0,$g_tab_width}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
+		}{
+			my $codeblock = $1;
+			my $result; # return value
+
+			$codeblock = _EncodeCode(_Outdent($codeblock));
+			$codeblock = _Detab($codeblock);
+			$codeblock =~ s/\A\n+//; # trim leading newlines
+			$codeblock =~ s/\s+\z//; # trim trailing whitespace
+
+			$result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
+
+			$result;
+		}egmx;
+
+	return $text;
+}
+
+
+sub _DoCodeSpans {
+#
+# 	*	Backtick quotes are used for <code></code> spans.
+# 
+# 	*	You can use multiple backticks as the delimiters if you want to
+# 		include literal backticks in the code span. So, this input:
+#     
+#         Just type ``foo `bar` baz`` at the prompt.
+#     
+#     	Will translate to:
+#     
+#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+#     
+#		There's no arbitrary limit to the number of backticks you
+#		can use as delimters. If you need three consecutive backticks
+#		in your code, use four for delimiters, etc.
+#
+#	*	You can use spaces to get literal backticks at the edges:
+#     
+#         ... type `` `bar` `` ...
+#     
+#     	Turns to:
+#     
+#         ... type <code>`bar`</code> ...
+#
+
+	my $text = shift;
+
+	$text =~ s@
+			(`+)		# $1 = Opening run of `
+			(.+?)		# $2 = The code block
+			(?<!`)
+			\1			# Matching closer
+			(?!`)
+		@
+ 			my $c = "$2";
+ 			$c =~ s/^[ \t]*//g; # leading whitespace
+ 			$c =~ s/[ \t]*$//g; # trailing whitespace
+ 			$c = _EncodeCode($c);
+			"<code>$c</code>";
+		@egsx;
+
+	return $text;
+}
+
+
+sub _EncodeCode {
+#
+# Encode/escape certain characters inside Markdown code runs.
+# The point is that in code, these characters are literals,
+# and lose their special Markdown meanings.
+#
+    local $_ = shift;
+
+	# Encode all ampersands; HTML entities are not
+	# entities within a Markdown code span.
+	s/&/&/g;
+
+	# Encode $'s, but only if we're running under Blosxom.
+	# (Blosxom interpolates Perl variables in article bodies.)
+	{
+		no warnings 'once';
+    	if (defined($blosxom::version)) {
+    		s/\$/$/g;	
+    	}
+    }
+
+
+	# Do the angle bracket song and dance:
+	s! <  !<!gx;
+	s! >  !>!gx;
+
+	# Now, escape characters that are magic in Markdown:
+	s! \* !$g_escape_table{'*'}!gx;
+	s! _  !$g_escape_table{'_'}!gx;
+	s! {  !$g_escape_table{'{'}!gx;
+	s! }  !$g_escape_table{'}'}!gx;
+	s! \[ !$g_escape_table{'['}!gx;
+	s! \] !$g_escape_table{']'}!gx;
+	s! \\ !$g_escape_table{'\\'}!gx;
+
+	return $_;
+}
+
+
+sub _DoItalicsAndBold {
+	my $text = shift;
+
+	# <strong> must go first:
+	$text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 }
+		{<strong>$2</strong>}gsx;
+
+	$text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }
+		{<em>$2</em>}gsx;
+
+	return $text;
+}
+
+
+sub _DoBlockQuotes {
+	my $text = shift;
+
+	$text =~ s{
+		  (								# Wrap whole match in $1
+			(
+			  ^[ \t]*>[ \t]?			# '>' at the start of a line
+			    .+\n					# rest of the first line
+			  (.+\n)*					# subsequent consecutive lines
+			  \n*						# blanks
+			)+
+		  )
+		}{
+			my $bq = $1;
+			$bq =~ s/^[ \t]*>[ \t]?//gm;	# trim one level of quoting
+			$bq =~ s/^[ \t]+$//mg;			# trim whitespace-only lines
+			$bq = _RunBlockGamut($bq);		# recurse
+
+			$bq =~ s/^/  /g;
+			# These leading spaces screw with <pre> content, so we need to fix that:
+			$bq =~ s{
+					(\s*<pre>.+?</pre>)
+				}{
+					my $pre = $1;
+					$pre =~ s/^  //mg;
+					$pre;
+				}egsx;
+
+			"<blockquote>\n$bq\n</blockquote>\n\n";
+		}egmx;
+
+
+	return $text;
+}
+
+
+sub _FormParagraphs {
+#
+#	Params:
+#		$text - string to process with html <p> tags
+#
+	my $text = shift;
+
+	# Strip leading and trailing lines:
+	$text =~ s/\A\n+//;
+	$text =~ s/\n+\z//;
+
+	my @grafs = split(/\n{2,}/, $text);
+
+	#
+	# Wrap <p> tags.
+	#
+	foreach (@grafs) {
+		unless (defined( $g_html_blocks{$_} )) {
+			$_ = _RunSpanGamut($_);
+			s/^([ \t]*)/<p>/;
+			$_ .= "</p>";
+		}
+	}
+
+	#
+	# Unhashify HTML blocks
+	#
+	foreach (@grafs) {
+		if (defined( $g_html_blocks{$_} )) {
+			$_ = $g_html_blocks{$_};
+		}
+	}
+
+	return join "\n\n", @grafs;
+}
+
+
+sub _EncodeAmpsAndAngles {
+# Smart processing for ampersands and angle brackets that need to be encoded.
+
+	my $text = shift;
+
+	# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+	#   http://bumppo.net/projects/amputator/
+ 	$text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g;
+
+	# Encode naked <'s
+ 	$text =~ s{<(?![a-z/?\$!])}{<}gi;
+
+	return $text;
+}
+
+
+sub _EncodeBackslashEscapes {
+#
+#   Parameter:  String.
+#   Returns:    The string, with after processing the following backslash
+#               escape sequences.
+#
+    local $_ = shift;
+
+    s! \\\\  !$g_escape_table{'\\'}!gx;		# Must process escaped backslashes first.
+    s! \\`   !$g_escape_table{'`'}!gx;
+    s! \\\*  !$g_escape_table{'*'}!gx;
+    s! \\_   !$g_escape_table{'_'}!gx;
+    s! \\\{  !$g_escape_table{'{'}!gx;
+    s! \\\}  !$g_escape_table{'}'}!gx;
+    s! \\\[  !$g_escape_table{'['}!gx;
+    s! \\\]  !$g_escape_table{']'}!gx;
+    s! \\\(  !$g_escape_table{'('}!gx;
+    s! \\\)  !$g_escape_table{')'}!gx;
+    s! \\>   !$g_escape_table{'>'}!gx;
+    s! \\\#  !$g_escape_table{'#'}!gx;
+    s! \\\+  !$g_escape_table{'+'}!gx;
+    s! \\\-  !$g_escape_table{'-'}!gx;
+    s! \\\.  !$g_escape_table{'.'}!gx;
+    s{ \\!  }{$g_escape_table{'!'}}gx;
+
+    return $_;
+}
+
+
+sub _DoAutoLinks {
+	my $text = shift;
+
+	$text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi;
+
+	# Email addresses: <address at domain.foo>
+	$text =~ s{
+		<
+        (?:mailto:)?
+		(
+			[-.\w]+
+			\@
+			[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+		)
+		>
+	}{
+		_EncodeEmailAddress( _UnescapeSpecialChars($1) );
+	}egix;
+
+	return $text;
+}
+
+
+sub _EncodeEmailAddress {
+#
+#	Input: an email address, e.g. "foo at example.com"
+#
+#	Output: the email address as a mailto link, with each character
+#		of the address encoded as either a decimal or hex entity, in
+#		the hopes of foiling most address harvesting spam bots. E.g.:
+#
+#	  <a href="&#x6D;ail&#x74;o:foo@e
+#       x&#x61;m&#x70;l&#x65;&#x2E;com">foo
+#       @ex&#x61;m&#x70;l&#x65;&#x2E;com</a>
+#
+#	Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+#	mailing list: <http://tinyurl.com/yu7ue>
+#
+
+	my $addr = shift;
+
+	srand;
+	my @encode = (
+		sub { '&#' .                 ord(shift)   . ';' },
+		sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' },
+		sub {                            shift          },
+	);
+
+	$addr = "mailto:" . $addr;
+
+	$addr =~ s{(.)}{
+		my $char = $1;
+		if ( $char eq '@' ) {
+			# this *must* be encoded. I insist.
+			$char = $encode[int rand 1]->($char);
+		} elsif ( $char ne ':' ) {
+			# leave ':' alone (to spot mailto: later)
+			my $r = rand;
+			# roughly 10% raw, 45% hex, 45% dec
+			$char = (
+				$r > .9   ?  $encode[2]->($char)  :
+				$r < .45  ?  $encode[1]->($char)  :
+							 $encode[0]->($char)
+			);
+		}
+		$char;
+	}gex;
+
+	$addr = qq{<a href="$addr">$addr</a>};
+	$addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part
+
+	return $addr;
+}
+
+
+sub _UnescapeSpecialChars {
+#
+# Swap back in all the special characters we've hidden.
+#
+	my $text = shift;
+
+	while( my($char, $hash) = each(%g_escape_table) ) {
+		$text =~ s/$hash/$char/g;
+	}
+    return $text;
+}
+
+
+sub _TokenizeHTML {
+#
+#   Parameter:  String containing HTML markup.
+#   Returns:    Reference to an array of the tokens comprising the input
+#               string. Each token is either a tag (possibly with nested,
+#               tags contained therein, such as <a href="<MTFoo>">, or a
+#               run of text between tags. Each element of the array is a
+#               two-element array; the first is either 'tag' or 'text';
+#               the second is the actual value.
+#
+#
+#   Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin.
+#       <http://www.bradchoate.com/past/mtregex.php>
+#
+
+    my $str = shift;
+    my $pos = 0;
+    my $len = length $str;
+    my @tokens;
+
+    my $depth = 6;
+    my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x  $depth);
+    my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) |  # comment
+                   (?s: <\? .*? \?> ) |              # processing instruction
+                   $nested_tags/ix;                   # nested tags
+
+    while ($str =~ m/($match)/g) {
+        my $whole_tag = $1;
+        my $sec_start = pos $str;
+        my $tag_start = $sec_start - length $whole_tag;
+        if ($pos < $tag_start) {
+            push @tokens, ['text', substr($str, $pos, $tag_start - $pos)];
+        }
+        push @tokens, ['tag', $whole_tag];
+        $pos = pos $str;
+    }
+    push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len;
+    \@tokens;
+}
+
+
+sub _Outdent {
+#
+# Remove one level of line-leading tabs or spaces
+#
+	my $text = shift;
+
+	$text =~ s/^(\t|[ ]{1,$g_tab_width})//gm;
+	return $text;
+}
+
+
+sub _Detab {
+#
+# Cribbed from a post by Bart Lateur:
+# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
+#
+	my $text = shift;
+
+	$text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge;
+	return $text;
+}
+
+
+1;
+
+__END__
+
+
+=pod
+
+=head1 NAME
+
+B<Markdown>
+
+
+=head1 SYNOPSIS
+
+B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ]
+    [ I<file> ... ]
+
+
+=head1 DESCRIPTION
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can  use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like <div> and <table> as well).
+
+For more information about Markdown's syntax, see:
+
+    http://daringfireball.net/projects/markdown/
+
+
+=head1 OPTIONS
+
+Use "--" to end switch parsing. For example, to open a file named "-z", use:
+
+	Markdown.pl -- -z
+
+=over 4
+
+
+=item B<--html4tags>
+
+Use HTML 4 style for empty element tags, e.g.:
+
+    <br>
+
+instead of Markdown's default XHTML style tags, e.g.:
+
+    <br />
+
+
+=item B<-v>, B<--version>
+
+Display Markdown's version number and copyright information.
+
+
+=item B<-s>, B<--shortversion>
+
+Display the short-form version number.
+
+
+=back
+
+
+
+=head1 BUGS
+
+To file bug reports or feature requests (other than topics listed in the
+Caveats section above) please send email to:
+
+    support at daringfireball.net
+
+Please include with your report: (1) the example input; (2) the output
+you expected; (3) the output Markdown actually produced.
+
+
+=head1 VERSION HISTORY
+
+See the readme file for detailed release notes for this version.
+
+1.0.1 - 14 Dec 2004
+
+1.0 - 28 Aug 2004
+
+
+=head1 AUTHOR
+
+    John Gruber
+    http://daringfireball.net
+
+    PHP port and other contributions by Michel Fortin
+    http://michelf.com
+
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2003-2004 John Gruber   
+<http://daringfireball.net/>   
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+  be used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+=cut
diff --git a/doc/mkdoc b/doc/mkdoc
index 3d977fc..e794fb7 100755
--- a/doc/mkdoc
+++ b/doc/mkdoc
@@ -1,10 +1,8 @@
 #!/usr/bin/perl
 
-my $MKD = "$ENV{HOME}/Markdown.pl";
+#   cd <g3 clone>/doc; ./mkdoc     # --> creates ../html/*.html
 
-# run this like so:
-
-#       $0 *.mkd >mt.mkd 2>mf
+my $MKD = "./Markdown.pl";
 
 use 5.10.0;
 use strict;
@@ -37,7 +35,7 @@ while (<>) {
                 : "$3"
             );
             $mt .= "\n";
-            $mf .= "[$2]: $b.html#$2\n" if $2;
+            $mf .= "[$2]: $b.html" . ($2 ne $b ? "#$2" : "") . "\n" if $2;
         }
     }
 }

commit 4f7d3d86515099774d6f1df869e40e89aa635119
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 07:24:47 2012 +0530

    Q: all doc stuff

diff --git a/doc/add.mkd b/doc/add.mkd
new file mode 100644
index 0000000..21d1a61
--- /dev/null
+++ b/doc/add.mkd
@@ -0,0 +1,34 @@
+# adding users and repos
+
+Do NOT add repos directly on the server.  Clone the 'gitolite-admin' repo to
+your workstation, make changes to it, then add, commit, and push.  When the
+push hits the server, the server "acts" upon your changes.
+
+Full documentation on the conf file is [here][conf].
+
+Here's a sample sequence, on your workstation, after your install is done
+
+    git clone git at host:gitolite-admin
+    cd gitolite-admin
+    vi conf/gitolite.conf
+
+    # now add lines like these:
+        repo foo
+            RW+ =   me
+            RW  =   alice
+            R   =   wally
+    # now save the file and add it
+    git add conf
+
+    # add a couple of users; get their pubkeys by email or something, then:
+    cp /some/where/alice.pub keydir
+    cp /else/where/wally.pub keydir
+    git add keydir
+
+    # now commit and push
+    git commit -m 'added repo foo'
+    git push
+
+    # at this point gitolite will create the new repo 'foo' (if it did not
+    # already exist) then update the authorized keys file to include alice and
+    # wally's pubkeys
diff --git a/doc/commands.mkd b/doc/commands.mkd
new file mode 100644
index 0000000..da2baa4
--- /dev/null
+++ b/doc/commands.mkd
@@ -0,0 +1,26 @@
+# gitolite "commands"
+
+Gitolite comes with several commands that users can run.  Remote user run the
+commands by saying:
+
+    ssh git at host command-name [args...]
+
+while on the server you can run
+
+    gitolite command [args...]
+
+Very few commands are designed to be run both ways, but it can be done, by
+checking for the presence of env var `GL_USER`.
+
+You can get a **list of available commands** by using the `help` command.
+Naturally, a remote user will see only a subset of what the server user will
+see.
+
+You add commands to the "allowed from remote" list by adding its name (or
+uncommenting it if it's already added but commented out) to the COMMANDS hash
+in the [rc][] file.
+
+If you write your own commands, put them in src/commands.
+
+**Note that this is also the place that all triggered programs go**.  In fact,
+all standalone programs related to gitolite go here.
diff --git a/doc/conf.mkd b/doc/conf.mkd
new file mode 100644
index 0000000..15c3756
--- /dev/null
+++ b/doc/conf.mkd
@@ -0,0 +1,31 @@
+# the gitolite.conf file
+
+This file is the crux of all of gitolite's access control.  The basic syntax
+is very simple.
+
+Note: `<user>+` means one or more user or user group names, `<repo>+` means
+one or more repo or repo group names, and `<refex>*` means zero or more
+refexes.
+
+  * [group][group] definitions (optional, for convenience)
+
+        @<group> = <user>+
+        @<group> = <repo>+
+
+  * [repo][repo] definitions and access [rules][]
+
+        repo <repo>+
+            <perm>  <refex>*    =   <user>+
+            # one or more such lines
+
+  * [gitolite options][options] that apply to the repo(s) in the last
+    "repo ..." line, for example:
+
+            option deny-rules = 1
+
+  * [git config][git-config] keys and values that also apply to the last named
+    repo(s), for example:
+
+            config hooks.emailprefix = '[%GL_REPO] '
+
+In addition, you can also have [include][] statements.
diff --git a/doc/cust.mkd b/doc/cust.mkd
new file mode 100644
index 0000000..fb79b21
--- /dev/null
+++ b/doc/cust.mkd
@@ -0,0 +1,23 @@
+# customising gitolite
+
+Here are the ways you can customise gitolite on the server.
+
+First, learn about:
+
+  * [git hooks][hooks] and [virtual refs][vref]
+
+  * [commands][] for your [users][] to run own][dev-notes]
+
+  * [triggers][] to be run by gitolite as various points in its execution
+
+  * [syntactic sugar][sugar] to change the conf language for your convenience
+
+For all of the above:
+
+  * [edit the rc file][rc] to enable optional features that are shipped in a
+    disabled state
+
+  * [write your own][dev-notes]
+
+(Note: "trigger" is the same concept as "hook", applied to gitolite; I just
+chose a different name to avoid constant ambiguity in documentation).
diff --git a/doc/dev-notes.mkd b/doc/dev-notes.mkd
new file mode 100644
index 0000000..854c547
--- /dev/null
+++ b/doc/dev-notes.mkd
@@ -0,0 +1,116 @@
+# notes for developers
+
+Gitolite has a huge bunch of existing features that gradually need to moved
+over.  Plus you may want to write your own programs to interact with it.
+
+Hints for developers wishing to help migrate features over from g2 are
+[here][dev-hints].
+
+Here are some random notes on developing hooks, commands, triggers, and sugar
+scripts.
+
+## environment variables
+
+In general, the following environment variables should always be available:
+
+    GL_BINDIR
+    GL_REPO_BASE
+    GL_ADMIN_BASE
+
+Commands invoked by a remote client will also have `GL_USER` set.  Hooks will
+have `GL_REPO` also set.
+
+## APIs
+
+### the shell API
+
+The following commands exist to help you write shell scripts that interact
+easily with gitolite.  Each of them responds to `h` so please run that for
+more info.
+
+  * `gitolite access` to check access rights given repo, user, type of access
+    (R, W, ...) and refname (optional).  Example use: src/commands/desc
+
+  * `gitolite creator` to get/check the creator of a repo.  Example use:
+    src/commands/desc
+
+  * `gitolite git-config` to check gitolite options or git config variables
+    directly from gitolite's "compiled output, (i.e., without looking at the
+    actual `repo.git/config` file or using the `git config` command).  Example
+    use: none yet
+
+  * `gitolite query-rc` to check the value of an RC variable.  Example use:
+    src/commands/desc.
+
+In addition, you can also look at the comments in src/Gitolite/Easy.pm (the
+perl API module) for ideas.
+
+### the perl API
+
+...is implemented by Gitolite::Easy; see src/Gitolite/Easy.pm.  This is a work
+in progress; for example it does not yet have the equivalent of `gitolite
+git-config`.  I'll add it when I or someone else needs it.
+
+## your own hooks
+
+### anything but the update hook
+
+If you want to add your own hook, it's easy as long as it's not the 'update'
+hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
+
+The rest is between you and 'man githooks' :-)
+
+### update hook
+
+If you want to add additional `update` hook functionality, do this:
+
+  * write and test your update hook separately from gitolite
+
+  * now add the code to src/VREF.  Let's say it is called "foo".
+
+  * to call your new update hook to all accesses for all repos, add this to
+    the end of your conf file:
+
+        repo @all
+            -       VREF/foo        =   @all
+
+As you probably guessed, you can now make your additional update hooks more
+selective, applying them only to some repos / users / combinations.
+
+Note: a normal update hook expects 3 arguments (ref, old SHA, new SHA).  A
+VREF will get those three, followed by at least 4 more.  Your VREF should just
+ignore the extra args.
+
+## your own commands
+
+You can add your own commands.  You can run them on the server (example,
+`gitolite access`).  Then you can enable certain commands to be allowed to run
+by a remote user by adding them to the "COMMANDS" hash of the [rc][] file.
+
+Commands are standalone programs, in any language you like.  They simply
+receive the arguments you append.  In addition, the env var `GL_USER` is
+available if it is being run remotely.  src/commands/desc is the best example
+at present.
+
+## your own trigger programs
+
+Trigger programs are just commands whose names have been added to the
+appropriate list in the [rc][] file.  Triggers get specific arguments
+depending on when they are called; see [here][triggers] for details.
+
+You can write programs that are both manually runnable as well as callable by
+trigger events, especially if they don't *need* any arguments.
+
+Look in the distributed [rc][] file for example programs; at this point there
+aren't many.
+
+## your own "sugar"
+
+Syntactic sugar helpers are NOT complete, standalone, programs.  They must
+include a perl sub called `sugar_script` that takes in a listref, and returns
+a listref.  The listrefs point to a list that contains the entire conf file
+(with all [include][] processing already done).  You create a new list with
+contents modified as you like and return a ref to it.
+
+There are a couple of examples in src/syntactic-sugar.
+
diff --git a/doc/dev-status.mkd b/doc/dev-status.mkd
new file mode 100644
index 0000000..29ae450
--- /dev/null
+++ b/doc/dev-status.mkd
@@ -0,0 +1,35 @@
+## #dev-status g3 development status
+
+Not yet done (will be tackled in this order unless someone asks):
+
+  * detailed documentation for new features
+  * querying the outside world for group info (see gl-get-membership-program
+    in g2)
+  * mirroring
+  * pulling in documentation for things that are the same in g2
+  * "unrestricted arguments" for some ADCs (like git-annexe)
+  * smart http
+  * special features (no-create-repos, shell-access, gl-all-read-all, etc)
+
+Help needed:
+
+  * I'd like distro packagers to play with it and help with migration advice
+    for distro-upgrades
+  * [rsync][pw2], htpasswd
+  * git-annexe support (but this has a pre-requisite in the previous list)
+
+Won't be done unless someone asks (saw no evidence that anyone used them in g2
+anyway!):
+
+  * mob branches
+  * password access
+  * specific ADCs -- there are too many for me to bother without applying
+    Pareto somewhere, so I choose to not do any and wait for people to ask :-)
+
+Done:
+
+  * core code
+  * test suite
+  * some documentation
+  * distro packaging instructions
+  * migration advice for common cases
diff --git a/doc/extras/auth.mkd b/doc/extras/auth.mkd
new file mode 100644
index 0000000..0918f07
--- /dev/null
+++ b/doc/extras/auth.mkd
@@ -0,0 +1,89 @@
+# authentication versus authorisation
+
+This document will explain why an "ssh issue" is almost never a "gitolite
+issue", and, indirectly, why I dont get too excited about the former.
+
+Note: for actual ssh troubleshooting see [this][ssh-troubleshooting].
+
+Here is a fundamental point: <font color="red">**Gitolite does not do
+authentication.  It only does authorisation**.</font>
+
+So first, let's loosely define these words:
+
+>   **Authentication** is the process of verifying that you are who you claim
+>   to be.  An authentication system will establish that I am the user
+>   "sitaram" on my work system.  The one behind gmail will similarly
+>   establish that I am "sitaramc".  And so on...
+
+>   **Authorisation** is the process of asking what you want to do and
+>   deciding if you're allowed to do it or not.
+
+Now, if you managed to read about [gitolite and ssh][gitolite-and-ssh], you
+know that gitolite is meant to be invoked as:
+
+    /full/path/to/gl-auth-command some-authenticated-gitolite-username
+
+(where the "gitolite username" is a "virtual" username; it does not have to
+be, and usually *isn't*, an actual *unix* username).
+
+As you can see, authentication happens before gitolite is called.
+
+## but... but... you have all that ssh stuff in there!
+
+The default mode of using gitolite does use ssh keys, but all it's doing is
+helping you **setup** ssh-based authentication **as a convenience to you**.
+
+You don't have to use it, though.  And many people don't.  The examples I know
+are [smart http][http], and ldap-backed sshd.  In both cases, gitolite has no
+role to play in creating users, setting up their passwords/keys, etc.  There's
+even a `GL_NO_SETUP_AUTHKEYS` option to make sure gitolite doesn't meddle with
+the authkeys file in such installations.
+
+## so you're basically saying you won't support "X"
+
+(where "X" is some ssh related behaviour change or feature)
+
+Well, if it's not a security issue I *probably* won't.  I'm willing to change
+my mind if enough people convince me they need it.  (There's a mailing list if
+you want to find others who also need the same thing.)
+
+While we're on the subject, locking someone out is *not* a security issue.
+Even if you locked yourself (the admin) out, the docs tell you how to recover
+from such errors.  You do need some password based method to get a shell
+command line on the server, of course.
+
+## appendix: how to use other authentication systems with gitolite
+
+The bottom line in terms of how to invoke gitolite has been described above,
+and as long as you manage to do that gitolite won't even know how the
+authentication was done.  Which in turn means you can use whatever
+authentication scheme you want.
+
+It also expects the `SSH_ORIGINAL_COMMAND` environment variable to contain the
+full command (typically starting with git-receive-pack or git-upload-pack)
+that the client sent.  Also, when using [smart http][http], things are somewhat
+different: gitolite uses certain environment variables that it expects httpd
+to have set up.  Even the user name comes from the `REMOTE_USER` environment
+variable instead of as a command line argument in this case.
+
+However, it has to be an authentication system that is compatible with sshd or
+httpd in some form.  Why?  Because the git *client* accessing the server only
+knows those 2 protocols to "speak git".  (Well, the `git://` protocol is
+unauthenticated, and `file://` doesn't really apply to this discussion, so
+we're ignoring those).
+
+For example, let's say you have an LDAP-based authentication system somewhere.
+It is possible to make apache use that to authenticate users, so when a user
+accesses a git url using `http://sitaram:password@git.example.com/repo`, it is
+LDAP that does the actual authentication.  [I wouldn't know how to do it but I
+know it is possible.  Patches to this doc explaining how are welcome!]
+
+There are also ssh daemons that use LDAP to store the authorised keys (instead
+of putting them all in `~/.ssh/authorized_keys`).  The clients will still need
+to generate keypairs and send them to the admin, but they can be more
+centrally stored and perhaps used by other programs or tools simultaneously,
+which can be useful.
+
+Finally, gitolite allows you to store *group* information externally too.  See
+[here][ldap] for more on this.
+
diff --git a/doc/extras/gitolite-and-ssh.mkd b/doc/extras/gitolite-and-ssh.mkd
new file mode 100644
index 0000000..380b81c
--- /dev/null
+++ b/doc/extras/gitolite-and-ssh.mkd
@@ -0,0 +1,145 @@
+## #glssh how gitolite uses ssh
+
+Although other forms of authentications exist (see the document on
+[authentication versus authorisation][auth]), ssh is the one that most git
+users use.
+
+***Therefore, gitolite is (usually) heavily dependent on ssh***.
+
+Most people didn't realise this, and even if they did they don't know ssh
+well enough to help themselves.  If you don't understand how ssh public key
+authentication works, or how the `~/.ssh/authorized_keys` file can be used to
+restrict users, etc., you will have endless amounts of trouble getting
+gitolite to work, because you'll be attacking the wrong problem.
+
+So please please please understand this before tearing your hair out and
+blaming ***git/gitolite*** for whatever is going wrong with your setup :-)
+
+### ssh basics
+
+Let's start with some basics, focusing *only* on the pieces relevant to
+`gitolite`.  If this is not detailed enough, please use google and learn more
+from somewhere, or maybe buy the OReilly ssh book.
+
+  * You can login to an ssh server by typing a password, but ssh can also use
+    ***public-private keys*** (also called "key pairs") for authentication.
+    `gitolite` *requires* you to use this mechanism for your users -- they
+    cannot log in using passwords.  Hopefully by the time you finish reading
+    this document you will understand why :-)
+
+    The way you set this up is you generate a key pair on your workstation,
+    and give the server the public key.  (I need not add that the "private"
+    key must be, well, kept *private*!)
+
+  * **generating a key pair on your workstation** is done by running the
+    command `ssh-keygen -t rsa`.  This produces two files in `~/.ssh`.  One is
+    `id_rsa`; this is the **private** key -- ***never*** let it out of your
+    machine.  The other is `id_rsa.pub`, which is the corresponding public
+    key.  This public key is usually just one long line of text.
+
+    * on Windows machines with msysgit installed, you should do this from
+      within a "git bash" window.  The command will report the full path where
+      the files have been written; make a note of this, and use those files in
+      any of the description that follows
+
+  * **adding your public key to the server**'s `~/.ssh/authorized_keys`
+    file is how ssh uses pubkeys to authenticate users.  Let's say
+    sita at work.station is trying to log in as git at serv.er.  What you have to do
+    is take the `~/.ssh/id_rsa.pub` file for user sita on work.station and
+    append its contents (remember it's only one line) to
+    `~/.ssh/authorized_keys` for user git on serv.er.
+
+    The `authorized_keys` file can have multiple public keys (from many
+    different people) added to it so any of them can log in to git at serv.er.
+
+    In the normal case (not gitolite, but your normal everyday shell access),
+    there's a command that does this, `ssh-copy-id`, which also fixes up
+    permissions etc., as needed, since sshd is a little picky about allowing
+    pubkey access if permissions on the server are loose.  Or you can do it
+    manually, as long as you know what you're doing and you're careful not to
+    erase or overwrite the existing contents of `~/.ssh/authorized_keys` on
+    the server!
+
+    But in the gitolite case, it's different; we'll get to that in a minute.
+
+    * **troubleshooting pubkey authentication failures**: if you are unable to
+      get ssh access to the server after doing all this, you'll have to look
+      in `/var/log/secure` or `/var/log/auth.log` or some such file on the
+      server to see what specific error `sshd` is complaining about.
+
+  * **restricting users to specific commands** is very important for gitolite.
+    If you read `man sshd` and look for `authorized_keys file format`, you'll
+    see a lot of options you can add to the public key line, which restrict
+    the incoming user in various ways.  In particular, note the `command=`
+    option, which means "regardless of what the incoming user is asking to do,
+    forcibly run this command instead".
+
+    Also note that when there are many public keys (i.e., lines) in the
+    `authorized_keys` file, each line can have a *different* set of options
+    and `command=` values.
+
+    Without this `command=` option, the ssh daemon will simply give you a
+    shell, which is not what we want for our gitolite keys (although we may
+    well have other keys which we use to get a shell).
+
+    **This is the backbone of what makes gitolite work; please make sure you
+    understand this**.
+
+### how does gitolite use all this ssh magic?
+
+These are two different questions you ought to be having by now: 
+
+  * how does it distinguish between me and someone else, since we're all
+    logging in as the same remote user "git"
+  * how does it restrict what I can do within a repository
+
+#### restricting shell access/distinguishing one user from another
+
+The answer to the first question is the `command=` we talked about before.  If
+you look in the `authorized_keys` file, you'll see entries like this (I chopped
+off the ends of course; they're pretty long lines):
+
+    command="[path]/gl-auth-command sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
+    command="[path]/gl-auth-command usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
+
+First, it finds out which of the public keys in this file match the incoming
+login.  That's crypto stuff, and I won't go into it.  Once the match has been
+found, it will run the command given on that line; e.g., if I logged in, it
+would run `[path]/gl-auth-command sitaram`.  So the first thing to note is
+that such users do not get "shell access", which is good!
+
+Before running the command, however, sshd sets up an environment variable
+called `SSH_ORIGINAL_COMMAND` which contains the actual git command that your
+workstation sent out.  This is the command that *would have run* if you did
+not have the `command=` part in the authorised keys file.
+
+When `gl-auth-command` gets control, it looks at the first argument
+("sitaram", "usertwo", etc) to determine who you are.  It then looks at the
+`SSH_ORIGINAL_COMMAND` variable to find out which repository you want to
+access, and whether you're reading or writing.
+
+Now that it has a user, repository, and access requested (read/write), gitolite looks
+at its config file, and either allows or rejects the request.
+
+But this cannot differentiate between different branches within a repo; that
+has to be done separately.
+
+#### restricting branch level actions
+
+[If you look inside the git source tree, there's a file among the "howto"s in
+there called `update-hook-example.txt`, which was the inspiration for this
+part of gitolite.]
+
+Git allows you to specify many "hooks", which get control as various events
+happen -- see `git help hooks` for details.  One of those hooks is the
+`update` hook, which, if it is present, is invoked just before a branch or a
+tag is about to be updated.  The hook is passed the name of the branch or tag,
+the old SHA1 value, and the new SHA1 value, as arguments.  Hooks that are
+called *before* an action happens are allowed to prevent that action from
+happening by returning an error code.
+
+When gitolite is told to create a new repository (by the admin), it installs
+a special update hook.  This hook takes all the information presented, looks
+at the config file, and decides to allow or reject the update.
+
+And that's basically it.
diff --git a/doc/extras/nagp.mkd b/doc/extras/nagp.mkd
new file mode 100644
index 0000000..7f783db
--- /dev/null
+++ b/doc/extras/nagp.mkd
@@ -0,0 +1,64 @@
+# not a gitolite problem
+
+These are issues I do not want to be emailed about.  That does not mean you
+cannot get help -- in all cases, you're welcome to ask on [irc or the mailing
+list][contact].  Irc especially has people with much more patience than I
+have, God bless them...
+
+## specific clients, or specific server OSs
+
+These are things I can not support.  That does not mean they will not work
+with gitolite -- on the contrary, lots of people are using them.
+
+But I personally don't use them, and I won't use them, and in my admittedly
+limited experience they have given me good reason to stay well away.
+
+Please ask for help on the [mailing list or IRC][contact].  Please do not
+email me directly.
+
+  * putty/plink
+  * jgit/Eclipse
+  * Mac OS client or server
+  * windows as a server
+  * ...probably some more I forgot; will update this list as I remember...
+
+## ssh
+
+The *superstar* of the "not a gitolite problem" category is actually ssh.
+
+Surprised?  It is so common that it has [its own document][auth] to tell
+you why it is *not* a gitolite problem, while [another one][ssh] tries to
+help you anyway!
+
+Everything I know is in that latter link.  Please email me about ssh ONLY if
+you find something wrong or missing in those documents.
+
+## git
+
+Example 1: when a first `git push` to a new repo fails, it is not because of
+gitolite, it is because you need to say `git push origin master` or something.
+This is a git issue.
+
+There are several such examples.  Gitolite is designed to look like just
+another bare repo server to a client (except requiring public keys -- no
+passwords allowed).  It is *completely transparent* when there is no
+authorisation failure (i.e., when the access is allowed, the remote client has
+no way of knowing gitolite was even installed!)
+
+Even "on disk", apart from reserving the `update` hook for itself, gitolite
+does nothing to your bare repos unless you tell it to (for example, adding
+'gitweb.owner' and such to the config file).
+
+BEFORE you think gitolite is a problem, try the same thing with a normal bare
+repo.  In most cases you can play with it just by doing something like this:
+
+    mkdir /tmp/throwaway
+    cd    /tmp/throwaway
+    git clone --mirror <some repo you have a URL for> bare.git
+    git clone bare.git worktree
+    cd worktree
+    <...try stuff>
+
+----
+
+In addition, the original nagp has more funny stuff...
diff --git a/doc/extras/regex.mkd b/doc/extras/regex.mkd
new file mode 100644
index 0000000..c334c2c
--- /dev/null
+++ b/doc/extras/regex.mkd
@@ -0,0 +1,34 @@
+# extremely brief regex overview
+
+Regexes are powerful.  Gitolite uses that power as much as it can.  If you
+can't handle that power, hire someone who can and become a manager.
+
+That said, here's a very quick overview of the highlights.
+
+`^` and `$` are called "anchors".  They anchor the match to the beginning and
+end of the string respectively.
+
+    ^foo    matches any string starting with 'foo'
+    foo$    matches any string ending with 'foo'
+    ^foo$   matches exact string 'foo'.
+
+To be precise, the last one is "any string starting and ending with *the same*
+'foo'".  "foofoo" does not match.
+
+`[0-9]` is an example of a character class; it matches any single digit.
+`[a-z]` matches any lower case alpha, and `[0-9a-f]` is the range of hex
+characters.  You should now guess what `[a-zA-Z0-9_]` does.
+
+`.` (the period) is special -- it matches any character.  If you want to match
+an actual period, you need to say `\.`.
+
+`*`, `?`, and `+` are quantifiers.  They apply to the previous token.  `a*`
+means "zero or more 'a' characters".  Similarly `a+` means "one or more", and
+`a?` means "zero or one".
+
+As a result, `.*` means "any number (including zero) of any character".
+
+The previous token need not be a single character; you can use parens to make
+it longer.  `(foo)+` matches one or more "foo", (like "foo", "foofoo",
+"foofoofoo", etc.)
+
diff --git a/doc/extras/ssh-troubleshooting.mkd b/doc/extras/ssh-troubleshooting.mkd
new file mode 100644
index 0000000..aeee3d9
--- /dev/null
+++ b/doc/extras/ssh-troubleshooting.mkd
@@ -0,0 +1,427 @@
+## #sshts ssh troubleshooting
+
+**This document must be read in full the first time.  If you start from some
+nice looking section in the middle it may not help you unless you're already
+an expert at ssh**.
+
+This document should help you troubleshoot ssh-related problems in installing
+and accessing gitolite.
+
+### IMPORTANT -- READ THIS FIRST
+
+#### caveats
+
+  * Before reading this document, it is **mandatory** to read and **completely
+    understand** [this][ssh], which is a very detailed look at how gitolite
+    uses ssh's features on the server side.  Don't assume you know all that;
+    if you knew it, you wouldn't be needing *this* document either!
+
+  * This document, and others linked from this, together comprise all the help
+    I can give you in terms of the ssh aspect of using gitolite.  If you're
+    installing gitolite, you're a "system admin", like it or not.  Ssh is
+    therefore a necessary skill.  Please take the time to learn at least
+    enough to get passwordless access working.
+
+  * Please note that authentication is not really gitolite's job at all.  I'd
+    rather spend time on actual gitolite features, code, and documentation
+    than authentication (i.e., ssh, in the common case).
+
+    Surprised?  [This][auth] might help explain better.
+
+#### naming conventions used
+
+  * Your workstation is the **client**.  Your userid on the client does not
+    matter, and it has no relation to your gitolite username.
+
+  * the server is called **server** and the "hosting user" is **git**.  If
+    this is an RPM/DEB install, the hosting user is probably called
+    "gitolite", however we will use "git" in this document.
+
+#### taking stock -- relevant files and directories
+
+  * the client has a `~/.ssh` containing a few keypairs.  It may also have a
+    `config` file.
+
+  * the client also has a clone of the "gitolite-admin" repo, which contains a
+    bunch of `*.pub` files in `keydir`.  We assume this clone is in `$HOME`;
+    if it is not, adjust instructions accordingly when needed.
+
+  * The git user on the server has a `~/.ssh/authorized_keys` file that the
+    ssh daemon uses to authenticate incoming users.  We often call this file
+    **authkeys** to save typing, and it always means the one on the server
+    (we're not interested in this file on the client side).
+
+  * the server also has a `~/.gitolite/keydir` which contains a bunch of
+    `*.pub` files.
+
+#### normal gitolite key handling
+
+Here's how normal gitolite key handling works:
+
+  * (on client) pub key changes like adding new ones, deleting old ones, etc.,
+    are done in the `keydir` directory in the gitolite-admin repo clone.  Then
+    the admin `git add`s and `git commit`s those changes, then `git push`es
+    them to the server.
+
+  * (on server) a successful push from the client makes git invoke the
+    post-update hook in the gitolite-admin repo.  This hook is installed by
+    gitolite, and it does a bunch of things which are quite transparent to
+    the admin, but we'll describe briefly here:
+
+      * the pubkey files from this push are checked-out into
+        `~/.gitolite/keydir` (and similarly the config files into
+        `~/.gitolite/conf`)
+
+      * the "compile" script then runs, which uses these files to populate
+        `~/.ssh/authorized_keys` on the server
+
+        The authkeys file may have other, (non-gitolite) keys also.  Those
+        lines are preserved.  Gitolite only touches lines that are found
+        between gitolite's "marker" lines (`# gitolite start` and `# gitolite
+        end`).
+
+### (Other resources)
+
+People who think installing gitolite is too hard should take a look at this
+[tutorial][tut] to **see how simple it *actually* is**.
+
+### common ssh problems
+
+Since I'm pretty sure at least some of you didn't bother to read the
+"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
+you there again.  Especially the first bullet.
+
+Done?  OK, read on...
+
+The following problem(s) indicate that pubkey access is not working at all, so
+you should start with [appendix 1][stsapp1_].  If that doesn't fix the problem, continue
+with the other appendices in sequence.
+
+  * running any git clone/fetch/ls-remote or just `ssh git at server info` asks
+    you for a password.
+
+The following problem(s) indicate that your pubkey is bypassing gitolite and
+going straight to a shell.  You should start with [appendix 2][stsapp2_] and continue with
+the rest in sequence.  [Appendix 5][stsapp5_] has some background info.
+
+  * running `ssh git at server info` gets you the output of the GNU 'info'
+    command instead of gitolite's version and access info.
+
+  * running `git clone git at server:repositories/reponame` (note presence of
+    `repositories/` in URL) works.
+
+    [A proper gitolite key will only let you `git clone git at server:reponame`
+    (note absence of `repositories/`)]
+
+  * you are able to clone repositories but are unable to push changes back
+    (the error complains about the `GL_RC` environment variable not being set,
+    and the `hooks/update` failing in some way).
+
+    [If you run `git remote -v` you will find that your clone URL included the
+    `repositories/` described above!]
+
+  * conversely, using the correct syntax, `git clone git at server:reponame`
+    (note absence of `repositories/` in the URL), gets you `fatal: 'reponame'
+    does not appear to be a git repository`, and yet you are sure 'reponame'
+    exists, you haven't mis-spelled it, etc.
+
+### step by step
+
+Since I'm pretty sure at least some of you didn't bother to read the
+"IMPORTANT: PLEASE READ FIRST" section above, let me take a minute to point
+you there again.  Especially the first bullet.
+
+Done?  OK, now the general outline for ssh troubleshooting is this:
+
+  * make sure the server's overall setup even *allows* pubkey based login.
+    I.e., check that git fetch/clone/ls-remote commands or a plain `ssh
+    git at server info` do NOT ask for a password.  If you do get asked for a
+    password, see [appendix 1][stsapp1_].
+
+  * match client-side pubkeys (`~/.ssh/*.pub`) with the server's authkeys
+    file.  To do this, run `sshkeys-lint`, which tells you in detail what key
+    has what access.  See [appendix 2][stsapp2_].
+
+  * at this point, we know that we have the right key, and that if sshd
+    receives that key, things will work.  But we're not done yet.  We still
+    need to make sure that this specific key is being offered/sent by the
+    client, instead of the default key.  See [appendix 3][stsapp3_] and [appendix 4][sshhostaliases].
+
+### random tips, tricks, and notes
+
+#### giving shell access to gitolite users
+
+We've managed (thanks to an idea from Jesse Keating) to make it possible for a
+single key to allow both gitolite access *and* shell access.
+
+This is done by copying the pubkey (to which you want to give shell access) to
+the server and running
+
+    gl-tool add-shell-user ~/foo.pub
+
+**IMPORTANT UPGRADE NOTE**: previous implementations of this feature were
+crap.  There was no easy/elegant way to ensure that someone who had repo admin
+access would not manage to get himself shell access.
+
+Giving someone shell access requires that you should have shell access in the
+first place, so the simplest way is to enable it from the server side only.
+
+#### losing your admin key
+
+If you lost the admin key, and need to re-establish ownership of the
+gitolite-admin repository with a fresh key, get a shell on the server and use
+the program called `gl-admin-push` that comes with gitolite.  See instructions
+[here][adminpush].
+
+#### simulating ssh-copy-id
+
+don't have `ssh-copy-id`?  This is broadly what that command does, if you want
+to replicate it manually.  The input is your pubkey, typically
+`~/.ssh/id_rsa.pub` from your client/workstation.
+
+  * it copies it to the server as some file
+
+  * it appends that file to `~/.ssh/authorized_keys` on the server
+    (creating it if it doesn't already exist)
+
+  * it then makes sure that all these files/directories have go-w perms
+    set (assuming user is "git"):
+
+        /home/git/.ssh/authorized_keys
+        /home/git/.ssh
+        /home/git
+
+[Actually, `sshd` requires that even directories *above* `~` (`/`, `/home`,
+typically) also must be `go-w`, but that needs root.  And typically
+they're already set that way anyway.  (Or if they're not, you've got
+bigger problems than gitolite install not working!)]
+
+#### problems with using non-openssh public keys
+
+Gitolite accepts public keys only in openssh format.  Trying to use an "ssh2"
+key (used by proprietary SSH software) results in:
+
+    WARNING: a pubkey file can only have one line (key); ignoring YourName.pub
+
+To convert ssh2-compatible keys to openssh run:
+
+    ssh-keygen -i -f /tmp/ssh2/YourName.pub > /tmp/openssh/YourName.pub
+
+then use the resulting pubkey as you normally would in gitolite.
+
+#### windows issues
+
+On windows, I have only used msysgit, and the openssh that comes with it.
+Over time, I have grown to distrust putty/plink due to the number of people
+who seem to have trouble when those beasts are involved (I myself have never
+used them for any kind of git access).  If you have unusual ssh problems that
+just don't seem to have any explanation, try removing all traces of
+putty/plink, including environment variables, etc., and then try again.
+
+Thankfully, someone contributed [contrib/putty.mkd][contrib_putty].
+
+### #stsapp1_ appendix 1: ssh daemon asks for a password
+
+>   **NOTE**: This section should be useful to anyone trying to get
+>   password-less access working.  It is not necessarily specific to gitolite,
+>   so keep that in mind if the wording feels a little more general than you
+>   were expecting.
+
+You have generated a keypair on your workstation (`ssh-keygen`) and copied the
+public part of it (`~/.ssh/id_rsa.pub`, by default) to the server.
+
+On the server you have appended this file to `~/.ssh/authorized_keys`.  Or you
+ran something, like the `gl-setup` step during a gitolite install, which
+should have done that for you.
+
+You now expect to log in without having to type in a password, but when you
+try, you are being asked for a password.
+
+This is a quick checklist:
+
+  * make sure you're being asked for a password and not a pass*phrase*.  Do
+    not confuse or mistake a prompt saying `Enter passphrase for key
+    '/home/sitaram/.ssh/id_rsa':` for a password prompt from the remote
+    server!
+
+    When you create an ssh keypair using `ssh-keygen`, you have the option of
+    protecting it with a passphrase.  When you subsequently use that keypair
+    to access a remote host, your *local* ssh client needs to unlock the
+    corresponding private key, and ssh will probably ask for the passphrase
+    you set when you created the keypair.
+
+    You have two choices to avoid this prompt every time you try to use the
+    private key.  The first is to create keypairs *without* a passphrase (just
+    hit enter when prompted for one).  **Be sure to add a passphrase later,
+    once everything is working, using `ssh-keygen -p`**.
+
+    The second is to use `ssh-agent` (or `keychain`, which in turn uses
+    `ssh-agent`) or something like that to manage your keys.  Other than
+    discussing one more potential trouble-spot with ssh-agent (see below),
+    further discussion of ssh-agent/keychain is out of scope of this document.
+
+  * ssh is very sensitive to permissions.  An extremely conservative setup is
+    given below, but be sure to do this on **both the client and the server**:
+
+        cd $HOME
+        chmod go-rwx .
+        chmod -R go-rwx .ssh
+
+  * actually, every component of the path to `~/.ssh/authorized_keys` all the
+    way upto the root directory must be at least `chmod go-w`.  So be sure to
+    check `/` and `/home` also.
+
+  * while you're doing this, make sure the owner and group info for each of
+    these components are correct.  `ls -ald ~ ~/.ssh ~/.ssh/authorized_keys`
+    will tell you what they are.
+
+  * you may also want to check `/etc/ssh/sshd_config` to see if the "git" user
+    is allowed to login at all.  For example, if that file contains an
+    `AllowUsers` config entry, then only users mentioned in that line are
+    allowed to log in!
+
+  * some OSs/distributions require that the "git" user should have a password
+    and/or not be a locked account.  You may want to check that as well.
+
+  * if all that fails, log onto the server as root, `cd /var/log`, and look
+    for a file called `auth.log` or `secure` or some such name.  Look inside
+    this file for messages matching the approximate time of your last attempt
+    to login, to see if they tell you what is the problem.
+
+### #stsapp2_ appendix 2: which key is which -- running sshkeys-lint
+
+Follow these steps on the client:
+
+  * get a copy of `~/.ssh/authorized_keys` from the server and put it in
+    `/tmp/foo` or something
+
+  * cd to `~/.ssh`
+
+  * run `sshkeys-lint *.pub < /tmp/foo`
+
+This tells you, for each pubkey, what type of access (if any) it has to the
+server.
+
+Note that it is not trying to log in or anything -- it's just comparing bits
+of text (the contents of STDIN taken as an authkeys file, and the contents of
+each of the `*.pub` files one by one).
+
+>   Note: It's also a stand-alone program, so even if your gitolite version is
+>   old, you can safely bring over just this program from a more recent
+>   gitolite and use it, without having to upgrade gitolite itself.
+
+If the pubkey file you're interested in appears to have the correct access to
+the server, you're done with this step.
+
+Otherwise you have to rename some keypairs and try again to get the effect you
+need.  Be careful:
+
+  * do not just rename the ".pub" file; you will have to rename the
+    corresponding private key also (the one with the same basename but without
+    an extension)
+
+  * if you're running ssh-agent, you may have to delete (using `ssh-add -D`)
+    and re-add identities for it to pick up the renamed ones correctly
+
+#### typical cause(s)
+
+The admin often has passwordless shell access to `git at server` already, and
+then used that same key to get access to gitolite (i.e., copied that same
+pubkey as YourName.pub and ran `gl-setup` on it).
+
+As a result, the same key appears twice in the authkeys file now, and since
+the ssh server will always use the first match, the second occurrence (which
+invokes gitolite) is ignored.
+
+To fix this, you have to use a different keypair for gitolite access.  The
+best way to do this is to create a new keypair, copy the pubkey to the server
+as YourName.pub, then run `gl-setup YourName.pub` on the server.  Remember to
+adjust your agent identities using ssh-add -D and ssh-add if you're using
+ssh-agent, otherwise these new keys may not work.
+
+### #stsapp3_ appendix 3: ssh client may not be offering the right key
+
+  * make sure the right private key is being offered.  Run ssh in very
+    verbose mode and look for the word "Offering", like so:
+
+        ssh -vvv user at host pwd 2> >(grep -i offer)
+
+    If some keys *are* being offered, but not the key that was supposed to be
+    used, you may be using ssh-agent (next bullet).  You may also need to
+    create some host aliases in `~/.ssh/config` ([appendix 4][sshhostaliases]).
+
+  * (ssh-agent issues) If `ssh-add -l` responds with either "The agent has no
+    identities." or "Could not open a connection to your authentication
+    agent.", then you can skip this bullet.
+
+    However, if `ssh-add -l` lists *any* keys at all, then something weird
+    happens.  Due to a quirk in ssh-agent, ssh will now *only* use one of
+    those keys, *even if you explicitly ask* for some other key to be used.
+
+    In that case, add the key you want using `ssh-add ~/.ssh/YourName` and try
+    the access again.
+
+### F=sshhostaliases appendix 4: host aliases
+
+(or "making git use the right options for ssh")
+
+The ssh command has several options for non-default items to be specified.
+Two common examples are `-p` for the port number if it is not 22, and `-i` for
+the public key file if you do not want to use just `~/.ssh/id_rsa` or such.
+
+Git has two ssh-based URL syntaxes, but neither allows specifying a
+non-default public key file.  And a port number is only allowed in one of
+them.  (See `man git-clone` for details).  Finally, hosts often have to be
+referred with IP addresses (such is life), or the name is very long, or hard
+to remember.
+
+Using a "host" para in `~/.ssh/config` lets you nicely encapsulate all this
+within ssh and give it a short, easy-to-remember, name.  Example:
+
+    host gitolite
+        user git
+        hostname a.long.server.name.or.annoying.IP.address
+        port 22
+        identityfile ~/.ssh/id_rsa
+
+Now you can simply use the one word `gitolite` (which is the host alias we
+defined here) and ssh will infer all those details defined under it -- just
+say `ssh gitolite` and `git clone gitolite:reponame` and things will work.
+
+(By the way, the 'port' and 'identityfile' lines are needed only if you have
+non-default values, although I put them in anyway just to be complete).
+
+If you have *more than one* pubkey with access to the *same* server, you
+**must** use this method to make git pick up the right key.  There is no other
+way to do this, as far as I know.
+
+[tut]: http://sites.google.com/site/senawario/home/gitolite-tutorial
+
+### #stsapp5_ appendix 5: why bypassing gitolite causes a problem
+
+When you bypass gitolite, you end up running your normal shell instead of the
+special gitolite entry point script `gl-auth-command`.
+
+This means commands (like 'info') are interpreted by the shell instead of
+gitolite.
+
+It also means git operations look for repos in `$HOME`.
+
+However, gitolite places all your repos in a subdirectory pointed to by
+`$REPO_BASE` in the rc file (default: `repositories`), and internally prefixes
+this before calling the actual git command you invoked.  Thus, the pathname of
+the repo that you use on the client is almost never the correct pathname on
+the server.  (This is by design.  Don't argue...)
+
+This means that, you get 2 kinds of errors if you bypass gitolite
+
+  * when you use `git at server:reponame` with a key that bypasses gitolite
+    (i.e., gets you a shell), this prefixing does not happen, and so the repo
+    is not found.  Neither a clone/fetch nor a push will work.
+
+  * conversely, consider `git at server:repositories/reponame.git`.  The clone
+    operation will work -- you're using the full Unix path, (assuming default
+    `$REPO_BASE` setting), and so the shell finds the repo where you said it
+    would be.  However, when you push, gitolite's **update hook** kicks in,
+    and fails to run because some of the environment variables it is expecting
+    are not present.
diff --git a/doc/extras/ssh.mkd b/doc/extras/ssh.mkd
new file mode 100644
index 0000000..a5ee5d4
--- /dev/null
+++ b/doc/extras/ssh.mkd
@@ -0,0 +1,11 @@
+# ssh
+
+There are two documents you need to read, in order:
+
+  * [gitolite and ssh][glssh] -- this explains how gitolite uses openssh's
+    features to provide any number of virtual users over just one actual
+    (unix) user, and so on
+
+  * [ssh troubleshooting][sshts] -- this is a rather long document but as far
+    as I know almost every known ssh related issue is in here.  If you find
+    something missing, send me an email with details.
diff --git a/doc/extras/unique.mkd b/doc/extras/unique.mkd
new file mode 100644
index 0000000..3864769
--- /dev/null
+++ b/doc/extras/unique.mkd
@@ -0,0 +1,6 @@
+# unique setups
+
+This page is for unique setups that I support.  At present there is only one
+-- Fedora.
+
+
diff --git a/doc/g2alt.mkd b/doc/g2alt.mkd
new file mode 100644
index 0000000..a578805
--- /dev/null
+++ b/doc/g2alt.mkd
@@ -0,0 +1,10 @@
+## #g2alt alternate implementations for some g2 features
+
+The following g2 features have been dropped but suitable (or better)
+alternatives exist.
+
+### gl-time for performance measurement
+
+Take a look at the 'cpu-time' program that you can set to run from the
+`POST_GIT` trigger.  Just set it to run as the last program in that sequence
+so it covers all previous programs.
diff --git a/doc/g2dropped.mkd b/doc/g2dropped.mkd
new file mode 100644
index 0000000..ab5f63c
--- /dev/null
+++ b/doc/g2dropped.mkd
@@ -0,0 +1,2 @@
+## #g2dropped g2 features dropped
+
diff --git a/doc/g2incompat.mkd b/doc/g2incompat.mkd
new file mode 100644
index 0000000..c425ec2
--- /dev/null
+++ b/doc/g2incompat.mkd
@@ -0,0 +1,35 @@
+## #g2incompat compatibility with g2
+
+The following incompatibilities exist, in vaguely decreasing order of
+severity.  **The ones in the first section are IMPORTANT because they allow
+access that was previously not allowed -- please fix your config before using
+the new gitolite!**
+
+### fallthru in NAME rules
+
+Fallthru on all VREFs is "success" now, so any NAME/ rules you have **MUST**
+change the ruleset in some way.  The simplest is to add the following line to
+the end of each repo's rule list:
+
+        -   NAME/       =   @all
+
+### subconf command in admin repo
+
+(This is also affected by the previous issue, 'fallthru in NAME rules'; please
+read that as well).
+
+If you're using delegation in your admin conf setup, please add the following
+lines to the end of the gitolite-admin rules in your conf/gitolite.conf file:
+
+    repo gitolite-admin
+        -   NAME/       =   @all
+
+    subconf "fragments/*.conf"
+
+The first part compensates for fallthru now being a success when processing
+[VREF][vref] rules (NAME rules are just one specific VREF).  Although,
+**ideally**, you should change your ruleset so that you no longer require that
+line.  As the [vref documentation][vref] says:
+
+>   **Virtual refs are best used as additional "deny" rules**, performing
+>   extra checks that core gitolite cannot.
diff --git a/doc/g2migr.mkd b/doc/g2migr.mkd
new file mode 100644
index 0000000..21cc4b2
--- /dev/null
+++ b/doc/g2migr.mkd
@@ -0,0 +1,122 @@
+## #g2migr migrating from g2
+
+<font color="red">
+
+**This document is a *MUST* read if you are currently using g2 and want to
+move to g3.**
+
+</font>
+
+First things first: g2 will be supported for a good long time.  My current
+*expert* users do not cause me any load anyway.
+
+Migration should be straightforward, but it is not automatic.  When you first
+run "gitolite setup [...]", gitolite3 will try to detect at least the big
+problems.  However, converting the RC file and the conf files is (as of now)
+still a manual exercise, though not very difficult.
+
+You must first read about [incompatible][g2incompat] features and
+[dropped][g2dropped] features.  Some features have been replaced with
+[alternatives][g2alt].
+
+Since the majority of changes are in the rc file, we list them all here.
+
+### rc file differences
+
+**DROPPED** variables (possible high impact): these could be show-stoppers for
+migration, at least for now.
+
+  * `BIG_INFO_CAP` -- if you think you must have this, try it without and see
+    if there's a difference.  If you *know* you need this, convince me.
+
+  * `GL_ALL_READ_ALL` -- same
+
+  * `GL_NO_CREATE_REPOS` -- if you think you need this, email me.  I know one
+    group who does need this so I will be putting it in eventually but not
+    right away.
+
+  * `HTPASSWD_FILE`, `RSYNC_BASE`, `SVNSERVE` -- need work.  Email me if you
+    are using any of these.
+
+  * `GL_GET_MEMBERSHIPS_PGM` -- is on my todo list
+
+  * `GL_LOGT` -- is now fixed; you can't change it.  Email me if this is a
+    problem.
+
+**DROPPED** variables (medium impact): these have alternative implementations
+or mechanisms, but you have to do some setup work.
+
+  * `GL_ADMINDIR` -- this is now at a fixed location: `~/.gitolite`.  If you
+    want it somewhere else go ahead and move it, then place a symlink from the
+    assumed location to the real one.
+
+  * `REPO_BASE` -- this is now at a fixed location: `~/repositories`.  If you
+    want it somewhere else go ahead and move it, then place a symlink from the
+    assumed location to the real one.
+
+  * `PROJECTS_LIST` -- it's called `GITWEB_PROJECTS_LIST` now, but more
+    importantly, it is only used by update-gitweb-access-list in
+    src/commands/post-compile.  This variable now has nothing to do with
+    gitolite core, and the rc is just helping to store settings for external
+    programs like that one.
+
+    `WEB_INTERFACE` and `GITWEB_URI_ESCAPE` are also gone; patches to the
+    update program to directly do those things are welcome.
+
+  * `GL_NO_DAEMON_NO_GITWEB` -- uncomment the appropriate lines in the rc
+    file, in both the `POST_COMPILE` and `POST_CREATE` trigger sections.
+
+  * `NICE_VALUE` -- use the `PRE_GIT` trigger to attach a program that renices
+    the pid given by $GL_TID (that's the pid of the initial gitolite entry
+    point, usually gitolite-shell, and propagates from there once set).
+
+    You may have to add this list to the rc file; if you don't know perl use
+    one of the others as a model or ask me.
+
+  * `GIT_PATH` -- gone, not needed.  Just add these lines to the end of the rc
+    file:
+
+        $ENV{PATH}="...whatever you want...";
+        1;
+
+  * `GL_NO_SETUP_AUTHKEYS` -- comment out the lines that call ssh-authkeys, in
+    the rc file.
+
+  * `GL_WILDREPOS_DEFPERMS` -- if you need this, add a `POST_CREATE` script
+    that does it.  Or email me and I will write it for you.
+
+  * `UPDATE_CHAINS_TO` -- use a [vref][] instead.  You can directly use the
+    chained-to script as a VREF; it'll work.
+
+  * `ADMIN_POST_UPDATE_CHAINS_TO` -- add your script to the `POST_COMPILE`
+    trigger chain.  You won't be getting any arguments but for the admin repo
+    the only argument that ever comes in is "refs/heads/master" anyway.
+
+  * `GL_ADC_PATH` -- obsolete; use [commands][] or add [your own][dev-notes].
+
+  * `GL_ALL_INCLUDES_SPECIAL` -- obsolete; @all always includes gitweb and
+    daemon now.  Use [deny-rules][] if you want to say `R = @all` but not have
+    it be visible to gitweb or daemon.
+
+  * `GL_PERFLOGT` -- see the entry for "gl-time" in the [alternative
+    implementations][g2alt] page.
+
+**DROPPED** variables (no impact/low impact): these variables should not
+actually affect anything anyway, so even if you had them set you should not
+feel their loss.
+
+  * `GL_CONF`, `GL_KEYDIR`, and `GL_CONF_COMPILED` -- you had no business
+    touching these anyway; if you did, move them into the expected default
+    locations before attempting to run `gitolite setup`
+  * `GL_PACKAGE_HOOKS` -- not needed anymore, but check if you had any custom
+    hooks set there and copy them across.
+  * `GL_WILDREPOS` -- dropped; this feature is default now.
+  * `GL_BIG_CONFIG` -- dropped; this feature is default now.
+
+**RENAMED** variables (no impact): these are functionally the same but are
+renamed.
+
+  * `REPO_UMASK` is now `UMASK`
+  * `GL_GITCONFIG_KEYS` is now `GITCONFIG_KEYS`
+  * `GL_WILDREPOS_PERM_CATS` is now the ROLES hash in the rc file
+  * `GL_SITE_INFO` is not `SITE_INFO`
diff --git a/doc/g3why.mkd b/doc/g3why.mkd
new file mode 100644
index 0000000..ae6cad0
--- /dev/null
+++ b/doc/g3why.mkd
@@ -0,0 +1,97 @@
+# why a completely new version?
+
+Gitolite started life as 400 lines of perl written on a weekend because I was
+quickly losing control of my projects at work, exacerbated by git newbies
+doing all the wrong things.  I really needed it!
+
+That little 400 line thing is now a huge bunch of programs that do all sorts
+of things (I mean, rsync access control in a git related program?  WTF!),
+because it kinda just *grew* while I wasn't looking.
+
+So, briefly, here are the advantages of g3:
+
+  * compile versus everything else
+
+    g2's "compile" script was doing way, way too much.  For example, dealing
+    with gitweb and git-daemon was a good chunk of code in g2.  In contrast,
+    here's how g3 generates gitweb's projects.list file:
+
+        (
+            gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
+            gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
+        ) |
+            cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
+
+  * core versus non-core
+
+    That's just the tip of the iceberg.  The commands above run from a script
+    that is itself outside gitolite, and can be enabled and disabled from the
+    rc file.  There are six different "events" within gitolite that can
+    trigger external programs, with specific arguments passed to them, much
+    like git's own hooks.  The example you saw is called from the
+    "POST_COMPILE" trigger.
+
+    And as you can see, these programs be in any language.
+
+  * get/set perms/desc, and ADCs
+
+    I've always wanted to kick setperms out of core and make it an ADC.
+    Sadly, it couldn't be done because when you update your repo's permissions
+    using setperms, that can affect gitweb/daemon access, which -- you guessed
+    right -- feeds back into the main code in complex ways.  It *had* to be an
+    "inside job".
+
+    But now, the new 'perms' program is quite external to gitolite.  And how
+    does it fix up gitweb/daemon permissions after it is done updating the
+    "gl-perms" file?
+
+        system("gitolite", "trigger", "POST_CREATE");
+
+  * syntax versus semantics
+
+    I got tired of people asking things like "why can't I have
+    backslash-escaped continuation lines?"  I designed it differently because
+    I don't like them but perhaps it's reasonable for some people.
+
+    Someone else wanted to use subdirectories of 'keydir' as group names.  Why
+    not?
+
+    G3 comes with a stackable set of "syntactic sugar" helpers.  And you can
+    write your own, though they do have to be in perl (because they're not
+    standalone programs).
+
+    Once the code is written and placed in the right place, all a site has to
+    do to enable it is to uncomment some lines in the rc file:
+
+        # these will run in sequence during the conf file parse
+        SYNTACTIC_SUGAR             =>
+            [
+                # 'continuation-lines',
+                # 'keysubdirs-as-groups',
+            <etc>
+
+  * roll your own
+
+    Having a decent shell API helps enormously.  You saw an example above but
+    how about if your boss asks you "I need a list of everyone who *currently*
+    has read access to the 'foo' repo"?
+
+    Sure you could look in conf/gitolite.conf, all its include files (if you
+    have any), and if the repo is user-created, then in its gl-perms.
+
+    Or you could do something like this:
+
+        gitolite list-users | gitolite access foo % R any | cut -f1
+
+  * over-engineered
+
+    g2 was, to some extent, over-engineered.  One of the best examples is the
+    documentation on hook-propagation in g2, which required even a *picture*
+    to make clear (always a bad sign).  In g3, the [hooks][] section is 4
+    sentences.
+
+Anyway you get the idea.
+
+The point is not that you can do all these cool tricks.  The point is they are
+possible because of the redesign.  There is no way on God's green earth I
+could have done this with the old code.
diff --git a/doc/group.mkd b/doc/group.mkd
new file mode 100644
index 0000000..f9e14b0
--- /dev/null
+++ b/doc/group.mkd
@@ -0,0 +1,32 @@
+# parts of the conf file
+
+## #group group definitions
+
+You can group repos or users for convenience.  The syntax is the same for both
+and does not distinguish; until you *use* the group name it could really be
+either.
+
+Here's an example:
+
+    @developers     =   dilbert alice wally
+
+Group definitions accumulate; this is the same as the above:
+
+    @developers     =   dilbert
+    @developers     =   alice
+    @developers     =   wally
+
+You can use one group in another group definition; the values will be expanded
+right there (meaning later additions will not appear in the second group).
+
+    @developers     =   dilbert alice
+    @interns        =   ashok
+    @staff          =   @interns @developers
+    @developers     =   wally
+
+    # wally is NOT part of @staff
+
+### special group `@all`
+
+`@all` is a special group name that is often convenient to use if you really
+mean "all repos" or "all users".
diff --git a/doc/hooks.mkd b/doc/hooks.mkd
new file mode 100644
index 0000000..499714d
--- /dev/null
+++ b/doc/hooks.mkd
@@ -0,0 +1,9 @@
+# hooks and gitolite
+
+Gitolite uses the `update` hook for all repos.  In addition, it uses the
+`post-update` hook for the gitolite-admin repo.
+
+If you want to add your own hook, it's easy as long as it's not the 'update'
+hook.  Just add it to `$HOME/.gitolite/hooks/common` and run `gitolite setup`.
+
+The rest is between you and 'man githooks' :-)
diff --git a/doc/index.mkd b/doc/index.mkd
new file mode 100644
index 0000000..0756294
--- /dev/null
+++ b/doc/index.mkd
@@ -0,0 +1,67 @@
+# Hosting git repositories
+
+Gitolite allows you to setup git hosting on a central server, with
+fine-grained access control and many more powerful features.
+
+Here's more on [what][] it is and [why][] you might need it.
+
+For current gitolite (call it "g2" for convenience) users,
+
+  * [Why][g3why] I rewrote gitolite.
+  * Development [status][dev-status] (**should change often for a while**)
+  * Specific migration [issues and steps][g2migr].
+
+Quick links:
+
+  * [Minimum requirements][minreq].
+  * Here's how to [get started][qi] installing and setting it up
+  * Don't know ssh well enough?  [Learn][ssh].  It's **IMPORTANT**.
+  * [Add users and repos][add].
+  * Learn about fine-grained access control with the [conf][] file
+  * Explain gitolite to your [users][].
+
+Not so "quick" links:
+
+  * The "master table of contents" link at the top of each page is the
+    **first** place you should check when looking for anything.
+
+  * Want to do more than just add users and repos and control their access?
+    Here's how to [customise][cust] your server installation.
+
+Additional reading for Unix newbies:
+
+  * [Regular Expressions][regex]
+
+## #what What is gitolite?
+
+Gitolite is an access control layer on top of git.  Here are the features that
+most people see:
+
+  * use a single unix user ("real" user) on the server
+  * provide access to many gitolite users
+      * they are not "real" users
+      * they do not get shell access
+  * control access to many git repositories
+      * read access controlled at the repo level
+      * write access controlled at the branch/tag/file/directory level,
+        including who can rewind, create, and delete branches/tags
+  * can be installed without root access, assuming git and perl are already
+    installed
+  * authentication is most commonly done using sshd, but you can also use
+    httpd if you prefer (this may require root access).
+
+## #contact contact
+
+  * author: sitaramc at gmail.com, sitaram at atc.tcs.com
+  * mailing list: gitolite at googlegroups.com
+  * list subscribe address : gitolite+subscribe at googlegroups.com
+  * IRC: #git and #gitolite on freenode.  Note that I live in India (UTC+0530
+    time zone).
+
+## #license license
+
+The gitolite *code* is released under GPL v2.  See COPYING for details.
+
+The gitolite documentation is provided under a [Creative Commons
+Attribution-NonCommercial-ShareAlike 3.0 Unported
+License](http://creativecommons.org/licenses/by-nc-sa/3.0/).
diff --git a/doc/install.mkd b/doc/install.mkd
new file mode 100644
index 0000000..bc08162
--- /dev/null
+++ b/doc/install.mkd
@@ -0,0 +1,58 @@
+# different ways to install gitolite
+
+Gitolite has only one server side "command" now, much like git itself.  And
+it's been designed so that you don't even really have to *install* it, as you
+will see.
+
+## simplest
+
+1.  Put all of `src` in one place, doesn't matter where; let's call it
+    /foo/bar.
+
+2.  Use the full path to run any gitolite commands, for example:
+
+        /foo/bar/gitolite setup -pk sitaram.pub
+
+## almost as simple
+
+1.  (same as above)
+
+2.  Symlink /foo/bar/gitolite to some directory that is on your PATH.  For
+    example:
+
+        ln -sf /foo/bar/gitolite ~/bin
+
+    Now you can just say
+
+        gitolite setup -pk sitaram.pub
+
+## packagers
+
+1.  Put src/Gitolite in `/usr/share/perl5/vendor_perl` or some such place.
+
+2.  Put the rest of src anywhere your distro policy allows.  (Fedora keeps
+    git's 150 executables in /usr/libexec/git-core, so maybe
+    /usr/libexec/gitolite?)
+
+3.  Symlink 'gitolite' to /usr/bin or something, similar to step 2 above,
+
+    OR
+
+    Put it directly in /usr/bin, and hardcode `GL_BINDIR` into it to tell it
+    where the others are.  I'd prefer it if you did not do this but you can.
+
+----
+
+Bottom line:
+
+  * `GL_BINDIR` must point to a place that contains `commands`, `VREF`, and
+    `syntactic-sugar` (so they must all be sibling directories).
+  * The `Gitolite` directory can also be there, or it can be anywhere in
+    perl's `@INC` path.
+
+## upgrading
+
+Just put the new version on top of wherever you kept the old one.  That's it.
+
+If you feel it should require a little more effort, pretend I said "you have
+to then run `gitolite setup`".  Can't hurt...
diff --git a/doc/list b/doc/list
new file mode 100644
index 0000000..b9f7411
--- /dev/null
+++ b/doc/list
@@ -0,0 +1,43 @@
+
+index.mkd
+why.mkd
+
+g3why.mkd
+dev-status.mkd
+g2migr.mkd
+g2incompat.mkd
+g2dropped.mkd
+g2alt.mkd
+
+minreq.mkd
+qi.mkd
+install.mkd
+add.mkd
+conf.mkd
+users.mkd
+
+rc.mkd
+cust.mkd
+
+group.mkd
+repo.mkd
+rules.mkd
+refex.mkd
+write-types.mkd
+
+dev-notes.mkd
+commands.mkd
+hooks.mkd
+triggers.mkd
+sugar.mkd
+vref.mkd
+
+misc.mkd
+pw.mkd
+testing.mkd
+extras/auth.mkd
+extras/nagp.mkd
+extras/regex.mkd
+extras/ssh.mkd
+extras/gitolite-and-ssh.mkd
+extras/ssh-troubleshooting.mkd
diff --git a/doc/minreq.mkd b/doc/minreq.mkd
new file mode 100644
index 0000000..c3ab4f4
--- /dev/null
+++ b/doc/minreq.mkd
@@ -0,0 +1,32 @@
+# minimum requirements for gitolite
+
+**Client**:
+
+  * git 1.6.6 or greater
+  * an ssh client that can talk to an openssh server, and can generate keys in
+    openssh's default format (the pubkey is just one long line).  Gitolite
+    will [not currently][pw1] convert such keys.
+
+For people still using Windows, msysgit works fine.  If you're using
+[putty/plink][ens], God bless you.  (It'll work, but I still want him to bless
+you).
+
+TODO: when smart http support works, ssh will no longer be a *requirement*,
+merely a *strong* suggestion :-)
+
+**Server**
+
+  * git 1.6.6 or greater
+  * perl 5.8.8 or greater
+  * an ssh server compatible with openssh, especially it's authorized keys
+    file format and features.
+  * any Unix or Unix like OS.  That said, I've occasionally had some weird
+    reports from [Mac OSX servers][ens]; good luck.
+  * a single, dedicated, userid to host it (usually 'git' or 'gitolite').
+
+These version numbers are subject to fine-tuning as I get feedback and make
+fixes where possible and needed.
+
+Sshd must be configured so that each users authkeys file is in the user's
+`$HOME`, inside `.ssh/authorized_keys`, and not in some central
+/var/something.
diff --git a/doc/misc.mkd b/doc/misc.mkd
new file mode 100644
index 0000000..0b8c9e7
--- /dev/null
+++ b/doc/misc.mkd
@@ -0,0 +1,80 @@
+# odds and ends
+
+Most of these items don't fit anywhere or fit in more than one place or are of
+the nature of background information.
+
+## #include include files
+
+Gitolite allows you to break up the configuration into multiple files and
+include them in the main file for convenience.
+
+    include     "foo.conf"
+
+will include the contents of the file "foo.conf" from the "conf" directory.
+
+Details:
+
+  * You can also use a glob (`include "*.conf"`), or put your include files
+    into subdirectories of "conf" (`include "foo/bar.conf"`), or both
+    (`include "repos/*.conf"`).
+
+  * Included files are always searched relative to the gitolite-admin repo's
+    "conf/" directory.
+
+  * If you ended up recursing, files that have been already processed once are
+    skipped, with a warning.
+
+<font color="gray">Advanced users: `subconf`, a command that is very closely
+related to `include`, is documented [here][subconf].</font>
+
+## #deny-rules applying deny rules at the pre-git access check
+
+The access [rules][] rules section describes the problem.  To recap, you want
+this:
+
+    @staff          =   alice bob wally ashok
+
+    repo foo
+        RW+         =   alice       # line 1
+        RW+ dev     =   bob         # line 2
+        -           =   wally       # line 3
+        RW  temp/   =   @staff      # line 4
+
+to deny Wally even *read* access.
+
+The way to do this is to add this line to the repo:
+
+    option deny-rules = 1
+
+If you want this for all your repos, just add this somewhere at the top of
+your conf file
+
+    repo @all
+        option deny-rules = 1
+
+## #rule-accum rule accumulation
+
+Gitolite was meant to collect rules from multiple places and apply them all.
+For example, this:
+
+    repo foo
+        RW  =   u1
+
+    @gr1 = foo bar
+
+    repo @gr1
+        RW  =   u2
+        R   =   u3
+
+    repo @all
+        R   =   gitweb
+
+is effectively the same as this, for repo foo:
+
+    repo foo
+        RW  =   u1
+        RW  =   u2
+        R   =   u3
+        R   =   gitweb
+
+This extends to patterns also, but I'll leave an example for later.
diff --git a/doc/mkdoc b/doc/mkdoc
new file mode 100755
index 0000000..3d977fc
--- /dev/null
+++ b/doc/mkdoc
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+
+my $MKD = "$ENV{HOME}/Markdown.pl";
+
+# run this like so:
+
+#       $0 *.mkd >mt.mkd 2>mf
+
+use 5.10.0;
+use strict;
+use warnings;
+
+chomp(@ARGV = `cat list`) if not @ARGV;
+ at ARGV = grep { $_ ne 'master-toc.mkd' and /./ } @ARGV;
+my @save = @ARGV;
+my $css = join("", <DATA>);
+
+my $mt = "# gitolite master table of contents/index\n";
+my $mf = '';
+my $fh;
+
+while (<>) {
+    $ARGV =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
+    my $b = $1;
+
+    if (/^(#+) (?:#(\S+) )?(.*)/) {
+        if ( length($1) == 1 ) {
+            $mt .= "\n";
+            $mt .= "  * [$3][$b]\n";
+            $mf .= "[$b]: $b.html\n";
+        } else {
+            $mt .= " " x ( 4 * ( length($1) - 1 ) );
+            $mt .= "  * ";
+            $mt .= (
+                $2
+                ? "[$3][$2]"
+                : "$3"
+            );
+            $mt .= "\n";
+            $mf .= "[$2]: $b.html#$2\n" if $2;
+        }
+    }
+}
+
+open($fh, ">", "master-toc.mkd")
+  and print $fh $mt
+  and close $fh;
+
+# after this, do this for every mkd (including the master-toc.mkd)
+
+#       cat $css_block > $base.html
+#       cat $base.mkd $mf | $MKD >> $base.html
+
+for my $mkd ("master-toc.mkd", @save) {
+    $mkd =~ /^(?:.*\/)?([^\/]+)\.mkd$/;
+    my $b = $1;
+
+    open($fh, ">", "../html/$b.html")
+      and print $fh $css
+      and close $fh;
+
+    my $mkt = `cat $mkd`;
+    $mkt =~ s/^(#+) #(\S+) /$1 <a name="$2"><\/a> /mg;
+    open($fh, "|-", "$MKD >> ../html/$b.html")
+      and print $fh $mkt, $mf
+      and close $fh;
+}
+
+__DATA__
+
+<head><style>
+    body        { background: #fff; margin-left:  40px;   font-size:  0.9em;  font-family: sans-serif; max-width: 800px; }
+    h1          { background: #ffb; margin-left: -30px;   border-top:    5px  solid #ccc; }
+    h2          { background: #ffb; margin-left: -20px;   border-top:    3px  solid #ddd; }
+    h3          { background: #ffb; margin-left: -10px; }
+    h4          { background: #ffb; }
+    code        { font-size:    1.1em;  background:  #ddf; }
+    pre         { margin-left:  2em;    background:  #ddf; }
+    pre code    { font-size:    1.1em;  background:  #ddf; }
+</style></head>
+
+<p style="text-align:center">
+    <a href="master-toc.html">master TOC</a>
+|
+    <a href="index.html">main page</a>
+|
+    <a href="index.html#license">license</a>
+</p>
diff --git a/doc/pw.mkd b/doc/pw.mkd
new file mode 100644
index 0000000..ceaf995
--- /dev/null
+++ b/doc/pw.mkd
@@ -0,0 +1,45 @@
+# patches welcome :-)
+
+These are places where I could use some help, with hints about how you would
+go about it.  In addition, any of the items in [dev-status][] are up for
+grabs, if you wish to jump in.  But let me know before you start, and how you
+plan to go about it.
+
+## #pw2 rsync
+
+The crux of the old rsync ADC was an access check to a repo whose name starts
+with EXTCMD.  g2 would treat repos whose names start with EXTCMD as "don't
+create this repo".  g3 makes no such distinctions, but you can get the same
+effect by using a repo group name that does not exist:
+
+    repo @rsync-set1
+        ...rules...
+
+and check that in the script.  (It might mean adding one more function to
+Gitolite::Easy but that's... easy!)
+
+## #pw1 converting non-openssh pubkeys automatically
+
+Gitolite's [triggers][] has can be used to fix this.  Here's pseudo-code:
+
+    chdir $(gitolite query-rc -n GL_ADMIN_BASE)/keydir
+    for each *.pub file in this directory and its subdirectories
+        detect its format and convert it
+        # just overwrite it; the original is saved in git anyway
+
+Let's say the program is called "convert-non-openssh-keys".  You send it to me
+and I add it to src/commands/post-compile/.  Then I update the default rc file
+to look at least like this instead
+
+    POST_COMPILE                =>
+        [
+            # 'post-compile/convert-non-openssh-keys',
+            'post-compile/ssh-authkeys',
+                ...
+                ...
+        ],
+
+making sure to place it *before* ssh-authkeys, and anyone who needs it can
+just uncomment it.
+
+Done!
diff --git a/doc/qi.mkd b/doc/qi.mkd
new file mode 100644
index 0000000..3337070
--- /dev/null
+++ b/doc/qi.mkd
@@ -0,0 +1,26 @@
+# getting started
+
+The quickest install, assuming your `$PATH` contains `$HOME/bin`, is:
+
+  * get the software
+
+        git clone git://github.com/sitaramc/gitolite
+
+        # (until this becomes "master")
+        cd gitolite
+        git checkout -f g3
+
+  * install it
+
+        ln -sf $PWD/src/gitolite $HOME/bin
+
+If you don't like that, there are [other install methods][install].
+
+Once the install is done, setup:
+
+    gitolite setup -pk your-name.pub
+
+And that's it.
+
+Next steps are usually [adding][add] users and repos and learning about
+[access control][conf].
diff --git a/doc/rc.mkd b/doc/rc.mkd
new file mode 100644
index 0000000..03613d0
--- /dev/null
+++ b/doc/rc.mkd
@@ -0,0 +1,20 @@
+# the "rc" file ($HOME/.gitolite.rc)
+
+The rc file for g3 is quite different from that of g2.  It has been designed
+to be (a) the only thing unique to your site, for most installations and (b)
+easy to extend when new needs show up, without having to touch core gitolite.
+
+g2 had a nasty rc file where every variable had to be declared.  As a result,
+ADCs that needed their own settings could not use it.
+
+Now it's a perl hash, and you can add any keys you want.
+
+Please look at the rc file that gets installed when you setup gitolite.  As
+you can see there are 3 types of variables in it:
+
+  * simple variables (like UMASK)
+  * lists (like `POST_COMPILE`, `POST_CREATE`)
+  * hashes (like `ROLES`, `COMMANDS`)
+
+Their purposes are to be found in each of their individual documentation files
+around; start with [customising gitolite][cust].
diff --git a/doc/refex.mkd b/doc/refex.mkd
new file mode 100644
index 0000000..e7d8aad
--- /dev/null
+++ b/doc/refex.mkd
@@ -0,0 +1,30 @@
+## #refex matching a ref and a refex
+
+A refex is a word I made up to mean "a regex that matches a ref".  If you know
+[regular expressions][regex] you're halfway there.
+
+The only extra info you need is:
+
+  * for convenience, a refex not starting with `refs/` is assumed to start
+    with `refs/heads/`.  This means normal branches can be referred to like
+    this:
+
+        RW  master      =   alice
+        # becomes 'refs/heads/master' internally
+
+    while tags will need to be fully qualified
+
+        RW  refs/tags/v[0-9]    =   bob
+
+  * a refex is implicitly anchored at the start, but not at the end.  In
+    regular expression lingo, a `^` is assumed at the start (but no `$` at the
+    end is assumed).  So a refex of `master` will allow all these:
+
+        refs/heads/master
+        refs/heads/master1
+        refs/heads/master2
+        refs/heads/master/full
+
+    If you want to restrict to just the one specific ref, use
+
+        RW  master$     =   alice
diff --git a/doc/repo.mkd b/doc/repo.mkd
new file mode 100644
index 0000000..2c3509b
--- /dev/null
+++ b/doc/repo.mkd
@@ -0,0 +1,23 @@
+## #repo repo definitions
+
+Example:
+
+    repo gitolite tsh gitpod
+        RW+     =   sitaram
+        RW  dev =   alice bob
+        R       =   @all
+
+The "repo" line can have any number of repo names or repo group names in it.
+However, it can only be one line; this will not work
+
+    repo foo
+    repo bar    # WRONG; 'foo' is now forgotten
+        RW      =   alice
+
+If you have too many, use a group name:
+
+    @myrepos    =   foo
+    @myrepos    =   bar
+
+    repo @myrepos
+        RW      =   alice
diff --git a/doc/rules.mkd b/doc/rules.mkd
new file mode 100644
index 0000000..c54b43f
--- /dev/null
+++ b/doc/rules.mkd
@@ -0,0 +1,82 @@
+## #rules access rules
+
+This is arguably the most complex part of day-to-day gitolite.  There are
+several interconnected ideas that make this hard to lay out easily if you're
+totally new to this, so read carefully.
+
+We will use this as a running example:
+
+    @staff          =   dilbert alice wally bob
+
+    repo foo
+        RW+         =   dilbert     # line 1
+        RW+ dev     =   alice       # line 2
+        -           =   wally       # line 3
+        RW  temp/   =   @staff      # line 4
+        R           =   ashok       # line 5
+
+### when does gitolite check access
+
+The "pre-git" check is before git is invoked.  Gitolite knows the repo name,
+user name, and attempted access (R or W), but no ref name.
+
+The "update" check is only for write operations, and it is just before git
+updates a ref.  This time gitolite knows the refname also.
+
+### how is a particular rule line matched
+
+For the **pre-git check**, any permission that contains "R" matches a read
+operation, and any permission that contains "W" matches a write operation.
+This is because we simply don't know enough to make finer distinctions at this
+point.
+
+In addition, *gitolite ignores deny rules during the pre-git check*.  <font
+color="gray">(You can [change this][deny-rules] if you wish, though it's
+rarely needed)</font>.  This means line 3 is ignored, and so Wally in our
+example will pass the pre-git check.
+
+For the **update check**, git gives us all the information we need.  Then:
+
+  * all the rules for a repo are [accumulated][rule-accum]
+
+  * then the rules pertaining to this repo *and* this user (or to a group to
+    which they belong, respectively) are kept; the rest are ignored
+
+  * these rules are examined *in the sequence they appeared in the conf file*.
+    For each rule:
+
+      * if the ref does not match the [refex][], the rule is skipped
+      * if it's a deny rule (the permissions field is a `-`), access is
+        **rejected** and the matching stops
+      * if the permission field matches the specific [type of
+        write][write-types] operation, access is **allowed** and the matching
+        stops
+
+  * if no rule ends with a decision, ("fallthru"), access is **rejected**.
+
+Now you need to understand how [refex][] matching happens and how the
+permissions match the various [types of write operations][write-types].
+
+Using these, you can see, in our example, that:
+
+  * everyone, even wally, can read the repo.
+  * dilbert can push, rewind, or delete any ref.
+  * alice can push, rewind, or delete any ref whose name starts with 'dev';
+    see [refex][] for details.
+  * alice can also push (but not rewind or delete) any ref whose name starts
+    with 'temp/'.  This applies to bob also.
+  * if it weren't for line 3, the previous statement would apply to wally
+    also.
+
+Interestingly, wally can get past the pre-git check because gitolite ignores
+deny rules for pre-git, but having got past it, he can't actually do anything.
+That's by design, and as I said if you don't like it you can ask gitolite to
+[deny at pre-git][deny-rules].
+
+### summary of permissions
+
+The full set of permissions, in regex syntax: `-|R|RW+?C?D?M?`.  This expands
+to one of `-`, `R`, `RW`, `RW+`, `RWC`, `RW+C`, `RWD`, `RW+D`, `RWCD`, or
+`RW+CD`, all but the first one optionally followed by an `M`.  And by now you
+know what they all mean.
+
diff --git a/doc/sugar.mkd b/doc/sugar.mkd
new file mode 100644
index 0000000..016de55
--- /dev/null
+++ b/doc/sugar.mkd
@@ -0,0 +1,11 @@
+# syntactic sugar
+
+Sugar scripts help you change the perceived syntax of the conf language.  The
+base syntax of the language is as described [here][conf], so sugar scripts
+take something *else* and convert it into that.
+
+That way, the admin sees additional features (like allowing continuation
+lines), while the parser in the core gitolite engine does not change.
+
+If you want to write your own sugar scripts, please read the "your own sugar"
+section in [dev-notes][] first then email me.
diff --git a/doc/testing.mkd b/doc/testing.mkd
new file mode 100644
index 0000000..feb2ad7
--- /dev/null
+++ b/doc/testing.mkd
@@ -0,0 +1,31 @@
+# testing gitolite
+
+Here's how to *run* the tests:
+
+    git clone git://github.com/sitaramc/gitolite
+    cd gitolite
+    git checkout -f g3
+
+    # if you're not ok with your ~/.ssh getting clobbered
+    prove
+
+    # if you're ok with your ~/.ssh getting clobbered
+    # prove t/*.t t/ssh*
+
+Gitolite's test suite is mostly written using [tsh][] -- the "testing shell".
+Take a look at some of the scripts and you will see what it looks like.  It
+has a few quirks and nuances; if you really care, email me.
+
+[tsh]: http://github.com/sitaramc/tsh
+
+The tests also use a somewhat convoluted system of environment variables in
+order to run *entirely* as a local user, without going through ssh at all.
+This lets a complete test suite run in about a fifth or less of the time it
+would otherwise take.
+
+If you think that defeats the purpose of the testing, you haven't read
+[this][auth] yet.
+
+There are 2 specific tests that deal with ssh though, which are run only on
+request, as you can see above, because they clobber your `~/.ssh`.  You have
+been warned.
diff --git a/doc/triggers.mkd b/doc/triggers.mkd
new file mode 100644
index 0000000..50e74bd
--- /dev/null
+++ b/doc/triggers.mkd
@@ -0,0 +1,90 @@
+# gitolite triggers
+
+## intro and sample rc excerpt
+
+Gitolite fires off external commands at six different times.  The [rc][] file
+specifies what commands to run at each trigger point, but for illustration,
+here's an excerpt:
+
+    %RC = (
+
+        <...several lines later...>
+
+        # comment out or uncomment as needed
+        # these will run in sequence after post-update
+        POST_COMPILE                =>
+            [
+                'post-compile/ssh-authkeys',
+                'post-compile/update-git-configs',
+                'post-compile/update-gitweb-access-list',
+                'post-compile/update-git-daemon-access-list',
+            ],
+
+        # comment out or uncomment as needed
+        # these will run in sequence after a new wild repo is created
+        POST_CREATE                 =>
+            [
+                'post-compile/update-git-configs',
+                'post-compile/update-gitweb-access-list',
+                'post-compile/update-git-daemon-access-list',
+            ],
+
+(As you can see, post-create runs 3 programs that also run from post-compile.
+This is perfectly fine, by the way)
+
+## manually firing triggers
+
+...from the server command line is easy.  For example:
+
+    gitolite trigger POST_COMPILE
+
+However if the triggered code depends on arguments (see next section) this
+won't work.  (The `POST_COMPILE` trigger programs all just happen to not
+require any arguments, so it works).
+
+## triggers and arguments
+
+All triggers receive the name of the trigger as a string (example,
+`"POST_COMPILE"`) as the first argument, so they can know who invoked them.
+(This allows you to write the same program and fire it from more than one
+trigger, as above).  In addition, they may receive other arguments pertaining
+to the event that happened.
+
+  * `ACCESS_CHECK`: this fires once after each access check.  The first is
+    just before invoking git-receive-pack or git-upload-pack.  The second,
+    which only applies to "write" operations, is from git's own 'update' hook.
+
+    Arguments: repo name, user name, [attempted access][perm], the ref being
+    updated, and the result of the access check.
+
+    The 'ref' is `any` for the first check, because at that point we don't
+    know what the actual ref is.  For the second check it could be, say,
+    `refs/heads/master` or some such.
+
+    The result is a text field that the `access()` function returned.
+    Programmatically, the only thing you should rely on is that if it contains
+    the upper case word "DENIED" then access was denied, otherwise it was
+    allowed.
+
+  * `PRE_GIT`: before running the git command.
+
+    Arguments: repo name, user name, [attempted access][perm], the string
+    `any`, and the git command ('git-receive-pack', 'git-upload-pack', or
+    'git-upload-archive') being invoked.
+
+  * `POST_GIT`: after the git command returns.
+
+    Arguments: same as for `PRE_GIT`, followed by the output of the perl
+    function "times" (i.e., 4 CPU times: user, system, cumulative user,
+    cumulative system)
+
+  * `POST_COMPILE`: after an admin push has successfully "compiled" the config
+    file.  By default, the next thing is to update the ssh authkeys file, then
+    all the 'git-config's, gitweb access, and daemon access.
+
+    Programs run by this trigger receive no extra arguments.
+
+  * `PRE_CREATE` and `POST_CREATE`: before and after a new "[wild][]" repo is
+    created by user action.
+
+    Arguments: repo name, user name.
diff --git a/doc/users.mkd b/doc/users.mkd
new file mode 100644
index 0000000..f9064b6
--- /dev/null
+++ b/doc/users.mkd
@@ -0,0 +1,96 @@
+# what users (not admins) need to know about gitolite
+
+...written for the one guy in the world no one will think of as "just a normal
+user" ;-)
+
+This document has some text, and a lot of links.  Most of this info *is*
+available in the rest of the documentation, but it's scattered and sparse.
+Collecting all of it, or at least links to it, in one place sounds useful.
+
+## accessing gitolite
+
+The most common setup is based on ssh, where your admin asks you to send him
+your public key, and uses that to setup your access.
+
+Your actual access is either a git command (like `git clone
+git at server:reponame`, and we won't be discussing these any more in this
+document), or an ssh command (like `ssh git at server info`).
+
+Note that you do *not* get a shell on the server -- the whole point of
+gitolite is to prevent that!
+
+## #info the info command
+
+The only command that is *always* available to every user is the `info`
+command (run `ssh git at host info -h` for help), which tells you what version of
+gitolite and git are on the server, and what repositories you have access to.
+The list of repos is very useful if you have doubts about the spelling of some
+new repo that you know was setup.
+
+## digression: two kinds of repos
+
+Gitolite has two kinds of repos.  Normal repos are specified by their full
+names in the config file.  "Wildcard" repos are specified by a regex in the
+config file.  Try the [`info` command][info] and see if it shows any lines
+that look like regex patterns, (with a "C" permission in addition to the "R"
+and the "W").
+
+If you see any, it means you are allowed to create brand new repos whose names
+fit that pattern.  When you create such a repo, your "ownership" of it (as far
+as gitolite is concerned) is *automatically* recorded by gitolite.
+
+## other commands
+
+### #perms set/get additional permissions for repos you created
+
+The gitolite config may have several permissions lines for your repo, like so:
+
+    repo pub/CREATOR/..*
+        RW+     =   CREATOR
+        RW      =   user1 user2
+        R       =   user3
+
+If that's all it had, you really can't do much.  Any changes to access must be
+done by the administrator.  (Note that "CREATOR" is a reserved word that gets
+expanded to your userid in some way, so the admin can literally add just the
+first two lines, and *every* authenticated user now has his own personal repo
+namespace, starting with `pub/<username>/`).
+
+To give some flexibility to users, the admin could add rules like this:
+
+        RW      =   WRITERS
+        R       =   READERS
+
+(he could also add other roles but then he needs to read the documentation).
+
+Once he does this, you can then use the `perms` command (run `ssh git at host
+perms -h` for help) to set permissions for other users by specifying which
+users are in the list of "READERS", and which in "WRITERS".
+
+If you think of READERS and WRITERS as "roles", it will help.  You can't
+change what access a role has, but you *can* say which users have that role.
+
+**Note**: there isn't a way for you to see the actual rule set unless you're
+given read access to the special 'gitolite-admin' repo.  Sorry.  The idea is
+that your admin will tell you what "roles" he added into rules for your repos,
+and what permissions those roles have.
+
+### #desc adding a description to repos you created
+
+The `desc` command is extremely simple.  Run `ssh git at host desc -h` for help.
+
+## "site-local" commands
+
+The main purpose of gitolite is to prevent you from getting a shell.  But
+there are commands that you often need to run on the server (i.e., cannot be
+done by pushing something to a repo).
+
+To enable this, gitolite allows the admin to setup scripts in a special
+directory that users can then run.  Gitolite comes with a set of working
+scripts that your admin may install, or may use as a starting point for his
+own, if he chooses.
+
+Think of these commands as equivalent to those in `COMMAND_DIR` in `man
+git-shell`.
+
+You can get a list of available commands by running `ssh git at host help`.
diff --git a/doc/vref.mkd b/doc/vref.mkd
new file mode 100644
index 0000000..c5263df
--- /dev/null
+++ b/doc/vref.mkd
@@ -0,0 +1,254 @@
+# virtual refs
+
+Here's an example to start you off.
+
+    repo    r1
+        RW+                         =   lead_dev dev2 dev3
+        -   VREF/COUNT/9            =   dev2 dev3
+        -   VREF/COUNT/3/NEWFILES   =   dev2 dev3
+
+Now dev2 and dev3 cannot push changes that affect more than 9 files at a time,
+nor those that have more than 3 new files.
+
+Another example is detecting duplicate pubkeys in a push to the admin repo:
+
+    repo gitolite-admin
+        # ... normal rules ...
+        -   VREF/DUPKEYS            =   @all
+
+----
+
+## rule matching recap
+
+You won't get any joy out of this if you don't understand at least
+[refex][]es and how [rules][] are processed.
+
+But VREFs have one **very important difference** from normal rules.  With
+VREFs, a **fallthru results in success**.  You'll see why this is more
+convenient as you read on.
+
+----
+
+## what is a virtual ref
+
+A ref like `refs/heads/master` is the main property of a push that gitolite
+uses to make its yes/no decision.  I call this a "real" ref.
+
+Any *other* property of the push that you want to use to help in the decision
+is therefore a *virtual* ref.  This could be a property that git knows about,
+like in the example above, or comes from outside git like, say, the current
+time; see examples section later for some ideas.
+
+## fallthru is success here
+
+Notice that you didn't need to add an `RW+ VREF/...` rule for user `lead_dev`
+in our example.  This section explains why.
+
+**Virtual refs are best used as additional "deny" rules**, performing extra
+checks that core gitolite cannot.
+
+Making fallthru be a "fail" forces you to add rules for all users, instead of
+just the ones who should have those extra checks.  Worse, since every virtual
+ref involves calling an external program, many of these calls may be wasted.
+
+There's another advantage to doing it this way: a VREF can choose to simply
+die if things look bad, and it will have the same effect, assuming you used
+the VREF only in [deny][] rules.
+
+This in turn means any existing update hook can be used as a VREF *as-is*, as
+long as it (a) prints nothing on success and (b) dies on failure.  See the
+email-check and dupkeys examples later.
+
+## how it works -- overview
+
+Briefly, a refex starting with `VREF/FOO` triggers a call to a program called
+`FOO` in `$GL_BINDIR/VREF`.
+
+That program is expected to print zero or more lines to its STDOUT; each line
+is taken by gitolite as a new "ref" to be matched against all the refexes for
+this user in the config.  Including the refex that caused the vref call, of
+course.
+
+Normally, you send back the refex itself, if the test determines that the rule
+should be matched, otherwise nothing.  So, in our example, we print
+`VREF/COUNT/9` if the count was indeed greater than 9.  Otherwise we just
+exit.
+
+## how it works -- details
+
+  * the VREF code is only called if there are any VREF rules for the user,
+    which means when the lead developer pushes, the VREF is not called at all.
+
+    Side note: this is enormously more efficient than adding additional
+    `update` hooks, which will get executed whether they are needed or not,
+    for every repo and every user!
+
+  * when dev2 or dev3 push, gitolite first checks the real ref
+    (`ref/heads/master` or whatever).  After this it looks at VREF rules, and
+    calls an external program for every one it finds.  Specifically, in a line
+    like
+
+           -   VREF/COUNT/3/NEWFILES    =   user
+
+    COUNT is the vref name, so the program called is
+    `$GL_BINDIR/VREF/COUNT`.
+
+    The program is passed **nine arguments** in this case (see next section
+    for details).
+
+  * the script can print anything it wants to STDOUT; the first word in each
+    such line will be treated as a virtual ref to be matched against all the
+    rules, while the rest, if any, is a message to be added to the standard
+    "...DENIED..." message that gitolite prints if that refex matches.
+
+    Usually it only makes sense to either
+
+      * print nothing -- if you don't want the rule that triggered it to match
+        (ie., whatever condition being tested was not violated; like if the
+        count of changed files did not exceed 9, in our earlier example)
+      * print the refex itself (plus an optional message), so that it matches
+        the line which invoked it
+
+### arguments passed to the vref code
+
+  * arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed
+    to the update hook (see 'man githooks')
+
+    This, combined with the fact that non-zero exits are detected, mean that
+    you can simply use an existing update.secondary as a new VREF as-is, no
+    changes needed.
+
+  * arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs.  These are the
+    same as the oldsha and newsha values, except if one of them is all-0.
+    (indicating a ref creation or deletion).  In that case the corresponding
+    'tree' SHA is set (by gitolite, as a courtesy) to the special SHA
+    `4b825dc642cb6eb9a060e54bf8d69288fbee4904`, which is the hash of an empty
+    tree.
+
+    (None of these shenanigans would have been needed if `git diff $oldsha
+    $newsha` would not error out when passed an all-0 SHA.)
+
+  * argument **6**: the attempted access flag.  Typically `W` or `+`, but
+    could also be `C`, `D`, or any of these 4 followed by `M`.  If you have to
+    ask what they mean, you haven't read enough gitolite documentation to be
+    able to make virtual refs work.
+
+  * argument **7**: is the entire refex; in our example
+    `VREF/COUNT/3/NEWFILES`.
+
+  * arguments **8 onward**: are the split out (by `/`) portions of the refex,
+    excluding the first two components.  In our example they would be `3`
+    followed by `NEWFILES`.
+
+Yes, argument 7 is redundant if you have 8 and 9.  It's meant to make it easy
+to write vref scripts in any language.  See script examples in source.
+
+## what (else) can the vref code pass back
+
+Actually, the vref code can pass anything back; each line in its output will
+be matched against all the rules as usual (with the exception that fallthru is
+not failure).
+
+For example, you could have a ruleset like this:
+
+    repo r1
+        # ... normal rules ...
+
+        -   VREF/TIME/WEEKEND       =   @interns
+        -   VREF/TIME/WEEKNIGHT     =   @interns
+        -   VREF/TIME/HOLIDAY       =   @interns
+
+and you could write the TIME vref code to passback any or all
+of the times that match.  Then if an intern tried to access the system, each
+rule would trigger a call to gl-bindir/VREF/TIME.
+
+The script should send back any of the applicable times (even more than one,
+or none at all, as the case may be).  So even if it was invoked using the
+first rule, it might pass back (to gitolite) a virtual ref saying
+'VREF/TIME/HOLIDAY', which would promptly cause the request to be denied.
+
+## VREFs shipped with gitolite
+
+### number of new files
+
+If a dev pushes more than 2 *new* files, the top commit needs to have a
+signed-off by line in its commit message.  For example if he has 4 new files
+this text should be:
+
+    4 new files signed-off by: <top commit author's email>
+
+The config entry for this is below (`NO_SIGNOFF` applies only to, and thus
+implies, `NEWFILES`):
+
+     RW+ VREF/COUNT/2/NO_SIGNOFF         =   sitaram
+     -   VREF/COUNT/2/NO_SIGNOFF         =   @all
+
+Notice how the refex in both cases is *exactly* the same.  If you make it
+different (even change the number on my access line), things won't work.
+
+Junior devs can't push more than 10 new files, even with a signed-off by line:
+
+     -   VREF/COUNT/10/NEWFILES          =   @junior_devs
+
+### advanced filetype detection
+
+Note: this is more for illustration than use; it's rather specific to one of
+the projects I manage but the idea is the important thing.
+
+Sometimes a file has a standard extension (that cannot be 'gitignore'd), but
+it is actually automatically generated.  Here's one way to catch it:
+
+     -   VREF/FILETYPE/AUTOGENERATED     =   @all
+
+You can look at `src/VREF/FILETYPE` to see how it handles the
+'AUTOGENERATED' option.  You could also have a more generic option, like
+perhaps BINARY, and handle that in the FILETYPE vref too.
+
+### checking author email
+
+Some people want to ensure that "you can only push your own commits".
+
+If you force it on everyone, this is a very silly idea (see "Philosophical
+Notes" section of `src/VREF/EMAIL-CHECK`).
+
+But there may be value in enforcing it just for the junior developers.
+
+The neat thing is that the existing `contrib/update.email-check` was just
+copied to `src/VREF/EMAIL-CHECK` and it works, because VREFs get
+the same first 3 arguments and those are all that it cares about.  (Note: you
+have to change one subroutine in that script if you want to use it)
+
+### catching duplicate pubkeys
+
+We covered this as a teaser example at the start.
+
+## other ideas -- code welcome!
+
+### "no non-merge first-parents"
+
+Shruggar on #gitolite wanted this.  Possible code to implement it would be
+something like this (untested)
+
+    [ -z "$(git rev-list --first-parent --no-merges $2..$3)" ]
+
+This can be implemented using `src/VREF/MERGE-CHECK` as a model.
+That script does what the 'in core' feature called [merge check][mergecheck]
+does, although the syntax to be used in conf/gitolite will be quite different.
+
+### other ideas for VREFs
+
+Here are some more ideas:
+
+  * number of commits (`git rev-list --count $old $new`)
+  * number of binary files in commit (currently I only know to count
+    occurrences of ` Bin ` in the output of `git diff --stat`
+  * number of *new* binary files (count ` Bin 0 ->` in `git diff --stat`
+    output)
+  * time of day/day of week (see example snippet somewhere above)
+  * IP address
+  * phase of the moon
+
+Note that pretty much anything that involves `$oldsha..$newsha` will have to
+deal with the issue that when you push a new tag or branch, the "old" part
+is all 0's, and unless you consider `--all` existing branches and tags it
+becomes meaningless in terms of "number of new files" etc.
diff --git a/doc/why.mkd b/doc/why.mkd
new file mode 100644
index 0000000..3d05124
--- /dev/null
+++ b/doc/why.mkd
@@ -0,0 +1,46 @@
+# Why is gitolite needed?
+
+Gitolite is separate from git, and needs to be installed and configured.  So...
+why do we bother?
+
+Gitolite is useful in any server that is going to host multiple git
+repositories, each with many developers, where some sort of access control is
+required.
+
+In theory, this can be done with plain old Unix permissions: each user is a
+member of one or more groups, each group "owns" one or more repositories, and
+using unix permissions (especially the setgid bit -- `chmod g+s`) you can
+allow/disallow users access to repos.
+
+But there are several disadvantages here:
+
+  * every user needs a userid and password on the server.  This is usually a
+    killer, especially in tightly controlled environments
+  * adding/removing access rights involves complex `usermod -G ...` mumblings
+    which most admins would rather not deal with
+  * *viewing* (aka auditing) the current set of permissions requires running
+    multiple commands to list directories and their permissions/ownerships,
+    users and their group memberships, and then correlating all these manually
+  * auditing historical permissions or permission changes is pretty much
+    impossible without extraneous tools
+  * errors or omissions in setting the permissions exactly can cause problems
+    of either kind: false accepts or false rejects
+  * without going into ACLs it is not possible to give some people read-only
+    access while some others have read-write access to a repo (unless you make
+    it world-readable).  Group access just doesn't have enough granularity
+  * it is absolutely impossible to restrict pushing by branch name or tag
+    name.
+
+Gitolite does away with all this:
+
+  * it uses ssh magic to remove the need to give actual unix userids to
+    developers
+  * it uses a simple but powerful config file format to specify access rights
+  * access control changes are affected by modifying this file, adding or
+    removing user's public keys, and "compiling" the configuration
+  * this also makes auditing trivial -- all the data is in one place, and
+    changes to the configuration are also logged, so you can audit them.
+  * finally, the config file allows distinguishing between read-only and
+    read-write access, not only at the repository level, but at the branch
+    level within repositories.
+
diff --git a/doc/write-types.mkd b/doc/write-types.mkd
new file mode 100644
index 0000000..e899318
--- /dev/null
+++ b/doc/write-types.mkd
@@ -0,0 +1,31 @@
+## #write-types different types of write operations
+
+Git supplies enough information to the update hook to be able to distinguish
+several types of writes.
+
+The most common are:
+
+  * `RW` -- create a ref or fast-forward push a ref.  No rewinds or deletes.
+  * `RW+` -- create, fast-forward push, rewind push, or delete a ref.
+
+Sometimes you want to allow people to push, but not *create* a ref.  Or
+rewind, but not *delete* a ref.  The `C` and `D` qualifiers help here.
+
+  * when a rule specifies `RWC` or `RW+C`, then *rules that do NOT have the C
+    qualifier will no longer permit **creating** a ref*
+
+    <font color="gray">Please do not confuse this with the standalone `C`
+    permission that allows someone to [create][] a **repo**</font>
+
+  * when a rule specifies `RWD` or `RW+D`, then *rules that do NOT have the C
+    qualifier will no longer permit **deleting** a ref*
+
+Note: These two can be combined, so you can have `RWCD` and `RW+CD` as well.
+
+One very rare need is to reject merge commits (a commit series that is not a
+straight line of commits).  The `M` qualifier helps here:
+
+  * When a rule has `M` appended to the permissions, *rules that do NOT have
+    it will reject a commit sequence that contains a merge commit* (i.e., they
+    only accept a straight line series of commits).
+
diff --git a/dot.pl b/dot.pl
new file mode 100644
index 0000000..40b9a7c
--- /dev/null
+++ b/dot.pl
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+my @a = `grep -r use.Gitolite . | grep -i '^./gitolite'`;
+
+# chomp(@a);
+open( my $fh, "|-", "tee module-tree.gv | dot -Tpng | tee module-tree.png | display" );
+
+ at a = map {
+    print $fh "#$_";
+    s/^\.\/gitolite\///i;
+    s/-/_/g;
+    s/\.\///;
+    s/\//_/g;
+    s/\.pm:/ -> /;
+    s/use Gitolite:://;
+    s/::/_/g;
+    s/:/ -> /;
+    s/;//;
+    s/^(\S+) -> \1$//;
+    s/.* -> Rc//;
+    s/.* -> Common//;
+    $_;
+} @a;
+
+# open(my $fh, "|-", "cat > /tmp/junkg3");
+print $fh "digraph G {\n";
+print $fh $_ for @a;
+print $fh "}\n";
+close $fh;

commit efb29ed135f8e0939f77352463b3bf735152463d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 24 10:12:08 2012 +0530

    enhance usage message for 'gitolite setup'

diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 67f0887..227dfe4 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -10,6 +10,15 @@ Usage:  gitolite setup [<option>]
 
 Setup gitolite, compile conf, and fixup hooks.  The pubkey is required on the
 first run.
+
+Subsequent runs:
+
+  - 'gitolite setup': fix up hooks if you brought in repos from outside, or if
+    someone has been playing around with the hooks and may have deleted some.
+
+  - 'gitolite setup -pk YourName.pub': replace keydir/YourName.pub and
+    recompile/push.  Useful if you lost your key.  In fact you can do this for
+    any key in keydir (but not in subdirectories).
 =cut
 
 # ----------------------------------------------------------------------

commit 8bffbfa02acfbd9674d5dfd8c57864b20597f93d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 24 09:12:10 2012 +0530

    3 old VREFs moved in,
    
    untested but they're just update hooks anyway so they should work fine

diff --git a/src/VREF/DUPKEYS b/src/VREF/DUPKEYS
new file mode 100755
index 0000000..7e479fa
--- /dev/null
+++ b/src/VREF/DUPKEYS
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# gitolite VREF to detect duplicate public keys
+
+# see gitolite doc/vref.mkd for what the arguments are
+sha=$3
+
+# git sets this; and we don't want it at this point...
+unset GIT_DIR
+
+# paranoia
+set -e
+
+# setup the temp area
+export TMPDIR=$GL_REPO_BASE_ABS
+export tmp=$(mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX);
+trap "rm -rf $tmp" EXIT;
+
+git archive $sha keydir | tar -C $tmp -xf -
+    # DO NOT try, say, 'GIT_WORK_TREE=$tmp git checkout $sha'.  It'll screw up
+    # both the 'index' and 'HEAD' of the repo.git.  Screwing up the index is
+    # BAD because now it goes out of sync with $GL_ADMINDIR.  Think of a push
+    # that had a deleted pubkey but failed a hooklet for some reason.  A
+    # subsequent push that fixes the error will now result in a $GL_ADMINDIR
+    # that still *has* that deleted pubkey!!
+
+    # And this is equally applicable to cases where you're using a
+    # post-receive or similar hook to live update a web site or something,
+    # which is a pretty common usage, I am given to understand.
+
+cd $tmp
+
+for f in `find keydir -name "*.pub"`
+do
+    ssh-keygen -l -f "$f"
+done | perl -ane '
+    die "FATAL: $F[2] is a duplicate of $seen{$F[1]}\n" if $seen{$F[1]};
+    $seen{$F[1]} = $F[2];
+'
+
+# as you can see, a vref can also 'die' if it wishes to, and it'll take the
+# whole update with it if it does.  No messing around with sending back a
+# vref, having it run through the matches, and printing the DENIED message,
+# etc.  However, if your push is running from a script, and that script is
+# looking for the word "DENIED" or something, then this won't work...
diff --git a/src/VREF/EMAIL-CHECK b/src/VREF/EMAIL-CHECK
new file mode 100755
index 0000000..34c66f5
--- /dev/null
+++ b/src/VREF/EMAIL-CHECK
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+
+# gitolite VREF to check if all *new* commits have author == pusher
+
+#                       THIS IS NOT READY TO USE AS IS
+#                       ------------------------------
+#           you MUST change the 'email_ok()' sub to suit *YOUR* site's
+#           gitolite username -> author email mapping!
+
+# See bottom of the program for important philosophical notes.
+
+use strict;
+use warnings;
+
+# mapping between gitolite userid and correct email address is encapsulated in
+# this subroutine; change as you like
+sub email_ok {
+    my ($author_email) = shift;
+    my $expected_email = "$ENV{GL_USER}\@atc.tcs.com";
+    return $author_email eq $expected_email;
+}
+
+my ( $ref, $old, $new ) = @ARGV;
+for my $rev (`git log --format="%ae\t%h\t%s" $new --not --all`) {
+    chomp($rev);
+    my ( $author_email, $hash, $subject ) = split /\t/, $rev;
+
+    # again, we use the trick that a vref can just choose to die instead of
+    # passing back a vref, having it checked, etc., if it's more convenient
+    die "$ENV{GL_USER}, you can't push $hash authored by $author_email\n" . "\t(subject of commit was $subject)\n"
+      unless email_ok($author_email);
+}
+
+exit 0;
+
+__END__
+
+The following discussion is for people who want to enforce this check on ALL
+their developers (i.e., not just the newbies).
+
+Doing this breaks the "D" in "DVCS", forcing all your developers to work to a
+centralised model as far as pushes are concerned.  It prevents amending
+someone else's commit and pushing (this includes rebasing, cherry-picking, and
+so on, which are all impossible now).  It also makes *any* off-line
+collabaration between two developers useless, because neither of them can push
+the result to the server.
+
+PHBs should note that validating the committer ID is NOT the same as reviewing
+the code and running QA/tests on it.  If you're not reviewing/QA-ing the code,
+it's probably worthless anyway.  Conversely, if you *are* going to review the
+code and run QA/tests anyway, then you don't really need to validate the
+author email!
+
+In a DVCS, if you *pushed* a series of commits, you have -- in some sense --
+signed off on them.  The most formal way to "sign" a series is to tack on and
+push a gpg-signed tag, although most people don't go that far.  Gitolite's log
+files are designed to preserve that accountability to *some* extent, though;
+see contrib/adc/who-pushed for an admin defined command that quickly and
+easily tells you who *pushed* a particular commit.
+
+Anyway, the point is that the only purpose of this script is to
+
+  * pander to someone who still has not grokked *D*VCS
+          OR
+  * tick off an item in some stupid PHB's checklist
+
diff --git a/src/VREF/MERGE-CHECK b/src/VREF/MERGE-CHECK
new file mode 100644
index 0000000..07f0351
--- /dev/null
+++ b/src/VREF/MERGE-CHECK
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# gitolite VREF to check if there are any merge commits in the current push.
+
+# THIS IS DEMO CODE; please read all comments below as well as
+# doc/vref.mkd before trying to use this.
+
+# usage in conf/gitolite.conf goes like this:
+
+#       -   VREF/MERGE_CHECK/master     =   @all
+#       # reject only if the merge commit is being pushed to the master branch
+#       -   VREF/MERGE_CHECK            =   @all
+#       # reject merge commits to any branch
+
+my $ref    = $ARGV[0];
+my $oldsha = $ARGV[1];
+my $newsha = $ARGV[2];
+my $refex  = $ARGV[6];
+
+# The following code duplicates some code from parse_conf_line() and some from
+# check_ref().  This duplication is the only thing that is preventing me from
+# removing the "M" permission code from 'core' gitolite and using this
+# instead.  However, it does demonstrate how you would do this if you had to
+# create any other similar features, for example someone wanted "no non-merge
+# first-parent", which is far too specific for me to add to 'core'.
+
+# -- begin duplication --
+my $branch_refex = $ARGV[7] || '';
+if ($branch_refex) {
+    $branch_refex =~ m(^refs/) or $branch_refex =~ s(^)(refs/heads/);
+} else {
+    $branch_refex = 'refs/.*';
+}
+exit 0 unless $ref =~ /^$branch_refex/;
+# -- end duplication --
+
+# we can't run this check for tag creation or new branch creation, because
+# 'git log' does not deal well with $oldsha = '0' x 40.
+if ( $oldsha eq "0" x 40 or $newsha eq "0" x 40 ) {
+    print STDERR "ref create/delete ignored for purposes of merge-check\n";
+    exit 0;
+}
+
+my $ret = `git rev-list -n 1 --merges $oldsha..$newsha`;
+print "$refex FATAL: merge commits not allowed\n" if $ret =~ /./;
+
+exit 0;

commit eeed52ba2e7319162ede0b62377164f750d24bed
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 17:23:26 2012 +0530

    list-users acquires an optional repo name patten to speed things up
    
    but see warnings in usage text.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 45d5133..99256d7 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -426,24 +426,31 @@ sub list_groups {
 }
 
 =for list_users
-Usage:  gitolite list-users
+Usage:  gitolite list-users [<repo name pattern>]
 
-  - lists all users/user groups in conf
-  - no options, no flags
-  - WARNING: may be slow if you have thousands of repos
+List all users and groups explicitly named in a rule.  User names not
+mentioned in an access rule will not show up; you have to run 'list-members'
+on each group name yourself to see them.
+
+WARNING: may be slow if you have thousands of repos.  The optional repo name
+pattern is an unanchored regex; it can speed things up if you're interested
+only in users of a matching set of repos.  This is only an optimisation, not
+an actual access list; you will still have to pipe it to 'gitolite access'
+with appropriate arguments to get an actual access list.
 =cut
 
 sub list_users {
-    usage() if @_;
+    my $patt = shift || '.';
+    usage() if $patt eq '-h' or @_;
     my $count = 0;
     my $total = 0;
 
     load_common();
 
     my @u = map { keys %{$_} } values %repos;
-    $total = scalar( keys %split_conf );
+    $total = scalar( grep { /$patt/ } keys %split_conf );
     warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
-    for my $one ( keys %split_conf ) {
+    for my $one ( grep { /$patt/ } keys %split_conf ) {
         load_1($one);
         $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
         push @u, map { keys %{$_} } values %one_repo;

commit 35ec0cf9270b37dd0d896b94ad19bffdfddf6fba
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 17:05:23 2012 +0530

    renamed ssh tests to prevent accidents to ~/.ssh

diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys
similarity index 100%
rename from t/ssh-authkeys.t
rename to t/ssh-authkeys
diff --git a/t/ssh-basic.t b/t/ssh-basic
similarity index 100%
rename from t/ssh-basic.t
rename to t/ssh-basic

commit 329d7571671bf4b413801807264f6d3c1f70624e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 12:36:05 2012 +0530

    partial-copy: manually spot-tested (i.e., no test in suite).  PW.

diff --git a/src/VREF/partial-copy b/src/VREF/partial-copy
new file mode 100755
index 0000000..4cb4440
--- /dev/null
+++ b/src/VREF/partial-copy
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+# push updated branches back to the "main" repo.
+
+# This must be run as the *last* VREF, though it doesn't matter what
+# permission you give to it
+
+die() { echo "$@" >&2; exit 1; }
+
+repo=$GL_REPO
+user=$GL_USER
+ref=$1          # we're running like an update hook
+old=$2
+new=$3
+
+# never send any STDOUT back, to avoid looking like a ref.  If we fail, git
+# will catch it by our exit code
+exec >&2
+
+main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
+[ -z "$main" ] && exit 0
+
+rand=$RANDOM
+export GL_BYPASS_UPDATE_HOOK=1
+
+git push -f $GL_REPO_BASE/$main.git $new:refs/heads/br-$rand || die "FATAL: failed to send $new"
+
+cd $GL_REPO_BASE/$main.git
+git update-ref -d refs/heads/br-$rand
+git update-ref $ref $new $old || die "FATAL: update-ref for $ref failed"
+
+exit 0
diff --git a/src/commands/partial-copy b/src/commands/partial-copy
new file mode 100755
index 0000000..19aa8d6
--- /dev/null
+++ b/src/commands/partial-copy
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# this is a wee bit expensive in terms of forks etc., compared to doing it in
+# perl, but I wanted to show how *easy* it actually is now.  And really,
+# you'll only notice if you access this repo like a hundred times a minute or
+# something so don't sweat it.
+
+# given a repo and a user, check if option('partialCopyOf') is set, and if so,
+# fetch all allowed branches from there.
+
+die() { echo "$@" >&2; exit 1; }
+
+# make sure we're being called from the pre_git trigger
+[ "$1" = "PRE_GIT" ] || die I must be called from PRE_GIT, not "$1"
+shift
+
+repo=$1
+user=$2
+main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
+[ -z "$main" ] && exit 0
+
+# "we", "our repo"  =>  the partial copy
+# "main", "pco"     =>  the one which we are a "partial copy of"
+
+cd $GL_REPO_BASE/$main.git
+
+for ref in `git for-each-ref refs/heads '--format=%(refname)'`
+do
+    gitolite access -q $repo $user R $ref &&
+    cd $GL_REPO_BASE/$repo.git
+
+    git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
+done
+
+exit 0

commit 0748b1225b6d5d07b9fed7fe0276510333947a65
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 22 14:45:30 2012 +0530

    external programs can get settings from rc; see below
    
    non-core programs can get their settings from the rc file also.
    cpu-time is a perl example and desc is a shell example.
    
    (info is not a good example because it does not use "Gitolite::Easy")

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 6b501b1..4d9299c 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -238,6 +238,18 @@ __DATA__
     UMASK                       =>  0077,
     GIT_CONFIG_KEYS             =>  '',
 
+    # settings used by external programs; uncomment and change as needed.  You
+    # can add your own variables for use in your own external programs; take a
+    # look at the cpu-time and desc commands for perl and shell samples.
+
+    # used by the cpu-time command
+    # DISPLAY_CPU_TIME          =>  1,
+    # CPU_TIME_WARN_LIMIT       =>  0.1,
+    # used by the desc command
+    # WRITER_CAN_UPDATE_DESC    =>  1,
+    # used by the info command
+    # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
+
     # add more roles (like MANAGER, TESTER, ...) here
     ROLES                       =>
         {
@@ -283,6 +295,14 @@ __DATA__
             'perms'             =>  1,
             'writes'            =>  1,
         },
+
+    # comment out or uncomment as needed
+    # these will run in sequence at the end, after a git operation has ended
+    POST_GIT                    =>
+        [
+            # if you use this, make this the last item in the list
+            # 'cpu-time',
+        ],
 );
 
 # ------------------------------------------------------------------------------
diff --git a/src/commands/cpu-time b/src/commands/cpu-time
new file mode 100755
index 0000000..d363328
--- /dev/null
+++ b/src/commands/cpu-time
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Easy;
+
+my ($trigger, $repo, $user, $aa, $ref, $verb, $utime, $stime, $cutime, $cstime) = @ARGV;
+
+# now do whatever you want with this data; the following is just an example.
+
+# Ideally, you will (a) write your own code with a different filename so later
+# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
+# to the rc file, and (c) change your rc file to call your program at the end
+# of the POST_GIT list.
+
+if (my $limit = $rc{CPU_TIME_WARN_LIMIT}) {
+    my $total = $utime + $cutime + $stime + $cstime;
+    # some code to send an email or whatever...
+    say2 "limit = $limit, actual = $total" if $total > $limit;
+}
+
+if ($rc{DISPLAY_CPU_TIME}) {
+    say2 "perf stats for $verb on repo '$repo':";
+    say2 "  user CPU time: " . ( $utime + $cutime );
+    say2 "  sys  CPU time: " . ( $stime + $cstime );
+}
+
diff --git a/src/commands/desc b/src/commands/desc
index 77466ee..4fa3060 100755
--- a/src/commands/desc
+++ b/src/commands/desc
@@ -16,7 +16,17 @@ repo=$1; shift
 
 # this shell script takes arguments that are completely under the user's
 # control, so make sure you quote those suckers!
-gitolite creator "$repo" $GL_USER || die You are not authorised
+
+# kernel.org needs 'desc' to be available to people who have "RW" or above,
+# not just the "creator".  In fact they need it for non-wild repos so there
+# *is* no creator.
+if gitolite query-rc -q WRITER_CAN_UPDATE_DESC
+then
+    gitolite access -q "$repo" $GL_USER W any || die You are not authorised
+else
+    gitolite creator "$repo" $GL_USER || die You are not authorised
+fi
+
 # if it passes, $repo is a valid repo name so it is known to contain only sane
 # characters.  This is because 'gitolite creator' return true only if there
 # *is* a repo of that name and it has a gl-creator file that contains the same
diff --git a/src/commands/info b/src/commands/info
index 0c503e1..66b7e68 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -80,3 +80,5 @@ for my $repo (@$repos) {
     print "\t$creator" if $lc;
     print "\n";
 }
+
+print "\n$rc{SITE_INFO}\n" if $rc{SITE_INFO};

commit 0b8b14463093beee749319337ec7f27d225a7369
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 12:34:48 2012 +0530

    trigger prefixes an extra first argument -- the trigger name

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index da91b79..6b501b1 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -177,7 +177,7 @@ sub trigger {
 
                 _warn("skipped command '$s'"), next if not -x $sfp;
                 trace( 2, "command: $s" );
-                _system( $sfp, @_ );    # they better all return with 0 exit codes!
+                _system( $sfp, $rc_section, @_ );    # they better all return with 0 exit codes!
             }
         }
         return;

commit b39100053d9f5a9240f34a35821e9f9776a126ac
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 07:59:35 2012 +0530

    POST_GIT triggers get 4 more arguments

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 20f4e5d..14136eb 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -98,7 +98,7 @@ sub main {
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
     _system( "git", "shell", "-c", "$verb $repodir" );
-    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
+    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb, times() );
 }
 
 # ----------------------------------------------------------------------

commit 14e13544336f89ba96ff032e55f82c89fdbf88e3
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 23 06:37:44 2012 +0530

    query-rc learns '-q' option

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 1af7260..da91b79 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -57,7 +57,7 @@ my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
 do $rc if -r $rc;
-if (defined($GL_ADMINDIR)) {
+if ( defined($GL_ADMINDIR) ) {
     say2 "";
     say2 "FATAL: $rc seems to be for older gitolite; checking compat";
     require Gitolite::Compat;
@@ -127,8 +127,9 @@ sub glrc {
 # exported functions
 # ----------------------------------------------------------------------
 
-my $all  = 0;
-my $nonl = 0;
+my $all   = 0;
+my $nonl  = 0;
+my $quiet = 0;
 
 sub query_rc {
 
@@ -144,7 +145,7 @@ sub query_rc {
     }
 
     my @res = map { $rc{$_} } grep { $rc{$_} } @vars;
-    print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if @res;
+    print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if not $quiet and @res;
     # shell truth
     exit 0 if @res;
     exit 1;
@@ -192,6 +193,7 @@ Usage:  gitolite query-rc -a
 
     -a          print all variables and values
     -n          do not append a newline
+    -q          exit code only (shell truth; 0 is success)
 
 Example:
 
@@ -200,18 +202,21 @@ Example:
 
     gitolite query-rc -a
     # prints all known variables and values, one per line
+
+Note: '-q' is best used with only one variable.
 =cut
 
 sub args {
     my $help = 0;
 
     GetOptions(
-        'all|a'  => \$all,
-        'nonl|n' => \$nonl,
-        'help|h' => \$help,
+        'all|a'   => \$all,
+        'nonl|n'  => \$nonl,
+        'quiet|q' => \$quiet,
+        'help|h'  => \$help,
     ) or usage();
 
-    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
+    usage("'-a' cannot be combined with other arguments or options") if $all and ( @ARGV or $nonl or $quiet );
     usage() if not $all and not @ARGV or $help;
     return @ARGV;
 }

commit e9ea674be9f119670af8458414fe3ba44067cdf2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 22 05:31:17 2012 +0530

    (minor) fixup various TODOs and such
    
    some got junked, some were already done or got done, and some were
    converted into actual todo items in the 'todo' file.

diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index b64f748..30261aa 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -51,8 +51,6 @@ sub parse {
             my @refs  = parse_refs( $2 || '' );
             my @users = parse_users($3);
 
-            # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users;
-
             for my $ref (@refs) {
                 for my $user (@users) {
                     add_rule( $perm, $ref, $user );
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index 3ae7f34..44f534a 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -55,12 +55,6 @@ sub incsub {
           or $include_glob =~ /^'(.+)'$/;
     $include_glob = $1;
 
-    # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
-    # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME;
-
-    # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly*
-    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) {
-
     trace( 2, $is_subconf, $include_glob );
 
     for my $file ( glob($include_glob) ) {
@@ -74,7 +68,6 @@ sub incsub {
             push @{$out}, "subconf $basename";
             explode( $file, $basename, $out );
             push @{$out}, "subconf $subconf";
-            # XXX g2 delegaton compat: deal with this: $subconf_seen++;
         } else {
             explode( $file, $subconf, $out );
         }
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 919e33c..a33931e 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -45,7 +45,6 @@ my @repolist;    # current repo list; reset on each 'repo ...' line
 my $subconf = 'master';
 my $nextseq = 0;
 my %ignored;
-# XXX you still have to "warn" if this has any entries
 
 # ----------------------------------------------------------------------
 
@@ -71,7 +70,6 @@ sub set_repolist {
         _warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
         _die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
     }
-    # XXX -- how do we deal with this? s/\bCREAT[EO]R\b/\$creator/g for @{ $repos_p };
 }
 
 sub parse_refs {
@@ -85,7 +83,6 @@ sub parse_refs {
     # fully qualify refs that dont start with "refs/" or "VREF/";
     # prefix them with "refs/heads/"
     @refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs;
-    # XXX what do we do? @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs;
 
     return @refs;
 }
@@ -114,15 +111,6 @@ sub add_rule {
         }
 
         push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
-
-        # XXX g2 diff: we're not doing a lint check for usernames versus pubkeys;
-        # maybe we can add that later
-
-        # XXX to do: C/R/W, then CREATE_IS_C, etc
-        # XXX to do: also NAME_LIMITS
-        # XXX and hacks like $creator -> "$creatror - wild"
-
-        # XXX consider if you want to use rurp_seen; initially no
     }
 }
 
@@ -131,7 +119,6 @@ sub add_config {
 
     $nextseq++;
     for my $repo (@repolist) {
-        # XXX should we check_subconf_repo_disallowed here?
         push @{ $configs{$repo} }, [ $nextseq, $key, $value ];
     }
 }
@@ -174,7 +161,6 @@ sub new_repos {
         next unless $repo =~ $REPONAME_PATT;    # skip repo patterns
         next if $repo =~ m(^\@|EXTCMD/);        # skip groups and fake repos
 
-        # XXX how do we deal with GL_NO_CREATE_REPOS?
         new_repo($repo) if not -d "$repo.git";
     }
 }
@@ -183,16 +169,11 @@ sub new_repo {
     my $repo = shift;
     trace( 3, $repo );
 
-    # XXX ignoring UMASK for now
-
     _mkdir("$repo.git");
     _chdir("$repo.git");
     _system("git init --bare >&2");
     _chdir( $rc{GL_REPO_BASE} );
     hook_1($repo);
-
-    # XXX ignoring creator for now
-    # XXX ignoring gl-post-init for now
 }
 
 sub new_wild_repo {
@@ -203,8 +184,6 @@ sub new_wild_repo {
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
     _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
-    # XXX git config, daemon, web...
-    # XXX pre-create, post-create
     trigger( 'POST_CREATE', $repo, $user );
 
     _chdir( $rc{GL_ADMIN_BASE} );
@@ -215,8 +194,6 @@ sub hook_repos {
     # all repos, all hooks
     _chdir( $rc{GL_REPO_BASE} );
 
-    # XXX g2 diff: we now don't care if it's a symlink -- it's upto the admin
-    # on the server to make sure things are kosher
     for my $repo (`find . -name "*.git" -prune`) {
         chomp($repo);
         $repo =~ s/\.git$//;
@@ -288,8 +265,6 @@ sub store_1 {
         $dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] );
     }
 
-    # XXX deal with this better now
-    # $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
     print $compiled_fh $dumped_data;
     close $compiled_fh;
 
@@ -308,16 +283,11 @@ sub store_common {
     my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] );
     $dumped_data .= Data::Dumper->Dump( [ \%configs ], [qw(*configs)] ) if %configs;
 
-    # XXX and again...
-    # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
-
     print $compiled_fh $dumped_data;
 
     if (%groups) {
         my %groups = %{ inside_out( \%groups ) };
         $dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
-        # XXX $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g;
-        # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
         print $compiled_fh $dumped_data;
     }
     print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 60f0b8d..c607417 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -117,11 +117,6 @@ sub owner_desc {
     my $lines = shift;
     my @ret;
 
-    # XXX compat breakage: (1) adding repo/owner does not automatically add an
-    # entry to projects.list -- we need a post-procesor for that, and (2)
-    # removing the 'repo' line no longer suffices to remove the config entry
-    # from projects.list.  Maybe the post-procesor should do that as well?
-
     # owner = "owner name"
     #   ->  config gitweb.owner = owner name
     # description = "some long description"
@@ -138,10 +133,6 @@ sub owner_desc {
     for my $line (@$lines) {
         if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
             my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
-            # XXX these two checks should go into add_config
-            # _die "bad repo name '$repo'" unless $repo =~ $REPONAME_PATT;
-            # _die "$fragment attempting to set description for $repo"
-            #   if check_fragment_repo_disallowed( $fragment, $repo );
             push @ret, "repo $repo";
             push @ret, "config gitweb.description = $desc";
             push @ret, "config gitweb.owner = $owner" if $owner;
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 12b8c23..1af7260 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -74,7 +74,6 @@ do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 # fix some env vars, setup gitolite internal "env" vars (aka rc vars)
 # ----------------------------------------------------------------------
 
-# fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 
 {
@@ -117,10 +116,6 @@ sub glrc {
         # search $HOME first
         return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
 
-        # XXX for fedora, we can add the following line, but I would really prefer
-        # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
-        # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
-
         return '';
     } elsif ( $cmd eq 'current-data-version' ) {
         return $current_data_version;
diff --git a/src/commands/access b/src/commands/access
index 11d2e9a..6da81c4 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -35,8 +35,6 @@ Advanced uses: see src/commands/post-compile/update-git-daemon-access-list for
 a good example.
 =cut
 
-# TODO: deal with "C", call it ^C
-
 usage() if not @ARGV or $ARGV[0] eq '-h';
 my $quiet = 0;
 if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
@@ -44,7 +42,6 @@ if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
 my ( $repo, $user, $aa, $ref ) = @ARGV;
 $aa  ||= '+';
 $ref ||= 'any';
-# XXX the 4th one below might need fine tuning
 _die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M)$/ );
 _die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
 
diff --git a/src/commands/post-compile/ssh-authkeys b/src/commands/post-compile/ssh-authkeys
index 792ffc5..5bfb4a8 100755
--- a/src/commands/post-compile/ssh-authkeys
+++ b/src/commands/post-compile/ssh-authkeys
@@ -18,7 +18,6 @@ trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
 my $akdir   = "$ENV{HOME}/.ssh";
 my $akfile  = "$ENV{HOME}/.ssh/authorized_keys";
 my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
-# XXX gl-time not yet coded (GL_PERFLOGT)
 my $auth_options = auth_options();
 
 sanity();
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 870dc74..20f4e5d 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -34,8 +34,6 @@ exit 0;
 
 # ----------------------------------------------------------------------
 
-# XXX lots of stuff from gl-auth-command is missing for now...
-
 sub in_local {
     if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) {
         print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
@@ -109,8 +107,8 @@ sub parse_soc {
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
     $soc ||= 'info';
 
-    if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git(\d)?)?'$) ) {
-        # TODO git archive
+    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+    if ( $soc =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$) ) {
         my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
         $ENV{D} = $trace_level if $trace_level;
         _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;

commit 446a611327b8c82466b2bcd0ca05b4704f622199
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 22 12:49:27 2012 +0530

    enhance the projects.list updater to look at 'gitweb.*' config settings also
    
    (not just 'R = gitweb')
    
    Can you tell I really, really, don't want anything to do with gitweb and
    daemon to be part of gitolite *core*?  :-)

diff --git a/src/commands/post-compile/update-gitweb-access-list b/src/commands/post-compile/update-gitweb-access-list
index 971100e..d7f5a95 100755
--- a/src/commands/post-compile/update-gitweb-access-list
+++ b/src/commands/post-compile/update-gitweb-access-list
@@ -7,5 +7,9 @@
 plf=$(gitolite query-rc GITWEB_PROJECTS_LIST)
 [ -z "$plf" ] && plf=$HOME/projects.list
 
-gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED | cut -f1 | sed -e 's/$/.git/' > $plf
+(
+    gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
+    gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
+) |
+    cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
 

commit 1c590e633f02f5bb908f6fe27e17f78655d98815
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 22 06:17:34 2012 +0530

    run compat checks when old rc found
    
    (also removed legacy-delegation sugar script)

diff --git a/src/Gitolite/Compat.pm b/src/Gitolite/Compat.pm
new file mode 100644
index 0000000..cd69768
--- /dev/null
+++ b/src/Gitolite/Compat.pm
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+
+# a quick and dirty program to warn about compatibilities issues in your
+# current gitolite 2 setup, with the new one.
+
+ at EXPORT = qw(
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+
+my $header_printed = 0;
+
+my $glrc = "$ENV{HOME}/.gitolite.rc";
+do "$glrc";
+if (defined($GL_ADMINDIR)) {
+    check_compat();
+    if ($header_printed) {
+        say2 "Please read the documentation for additional details on migrating.\n\n"
+    } else {
+        say2 "
+It looks like there were no real issues found, but you should still read the
+documentation for additional details on migrating.
+
+";
+    }
+}
+
+sub check_compat {
+    chdir($GL_ADMINDIR) or die "FATAL: could not chdir to $GL_ADMINDIR\n";
+
+    my $conf = `find . -name "*.conf" | xargs cat`;
+
+    g2warn("MUST fix", "fallthru in NAME rules; this affects user's push rights")
+      if $conf =~ m(NAME/);
+
+    g2warn("MUST fix", "subconf command in admin repo; this affects conf compilation")
+      if $conf =~ m(NAME/conf/fragments);
+}
+
+sub header {
+    return if $header_printed;
+    $header_printed++;
+
+    say2 "
+    The following is a list of compat issues found, if any.  Please see the
+    compatibility with gitolite 2' section of the documentation for additional
+    details on each issue found.\n";
+}
+
+sub g2warn {
+    my ($cat, $msg) = @_;
+    header();
+    say2 "$cat: $msg\n";
+}
+
+1;
+
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index ad2de11..12b8c23 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -57,7 +57,13 @@ my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
 do $rc if -r $rc;
-_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
+if (defined($GL_ADMINDIR)) {
+    say2 "";
+    say2 "FATAL: $rc seems to be for older gitolite; checking compat";
+    require Gitolite::Compat;
+
+    exit 1;
+}
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
@@ -246,7 +252,6 @@ __DATA__
     SYNTACTIC_SUGAR             =>
         [
             # 'continuation-lines',
-            'legacy-delegation-abort',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/syntactic-sugar/legacy-delegation-abort b/src/syntactic-sugar/legacy-delegation-abort
deleted file mode 100755
index 1625632..0000000
--- a/src/syntactic-sugar/legacy-delegation-abort
+++ /dev/null
@@ -1,26 +0,0 @@
-# vim: syn=perl:
-
-# "sugar script" (syntactic sugar helper) for gitolite3
-
-# Aborts if you appear to be using legacy delegation.
-
-# Note: you cannot fix this using sugar; subconf processing has already
-# happened by the time it gets to this point.
-
-# TODO: supply doc keyword, and that doc should tell people to add this to the
-# end of the main conf file:
-
-#       repo gitolite-admin
-#           -   NAME/       =   @all
-#       subconf "fragments/*.conf"
-
-sub sugar_script {
-    Gitolite::Common::trace( 2, "running 'legacy-delegation-warn' sugar script..." );
-    my $lines = shift;
-
-    my $text = join("\n", @$lines);
-    if ($text =~ m(NAME/conf/fragments/) and $text !~ /^subconf /m) {
-        die "\t**** ABORT ****\nYou may be using legacy delegation; see docs\n";
-    }
-    return $lines;
-}
diff --git a/t/deleg-1.t b/t/deleg-1.t
index bf47d4a..b969543 100755
--- a/t/deleg-1.t
+++ b/t/deleg-1.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # delegation tests -- part 1
 # ----------------------------------------------------------------------
 
-try "plan 55";
+try "plan 53";
 
 try "
     DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
@@ -33,7 +33,6 @@ confreset;confadd '
         subconf "fragments/*.conf"
 ';
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "/ABORT/; /legacy delegation/";
 
 mkdir "conf/fragments";
 put   "conf/fragments/u1r.conf", '
diff --git a/t/deleg-2.t b/t/deleg-2.t
index 8c5a400..54f5156 100755
--- a/t/deleg-2.t
+++ b/t/deleg-2.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # delegation tests -- part 2
 # ----------------------------------------------------------------------
 
-try "plan 56";
+try "plan 54";
 
 try "
     DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
@@ -35,9 +35,6 @@ confreset;confadd '
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
-try "
-        /ABORT/; /legacy delegation/
-";
 
 try "mkdir -p conf/fragments; ok";
 

commit 8dc43affdb537a9d94a79a97ca117e6f5f3c5569
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 19:21:12 2012 +0530

    minor changes to testing setup

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index c8e1f52..11d1c9d 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -85,7 +85,7 @@ sub confreset {
     system("find ~/repositories -name '*.git' |xargs rm -rf");
     system("mv ~/repositories/.ga ~/repositories/gitolite-admin.git");
     system("mv ~/repositories/.te ~/repositories/testing.git       ");
-    put "conf/gitolite.conf", '
+    put "|cut -c9- > conf/gitolite.conf", '
         repo    gitolite-admin
             RW+     =   admin
         repo    testing
diff --git a/t/reset b/t/reset
index cfdc3db..fa9bbcf 100755
--- a/t/reset
+++ b/t/reset
@@ -9,4 +9,25 @@ BEGIN {
 # this is hardcoded; change it if needed
 use lib "src";
 use Gitolite::Test;
-try 'put';
+
+use Cwd;
+my $workdir = getcwd();
+
+confreset;confadd '
+    repo foo/..*
+        C   =   u1 u2 u3
+        RW+ =   CREATOR
+        RW  =   WRITERS
+        R   =   READERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    mkdir -p keydir
+    cp \$HOME/.ssh/u*.pub keydir
+    cp \$HOME/.ssh/admin.pub keydir
+    git add keydir
+    git commit -m 6k
+    glt push admin origin
+";

commit 938acc589f01e24fa9f5cc3732fd72a831ea9a2a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 17:30:16 2012 +0530

    test daemon/gitweb updates by setting perms

diff --git a/t/daemon-gitweb-via-perms.t b/t/daemon-gitweb-via-perms.t
new file mode 100755
index 0000000..33b8e90
--- /dev/null
+++ b/t/daemon-gitweb-via-perms.t
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 24";
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+confreset;confadd '
+
+ at leads = u1 u2
+ at devs = u1 u2 u3 u4
+
+ at gbar = bar/CREATOR/..*
+repo    @gbar
+    C               =   @leads
+    RW+             =   @leads
+    RW              =   WRITERS @devs
+    R               =   READERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+chdir($rb);
+my $h = $ENV{HOME};
+
+try "
+    glt ls-remote u1 file:///bar/u1/try1
+    /Initialized empty Git repository in .*/bar/u1/try1.git/
+
+    find . -name git-daemon-export-ok
+    /testing.git/git-daemon-export-ok/
+
+    cat $h/projects.list
+    /testing.git/
+
+    glt ls-remote u1 file:///bar/u1/try2
+    /Initialized empty Git repository in .*/bar/u1/try2.git/
+
+    find $h/repositories -name git-daemon-export-ok
+    /testing.git/git-daemon-export-ok/
+
+    cat $h/projects.list
+    /testing.git/
+
+    glt perms u1 bar/u1/try1 + READERS daemon
+    !/./
+
+    glt perms u1 -l bar/u1/try1
+    /READERS daemon/
+
+    find $h/repositories -name git-daemon-export-ok
+    /repositories/testing.git/git-daemon-export-ok/
+    /repositories/bar/u1/try1.git/git-daemon-export-ok/
+
+    cat $h/projects.list
+    /testing.git/
+
+    glt perms u1 bar/u1/try2 + READERS gitweb
+
+    glt perms u1 -l bar/u1/try2
+    /READERS gitweb/
+
+    find $h/repositories -name git-daemon-export-ok
+    /testing.git/git-daemon-export-ok/
+    /bar/u1/try1.git/git-daemon-export-ok/
+
+    cat $h/projects.list
+    /bar/u1/try2.git/
+    /testing.git/
+";

commit 320356d66c6b264d090726b0d73ddf543a4d8006
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 16:23:50 2012 +0530

    cleaned up logging quite a bit; details:
    
      - a remote "id" (usually the IP) is generated and logged on the first
        log message in a "transaction"
    
      - speaking of which, a new "transaction ID" is logged that stays the
        same for each input command/invocation, tying together all the
        spawned commands
    
      - so now time stamps can be generated each time they are needed,
        rather than re-use the one at the beginning
    
      - log messages have a keyword at the start now
            remote, (create), check1 -- from gitolite-shell
            update, check2 -- from update
            post-up -- from post-update
            command -- from gitolite
            die, system -- from anywhere

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 6dbc6e3..0dc503f 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -10,7 +10,7 @@ package Gitolite::Common;
   say2    _die    _system slurp             tsh_lines
           trace           cleanup_conf_line tsh_try
           usage                             tsh_run
-          gen_ts_lfn
+          gen_lfn
           gl_log
 );
 #>>>
@@ -72,7 +72,7 @@ sub _warn {
 }
 
 sub _die {
-    gl_log( "_die:", @_ );
+    gl_log( 'die', @_ );
     if ( $ENV{D} and $ENV{D} >= 3 ) {
         confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
     } elsif ( defined( $ENV{D} ) ) {
@@ -112,7 +112,7 @@ sub _system {
     # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
     # exit with <rc of system()> if it applies, else just "exit 1".
     trace( 2, @_ );
-    gl_log( "_system:", @_ );
+    gl_log( 'system', @_ );
     if ( system(@_) != 0 ) {
         trace( 1, "system() failed", @_, "-> $?" );
         if ( $? == -1 ) {
@@ -205,9 +205,8 @@ sub cleanup_conf_line {
     }
 }
 
-# generate a timestamp.  If a template is passed generate a log file name
-# based on it also
-sub gen_ts_lfn {
+# generate a timestamp
+sub gen_ts {
     my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
     $y += 1900; $m++;    # usual adjustments
     for ( $s, $min, $h, $d, $m ) {
@@ -215,7 +214,16 @@ sub gen_ts_lfn {
     }
     my $ts = "$y-$m-$d.$h:$min:$s";
 
-    return $ts unless @_;
+    return $ts;
+}
+
+# generate a log file name
+sub gen_lfn {
+    my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
+    $y += 1900; $m++;    # usual adjustments
+    for ( $s, $min, $h, $d, $m ) {
+        $_ = "0$_" if $_ < 10;
+    }
 
     my ($template) = shift;
     # substitute template parameters and set the logfile name
@@ -223,7 +231,7 @@ sub gen_ts_lfn {
     $template =~ s/%m/$m/g;
     $template =~ s/%d/$d/g;
 
-    return ( $ts, $template );
+    return $template;
 }
 
 sub gl_log {
@@ -231,13 +239,15 @@ sub gl_log {
     # called even before they are set, we have no choice but to dump to STDERR
     # (and probably call "logger").
     my $msg = join( "\t", @_ );
+    $msg =~ s/[\n\r]+/<<newline>>/g;
 
-    my $ts = $ENV{GL_TS} || gen_ts_lfn();
+    my $ts  = gen_ts();
+    my $tid = $ENV{GL_TID} ||= $$;
 
     my $fh;
     logger_plus_stderr( "$ts no GL_LOGFILE env var", "$ts $msg" ) if not $ENV{GL_LOGFILE};
     open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr( "open log failed: $!", $msg );
-    print $lfh "$ts\t$msg\n";
+    print $lfh "$ts\t$tid\t$msg\n";
     close $lfh;
 }
 
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index d60c3fa..d9fa47a 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -20,7 +20,7 @@ use warnings;
 
 sub post_update {
     trace( 2, @ARGV );
-    gl_log( 'post-update', @ARGV );
+    gl_log( 'post-up', @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index dae4412..53be05a 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -28,20 +28,21 @@ sub update {
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
-    gl_log( 'update:check', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, '->', $ret );
     trigger( 'ACCESS_CHECK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
-    gl_log( 'update:OK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
 
+    gl_log( 'check2', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV, '->', $ret );
     exit 0;
 }
 
 sub check_vrefs {
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
     my $name_seen = 0;
+    my $n_vrefs = 0;
     for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
+        $n_vrefs++;
         if ( $vref =~ m(^VREF/NAME/) ) {
             # this one is special; we process it right here, and only once
             next if $name_seen++;
@@ -64,6 +65,7 @@ sub check_vrefs {
               : "$vref: helper program exit status $?";
         }
     }
+    return $n_vrefs;
 }
 
 sub check_vref {
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 7b83e9b..ad2de11 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -72,9 +72,12 @@ do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 
 {
-    my ( $ts, $lfn ) = gen_ts_lfn( $rc{LOG_TEMPLATE} );
-    $rc{GL_LOGFILE} = $ENV{GL_LOGFILE} = $lfn;
-    $rc{GL_TS}      = $ENV{GL_TS}      = $ts;
+    $rc{GL_TID} = $ENV{GL_TID} ||= $$;
+    # TID: loosely, transaction ID.  The first PID at the entry point passes
+    # it down to all its children so you can track each access, across all the
+    # various commands it spawns and actions it generates.
+
+    $rc{GL_LOGFILE} = $ENV{GL_LOGFILE} ||= gen_lfn( $rc{LOG_TEMPLATE} );
 }
 
 # these two are meant to help externally written commands (see
diff --git a/src/gitolite b/src/gitolite
index 7217bb9..341657f 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -46,7 +46,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 my ( $command, @args ) = @ARGV;
-gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE};
+gl_log( 'command', @ARGV ) if -d $rc{GL_ADMIN_BASE};
 args();
 
 # the first two commands need options via @ARGV, as they have their own
@@ -91,6 +91,8 @@ if ( $command eq 'setup' ) {
     _die "unknown gitolite sub-command";
 }
 
+gl_log( '==end==' ) if $$ == $ENV{GL_TID};
+
 sub args {
     usage() if not $command or $command eq '-h';
 }
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 584c5f4..870dc74 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -15,17 +15,20 @@ use strict;
 use warnings;
 
 # the main() sub expects ssh-ish things; set them up...
+my $id = '';
 if ( exists $ENV{G3T_USER} ) {
-    in_local();    # file:// masquerading as ssh:// for easy testing
+    $id = in_local();    # file:// masquerading as ssh:// for easy testing
 } elsif ( exists $ENV{SSH_CONNECTION} ) {
-    in_ssh();
+    $id = in_ssh();
 } elsif ( exists $ENV{REQUEST_URI} ) {
-    in_http();
+    $id = in_http();
 } else {
     _die "who the *heck* are you?";
 }
 
-main();
+main($id);
+
+gl_log( '==end==' ) if $$ == $ENV{GL_TID};
 
 exit 0;
 
@@ -38,6 +41,7 @@ sub in_local {
         print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
         print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
     }
+    return 'local';
 }
 
 sub in_http {
@@ -49,6 +53,10 @@ sub in_ssh {
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
     $soc =~ s/[\n\r]+/<<newline>>/g;
     _die "I don't like newlines in the command: $soc\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
+
+    my $ip;
+    ($ip = $ENV{SSH_CONNECTION} || '(no-IP)') =~ s/ .*//;
+    return $ip;
 }
 
 # ----------------------------------------------------------------------
@@ -56,7 +64,9 @@ sub in_ssh {
 # call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
 # has been setup (even if it's not actually coming via ssh).
 sub main {
-    gl_log( 'gitolite-shell', @ARGV, $ENV{SSH_ORIGINAL_COMMAND} );
+    my $id = shift;
+
+    gl_log( 'remote', $id, @ARGV, $ENV{SSH_ORIGINAL_COMMAND} );
     umask $rc{UMASK};
 
     # set up the user
@@ -73,7 +83,7 @@ sub main {
         require Gitolite::Conf::Store;
         Gitolite::Conf::Store->import;
         new_wild_repo( $repo, $user );
-        gl_log( 'gitolite-shell:new_wild_repo', $repo, $user );
+        gl_log( 'create', $repo, $user );
     }
 
     # a ref of 'any' signifies that this is a pre-git check, where we don't
@@ -82,9 +92,9 @@ sub main {
     # more information.
     my $ret = access( $repo, $user, $aa, 'any' );
     trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
-    gl_log( 'gitolite-shell:check', $repo, $user, $aa, 'any', '->', $ret );
+    gl_log( 'check1', $repo, $user, $aa, 'any', '->', $ret );
     trigger( 'ACCESS_CHECK', $repo, $user, $aa, 'any', $ret );
-    _die $ret if $ret =~ /DENIED/;
+    _die $ret . "\n(or you mis-spelled the reponame)" if $ret =~ /DENIED/;
 
     check_repo_write_enabled($repo) if $aa eq 'W';
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
diff --git a/t/0-me-first.t b/t/0-me-first.t
index cc732e3..0661a13 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -53,7 +53,7 @@ try "
 
     # log file
     cat \$(gitolite query-rc GL_LOGFILE);
-                                    ok;     /update:OK/
+                                    ok;     /check2/
                                             /aa\tu1\t\\+\trefs/heads/master/
                                             /2d066fb4860c29cf321170c17695c6883f3d50e8/
                                             /284951dfa11d58f99ab76b9f4e4c1ad2f2461236/

commit bb9f045ec37220c75b08e805b3325e28aad1da18
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 13:23:32 2012 +0530

    trigger POST_CREATE from user actions
    
      - uncomment the POST_CREATE section in rc by default now
      - have perms call 'gitolite trigger POST_CREATE'

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 79ec246..7b83e9b 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -260,9 +260,9 @@ __DATA__
     # these will run in sequence after a new wild repo is created
     POST_CREATE                 =>
         [
-            # 'post-compile/update-git-configs',
-            # 'post-compile/update-gitweb-access-list',
-            # 'post-compile/update-git-daemon-access-list',
+            'post-compile/update-git-configs',
+            'post-compile/update-gitweb-access-list',
+            'post-compile/update-git-daemon-access-list',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/commands/perms b/src/commands/perms
index 08faff4..a476241 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -35,6 +35,7 @@ if ( $ARGV[0] eq '-l' ) {
 }
 
 setperms(@ARGV);
+_system("gitolite", "trigger", "POST_CREATE");
 
 # ----------------------------------------------------------------------
 
@@ -62,7 +63,7 @@ sub setperms {
             push @a, $_;
         }
         _print( $pf, @a );
-        exit;
+        return;
     }
 
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if @_ != 3;
diff --git a/t/git-config.t b/t/git-config.t
index b3ea176..83ad7f4 100755
--- a/t/git-config.t
+++ b/t/git-config.t
@@ -9,12 +9,25 @@ use Gitolite::Test;
 # git config settings
 # ----------------------------------------------------------------------
 
-try "plan 21";
+try "plan 57";
 
 try "pwd";
 my $od = text();
 chomp($od);
 
+# try an invalid config key
+confreset;confadd '
+
+    repo @all
+        config foo.bar  =   dft
+';
+
+try "ADMIN_PUSH set1; /FATAL/" or die text();
+try "
+    /git config foo.bar not allowed/
+    /check GIT_CONFIG_KEYS in the rc file/
+";
+
 # make foo.bar a valid gc key
 $ENV{G3T_RC} = "$ENV{HOME}/g3trc";
 put "$ENV{G3T_RC}", "\$rc{GIT_CONFIG_KEYS} = 'foo\.bar';\n";
@@ -101,3 +114,77 @@ testing.git/config:	bar = dft
 testing.git/config:	bare = true
 testing.git/config:[foo]
 ';
+
+try "cd $od; ok";
+
+confadd '
+
+    repo bar
+        RW      =   u2
+        config foo.bar  =   
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd $rb;                             ok
+    egrep foo\\|bar *.git/config | sort
+";
+
+cmp 'bar.git/config:	bare = true
+bar.git/config:[foo]
+foo.git/config:	bare = true
+foo.git/config:	bar = f1
+foo.git/config:[foo]
+frob.git/config:	bare = true
+frob.git/config:	bar = none
+frob.git/config:[foo]
+gitolite-admin.git/config:	bare = true
+testing.git/config:	bar = dft
+testing.git/config:	bare = true
+testing.git/config:[foo]
+';
+
+try "cd $od; ok";
+
+confreset;confadd '
+
+    repo @gr1
+        RW      =   u1
+        config foo.bar  =   f1
+
+    repo bar/CREATOR/[one].*
+        C       =   u2
+        RW      =   u2
+        config foo.bar  =   one
+
+    @gr1 = foo frob
+
+';
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "
+    glt ls-remote u2 file:///bar/u2/one;        ok;     /Initialized empty/
+    glt ls-remote u2 file:///bar/u2/two;        !ok;    /DENIED by fallthru/
+";
+
+try "
+    cd $rb;                             ok
+    egrep foo\\|bar *.git/config | sort
+    find . -name config | xargs egrep foo\\|bar | sort
+";
+
+cmp './bar/u2/one.git/config:	bare = true
+./bar/u2/one.git/config:	bar = one
+./bar/u2/one.git/config:[foo]
+./foo.git/config:	bare = true
+./foo.git/config:	bar = f1
+./foo.git/config:[foo]
+./frob.git/config:	bare = true
+./frob.git/config:	bar = f1
+./frob.git/config:[foo]
+./gitolite-admin.git/config:	bare = true
+./testing.git/config:	bar = dft
+./testing.git/config:	bare = true
+./testing.git/config:[foo]
+';

commit 5e2e13aac2627023354038a25fdb8c5327c4eb6a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 09:34:39 2012 +0530

    review all user input, system(), and ``

diff --git a/src/commands/desc b/src/commands/desc
index 32d0a94..77466ee 100755
--- a/src/commands/desc
+++ b/src/commands/desc
@@ -16,14 +16,18 @@ repo=$1; shift
 
 # this shell script takes arguments that are completely under the user's
 # control, so make sure you quote those suckers!
-
 gitolite creator "$repo" $GL_USER || die You are not authorised
+# if it passes, $repo is a valid repo name so it is known to contain only sane
+# characters.  This is because 'gitolite creator' return true only if there
+# *is* a repo of that name and it has a gl-creator file that contains the same
+# text as $GL_USER.
+
 descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
 
 if [ -z "$1" ]
 then
-    [ -r $descfile ] && cat $descfile
+    [ -r "$descfile" ] && cat "$descfile"
     exit 0
 fi
 
-echo "$*" > $descfile
+echo "$*" > "$descfile"
diff --git a/src/commands/perms b/src/commands/perms
index 45004dc..08faff4 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -69,6 +69,7 @@ sub setperms {
     my ( $op, $role, $user ) = @_;
     _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
     _die "Invalid role '$role'; check the rc file" if not $rc{ROLES}{$role};
+    _die "Invalid user '$user'" if not $user =~ $USERNAME_PATT;
 
     my $text = '';
     my @text = slurp($pf) if -f $pf;
diff --git a/src/commands/post-compile/ssh-authkeys b/src/commands/post-compile/ssh-authkeys
index 6f8f23f..792ffc5 100755
--- a/src/commands/post-compile/ssh-authkeys
+++ b/src/commands/post-compile/ssh-authkeys
@@ -87,6 +87,7 @@ sub fp {
     my $in = shift || '';
     if ( $in =~ /\.pub$/ ) {
         # single pubkey file
+        _die "bad pubkey file '$in'" unless $in =~ $REPONAME_PATT;
         return fp_file($in);
     } elsif ( -f $in ) {
         # an authkeys file
@@ -99,7 +100,7 @@ sub fp {
 
 sub fp_file {
     my $f  = shift;
-    my $fp = `ssh-keygen -l -f $f`;
+    my $fp = `ssh-keygen -l -f '$f'`;
     chomp($fp);
     _die "fingerprinting failed for $f" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
     $fp = $1;

commit 98a6b08ff441ec1551d8acb89c2174a340a8837a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 07:49:13 2012 +0530

    'desc' command added (manually smoke tested only; no test script)

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 1202449..79ec246 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -271,6 +271,7 @@ __DATA__
         {
             'help'              =>  1,
             'info'              =>  1,
+            'desc'              =>  1,
             'perms'             =>  1,
             'writes'            =>  1,
         },
diff --git a/src/commands/desc b/src/commands/desc
new file mode 100755
index 0000000..32d0a94
--- /dev/null
+++ b/src/commands/desc
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Usage:    ssh git at host desc <repo>
+#           ssh git at host desc <repo> <description string>
+#
+# Show or set description for user-created ("wild") repo.
+
+die() { echo "$@" >&2; exit 1; }
+usage() { perl -lne 'print substr($_, 2) if /^# Usage/../^$/' < $0; exit 1; }
+[ -z "$1" ] && usage
+[ "$1" = "-h" ] && usage
+[ -z "$GL_USER" ] && die GL_USER not set
+
+# ----------------------------------------------------------------------
+repo=$1; shift
+
+# this shell script takes arguments that are completely under the user's
+# control, so make sure you quote those suckers!
+
+gitolite creator "$repo" $GL_USER || die You are not authorised
+descfile=`gitolite query-rc GL_REPO_BASE`/"$repo".git/description
+
+if [ -z "$1" ]
+then
+    [ -r $descfile ] && cat $descfile
+    exit 0
+fi
+
+echo "$*" > $descfile
diff --git a/t/glt b/t/glt
index bc8f719..3b3daf8 100755
--- a/t/glt
+++ b/t/glt
@@ -12,6 +12,7 @@ my $rc;
 my %extcmds = (
     help        => 1,
     info        => 1,
+    desc        => 1,
     perms       => 1,
     writes      => 1,
 );

commit 878bb3009a7fab287548d440fd2e10a315b71112
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 07:01:42 2012 +0530

    last check in the "dammit, don't call creator() on a missing repo" series :)

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index f3614d9..45d5133 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -113,6 +113,7 @@ sub git_config {
     my ( $repo, $key ) = @_;
     $key ||= '.';
 
+    return {} if repo_missing($repo);
     load($repo);
 
     # read comments bottom up

commit 80a234c0f6c9d4a9f6b67a8586dbc889919cef5a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 06:29:19 2012 +0530

    upgraded the basic test a bit for quicker turnaround on some experiments

diff --git a/t/0-me-first.t b/t/0-me-first.t
index afdd414..cc732e3 100755
--- a/t/0-me-first.t
+++ b/t/0-me-first.t
@@ -9,13 +9,17 @@ use Gitolite::Test;
 # initial smoke tests
 # ----------------------------------------------------------------------
 
-try "plan 55";
+try "plan 65";
 
 # basic push admin repo
 confreset;confadd '
     repo aa
         RW+     =   u1
         RW      =   u2 u3
+
+    repo cc/..*
+        C       =   u4
+        RW+     =   CREATOR u5
 ';
 
 try "ADMIN_PUSH set1; !/FATAL/" or die text();
@@ -64,4 +68,11 @@ try "
     tc d-485;                       ok;     /master 1c01d32. d-485/
     glt push u2 -f origin HEAD;     !ok;    reject
                                             /\\+ refs/heads/master aa u2 DENIED by fallthru/
+
+    # non-existent repos etc
+    glt ls-remote u4 file:///bb;    !ok;    /DENIED by fallthru/
+    glt ls-remote u4 file:///cc/1;  ok;     /Initialized empty/
+    glt ls-remote u5 file:///cc/1;  ok;     perl s/TRACE.*//g; !/\\S/
+    glt ls-remote u5 file:///cc/2;  !ok;    /DENIED by fallthru/
+    glt ls-remote u6 file:///cc/2;  !ok;    /DENIED by fallthru/
 ";

commit 661fefbd0eabec080e1ac98326089ea1ce235f05
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 21 00:43:25 2012 +0530

    added more personal branch tests

diff --git a/t/personal-branches.t b/t/personal-branches.t
index 2c8e6fb..4c53537 100755
--- a/t/personal-branches.t
+++ b/t/personal-branches.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # personal branches
 # ----------------------------------------------------------------------
 
-try "plan 39";
+try "plan 64";
 
 confreset;confadd '
     @admins     =   admin dev1
@@ -49,3 +49,52 @@ try "
     gitolite access \@g1 u1 W refs/heads/p/u2/foo;      !ok;    /W refs/heads/p/u2/foo \@g1 u1 DENIED by fallthru/
     gitolite access t1 u1 + refs/heads/p/u2/foo;        !ok;    /\\+ refs/heads/p/u2/foo t1 u1 DENIED by fallthru/
 ";
+
+confreset; confadd '
+    @staff = u1 u2 u3 u4 u5 u6
+    @gfoo = foo
+    repo  @gfoo
+          RW+                       = u1 u2
+          RW+   p/USER/             = u3 u4
+          RW    temp                = u5 u6
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    DEF OK  =   gitolite access foo %1 %2 refs/heads/%3;    ok
+    DEF NOK =   gitolite access foo %1 %2 refs/heads/%3;    !ok
+";
+
+try "
+
+# u1 and u2 can push
+    OK  u1  W   master
+    OK  u2  W   master
+    OK  u2  W   p/u1/foo
+    OK  u1  W   p/u2/foo
+    OK  u1  W   p/u3/foo
+
+# u3 cant push u1/u4 personal branches
+    NOK u3  W   p/u1/foo
+    NOK u3  W   p/u4/doo
+
+# u4 can push u4 personal branch
+    OK  u4  W   p/u4/foo
+# u5 push temp
+    OK  u5  W   temp
+
+# u1 and u2 can rewind
+    OK  u1  +   master
+    OK  u2  +   p/u1/foo
+    OK  u1  +   p/u2/foo
+    OK  u1  +   p/u3/foo
+
+# u3 cant rewind u1/u4 personal branches
+    NOK u3  +   p/u1/foo
+    NOK u3  +   p/u4/foo
+# u4 can rewind u4 personal branch
+    OK  u4  +   p/u4/foo
+# u5 cant rewind temp
+    NOK u5  +   temp
+";

commit 139c08d3a12b225c97c9845da2c6863430a82f1e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 23:51:18 2012 +0530

    abort on suspicious ref names
    
    (and the other Dan Carpenter finding too, while we're about it!)
    
    Note that neither of these is an actual issue, (and even less likely now
    that gitolite is pure perl and no shell metas used) but it's just
    playing safe.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index b987662..f3614d9 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -71,6 +71,9 @@ sub access {
     my $deny_rules = option($repo, 'deny-rules');
     load($repo);
 
+    # sanity check the only piece the user can control
+    _die "invalid characters in ref or filename: $ref\n" unless $ref =~ $REF_OR_FILENAME_PATT;
+
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
     # check to give valid results.
     if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing($repo) ) {
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 2bf6a78..584c5f4 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -45,6 +45,10 @@ sub in_http {
 }
 
 sub in_ssh {
+    $ENV{SSH_ORIGINAL_COMMAND} ||= '';
+    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+    $soc =~ s/[\n\r]+/<<newline>>/g;
+    _die "I don't like newlines in the command: $soc\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;
 }
 
 # ----------------------------------------------------------------------
diff --git a/t/invalid-refnames-filenames.t b/t/invalid-refnames-filenames.t
new file mode 100755
index 0000000..7cd50a2
--- /dev/null
+++ b/t/invalid-refnames-filenames.t
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# invalid refnames
+# ----------------------------------------------------------------------
+
+try "plan 57";
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+confreset; confadd '
+    repo aa
+        RW+                 =   @all
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+cd ..
+rm -rf aa
+glt clone u1 file:///aa
+cd aa
+tc v-869
+
+glt push u1 origin HEAD
+        /To file:///aa/
+        POK; /\\* \\[new branch\\]      HEAD -> master/
+
+# push file aa,bb ok
+tc  aa,bb
+glt push u1 origin HEAD
+        /To file:///aa/
+        POK; /HEAD -> master/
+
+# push file aa=bb ok
+tc  aa=bb
+glt push u1 origin HEAD
+        /To file:///aa/
+        POK; /HEAD -> master/
+
+# push to branch dd,ee ok
+glt push u1 origin master:dd,ee
+        /To file:///aa/
+        POK; /\\* \\[new branch\\]      master -> dd,ee/
+
+# push to branch dd=ee fail
+glt push u1 origin master:dd=ee
+        /invalid characters in ref or filename: refs/heads/dd=ee/
+        reject
+";
+
+confreset; confadd '
+    repo aa
+        RW+                 =   @all
+        RW+ NAME/           =   @all
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+cd ..
+rm -rf aa
+glt clone u1 file:///aa
+cd aa
+tc  file-1
+
+glt push u1 origin HEAD
+        /To file:///aa/
+        POK; /\\* \\[new branch\\]      HEAD -> master/
+
+# push file aa,bb ok
+tc  aa,bb
+glt push u1 origin HEAD
+        /To file:///aa/
+        POK; /HEAD -> master/
+
+# push file aa=bb fail
+tc  aa=bb
+glt push u1 origin HEAD
+        /To file:///aa/
+        /invalid characters in ref or filename: VREF/NAME/aa=bb/
+        reject
+
+# push to branch dd,ee ok
+git reset --hard HEAD^
+tc  some-file
+glt push u1 origin master:dd,ee
+        /To file:///aa/
+        POK; /\\* \\[new branch\\]      master -> dd,ee/
+
+# push to branch dd=ee fail
+glt push u1 origin master:dd=ee
+        /invalid characters in ref or filename: refs/heads/dd=ee/
+        reject
+";

commit 999f9cd39d0d787fc390c6a8ccc17cf84702017f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 23:00:29 2012 +0530

    make site-local scripts easier to write
    
      - new Gitolite::Easy module hides all the other stuff
      - (put GL_ADMIN_BASE and GL_REPO_BASE into %ENV)
      - new 'gitolite creator' shell command
      - 'writes' command modified to use Gitolite::Easy.  It is also the
        only dual mode command -- it can be invoked remotely as well as
        locally.  I deem that the required trick to make other remote-only
        commands work locally is too much trouble for what is probably a
        rarely used command.

diff --git a/src/Gitolite/Easy.pm b/src/Gitolite/Easy.pm
new file mode 100644
index 0000000..81ff02f
--- /dev/null
+++ b/src/Gitolite/Easy.pm
@@ -0,0 +1,95 @@
+package Gitolite::Easy;
+
+# easy access to gitolite from external perl programs
+# ----------------------------------------------------------------------
+# most/all functions in this module test $ENV{GL_USER}'s rights and
+# permissions so it needs to be set.
+
+#<<<
+ at EXPORT = qw(
+  is_admin
+  is_super_admin
+  in_group
+  owns
+  can_read
+  can_write
+
+  %rc
+  say
+  say2
+  _print
+  usage
+);
+#>>>
+use Exporter 'import';
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $user;
+
+# ----------------------------------------------------------------------
+
+# shell equivalent
+#   if gitolite access -q gitolite-admin $GL_USER W; then ...
+sub is_admin {
+    valid_user();
+    return not( access( 'gitolite-admin', $user, 'W', 'any' ) =~ /DENIED/ );
+}
+
+# shell equivalent
+#   if gitolite access -q gitolite-admin $GL_USER W VREF/NAME/; then ...
+sub is_super_admin {
+    valid_user();
+    return not( access( 'gitolite-admin', $user, 'W', 'VREF/NAME/' ) =~ /DENIED/ );
+}
+
+# shell equivalent
+#   if gitolite list-memberships $GL_USER | grep -x $GROUPNAME >/dev/null; then ...
+sub in_group {
+    valid_user();
+    my $g = shift;
+
+    return grep { $_ eq $g } @{ list_memberships($user) };
+}
+
+# shell equivalent
+#   if gitolite creator $REPONAME $GL_USER; then ...
+sub owns {
+    valid_user();
+    my $r = shift;
+
+    # prevent unnecessary disclosure of repo existence info
+    return 0 if repo_missing($r);
+
+    return ( creator($r) eq $user );
+}
+
+# shell equivalent
+#   if gitolite access -q $REPONAME $GL_USER R; then ...
+sub can_read {
+    valid_user();
+    my $r = shift;
+    return not( access( $r, $user, 'R', 'any' ) =~ /DENIED/ );
+}
+
+# shell equivalent
+#   if gitolite access -q $REPONAME $GL_USER W; then ...
+sub can_write {
+    valid_user();
+    my $r = shift;
+    return not( access( $r, $user, 'W', 'any' ) =~ /DENIED/ );
+}
+
+# ----------------------------------------------------------------------
+
+sub valid_user {
+    _die "GL_USER not set" unless exists $ENV{GL_USER};
+    $user = $ENV{GL_USER};
+}
+
+1;
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index d10f049..1202449 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -77,6 +77,11 @@ $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
     $rc{GL_TS}      = $ENV{GL_TS}      = $ts;
 }
 
+# these two are meant to help externally written commands (see
+# src/commands/writes for an example)
+$ENV{GL_REPO_BASE}  = $rc{GL_REPO_BASE};
+$ENV{GL_ADMIN_BASE} = $rc{GL_ADMIN_BASE};
+
 # ----------------------------------------------------------------------
 
 use strict;
@@ -267,6 +272,7 @@ __DATA__
             'help'              =>  1,
             'info'              =>  1,
             'perms'             =>  1,
+            'writes'            =>  1,
         },
 );
 
diff --git a/src/commands/creator b/src/commands/creator
new file mode 100755
index 0000000..b50aae5
--- /dev/null
+++ b/src/commands/creator
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage:  gitolite creator [-n] <reponame> [<username>]
+
+Print the creator name for the repo.  A '-n' suppresses the newline.
+
+When an optional username is supplied, it checks if the user is the creator of
+the repo and returns an exit code (shell truth, 0 for success) instead of
+printing anything, which makes it possible to do this in shell:
+
+    if gitolite creator someRepo someUser
+    then
+        ...
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+my $nl = "\n";
+if ($ARGV[0] eq '-n') {
+    $nl = '';
+    shift;
+}
+my $repo = shift;
+my $user = shift || '';
+
+my $creator = '';
+$creator = creator($repo) if not repo_missing($repo);
+if ($user) {
+    exit 0 if $creator eq $user;
+    exit 1;
+}
+return ($creator eq $user) if $user;
+print "$creator$nl";
diff --git a/src/commands/writes b/src/commands/writes
index d530452..0dc5d6a 100755
--- a/src/commands/writes
+++ b/src/commands/writes
@@ -3,9 +3,7 @@ use strict;
 use warnings;
 
 use lib $ENV{GL_BINDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
+use Gitolite::Easy;
 
 =for usage
 Usage: gitolite writes on|off <reponame>|@all
@@ -24,6 +22,12 @@ my $on = ( shift eq 'on' );
 
 my $repo = shift;
 
+if ( $repo eq '@all' ) {
+    die "you are not authorized\n" if $ENV{GL_USER} and not is_admin();
+} else {
+    die "you are not authorized\n" if $ENV{GL_USER} and not owns($repo);
+}
+
 my $msg = join( " ", @ARGV );
 # try STDIN only if no msg found in args *and* it's an 'off' command
 if ( not $msg and not $on ) {
@@ -32,7 +36,7 @@ if ( not $msg and not $on ) {
 }
 
 my $sf = ".gitolite.down";
-my $rb = $rc{GL_REPO_BASE};
+my $rb = $ENV{GL_REPO_BASE};
 
 if ( $repo eq '@all' ) {
     target( $ENV{HOME} );
diff --git a/t/glt b/t/glt
index 5b33f62..bc8f719 100755
--- a/t/glt
+++ b/t/glt
@@ -13,6 +13,7 @@ my %extcmds = (
     help        => 1,
     info        => 1,
     perms       => 1,
+    writes      => 1,
 );
 
 $ENV{G3T_USER} = $user;

commit 5deafb68238d9eebb9c03d0665376949a04d87ed
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 17:02:58 2012 +0530

    fix some "this specific user" dependencies for testing

diff --git a/t/basic.t b/t/basic.t
index 0b8017a..37f9b12 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -45,7 +45,7 @@ try "
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     PUSH admin;                 ok;     /master -> master/
-    tsh empty;                  ok;
+    empty;                      ok;
     PUSH admin master:mm
                                 !ok;    gsh
                                         /DENIED by refs/heads/mm/
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index fa11a6c..98f2d79 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -12,6 +12,7 @@ use Gitolite::Test;
 $ENV{GL_BINDIR} = "$ENV{PWD}/src";
 
 my $ak = "$ENV{HOME}/.ssh/authorized_keys";
+mkdir("$ENV{HOME}/.ssh", 0700) if not -d "$ENV{HOME}/.ssh";
 my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir";
 
 try "plan 49";
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 6d8a10a..14a1cf4 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -24,6 +24,9 @@ try "
     cp $bd/../t/keys/u[1-6]* $h/.ssh;       ok or die 2
     cp $bd/../t/keys/admin*  $h/.ssh;       ok or die 3
     cp $bd/../t/keys/config  $h/.ssh;       ok or die 4
+        cat $h/.ssh/config
+        perl s/%USER/$ENV{USER}/
+        put $h/.ssh/config
 
     mkdir                  $ab/keydir;      ok or die 5
     cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6

commit 43f95f9b227eb9b294415f93c65c80e55f2d5ef9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 16:52:38 2012 +0530

    (minor) help command usage message changed

diff --git a/src/commands/help b/src/commands/help
index cb7b2f3..f954f8e 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -7,9 +7,12 @@ use Gitolite::Rc;
 use Gitolite::Common;
 
 =for usage
-Usage:  gitolite help
+Usage:  ssh git at host help       # via ssh
+        gitolite help           # directly on server command line
 
 Prints a list of custom commands available at this gitolite installation.
+
+Each command has its own help, accessed by passing it '-h' again.
 =cut
 
 usage() if @ARGV;
diff --git a/t/glt b/t/glt
index b1cf4fe..5b33f62 100755
--- a/t/glt
+++ b/t/glt
@@ -10,6 +10,7 @@ my $user = shift or die "need user";
 my $rc;
 
 my %extcmds = (
+    help        => 1,
     info        => 1,
     perms       => 1,
 );

commit f0355d749ba64bf04f5a8ba39723e3dd59b41bad
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 16:43:45 2012 +0530

    'gitolite writes off/on...' done

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 752da49..b987662 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -5,11 +5,15 @@ package Gitolite::Conf::Load;
 
 @EXPORT = qw(
   load
+
   access
   git_config
+
   option
   repo_missing
+  check_repo_write_enabled
   creator
+
   vrefs
   lister_dispatch
 );
@@ -154,6 +158,15 @@ sub repo_missing {
     return not -d "$rc{GL_REPO_BASE}/$repo.git";
 }
 
+sub check_repo_write_enabled {
+    my ($repo) = shift;
+    for my $f ("$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down") {
+        next unless -f $f;
+        _die slurp($f) if -s $f;
+        _die "sorry, writes are currently disabled (no more info available)";
+    }
+}
+
 # ----------------------------------------------------------------------
 
 sub load_common {
diff --git a/src/commands/writes b/src/commands/writes
new file mode 100755
index 0000000..d530452
--- /dev/null
+++ b/src/commands/writes
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage: gitolite writes on|off <reponame>|@all
+
+'on' enables, 'off' disables, writes (pushes) to the named repo or all repos.
+
+With 'off', any subsequent text is taken to be the message to be shown to
+users when their pushes get rejected.  If it is not supplied, it will take it
+from STDIN; this allows longer messages.
+=cut
+
+usage() if not @ARGV or @ARGV < 2 or $ARGV[0] eq '-h';
+
+usage() if $ARGV[0] ne 'on' and $ARGV[0] ne 'off';
+my $on = ( shift eq 'on' );
+
+my $repo = shift;
+
+my $msg = join( " ", @ARGV );
+# try STDIN only if no msg found in args *and* it's an 'off' command
+if ( not $msg and not $on ) {
+    say2 "...please type the message to be shown to users:";
+    $msg = join( "", <> );
+}
+
+my $sf = ".gitolite.down";
+my $rb = $rc{GL_REPO_BASE};
+
+if ( $repo eq '@all' ) {
+    target( $ENV{HOME} );
+} else {
+    target("$rb/$repo.git");
+}
+
+sub target {
+    my $repodir = shift;
+    if ($on) {
+        unlink "$repodir/$sf";
+    } else {
+        _print( "$repodir/$sf", $msg );
+    }
+}
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 99f2cce..2bf6a78 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -82,6 +82,7 @@ sub main {
     trigger( 'ACCESS_CHECK', $repo, $user, $aa, 'any', $ret );
     _die $ret if $ret =~ /DENIED/;
 
+    check_repo_write_enabled($repo) if $aa eq 'W';
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
     _system( "git", "shell", "-c", "$verb $repodir" );
diff --git a/t/writes.t b/t/writes.t
new file mode 100755
index 0000000..08a8143
--- /dev/null
+++ b/t/writes.t
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+use Cwd;
+my $workdir = getcwd();
+
+# 'gitolite writes' command
+# ----------------------------------------------------------------------
+
+my $sf = ".gitolite.down";
+
+try "plan 58";
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+# delete the down file
+unlink "$ENV{HOME}/$sf";
+
+# add foo, bar/..* repos to the config and push
+confreset;confadd '
+    repo foo
+        RW  =   u1
+        R   =   u2
+
+    repo bar/..*
+        C   =   u2 u4 u6
+        RW  =   CREATOR
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    # clone and push to foo
+    CLONE u1 foo;               ok
+    cd foo;                     ok
+    tc f1;                      ok
+    PUSH u1 master;             ok;     /new branch/
+
+    # auto-clone and push to bar/u2
+    cd ..
+    CLONE u2 bar/u2;            ok;     /appear to have cloned an empty/
+                                        /Initialized empty/
+    cd u2;
+    tc f2
+    PUSH u2 master;             ok;
+
+    # disable site with some message
+    gitolite writes off \@all testing site-wide disable; ok
+
+    # try push foo and see fail + message
+    cd ../foo;                  ok
+    tc f3;                      ok
+    PUSH u1;                    !ok;    /testing site-wide disable/
+    # try push bar/u2 and ...
+    cd ../u2;                   ok
+    tc f4;                      ok
+    PUSH u2;                    !ok;    /testing site-wide disable/
+
+    # try auto-create push bar/u4 and this works!!
+    cd ..
+    CLONE u4 bar/u4;            ok;     /appear to have cloned an empty/
+                                        /Initialized empty/
+                                        !/testing site-wide disable/
+    cd u4;                      ok
+
+    # enable site
+    gitolite writes on \@all; ok
+
+    # try same 3 again
+
+    # try push foo and see fail + message
+    cd ../foo;                  ok
+    tc g3;                      ok
+    PUSH u1;                    ok;    /master -> master/
+    # try push bar/u2 and ...
+    cd ../u2;                   ok
+    tc g4;                      ok
+    PUSH u2;                    ok;    /master -> master/
+
+    # try auto-create push bar/u4 and this works!!
+    cd ..
+    CLONE u6 bar/u6;            ok;     /appear to have cloned an empty/
+                                        /Initialized empty/
+                                        !/testing site-wide disable/
+    cd u6;                      ok
+
+    # disable just foo
+    gitolite writes off foo foo down
+
+    # try push foo and see the message
+    cd ../foo;                  ok
+    tc g3;                      ok
+    PUSH u1;                    !ok;    /foo down/
+                                        !/testing site-wide disable/
+    # push bar/u2 ok
+    cd ../u2
+    tc g4
+    PUSH u2;                    ok;     /master -> master/
+
+    # enable foo, disable bar/u2
+    gitolite writes on foo
+    gitolite writes off bar/u2 the bar is closed
+
+    # try both
+    cd ../foo;                  ok
+    tc h3;                      ok
+    PUSH u1;                    ok;     /master -> master/
+    # push bar/u2 ok
+    cd ../u2
+    tc h4
+    PUSH u2;                    !ok;    /the bar is closed/
+";

commit 1ec8be663ea5a16b6c121d8a859279168735b6b8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 16:15:55 2012 +0530

    (test infrastructure) CLONE/PUSH macros redefined

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 52b3445..c8e1f52 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -45,8 +45,8 @@ try "
     DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
     DEF CHECK_SETUP = CS_1; git log; ok; /6b18ec2ab0f765122ec133959b36c57f77d4565c/
 
-    DEF CLONE = glt clone
-    DEF PUSH  = glt push
+    DEF CLONE = glt clone %1 file:///%2
+    DEF PUSH  = glt push %1 origin
 
     # clean install
     mkdir -p $ENV{HOME}/bin
diff --git a/t/basic.t b/t/basic.t
index 157f320..0b8017a 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -15,11 +15,11 @@ try "
 
     # subtest 1
     cd ..
-    CLONE dev2 file://gitolite-admin ga2
+    CLONE dev2 gitolite-admin ga2
                                 !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    CLONE admin --progress file://gitolite-admin ga2
+    glt clone admin --progress file:///gitolite-admin ga2
                                 ok;     gsh
                                         /Counting/; /Compressing/; /Total/
     cd gitolite-admin;          ok
@@ -41,12 +41,12 @@ try "
     git add conf;               ok
     git status -s;              ok;     /M  conf/gitolite.conf/
     git commit -m t01a;         ok;     /master.*t01a/
-    PUSH dev2 origin;           !ok;    gsh
+    PUSH dev2;                  !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    PUSH admin origin;          ok;     /master -> master/
+    PUSH admin;                 ok;     /master -> master/
     tsh empty;                  ok;
-    PUSH admin origin master:mm
+    PUSH admin master:mm
                                 !ok;    gsh
                                         /DENIED by refs/heads/mm/
                                         reject
@@ -72,31 +72,31 @@ try "
 
     # clone
     cd ..;                      ok;
-    CLONE u1 file://t1;         !ok;    gsh
+    CLONE u1 t1;                !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    CLONE u2 file://t1;         ok;     gsh
+    CLONE u2 t1;                ok;     gsh
                                         /warning: You appear to have cloned an empty repository./
     ls -al t1;                  ok;     /$ENV{USER}.*$ENV{USER}.*\.git/
     cd t1;                      ok;
 
     # push
     test-commit tc1 tc2 tc2;    ok;     /a530e66/
-    PUSH u2 origin;             !ok;    gsh
+    PUSH u2;                    !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    PUSH u3 origin master;      ok;     gsh
+    PUSH u3 master;             ok;     gsh
                                         /master -> master/
 
     # rewind
     reset-h HEAD^;              ok;     /HEAD is now at aa2b5c5 tc2/
     test-tick; test-commit tc3; ok;     /3ffced1/
-    PUSH u3 origin;             !ok;    gsh
+    PUSH u3;                    !ok;    gsh
                                         /rejected.*master -> master.*non-fast-forward./
-    PUSH u3 -f origin;          !ok;    gsh
+    PUSH u3 -f;                 !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    PUSH u4 origin +master;     ok;     gsh
+    PUSH u4 +master;            ok;     gsh
                                         / \\+ a530e66...3ffced1 master -> master.*forced update./
 ";
 
@@ -131,12 +131,12 @@ try "
 ";
 
 try "
-    CLONE u1 file://aa;         ok;     gsh
+    CLONE u1 aa;                ok;     gsh
     cd aa;                      ok
     test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9
                                 ok
-    PUSH u1 origin HEAD;        ok;     gsh
-                                        /To file://aa/
+    PUSH u1 HEAD;               ok;     gsh
+                                        /To file:///aa/
                                         /\\* \\[new branch\\]      HEAD -> master/
     branch dev;                 ok
     branch foo;                 ok
@@ -144,52 +144,52 @@ try "
     # u1 rewind master ok
     reset-h HEAD^;              ok
     test-commit r1;             ok
-    PUSH u1 origin +master;     ok;     gsh
-                                        /To file://aa/
+    PUSH u1 +master;            ok;     gsh
+                                        /To file:///aa/
                                         /\\+ 27ed463...05adfb0 master -> master .forced update./
 
     # u2 rewind master !ok
     reset-h HEAD^;              ok
     test-commit r2;             ok
-    PUSH u2 origin +master;     !ok;    gsh
+    PUSH u2 +master;            !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # u3 rewind master ok
     reset-h HEAD^;              ok
     test-commit r3;             ok
-    PUSH u3 origin +master;     ok;     gsh
-                                        /To file://aa/
+    PUSH u3 +master;            ok;     gsh
+                                        /To file:///aa/
                                         /\\+ 05adfb0...6a532fe master -> master .forced update./
 
     # u4 push master ok
     test-commit u4;             ok
-    PUSH u4 origin master;      ok;     gsh
-                                        /To file://aa/
+    PUSH u4 master;             ok;     gsh
+                                        /To file:///aa/
                                         /6a532fe..f929773 +master -> master/
 
     # u4 rewind master !ok
     reset-h HEAD^;              ok
-    PUSH u4 origin +master;     !ok;    gsh
+    PUSH u4 +master;            !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # u3,u4 push other branches !ok
-    PUSH u3 origin dev;         !ok;    gsh
+    PUSH u3 dev;                !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    PUSH u4 origin dev;         !ok;    gsh
+    PUSH u4 dev;                !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    PUSH u3 origin foo;         !ok;    gsh
+    PUSH u3 foo;                !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    PUSH u4 origin foo;         !ok;    gsh
+    PUSH u4 foo;                !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # clean up for next set
-    PUSH u1 -f origin master dev foo
+    glt push u1 -f origin master dev foo
                                 ok;     gsh
                                         /f929773...6a532fe master -> master .forced update./
                                         /new branch.*dev -> dev/
@@ -197,18 +197,18 @@ try "
 
     # u5 push master !ok
     test-commit u5
-    PUSH u5 origin master;      !ok;    gsh
+    PUSH u5 master;             !ok;    gsh
                                         reject
                                         /DENIED by refs/heads/master/
 
     # u5 rewind dev ok
-    PUSH u5 origin +dev^:dev
+    PUSH u5 +dev^:dev
                                 ok;     gsh
                                         /\\+ 27ed463...1ad477a dev\\^ -> dev .forced update./
 
 
     # u5 rewind foo !ok
-    PUSH u5 origin +foo^:foo
+    PUSH u5 +foo^:foo
                                 !ok;    gsh
                                         reject
                                         /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/
@@ -218,15 +218,15 @@ try "
     /Switched to branch 'foo'/
 
     test-commit u5
-    PUSH u5 origin foo;         ok;     gsh
+    PUSH u5 foo;                ok;     gsh
                                         /27ed463..83da62c *foo -> foo/
 
     # u1 delete dev ok
-    PUSH u1 origin :dev;        ok;     gsh
+    PUSH u1 :dev;               ok;     gsh
                                         / - \\[deleted\\] *dev/
 
     # push it back
-    PUSH u1 origin dev;         ok;     gsh
+    PUSH u1 dev;                ok;     gsh
                                         /\\* \\[new branch\\] *dev -> dev/
 
 ";
@@ -244,15 +244,15 @@ try "
 
     cd ..;                      ok
 
-    CLONE tester file://r1;     ok;     gsh
+    CLONE tester r1;            ok;     gsh
                                         /Cloning into 'r1'.../
     cd r1;                      ok
     test-commit r1a r1b r1c r1d r1e r1f
                                 ok
-    PUSH tester origin HEAD;    ok;     gsh
+    PUSH tester HEAD;           ok;     gsh
                                         /\\* \\[new branch\\] *HEAD -> master/
     git branch v1
-    PUSH tester origin v1;      ok;     gsh
+    PUSH tester v1;             ok;     gsh
                                         /\\* \\[new branch\\] *v1 -> v1/
 
 ";
@@ -271,14 +271,14 @@ try "
 
     cd ..;                      ok
 
-    CLONE tester file://r2;     ok;     gsh
+    CLONE tester r2;            ok;     gsh
                                         /Cloning into 'r2'.../
     cd r2;                      ok
     test-commit r2a r2b r2c r2d r2e r2f
                                 ok
-    PUSH tester origin HEAD;    ok;     gsh
+    PUSH tester HEAD;           ok;     gsh
                                         /\\* \\[new branch\\] *HEAD -> master/
     git branch v1
-    PUSH tester origin v1;      !ok;    gsh
+    PUSH tester v1;             !ok;    gsh
                                         /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
 "
diff --git a/t/vrefs-1.t b/t/vrefs-1.t
index 468a338..d7ecaa4 100755
--- a/t/vrefs-1.t
+++ b/t/vrefs-1.t
@@ -33,20 +33,20 @@ try "
     ADMIN_PUSH vr1a
     cd ..
     ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
-    CLONE u1 file://foo;        ok;     /Cloning into/
+    CLONE u1 foo;               ok;     /Cloning into/
                                         /You appear to have cloned an empty/
     cd foo;                     ok
     ls -Al;                     ok;     /\.git/
 
     # VREF not called for u1
     tc a1 a2 a3 a4 a5;          ok;     /aaf9e8e/
-    PUSH u1 origin master;      ok;     /new branch.*master -. master/
+    PUSH u1 master;             ok;     /new branch.*master -. master/
                                         !/helper program missing/
                                         !/hook declined/
                                         !/remote rejected/
     # VREF is called for u2
     tc b1;                      ok;     /1f440d3/
-    PUSH u2 origin;             !ok;    /helper program missing/
+    PUSH u2;                    !ok;    /helper program missing/
                                         /hook declined/
                                         /remote rejected/
 ";
@@ -73,48 +73,48 @@ try "
     cd ../foo;                  ok
 
     # u2 1 file
-    PUSH u2 origin;             ok;     /aaf9e8e..1f440d3.*master -. master/
+    PUSH u2;                    ok;     /aaf9e8e..1f440d3.*master -. master/
 
     # u2 2 files
     tc b2 b3;                   ok;     /c3397f7/
-    PUSH u2 origin;             ok;     /1f440d3..c3397f7.*master -. master/
+    PUSH u2;                    ok;     /1f440d3..c3397f7.*master -. master/
 
     # u2 3 files
     tc c1 c2 c3;                ok;     /be242d7/
-    PUSH u2 origin;             !ok;    /W VREF/COUNT/2 foo u2 DENIED by VREF/COUNT/2/
+    PUSH u2;                    !ok;    /W VREF/COUNT/2 foo u2 DENIED by VREF/COUNT/2/
                                         /too many changed files in this push/
                                         /hook declined/
                                         /remote rejected/
 
     # u4 3 files
-    PUSH u4 origin;             ok;     /c3397f7..be242d7.*master -. master/
+    PUSH u4;                    ok;     /c3397f7..be242d7.*master -. master/
 
     # u4 4 files
     tc d1 d2 d3 d4;             ok;     /88d80e2/
-    PUSH u4 origin;             ok;     /be242d7..88d80e2.*master -. master/
+    PUSH u4;                    ok;     /be242d7..88d80e2.*master -. master/
 
     # u4 5 files
     tc d5 d6 d7 d8 d9;          ok;     /e9c60b0/
-    PUSH u4 origin;             !ok;    /W VREF/COUNT/4 foo u4 DENIED by VREF/COUNT/4/
+    PUSH u4;                    !ok;    /W VREF/COUNT/4 foo u4 DENIED by VREF/COUNT/4/
                                         /too many changed files in this push/
                                         /hook declined/
                                         /remote rejected/
 
     # u1 all files
-    PUSH u1 origin;             ok;     /88d80e2..e9c60b0.*master -. master/
+    PUSH u1;                    ok;     /88d80e2..e9c60b0.*master -. master/
 
     # u6 6 old files
     test-tick
     tc d1 d2 d3 d4 d5 d6
                                 ok;     /2773f0a/
-    PUSH u6 origin;             ok;     /e9c60b0..2773f0a.*master -. master/
+    PUSH u6;                    ok;     /e9c60b0..2773f0a.*master -. master/
     tag six
 
     # u6 updates 7 old files
     test-tick; test-tick
     tc d1 d2 d3 d4 d5 d6 d7
                                 ok;     /d3fb574/
-    PUSH u6 origin;             !ok;    /W VREF/COUNT/6 foo u6 DENIED by VREF/COUNT/6/
+    PUSH u6;                    !ok;    /W VREF/COUNT/6 foo u6 DENIED by VREF/COUNT/6/
                                         /too many changed files in this push/
                                         /hook declined/
                                         /remote rejected/
@@ -124,7 +124,7 @@ try "
     test-tick; test-tick
     tc d1 d2 n1 n2 n3 n4
                                 ok;     /9e90848/
-    PUSH u6 origin;             !ok;    /W VREF/COUNT/3/NEWFILES foo u6 DENIED by VREF/COUNT/3/NEWFILES/
+    PUSH u6;                    !ok;    /W VREF/COUNT/3/NEWFILES foo u6 DENIED by VREF/COUNT/3/NEWFILES/
                                         /too many new files in this push/
                                         /hook declined/
                                         /remote rejected/
@@ -134,5 +134,5 @@ try "
     test-tick; test-tick
     tc d1 d2 d3 n1 n2 n3
                                 ok;     /e47ff5d/
-    PUSH u6 origin;             ok;     /2773f0a..e47ff5d.*master -. master/
+    PUSH u6;                    ok;     /2773f0a..e47ff5d.*master -. master/
 ";
diff --git a/t/vrefs-2.t b/t/vrefs-2.t
index b44af42..eb9d43a 100755
--- a/t/vrefs-2.t
+++ b/t/vrefs-2.t
@@ -33,7 +33,7 @@ try "
     cd ..
     # setup
     ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
-    CLONE u1 file://foo;        ok;     /Cloning into/
+    CLONE u1 foo;               ok;     /Cloning into/
                                         /You appear to have cloned an empty/
     cd foo;                     ok
     ls -Al;                     ok;     /\.git/
@@ -41,16 +41,16 @@ try "
     # u1 push 15 new files
     tc a b c d e f g h i j k l m n o
                                 ok;     /d8c0392/
-    PUSH u1 origin master;      ok;     /new branch.*master -. master/
+    PUSH u1 master;             ok;     /new branch.*master -. master/
 
     # u2 push 2 new 10 old without signoff
     tc a b c d e f g h i j u2a u2b
                                 ok;     /6787ac9/
-    PUSH u2 origin;             ok;     /d8c0392..6787ac9.*master -. master/
+    PUSH u2;                    ok;     /d8c0392..6787ac9.*master -. master/
 
     # u2 fail to push 3 new files without signoff
     tc u2c u2d u2e;             ok;     /a74562b/
-    PUSH u2 origin;             !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u2 DENIED by VREF/COUNT/2/NO_SIGNOFF/
+    PUSH u2;                    !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u2 DENIED by VREF/COUNT/2/NO_SIGNOFF/
                                         /top commit message should include the text .3 new files signed-off by: tester.example.com./
                                         /hook declined/
                                         /remote rejected/
@@ -59,16 +59,16 @@ try "
                                 ok;     /8dd31aa/
     git commit --allow-empty -m '15 new files signed-off by: tester\@example.com'
                                 ok;     /.master 6126489. 15 new files signed-off by: tester.example.com/
-    PUSH u2 origin;             ok;     /6787ac9..6126489.*master -. master/
+    PUSH u2;                    ok;     /6787ac9..6126489.*master -. master/
 
     # u4 push 2 new 10 old files without signoff
     tc u4a u4b a b c d e f g h i j
                                 ok;     /76c5593/
-    PUSH u4 origin;             ok;     /6126489..76c5593.*master -. master/
+    PUSH u4;                    ok;     /6126489..76c5593.*master -. master/
 
     # u4 fail push 3 new files withoug signoff
     tc u4c u4d u4e;             ok;     /2a84398/
-    PUSH u4 origin;             !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u4 DENIED by VREF/COUNT/2/NO_SIGNOFF/
+    PUSH u4;                    !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u4 DENIED by VREF/COUNT/2/NO_SIGNOFF/
                                         /top commit message should include the text .3 new files signed-off by: tester.example.com./
                                         /hook declined/
                                         /remote rejected/
@@ -78,14 +78,14 @@ try "
                                 ok;     /09b646a/
     git commit --allow-empty -m '10 new files signed-off by: tester\@example.com'
                                 ok;     /.master 47f84b0. 10 new files signed-off by: tester.example.com/
-    PUSH u4 origin;             ok;     /76c5593..47f84b0.*master -. master/
+    PUSH u4;                    ok;     /76c5593..47f84b0.*master -. master/
 
     # u4 fail push 11 new files even with signoff
     tc u4ab u4ac u4ad u4ae u4af u4ag u4ah u4ai u4aj u4ak u4al
                                 ok;     /90e7344/
     git commit --allow-empty -m '11 new files signed-off by: tester\@example.com'
                                 ok;     /.master 1f36537. 11 new files signed-off by: tester.example.com/
-    PUSH u4 origin;             !ok;    /W VREF/COUNT/10/NEWFILES foo u4 DENIED by VREF/COUNT/10/NEWFILES/
+    PUSH u4;                    !ok;    /W VREF/COUNT/10/NEWFILES foo u4 DENIED by VREF/COUNT/10/NEWFILES/
                                         /too many new files in this push/
                                         /hook declined/
                                         /remote rejected/
@@ -94,7 +94,7 @@ try "
     glt fetch u1 origin;        ok;
     reset-h origin/master;      ok;
     tc not-really.java;         ok;     /0f88b2e/
-    PUSH u4 origin;             ok;     /47f84b0..0f88b2e.*master -. master/
+    PUSH u4;                    ok;     /47f84b0..0f88b2e.*master -. master/
 ";
 
 put "|cat >> not-really.java", "
@@ -103,7 +103,7 @@ put "|cat >> not-really.java", "
 
 try "
     commit -am pbc;             ok;     /b2df6ef/
-    PUSH u4 origin;             !ok;    /W VREF/FILETYPE/AUTOGENERATED foo u4 DENIED by VREF/FILETYPE/AUTOGENERATED/
+    PUSH u4;                    !ok;    /W VREF/FILETYPE/AUTOGENERATED foo u4 DENIED by VREF/FILETYPE/AUTOGENERATED/
                                         /hook declined/
                                         /remote rejected/
 ";

commit bc2bd7a78c6d7296bd0e4ffb4b1cc8f2249770ab
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 12:51:00 2012 +0530

    oops; the $repo argument to PRE_ and POST_GIT triggers was wrong!

diff --git a/src/gitolite-shell b/src/gitolite-shell
index eac506d..99f2cce 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -82,9 +82,9 @@ sub main {
     trigger( 'ACCESS_CHECK', $repo, $user, $aa, 'any', $ret );
     _die $ret if $ret =~ /DENIED/;
 
-    $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
     trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
-    _system( "git", "shell", "-c", "$verb $repo" );
+    my $repodir = "'$rc{GL_REPO_BASE}/$repo.git'";
+    _system( "git", "shell", "-c", "$verb $repodir" );
     trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
 }
 

commit 1de773ab8e4ed4b77282bf4799de10733472831d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 12:21:24 2012 +0530

    perm-roles.t

diff --git a/t/perm-roles.t b/t/perm-roles.t
new file mode 100755
index 0000000..6264644
--- /dev/null
+++ b/t/perm-roles.t
@@ -0,0 +1,218 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# permissions using role names
+# ----------------------------------------------------------------------
+
+try "plan 91";
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+confreset; confadd '
+    @g1 = u1
+    @g2 = u2
+    @g3 = u3
+    @g4 = u4
+        repo foo/CREATOR/..*
+          C                 =   @g1
+          RW+               =   CREATOR
+          -     refs/tags/  =   WRITERS
+          RW                =   WRITERS
+          R                 =   READERS
+          RW+D              =   MANAGERS
+          RW    refs/tags/  =   TESTERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+cd ..
+
+# make foo/u1/u1r1
+rm -rf ~/td/u1r1
+glt clone u1 file:///foo/u1/u1r1
+        /Initialized empty Git repository in .*/foo/u1/u1r1.git//
+cd u1r1
+
+# CREATOR can push
+tc e-549 e-550
+glt push u1 file:///foo/u1/u1r1 master:master
+        POK; /master -> master/
+# CREATOR can create branch
+tc w-277 w-278
+glt push u1 file:///foo/u1/u1r1 master:b1
+        POK; /master -> b1/
+# CREATOR can rewind branch
+git reset --hard HEAD^
+tc d-987 d-988
+glt push u1 file:///foo/u1/u1r1 +master:b1
+        POK; /master -> b1 \\(forced update\\)/
+# CREATOR cannot delete branch
+glt push u1 file:///foo/u1/u1r1 :b1
+        /D refs/heads/b1 foo/u1/u1r1 u1 DENIED by fallthru/
+        reject
+
+# CREATOR can push a tag
+git tag t1 HEAD^^
+glt push u1 file:///foo/u1/u1r1 t1
+        POK; /\\[new tag\\]         t1 -> t1/
+
+# add u2 to WRITERS
+echo WRITERS \@g2 | glt perms u1 foo/u1/u1r1
+glt perms u1 -l foo/u1/u1r1
+        /WRITERS \@g2/
+
+glt fetch u1
+git reset --hard origin/master
+
+# WRITERS can push
+tc j-185 j-186
+glt push u2 file:///foo/u1/u1r1 master:master
+        POK; /master -> master/
+# WRITERS can create branch
+tc u-420 u-421
+glt push u2 file:///foo/u1/u1r1 master:b2
+        POK; /master -> b2/
+# WRITERS cannot rewind branch
+git reset --hard HEAD^
+tc l-136 l-137
+glt push u2 file:///foo/u1/u1r1 +master:b2
+        /\\+ refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
+        reject
+# WRITERS cannot delete branch
+glt push u2 file:///foo/u1/u1r1 :b2
+        /D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
+        reject
+# WRITERS cannot push a tag
+git tag t2 HEAD^^
+glt push u2 file:///foo/u1/u1r1 t2
+        /W refs/tags/t2 foo/u1/u1r1 u2 DENIED by refs/tags//
+        reject
+
+# change u2 to READERS
+echo READERS u2 | glt perms u1 foo/u1/u1r1
+glt perms u1 -l foo/u1/u1r1
+        /READERS u2/
+
+glt fetch u1
+git reset --hard origin/master
+
+# READERS cannot push at all
+tc v-753 v-754
+glt push u2 file:///foo/u1/u1r1 master:master
+        /W any foo/u1/u1r1 u2 DENIED by fallthru/
+
+# add invalid category MANAGERS
+    /usr/bin/printf 'READERS u6\\nMANAGERS u2\\n' | glt perms u1 foo/u1/u1r1
+        !ok
+        /Invalid role 'MANAGERS'/
+";
+
+# make MANAGERS valid
+put "$ENV{HOME}/g3trc", "\$rc{ROLES}{MANAGERS} = 1;\n";
+
+# add u2 to now valid MANAGERS
+try "
+    ENV G3T_RC=$ENV{HOME}/g3trc
+    gitolite compile;   ok or die compile failed
+    /usr/bin/printf 'READERS u6\\nMANAGERS u2\\n' | glt perms u1 foo/u1/u1r1
+                            ok;    !/Invalid role 'MANAGERS'/
+    glt perms u1 -l foo/u1/u1r1
+";
+
+cmp 'READERS u6
+MANAGERS u2
+';
+
+try "
+glt fetch u1
+git reset --hard origin/master
+
+# MANAGERS can push
+tc d-714 d-715
+glt push u2 file:///foo/u1/u1r1 master:master
+        POK; /master -> master/
+
+# MANAGERS can create branch
+tc n-614 n-615
+glt push u2 file:///foo/u1/u1r1 master:b3
+        POK; /master -> b3/
+# MANAGERS can rewind branch
+git reset --hard HEAD^
+tc a-511 a-512
+glt push u2 file:///foo/u1/u1r1 +master:b3
+        POK; /master -> b3 \\(forced update\\)/
+# MANAGERS cannot delete branch
+glt push u2 file:///foo/u1/u1r1 :b3
+        / - \\[deleted\\]         b3/
+# MANAGERS can push a tag
+git tag t3 HEAD^^
+glt push u2 file:///foo/u1/u1r1 t3
+        POK; /\\[new tag\\]         t3 -> t3/
+
+# add invalid category TESTERS
+echo TESTERS u2 | glt perms u1 foo/u1/u1r1
+        !ok
+        /Invalid role 'TESTERS'/
+";
+
+# make TESTERS valid
+put "|cat >> $ENV{HOME}/g3trc", "\$rc{ROLES}{TESTERS} = 1;\n";
+
+try "
+gitolite compile;   ok or die compile failed
+# add u2 to now valid TESTERS
+echo TESTERS u2 | glt perms u1 foo/u1/u1r1
+        !/Invalid role 'TESTERS'/
+glt perms u1 -l foo/u1/u1r1
+";
+
+cmp 'TESTERS u2
+';
+
+try "
+glt fetch u1
+git reset --hard origin/master
+
+# TESTERS cannot push
+tc d-134 d-135
+glt push u2 file:///foo/u1/u1r1 master:master
+        /W refs/heads/master foo/u1/u1r1 u2 DENIED by fallthru/
+        reject
+# TESTERS cannot create branch
+tc p-668 p-669
+glt push u2 file:///foo/u1/u1r1 master:b4
+        /W refs/heads/b4 foo/u1/u1r1 u2 DENIED by fallthru/
+        reject
+# TESTERS cannot delete branch
+glt push u2 file:///foo/u1/u1r1 :b2
+        /D refs/heads/b2 foo/u1/u1r1 u2 DENIED by fallthru/
+        reject
+# TESTERS can push a tag
+git tag t4 HEAD^^
+glt push u2 file:///foo/u1/u1r1 t4
+        POK; /\\[new tag\\]         t4 -> t4/
+";
+
+# make TESTERS invalid again
+put "$ENV{HOME}/g3trc", "\$rc{ROLES}{MANAGERS} = 1;\n";
+
+try "
+gitolite compile;   ok or die compile failed
+# CREATOR can push
+glt fetch u1
+git reset --hard origin/master
+tc y-626 y-627
+glt push u1 file:///foo/u1/u1r1 master:master
+        POK; /master -> master/
+# TESTERS is an invalid category
+git tag t5 HEAD^^
+glt push u2 file:///foo/u1/u1r1 t5
+        /role 'TESTERS' not allowed, ignoring/
+        /W any foo/u1/u1r1 u2 DENIED by fallthru/
+";

commit 34cfdb435509be311bc014fcc41088d57407d567
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 10:18:02 2012 +0530

    add some checks to perms
    
      - check user auth
      - check role names in legacy mode

diff --git a/src/commands/perms b/src/commands/perms
index e70ca81..45004dc 100755
--- a/src/commands/perms
+++ b/src/commands/perms
@@ -40,7 +40,7 @@ setperms(@ARGV);
 
 sub getperms {
     my $repo = shift;
-    _die "repo '$repo' missing" if repo_missing($repo);
+    _die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     print slurp($pf) if -f $pf;
@@ -50,13 +50,18 @@ sub getperms {
 
 sub setperms {
     my $repo = shift;
-    _die "repo '$repo' missing" if repo_missing($repo);
+    _die "sorry you are not authorised" if repo_missing($repo) or creator($repo) ne $ENV{GL_USER};
     my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
 
     if ( not @_ ) {
         # legacy mode; pipe data in
         @ARGV = ();
-        _print( $pf, <> );
+        my @a;
+        for (<>) {
+            _die "Invalid role '$1'; check the rc file" if /(\S+)/ and not $rc{ROLES}{$1};
+            push @a, $_;
+        }
+        _print( $pf, @a );
         exit;
     }
 

commit a6d8184a5667de7d82989cafb2d394442728eb3b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 09:19:48 2012 +0530

    rule sequences test

diff --git a/t/rule-seq.t b/t/rule-seq.t
new file mode 100755
index 0000000..b7e3386
--- /dev/null
+++ b/t/rule-seq.t
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# rule sequence
+# ----------------------------------------------------------------------
+
+# this is the specific example in commit 32056e0 of g2
+
+try "plan 27";
+
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+confreset; confadd '
+    @private-owners = u1 u2
+    @experienced-private-owners = u3 u4
+
+    repo CREATOR/.*
+      C   = @private-owners @experienced-private-owners
+      RWD = CREATOR
+      RW  = WRITERS
+      R   = READERS
+      -   = @private-owners
+      RW+D = CREATOR
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+    glt clone u1 file:///u1/r1
+        /Initialized empty Git repository in .*/u1/r1.git//
+    cd r1
+    tc h-395
+    glt push u1 origin master
+    git checkout -b br1
+    tc m-367
+    tc i-747
+
+    # u1 create branch
+    glt push u1 origin br1
+        /\\* \\[new branch\\]      br1 -> br1/
+        POK; /br1 -> br1/
+
+    # u1 rewind branch
+    git reset --hard HEAD^
+    tc e-633
+    glt push u1 origin +br1
+        /\\+ refs/heads/br1 u1/r1 u1 DENIED by refs//
+        /error: hook declined to update refs/heads/br1/
+        reject
+
+    # u1 delete branch
+    glt push u1 origin :br1
+        /\\[deleted\\]         br1/
+
+    cd ..
+    rm -rf r1
+    glt clone u3 file:///u3/r1
+        /Initialized empty Git repository in .*/u3/r1.git//
+    cd r1
+    tc p-274
+    glt push u3 origin master
+    git checkout -b br1
+    tc s-613
+    tc k-988
+
+    # u3 create branch
+    glt push u3 origin br1
+        /\\* \\[new branch\\]      br1 -> br1/
+        POK; /br1 -> br1/
+
+    # u3 rewind branch
+    git reset --hard HEAD^
+    tc n-919
+    glt push u3 origin +br1
+        /To file:///u3/r1/
+        /\\+ .......\\.\\.\\........ br1 -> br1 \\(forced update\\)/
+
+    # u3 delete branch
+    glt push u3 origin :br1
+        /\\[deleted\\]         br1/
+";

commit ed47d1aef89950f9eeb0f2497af0517e4ab8fc85
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 09:13:05 2012 +0530

    two significant fixes to load:
    
      - don't look for user-roles if the repo is missing (doesn't make sense
        and because we roll in the <perm> = CREATOR function into that, it
        causes bugs like [1] below)
    
      - allow ^CREATOR/ in repo names (i.e., don't insist it has to be
        /CREATOR/)
    
    ----
    
    [1] here's the bug
    
        repo foo/..*
            C   =   u1
            RW+ =   CREATOR # <--- this line
            R   =   READERS
            RW  =   WRITERS
    
        causes
            GL_USER=u2 gitolite info
    
        to print
            hello u2, this is gitolite3 (unknown) on git 1.7.7.6
    
             R W  	foo/..*
             R W  	testing
    
        when in reality it should not be looking at CREATOR at all.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index bf4dd0f..752da49 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -282,7 +282,7 @@ sub memberships {
         }
     }
 
-    if ( $type eq 'user' and $repo ) {
+    if ( $type eq 'user' and $repo and not repo_missing($repo)  ) {
         # find the roles this user has when accessing this repo and add those
         # in as groupnames he is a member of.  You need the already existing
         # memberships for this; see below this function for an example
@@ -361,7 +361,9 @@ sub generic_name {
     # In particular, 'gitolite access' can't be used to check ^C perms.
     $creator = creator($base);
 
-    ( $base2 = $base ) =~ s(/$creator/)(/CREATOR/) if $creator;
+    $base2 = $base;
+    $base2 =~ s(/$creator/)(/CREATOR/) if $creator;
+    $base2 =~ s(^$creator/)(CREATOR/)  if $creator;
     $base2 = '' if $base2 eq $base;    # if there was no change
 
     return $base2;

commit 545c00aa26652e5b2b3888793f99ebb5afd43f00
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 08:51:23 2012 +0530

    perms with groups test

diff --git a/t/perms-groups.t b/t/perms-groups.t
new file mode 100755
index 0000000..1681d33
--- /dev/null
+++ b/t/perms-groups.t
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# assigning roles to groups instead of users
+# ----------------------------------------------------------------------
+
+try "plan 31";
+
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+confreset; confadd '
+    @leads = u1 u2
+    @devs = u1 u2 u3 u4
+
+    @gbar = bar/CREATOR/..*
+    repo    @gbar
+        C               =   @leads
+        RW+             =   CREATOR
+        RW              =   WRITERS
+        R               =   READERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+    # u1 auto-creates a repo
+    glt ls-remote u1 file:///bar/u1/try1
+        /Initialized empty Git repository in .*/bar/u1/try1.git//
+    # default permissions for u2 and u4
+    glt info u1 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+    glt info u2 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+    glt info u4 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+
+    # \@leads can RW try1
+    echo WRITERS \@leads | glt perms u1 bar/u1/try1; ok
+    glt info u1 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+    glt info u2 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+    glt info u4 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+
+    # \@devs can R try1
+    echo READERS \@devs | glt perms u1 bar/u1/try1; ok
+    glt perms u1 -l bar/u1/try1
+        /READERS \@devs/
+        !/WRITERS \@leads/
+
+    glt info u1 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+
+    glt info u2 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+        /R *\tbar/u1/try1\tu1/
+
+    glt info u4 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+        /R *\tbar/u1/try1\tu1/
+
+# combo of previous 2
+    /usr/bin/printf 'READERS \@devs\\nWRITERS \@leads\\n' | glt perms u1 bar/u1/try1; ok
+    glt perms u1 -l bar/u1/try1
+        /READERS \@devs/
+        /WRITERS \@leads/
+    glt info u1 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+    glt info u2 -p -lc
+        /R W *\tbar/u1/try1\tu1/
+    glt info u4 -p -lc
+        !/R W *\tbar/u1/try1\tu1/
+        /R *\tbar/u1/try1\tu1/
+";

commit 741512482bf1b3e03fb4302e05cace1b7b92e143
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 07:43:20 2012 +0530

    'info' learned not to show ^C column when used with '-p'

diff --git a/src/commands/info b/src/commands/info
index b3744e3..0c503e1 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -53,12 +53,15 @@ my $lr = lister_dispatch('list-repos');
 
 my $perm;
 my $repos;
+my @aa;
 
 if ($phy) {
     _chdir( $rc{GL_REPO_BASE} );
     $repos = list_phy_repos(1);
+    @aa = qw(R W);
 } else {
     $repos = $lr->();
+    @aa = qw(R W ^C);
 }
 
 my $creator = '';
@@ -66,7 +69,8 @@ for my $repo (@$repos) {
     next unless $repo =~ /$patt/;
     my $perm = '';
     $creator = creator($repo) if $lc;
-    for my $aa (qw(R W ^C)) {
+
+    for my $aa (@aa) {
         my $ret = access( $repo, $user, $aa, $ref );
         $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
     }
diff --git a/t/info.t b/t/info.t
index 1a1cd69..7844fa5 100755
--- a/t/info.t
+++ b/t/info.t
@@ -71,33 +71,33 @@ try "
 
 try "
     glt info u1 -p; ok; GS u1
-        /R W  \tt1/
-        /R    \tt2/
-        /R W  \ttesting/
-        !/R W  \tt3/
+        /R W *\tt1/
+        /R   *\tt2/
+        /R W *\ttesting/
+        !/R W *\tt3/
     glt info u2 -p; ok; GS u2
-        /R    \tt1/
-        /R W  \tt2/
-        /R W  \ttesting/
-        !/R W  \tt3/
+        /R   *\tt1/
+        /R W *\tt2/
+        /R W *\ttesting/
+        !/R W *\tt3/
     glt info u3 -p; ok; GS u3
-        /R W  \tt3/
-        /R W  \ttesting/
-        !/R    \tt1/
-        !/R W  \tt2/
+        /R W *\tt3/
+        /R W *\ttesting/
+        !/R   *\tt1/
+        !/R W *\tt2/
     glt info u4 -p; ok; GS u4
-        /R    \tt3/
-        /R W  \ttesting/
-        !/R    \tt1/
-        !/R W  \tt2/
+        /R   *\tt3/
+        /R W *\ttesting/
+        !/R   *\tt1/
+        !/R W *\tt2/
     glt info u5 -p; ok; GS u5
-        /R W  \ttesting/
-        !/R    \tt1/
-        !/R W  \tt2/
-        !/R W  \tt3/
+        /R W *\ttesting/
+        !/R   *\tt1/
+        !/R W *\tt2/
+        !/R W *\tt3/
     glt info u6 -p; ok; GS u6
-        /R W  \ttesting/
-        !/R    \tt1/
-        !/R W  \tt2/
-        !/R W  \tt3/
+        /R W *\ttesting/
+        !/R   *\tt1/
+        !/R W *\tt2/
+        !/R W *\tt3/
 ";
diff --git a/t/sequence.t b/t/sequence.t
index 85be29a..f88348a 100755
--- a/t/sequence.t
+++ b/t/sequence.t
@@ -38,8 +38,8 @@ try "
         /WRITERS u2/
     # expand
     glt info u2 -p
-        /R W  \tfoo/u1/bar/
-        /R W  \ttesting/
+        /R W *\tfoo/u1/bar/
+        /R W *\ttesting/
 
     # push
     cd ..
@@ -81,8 +81,8 @@ try "
         /WRITERS u2/
     # expand
     glt info u2 -p
-        /R W  \tfoo/u1/bar/
-        /R W  \ttesting/
+        /R W *\tfoo/u1/bar/
+        /R W *\ttesting/
 
     # push
     cd ..

commit 2e1f840f1372765c3ee7f0a61e1bf2f4bd8be266
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 07:19:07 2012 +0530

    'info' learns '-lc' option (and load.pm exports creator())

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 42a4294..bf4dd0f 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -9,6 +9,7 @@ package Gitolite::Conf::Load;
   git_config
   option
   repo_missing
+  creator
   vrefs
   lister_dispatch
 );
diff --git a/src/commands/info b/src/commands/info
index 5a50e9e..b3744e3 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -10,25 +10,37 @@ use Gitolite::Common;
 use Gitolite::Conf::Load;
 
 =for usage
-Usage:  gitolite info [-p] [optional repo name pattern]
+Usage:  gitolite info [-p [-lc] [<repo name pattern>]
 
 List all repos/repo groups you can access.  By default, it shows you what the
 conf file specified, which means group names and wild card patterns may show
-up.
+up.  Example, if the conf file looked like this:
 
-With '-p' it looks at actual (physical) repos instead.
+    @oss = git gitolite linux
+    repo @oss
+        RW+ =   YourName
+
+then running 'ssh git at host info' will only show you '@oss'.
+
+'-p' looks at actual (physical) repos instead; in our example this will show
+you git, gitolite, and linux.
+
+'-lc' lists creators as an additional field at the end; this option is only
+available with '-p'.
 
 The optional pattern is an unanchored regex that will limit the repos
 searched, in both cases.  It might speed up things a little if you have more
 than a few thousand repos.
 =cut
 
-my ( $help, $phy, $patt ) = ('') x 3;
+my ( $help, $phy, $lc, $patt ) = ('') x 4;
 GetOptions(
-    'p' => \$phy,
-    'h' => \$help,
+    'lc' => \$lc,
+    'p'  => \$phy,
+    'h'  => \$help,
 ) or usage();
 
+usage("'-lc' requires '-p'") if $lc and not $phy;
 usage() if @ARGV > 1 or $help;
 $patt = shift || '.';
 
@@ -49,13 +61,18 @@ if ($phy) {
     $repos = $lr->();
 }
 
+my $creator = '';
 for my $repo (@$repos) {
     next unless $repo =~ /$patt/;
     my $perm = '';
+    $creator = creator($repo) if $lc;
     for my $aa (qw(R W ^C)) {
         my $ret = access( $repo, $user, $aa, $ref );
         $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
     }
     $perm =~ s/\^//;
-    print "$perm\t$repo\n" if $perm =~ /\S/;
+    next unless $perm =~ /\S/;
+    print "$perm\t$repo";
+    print "\t$creator" if $lc;
+    print "\n";
 }

commit 9f1e360ef30f11348a3663a79d398b5eff219de5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Mar 20 06:41:14 2012 +0530

    deny-create test (the RWC stuff)

diff --git a/t/deny-create.t b/t/deny-create.t
new file mode 100755
index 0000000..67451ea
--- /dev/null
+++ b/t/deny-create.t
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# deny-create, the RW.*C flag
+# ----------------------------------------------------------------------
+
+try "plan 72";
+
+try "DEF POK = !/DENIED/; !/failed to push/";
+
+# test "C" permissions
+
+confreset; confadd '
+    @leads = u1 u2
+    @devs = u1 u2 u3 u4
+
+    @gfoo = foo
+    repo    @gfoo
+        RW+C                =   @leads
+        RW+C personal/USER/ =   @devs
+        RW                  =   @devs
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+    glt clone u1 file:///foo
+
+    cd foo
+    tc t-413 t-414 t-415 t-416 t-417
+
+    # u1 can push/rewind master on foo
+    glt push u1 origin master
+        POK; /master -> master/
+    glt push u1 -f origin master^^:master
+        POK; /master\\^\\^ -> master/
+
+    # u2 can create newbr1 on foo
+    glt push u2 file:///foo master:newbr1
+        POK; /master -> newbr1/
+
+    # u2 can create newtag on foo
+    git tag newtag
+    glt push u2 file:///foo newtag
+        POK; /newtag -> newtag/
+
+    # u3 can push newbr1 on foo
+    tc u-962 u-963 u-964 u-965 u-966
+    glt push u3 file:///foo master:newbr1
+        POK; /master -> newbr1/
+
+    # u4 canNOT create newbr2 on foo
+    tc e-615 e-616 e-617 e-618 e-619
+    glt push u3 file:///foo master:newbr2
+        /C refs/heads/newbr2 foo u3 DENIED by fallthru/
+        reject
+
+    # u4 canNOT create newtag2 on foo
+    git tag newtag2
+    glt push u3 file:///foo newtag2
+        /C refs/tags/newtag2 foo u3 DENIED by fallthru/
+        reject
+
+    # u4 can create/rewind personal/u4/newbr3 on foo
+    tc f-664 f-665 f-666 f-667 f-668
+    glt push u4 file:///foo master:personal/u4/newbr3
+        POK; /master -> personal/u4/newbr3/
+    glt push u4 -f origin master^^:personal/u4/newbr3
+        POK; /master\\^\\^ -> personal/u4/newbr3/
+";
+
+# bar, without "C" permissions, should behave like old
+
+confadd '
+    @leads = u1 u2
+    @devs = u1 u2 u3 u4
+
+    @gbar = bar
+    repo    @gbar
+        RW+                 =   @leads
+        RW+  personal/USER/ =   @devs
+        RW                  =   @devs
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+    glt clone u1 file:///bar
+
+    cd bar
+    tc u-907 u-908 u-909 u-910 u-911
+
+    # u1 can push/rewind master on bar
+    glt push u1 origin master
+        POK; /master -> master/
+    glt push u1 -f origin master^^:master
+        POK; /master\\^\\^ -> master/
+
+    # u2 can create newbr1 on bar
+    glt push u2 file:///bar master:newbr1
+        POK; /master -> newbr1/
+
+    # u2 can create newtag on bar
+    git tag newtag
+    glt push u2 file:///bar newtag
+        POK; /newtag -> newtag/
+
+    # u3 can push newbr1 on bar
+    tc y-862 y-863 y-864 y-865 y-866
+    glt push u3 file:///bar master:newbr1
+        POK; /master -> newbr1/
+
+    # u4 can create newbr2 on bar
+    tc q-417 q-418 q-419 q-420 q-421
+    glt push u3 file:///bar master:newbr2
+        POK; /master -> newbr2/
+
+    # u4 can create newtag2 on bar
+    git tag newtag2
+    glt push u3 file:///bar newtag2
+        POK; /newtag2 -> newtag2/
+
+    # u4 can create/rewind personal/u4/newbr3 on bar
+    tc v-605 v-606 v-607 v-608 v-609
+    glt push u4 file:///bar master:personal/u4/newbr3
+        POK; /master -> personal/u4/newbr3/
+    glt push u4 -f origin master^^:personal/u4/newbr3
+        POK; /master\\^\\^ -> personal/u4/newbr3/
+
+";

commit 0614655252bb98df3ca499041ad548accacfd44f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 21:59:01 2012 +0530

    (minor) sequence.t

diff --git a/t/sequence.t b/t/sequence.t
new file mode 100755
index 0000000..85be29a
--- /dev/null
+++ b/t/sequence.t
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# uhh, seems to be another rule sequence test
+# ----------------------------------------------------------------------
+
+try "plan 40";
+
+confreset;confadd '
+    @staff = u1 u2 u3
+    @gfoo = foo/CREATOR/..*
+    repo  @gfoo
+          C       = u1
+          RW+     = CREATOR
+          RW      = WRITERS
+          -       = @staff
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+    glt clone u1 file:///foo/u1/bar;    ok
+        /Initialized empty Git repository in .*/foo/u1/bar.git//
+
+    cd bar
+    tc p-906
+    glt push u1 origin master
+        /To file:///foo/u1/bar/
+        /\\[new branch\\]      master -> master/
+    echo WRITERS u2 | glt perms u1 foo/u1/bar
+    glt perms u1 -l foo/u1/bar
+        /WRITERS u2/
+    # expand
+    glt info u2 -p
+        /R W  \tfoo/u1/bar/
+        /R W  \ttesting/
+
+    # push
+    cd ..
+    glt clone u2 file:///foo/u1/bar u2bar
+        /Cloning into 'u2bar'.../
+    cd u2bar
+    tc p-222
+    glt push u2
+        /master -> master/
+        !/DENIED/
+        !/failed to push/
+";
+
+confreset;confadd '
+    @staff = u1 u2 u3
+    @gfoo = foo/CREATOR/.+
+    repo  @gfoo
+          C       = u1
+          RW+     = CREATOR
+          -       = @staff
+          RW      = WRITERS
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..
+    rm -rf bar u2bar
+    glt clone u1 file:///foo/u1/bar;    ok
+        /Initialized empty Git repository in .*/foo/u1/bar.git//
+
+    cd bar
+    tc p-906
+    glt push u1 origin master
+        /To file:///foo/u1/bar/
+        /\\[new branch\\]      master -> master/
+    echo WRITERS u2 | glt perms u1 foo/u1/bar
+    glt perms u1 -l foo/u1/bar
+        /WRITERS u2/
+    # expand
+    glt info u2 -p
+        /R W  \tfoo/u1/bar/
+        /R W  \ttesting/
+
+    # push
+    cd ..
+    glt clone u2 file:///foo/u1/bar u2bar
+        /Cloning into 'u2bar'.../
+    cd u2bar
+    tc p-222
+    glt push u2
+        !ok
+        reject
+        /W refs/heads/master foo/u1/bar u2 DENIED by refs/\\.\\*/
+";

commit 3f7edfea672c6736ff9a85b6064bde5524c0d707
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 21:34:07 2012 +0530

    usage() needed some minor fixes...

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index a80b2db..6dbc6e3 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -84,15 +84,11 @@ sub _die {
 
 sub usage {
     _warn(shift) if @_;
-    my ( $script, $function ) = ( caller(1) )[ 1, 3 ];
-    if ( not $script ) {
-        $script   = (caller)[1];
-        $function = 'usage';
-    }
-    dbg( "u s a g e", $script, $function );
+    my $script = (caller)[1];
+    my $function = ( ( ( caller(1) )[3] ) || ( ( caller(0) )[3] ) );
     $function =~ s/.*:://;
     my $code = slurp($script);
-    $code =~ /^=for $function(.*?)^=cut/sm;
+    $code =~ /^=for $function\b(.*?)^=cut/sm;
     say2( $1 ? $1 : "...no usage message in $script" );
     exit 1;
 }
diff --git a/src/commands/access b/src/commands/access
index f42e147..11d2e9a 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -24,15 +24,6 @@ Notes:
     The 'any' ref is special -- it ignores deny rules (see docs for what this
     means and exceptions).
 
-Advanced use (examples only):
-
-    gitolite list-phy-repos | gitolite access % gitweb R | grep -v DENIED | cut -f1 > ~/projects.list
-    # now people can stop thinking gitolite has anything to do with gitweb!
-
-    gitolite list-phy-repos | grep foo |
-        perl -lne 'print "$_ gitweb\n$_ daemon"' |
-        gitolite access % % R | grep -v DENIED | cut -f1 > insecure.repos
-
 For each case where access is not denied, one line is printed like this:
 
     reponame<tab>username<tab>access rights
@@ -40,6 +31,8 @@ For each case where access is not denied, one line is printed like this:
 This is orders of magnitude faster than running the command multiple times;
 you'll notice if you have more than a hundred or so repos.
 
+Advanced uses: see src/commands/post-compile/update-git-daemon-access-list for
+a good example.
 =cut
 
 # TODO: deal with "C", call it ^C
diff --git a/src/commands/help b/src/commands/help
index 211b73a..cb7b2f3 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -12,6 +12,8 @@ Usage:  gitolite help
 Prints a list of custom commands available at this gitolite installation.
 =cut
 
+usage() if @ARGV;
+
 my $user = $ENV{GL_USER} || '';
 print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
 
diff --git a/src/commands/info b/src/commands/info
index 92efe81..5a50e9e 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -29,7 +29,7 @@ GetOptions(
     'h' => \$help,
 ) or usage();
 
-usage() if @ARGV > 1;
+usage() if @ARGV > 1 or $help;
 $patt = shift || '.';
 
 my $user = $ENV{GL_USER} or _die "GL_USER not set";

commit 32494cfa0cf47f8a0642bc0fb145c54ed51dcd37
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 21:01:08 2012 +0530

    info learned '-p' and pattern limiting.
    
    '-p' is what gives you 'expand' now

diff --git a/src/commands/info b/src/commands/info
index 65e7f30..92efe81 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -2,19 +2,35 @@
 use strict;
 use warnings;
 
+use Getopt::Long;
+
 use lib $ENV{GL_BINDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
 
 =for usage
-Usage:  gitolite info
+Usage:  gitolite info [-p] [optional repo name pattern]
+
+List all repos/repo groups you can access.  By default, it shows you what the
+conf file specified, which means group names and wild card patterns may show
+up.
 
-  - list all repos/repo groups you can access
-  - no options, no flags
+With '-p' it looks at actual (physical) repos instead.
+
+The optional pattern is an unanchored regex that will limit the repos
+searched, in both cases.  It might speed up things a little if you have more
+than a few thousand repos.
 =cut
 
-usage() if @ARGV;
+my ( $help, $phy, $patt ) = ('') x 3;
+GetOptions(
+    'p' => \$phy,
+    'h' => \$help,
+) or usage();
+
+usage() if @ARGV > 1;
+$patt = shift || '.';
 
 my $user = $ENV{GL_USER} or _die "GL_USER not set";
 my $ref = 'any';
@@ -22,22 +38,24 @@ my $ref = 'any';
 print "hello $user, this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
 
 my $lr = lister_dispatch('list-repos');
-my $lm = lister_dispatch('list-members');
 
-for ( @{ $lr->() } ) {
+my $perm;
+my $repos;
+
+if ($phy) {
+    _chdir( $rc{GL_REPO_BASE} );
+    $repos = list_phy_repos(1);
+} else {
+    $repos = $lr->();
+}
+
+for my $repo (@$repos) {
+    next unless $repo =~ /$patt/;
     my $perm = '';
     for my $aa (qw(R W ^C)) {
-        my $ret = access( $_, $user, $aa, $ref );
+        my $ret = access( $repo, $user, $aa, $ref );
         $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
     }
-    next unless $perm =~ /\S/;
-    if (/^\@/) {
-        print "\n$perm\t$_\n";
-        for ( @{ $lm->($_) } ) {
-            print "$perm\t$_\n";
-        }
-        print "\n";
-    } else {
-        print "$perm\t$_\n";
-    }
+    $perm =~ s/\^//;
+    print "$perm\t$repo\n" if $perm =~ /\S/;
 }
diff --git a/t/info.t b/t/info.t
index e0538d7..1a1cd69 100755
--- a/t/info.t
+++ b/t/info.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # the info command
 # ----------------------------------------------------------------------
 
-try 'plan 39';
+try 'plan 83';
 
 try "## info";
 
@@ -32,27 +32,72 @@ try "
                                         /Initialized.*empty.*t2.git/
                                         /Initialized.*empty.*t3.git/
 ";
+
+# GS == greeting string
+try "DEF GS = /hello %1, this is gitolite/";
+
+try "
+    glt info u1; ok; GS u1
+        /R W  \t\@t1/
+        /R    \tt2/
+        /R W  \ttesting/
+        !/R W  \tt3/
+    glt info u2; ok; GS u2
+        /R    \t\@t1/
+        /R W  \tt2/
+        /R W  \ttesting/
+        !/R W  \tt3/
+    glt info u3; ok; GS u3
+        /R W  \tt3/
+        /R W  \ttesting/
+        !/R    \t\@t1/
+        !/R W  \tt2/
+    glt info u4; ok; GS u4
+        /R    \tt3/
+        /R W  \ttesting/
+        !/R    \t\@t1/
+        !/R W  \tt2/
+    glt info u5; ok; GS u5
+        /R W  \ttesting/
+        !/R    \t\@t1/
+        !/R W  \tt2/
+        !/R W  \tt3/
+    glt info u6; ok; GS u6
+        /R W  \ttesting/
+        !/R    \t\@t1/
+        !/R W  \tt2/
+        !/R W  \tt3/
+";
+
 try "
-    glt info u1;                ok;     /R W  \t\@t1/
-                                        /R W  \tt1/
-                                        /R    \tt2/
-                                        !/t3/
-                                        /R W  \ttesting/
-    glt info u2;                ok;     /R    \t\@t1/
-                                        /R    \tt1/
-                                        /R W  \tt2/
-                                        !/t3/
-                                        /R W  \ttesting/
-    glt info u3;                ok;     /R W  \tt3/
-                                        !/\@t1/
-                                        !/t[12]/
-                                        /R W  \ttesting/
-    glt info u4;                ok;     /R    \tt3/
-                                        !/\@t1/
-                                        !/t[12]/
-                                        /R W  \ttesting/
-    glt info u5;                ok;     !/t[123]/
-                                        /R W  \ttesting/
-    glt info u6;                ok;     !/t[123]/
-                                        /R W  \ttesting/
-    " or die;
+    glt info u1 -p; ok; GS u1
+        /R W  \tt1/
+        /R    \tt2/
+        /R W  \ttesting/
+        !/R W  \tt3/
+    glt info u2 -p; ok; GS u2
+        /R    \tt1/
+        /R W  \tt2/
+        /R W  \ttesting/
+        !/R W  \tt3/
+    glt info u3 -p; ok; GS u3
+        /R W  \tt3/
+        /R W  \ttesting/
+        !/R    \tt1/
+        !/R W  \tt2/
+    glt info u4 -p; ok; GS u4
+        /R    \tt3/
+        /R W  \ttesting/
+        !/R    \tt1/
+        !/R W  \tt2/
+    glt info u5 -p; ok; GS u5
+        /R W  \ttesting/
+        !/R    \tt1/
+        !/R W  \tt2/
+        !/R W  \tt3/
+    glt info u6 -p; ok; GS u6
+        /R W  \ttesting/
+        !/R    \tt1/
+        !/R W  \tt2/
+        !/R W  \tt3/
+";

commit 5b5c02f22699dcb6c7f6737a993cc2f0d40e2656
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 17:24:53 2012 +0530

    merge-check test

diff --git a/t/merge-check.t b/t/merge-check.t
new file mode 100755
index 0000000..33a5356
--- /dev/null
+++ b/t/merge-check.t
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# merge check -- the M flag
+# ----------------------------------------------------------------------
+
+try "plan 57";
+
+confreset;confadd '
+    repo  foo
+          RW+M      =   u1
+          RW+       =   u2
+          RWM      .=   u3
+          RW        =   u4
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+# setup a merged push
+
+try "
+    cd ..
+    ls -al foo;         !ok;    /cannot access foo: No such file or directory/
+    glt clone u1 file:///foo
+                        ok;     /Cloning into/
+                                /You appear to have cloned an empty/
+";
+
+try "
+    cd foo;             ok
+    ls -Al;             ok;     /\.git/
+    test-commit aa;     ok;     /1 files changed, 1 insertions/
+    tag start;          ok
+    glt push u1 origin master
+                        ok;     /new branch.*master.-..master/
+                                /create.delete ignored.*merge-check/
+    checkout -b new;    ok;     /Switched to a new branch 'new'/
+    test-commit bb cc;  ok
+    checkout master;    ok;     /Switched to branch 'master'/
+    test-commit dd ee;  ok
+    git merge new;      ok;     /Merge made.*recursive/
+    test-commit ff;     ok
+    tag end;            ok
+";
+
+# push by u4 should fail
+try "
+    glt push u4 file:///foo master
+                        !ok;    /WM refs/heads/master foo u4 DENIED by fallthru/
+                                /To file:///foo/
+                                /remote rejected.*hook declined/
+                                /failed to push some refs/
+";
+
+# push by u3 should succeed
+try "
+    glt push u3 file:///foo master
+                        ok;     /To file:///foo/; /master.-..master/
+";
+
+# rewind by u3 should fail
+try "
+    reset-h start;      ok;     /HEAD is now at .* aa /
+    glt push u3 file:///foo +master
+                         !ok;   /rejected.*hook declined/
+                                /failed to push some refs/
+";
+
+# rewind by u2 should succeed
+try "
+    glt push u2 file:///foo +master
+                         ok;    /To file:///foo/
+                                /forced update/
+";
+
+# push by u2 should fail
+try "
+    reset-h end;        ok;     /HEAD is now at .* ff /
+    glt push u2 file:///foo master
+                        !ok;    /WM refs/heads/master foo u2 DENIED by fallthru/
+                                /To file:///foo/
+                                /remote rejected.*hook declined/
+                                /failed to push some refs/
+";
+
+# push by u1 should succeed
+try "
+    glt push u1 file:///foo master
+                        ok;     /master.-..master/
+";

commit af1191902542671565c6fc82519d97818ead2760
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 16:15:21 2012 +0530

    git-configs update code done

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 5050852..d10f049 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -246,6 +246,7 @@ __DATA__
     POST_COMPILE                =>
         [
             'post-compile/ssh-authkeys',
+            'post-compile/update-git-configs',
             'post-compile/update-gitweb-access-list',
             'post-compile/update-git-daemon-access-list',
         ],
@@ -254,6 +255,7 @@ __DATA__
     # these will run in sequence after a new wild repo is created
     POST_CREATE                 =>
         [
+            # 'post-compile/update-git-configs',
             # 'post-compile/update-gitweb-access-list',
             # 'post-compile/update-git-daemon-access-list',
         ],
diff --git a/src/commands/post-compile/update-git-configs b/src/commands/post-compile/update-git-configs
new file mode 100755
index 0000000..df85f35
--- /dev/null
+++ b/src/commands/post-compile/update-git-configs
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+# update git-config entries in each repo
+# ----------------------------------------------------------------------
+
+use FindBin;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $RB = $rc{GL_REPO_BASE};
+_chdir ($RB);
+my $lpr = list_phy_repos();
+
+for my $pr (@$lpr) {
+    my $gc = git_config($pr, '.');
+    while ( my ($key, $value) = each(%{ $gc }) ) {
+        next if $key =~ /^gitolite-options\./;
+        if ($value ne "") {
+            $value =~ s/^['"](.*)["']$/$1/;
+            $value =~ s/%GL_REPO/$pr/g;
+            system("git", "config", "--file", "$RB/$pr.git/config", $key, $value);
+        } else {
+            system("git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key);
+        }
+    }
+}
diff --git a/t/git-config.t b/t/git-config.t
new file mode 100755
index 0000000..b3ea176
--- /dev/null
+++ b/t/git-config.t
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# git config settings
+# ----------------------------------------------------------------------
+
+try "plan 21";
+
+try "pwd";
+my $od = text();
+chomp($od);
+
+# make foo.bar a valid gc key
+$ENV{G3T_RC} = "$ENV{HOME}/g3trc";
+put "$ENV{G3T_RC}", "\$rc{GIT_CONFIG_KEYS} = 'foo\.bar';\n";
+
+confreset;confadd '
+
+    repo @all
+        config foo.bar  =   dft
+
+    repo gitolite-admin
+        RW+     =   admin
+        config foo.bar  =
+
+    repo testing
+        RW+     =   @all
+
+    repo foo
+        RW      =   u1
+        config foo.bar  =   f1
+
+    repo frob
+        RW      =   u3
+
+    repo bar
+        RW      =   u2
+        config foo.bar  =   one
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+try "
+    cd $rb;                             ok
+    egrep foo\\|bar *.git/config | sort
+";
+cmp 'bar.git/config:	bare = true
+bar.git/config:	bar = one
+bar.git/config:[foo]
+foo.git/config:	bare = true
+foo.git/config:	bar = f1
+foo.git/config:[foo]
+frob.git/config:	bar = dft
+frob.git/config:	bare = true
+frob.git/config:[foo]
+gitolite-admin.git/config:	bare = true
+testing.git/config:	bar = dft
+testing.git/config:	bare = true
+testing.git/config:[foo]
+';
+
+try "cd $od; ok";
+
+confadd '
+
+    repo frob
+        RW      =   u3
+        config foo.bar  =   none
+
+    repo bar
+        RW      =   u2
+        config foo.bar  =   one
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd $rb;                             ok
+    egrep foo\\|bar *.git/config | sort
+";
+
+cmp 'bar.git/config:	bare = true
+bar.git/config:	bar = one
+bar.git/config:[foo]
+foo.git/config:	bare = true
+foo.git/config:	bar = f1
+foo.git/config:[foo]
+frob.git/config:	bare = true
+frob.git/config:	bar = none
+frob.git/config:[foo]
+gitolite-admin.git/config:	bare = true
+testing.git/config:	bar = dft
+testing.git/config:	bare = true
+testing.git/config:[foo]
+';

commit 7b1efe8a7bf3bd42b3c07bf536e75c461c159cfc
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 16:03:59 2012 +0530

    (minor)

diff --git a/t/all-yall.t b/t/all-yall.t
index 7174e4b..947c753 100755
--- a/t/all-yall.t
+++ b/t/all-yall.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# could anything be clearer than "all y'all"?
 # ----------------------------------------------------------------------
 
 try "plan 26";
diff --git a/t/deleg-1.t b/t/deleg-1.t
index 0bff0db..bf47d4a 100755
--- a/t/deleg-1.t
+++ b/t/deleg-1.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# delegation tests -- part 1
 # ----------------------------------------------------------------------
 
 try "plan 55";
diff --git a/t/deleg-2.t b/t/deleg-2.t
index b98d144..8c5a400 100755
--- a/t/deleg-2.t
+++ b/t/deleg-2.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# delegation tests -- part 2
 # ----------------------------------------------------------------------
 
 try "plan 56";
diff --git a/t/deny-rules.t b/t/deny-rules.t
index 2492887..9726ac7 100755
--- a/t/deny-rules.t
+++ b/t/deny-rules.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# branch permissions test
+# deny rules
 # ----------------------------------------------------------------------
 
 try "plan 11";
diff --git a/t/include-subconf.t b/t/include-subconf.t
index 7bb5b04..a5fc908 100755
--- a/t/include-subconf.t
+++ b/t/include-subconf.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# include and subconf
+# ----------------------------------------------------------------------
+
 try 'plan 37';
 
 confreset; confadd '
diff --git a/t/info.t b/t/info.t
index 22019e3..e0538d7 100755
--- a/t/info.t
+++ b/t/info.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# the info command
+# ----------------------------------------------------------------------
+
 try 'plan 39';
 
 try "## info";
diff --git a/t/listers.t b/t/listers.t
index 84add2f..b824dd8 100755
--- a/t/listers.t
+++ b/t/listers.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# the various list-* commands
+# ----------------------------------------------------------------------
+
 try 'plan 30';
 
 try "## info";
diff --git a/t/personal-branches.t b/t/personal-branches.t
index 04e911f..2c8e6fb 100755
--- a/t/personal-branches.t
+++ b/t/personal-branches.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# personal branches
 # ----------------------------------------------------------------------
 
 try "plan 39";
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index e4454ee..fa11a6c 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# testing the (separate) authkeys handler
+# ----------------------------------------------------------------------
+
 $ENV{GL_BINDIR} = "$ENV{PWD}/src";
 
 my $ak = "$ENV{HOME}/.ssh/authorized_keys";
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 62e9345..6d8a10a 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -7,6 +7,9 @@ use lib "src";
 use Gitolite::Common;
 use Gitolite::Test;
 
+# basic tests using ssh
+# ----------------------------------------------------------------------
+
 my $bd = `gitolite query-rc -n GL_BINDIR`;
 my $h  = $ENV{HOME};
 my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
diff --git a/t/vrefs-1.t b/t/vrefs-1.t
index 4512ab8..468a338 100755
--- a/t/vrefs-1.t
+++ b/t/vrefs-1.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# VREFs - part 1
+# ----------------------------------------------------------------------
+
 try "plan 90";
 
 put "conf/gitolite.conf", "
diff --git a/t/vrefs-2.t b/t/vrefs-2.t
index 3ff1037..b44af42 100755
--- a/t/vrefs-2.t
+++ b/t/vrefs-2.t
@@ -6,6 +6,9 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
+# VREFs - part 2
+# ----------------------------------------------------------------------
+
 try "plan 74";
 
 put "../gitolite-admin/conf/gitolite.conf", "
diff --git a/t/wild-1.t b/t/wild-1.t
index 23ecc2d..2824e37 100755
--- a/t/wild-1.t
+++ b/t/wild-1.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# wild repos - part 1
 # ----------------------------------------------------------------------
 
 try "plan 66";
diff --git a/t/wild-2.t b/t/wild-2.t
index 2d213c9..0e49538 100755
--- a/t/wild-2.t
+++ b/t/wild-2.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# wild repos - part 2
 # ----------------------------------------------------------------------
 
 try "plan 65";

commit e6ba17fc527c1c3aba06bf00e3be51bf277327b1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 14:50:48 2012 +0530

    "deny-rules" (used to be called "deny-repo" in g2)

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index f1d342c..42a4294 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -63,6 +63,7 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
+    my $deny_rules = option($repo, 'deny-rules');
     load($repo);
 
     # when a real repo doesn't exist, ^C is a pre-requisite for any other
@@ -81,7 +82,7 @@ sub access {
         trace( 3, "perm=$perm, refex=$refex" );
 
         # skip 'deny' rules if the ref is not (yet) known
-        next if $perm eq '-' and $ref eq 'any';
+        next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
 
         # rule matches if ref matches or ref is any (see gitolite-shell)
         next unless $ref =~ /^$refex/ or $ref eq 'any';
diff --git a/t/deny-rules-2.t b/t/deny-rules-2.t
new file mode 100755
index 0000000..d59f144
--- /dev/null
+++ b/t/deny-rules-2.t
@@ -0,0 +1,172 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# more on deny-rules
+# ----------------------------------------------------------------------
+
+try "plan 126";
+
+try "
+    DEF GOOD    = /refs/\\.\\*/
+    DEF BAD     = /DENIED/
+
+    DEF Ryes    = gitolite access %1 %2 R any;  ok; GOOD
+    DEF Rno     = gitolite access %1 %2 R any;  !ok; BAD
+
+    DEF Wyes    = gitolite access %1 %2 W any;  ok; GOOD
+    DEF Wno     = gitolite access %1 %2 W any;  !ok; BAD
+
+    DEF GWyes   = Ryes %1 gitweb
+    DEF GWno    = Rno  %1 gitweb
+
+    DEF GDyes   = Ryes %1 daemon
+    DEF GDno    = Rno  %1 daemon
+";
+
+confreset;confadd '
+    repo one
+        RW+ =   u1
+        R   =   u2
+        -   =   u2 u3
+        R   =   @all
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    Wyes one u1
+
+    Ryes one u2
+    Wno  one u2
+
+    Ryes one u3
+    Wno  one u3
+
+    Ryes one u6
+    Wno  one u6
+
+    GDyes one
+    GWyes one
+";
+
+confadd '
+    option deny-rules = 1
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    Wyes one u1
+
+    Ryes one u2
+    Wno  one u2
+
+    Rno  one u3
+
+    Ryes one u6
+    Wno  one u6
+
+    GDyes one
+    GWyes one
+";
+
+confadd '
+    repo two
+        RW+ =   u1
+        R   =   u2
+        -   =   u2 u3 gitweb daemon
+        R   =   @all
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    GWyes two
+    GDyes two
+";
+
+confadd '
+    option deny-rules = 1
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    GWno  two
+    GDno  two
+";
+
+# set 3 -- allow gitweb to all but admin repo
+
+confadd '
+    repo gitolite-admin
+        -   =   gitweb daemon
+    option deny-rules = 1
+
+    repo three
+        RW+ =   u3
+        R   =   gitweb daemon
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    GDyes   three
+    GWyes   three
+    GDno    gitolite-admin
+    GWno    gitolite-admin
+";
+
+# set 4 -- allow gitweb to all but admin repo
+
+confadd '
+    repo four
+        RW+ =   u4
+        -   =   gitweb daemon
+
+    repo @all
+        R   =   @all
+';
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    GDyes   four
+    GWyes   four
+    GDno    gitolite-admin
+    GWno    gitolite-admin
+";
+
+# set 5 -- go wild
+
+confreset; confadd '
+    repo foo/..*
+        C   =   u1
+        RW+ =   CREATOR
+        -   =   gitweb daemon
+        R   =   @all
+
+    repo bar/..*
+        C   =   u2
+        RW+ =   CREATOR
+        -   =   gitweb daemon
+        R   =   @all
+    option deny-rules = 1
+';
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    glt ls-remote u1 file:///foo/one
+    glt ls-remote u2 file:///bar/two
+    Wyes foo/one u1
+    Wyes bar/two u2
+
+    GDyes foo/one
+    GDyes foo/one
+    GWno  bar/two
+    GWno  bar/two
+";
diff --git a/t/deny-rules.t b/t/deny-rules.t
new file mode 100755
index 0000000..2492887
--- /dev/null
+++ b/t/deny-rules.t
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# branch permissions test
+# ----------------------------------------------------------------------
+
+try "plan 11";
+
+confreset;confadd '
+    # start with...
+
+    repo gitolite-admin
+        -   =   gitweb daemon
+    option deny-rules = 1
+
+    # main ruleset goes here
+
+    @ga = a
+    @gb = b
+    @gc = c
+
+    # and end with
+
+    repo @ga
+        RW  =   u1
+        -   =   @all
+    option deny-rules = 1
+
+    repo @gb
+        RW  =   u2
+        -   =   daemon
+    option deny-rules = 1
+
+    repo @gc
+        RW  =   u3
+
+    repo @all
+        R   =   @all
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+try "
+    cat $ENV{HOME}/projects.list;                           ok
+";
+cmp 'b.git
+c.git
+testing.git
+';
+
+try "
+    cd ..
+    cd ..
+    echo $rb
+    find $rb -name git-daemon-export-ok | sort
+    perl s,$rb/,,g
+";
+cmp 'c.git/git-daemon-export-ok
+testing.git/git-daemon-export-ok
+'

commit 3e1746b267067663affdd4ad62e7a3e07e3ffef7
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 14:31:21 2012 +0530

    change die to warn if split_conf is not set but gl-conf is present
    
    (although the opposite case is still a "die")
    
    We found out how this can happen: if you change
    
        repo r1 r2
    
    to
    
        @g = r1 r2
        repo @g
    
    as found by t/deleg-2.t, which suddenly started breaking after an
    apparently unrelated commit :-)

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index b8d3c96..f1d342c 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -195,7 +195,7 @@ sub load_1 {
     }
 
     if ( -f "gl-conf" ) {
-        _die "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
+        _warn "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
 
         my $cc = "gl-conf";
         _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;

commit b1a75b78892461686d1e44c43567e13be7c4f165
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 14:04:17 2012 +0530

    gitweb/daemon post-create scripts done

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index ff87e1d..5050852 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -246,6 +246,16 @@ __DATA__
     POST_COMPILE                =>
         [
             'post-compile/ssh-authkeys',
+            'post-compile/update-gitweb-access-list',
+            'post-compile/update-git-daemon-access-list',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence after a new wild repo is created
+    POST_CREATE                 =>
+        [
+            # 'post-compile/update-gitweb-access-list',
+            # 'post-compile/update-git-daemon-access-list',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/commands/post-compile/update-git-daemon-access-list b/src/commands/post-compile/update-git-daemon-access-list
new file mode 100755
index 0000000..feb4a37
--- /dev/null
+++ b/src/commands/post-compile/update-git-daemon-access-list
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# this is probably the *fastest* git-daemon update possible.
+
+export EO=git-daemon-export-ok
+export RB=$(gitolite query-rc GL_REPO_BASE)
+
+gitolite list-phy-repos | gitolite access % daemon R any |
+    perl -lane '
+        unlink "$ENV{RB}/$F[0].git/$ENV{EO}" if /DENIED/;
+        print $F[0] unless /DENIED/
+    ' |
+    while read r
+    do
+        > $RB/$r.git/$EO
+    done
+
+# A bit of explanation may be in order.  The gitolite output looks somewhat
+# like this:
+
+#   bar^Idaemon^IR any bar daemon DENIED by fallthru$
+#   foo^Idaemon^Irefs/.*$
+#   fubar^Idaemon^Irefs/.*$
+#   gitolite-admin^Idaemon^IR any gitolite-admin daemon DENIED by fallthru$
+#   testing^Idaemon^Irefs/.*$
+
+# where I've type "^I" to denote a tab.
+
+# Shell has to fork 'rm' to delete a file but perl doesn't.  So removing the
+# export-ok file from repos where needed is done in perl.
+
+# On the other hand, perls requires a bit more *code* to even create an empty
+# file.  Shell can do it with just "> file", and it doesn't fork for this.  So
+# that part is handled in shell.
+
+# You'll also see that the perl part is taking what it needs from the input
+# and passing the rest on, so the shell part doesn't have to do any grepping,
+# which would be a horrible slowdown.
+
+# $F and the rest is the magic of perl's flags (man perlrun).
diff --git a/src/commands/post-compile/update-gitweb-access-list b/src/commands/post-compile/update-gitweb-access-list
new file mode 100755
index 0000000..971100e
--- /dev/null
+++ b/src/commands/post-compile/update-gitweb-access-list
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# this is literally the simplest gitweb update possible.  You are free to add
+# whatever you want and contribute it back, as long as it is upward
+# compatible.
+
+plf=$(gitolite query-rc GITWEB_PROJECTS_LIST)
+[ -z "$plf" ] && plf=$HOME/projects.list
+
+gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED | cut -f1 | sed -e 's/$/.git/' > $plf
+
diff --git a/t/all-yall.t b/t/all-yall.t
new file mode 100755
index 0000000..7174e4b
--- /dev/null
+++ b/t/all-yall.t
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 26";
+
+confreset;confadd '
+    repo @all
+        R   =   @all
+    repo foo
+        RW+ =   u1
+    repo bar
+        RW+ =   u2
+    repo dev/..*
+        C   =   u3 u4
+        RW+ =   CREATOR
+';
+
+try "
+    rm $ENV{HOME}/projects.list
+";
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    glt ls-remote u1 file:///dev/wild1
+                /FATAL: R any dev/wild1 u1 DENIED by fallthru/
+
+    glt clone u3 file:///dev/wild1
+                /Cloning into 'wild1'.../
+                /Initialized empty Git repository in .*/dev/wild1.git//
+                /warning: You appear to have cloned an empty repository./
+
+    cd wild1
+    tc n-855 n-856
+    glt push u3 origin master:wild1
+                /To file:///dev/wild1/
+                /\\* \\[new branch\\]      master -> wild1/
+    glt push u1 file:///foo master:br-foo
+                /To file:///foo/
+                /\\* \\[new branch\\]      master -> br-foo/
+    glt push u2 file:///bar master:br-bar
+                /To file:///bar/
+                /\\* \\[new branch\\]      master -> br-bar/
+
+    glt ls-remote u6 file:///foo
+                /refs/heads/br-foo/
+
+    glt ls-remote u6 file:///bar
+                /refs/heads/br-bar/
+
+    glt ls-remote u6 file:///dev/wild1
+                /refs/heads/wild1/
+";
+
+try "
+    gitolite post-compile/update-git-daemon-access-list;    ok
+    gitolite post-compile/update-gitweb-access-list;        ok
+    cat $ENV{HOME}/projects.list;                           ok
+";
+cmp 'bar.git
+dev/wild1.git
+foo.git
+gitolite-admin.git
+testing.git
+';
+
+my $rb = `gitolite query-rc -n GL_REPO_BASE`;
+
+try "
+    cd ..
+    cd ..
+    echo $rb
+    find $rb -name git-daemon-export-ok | sort
+    perl s,$rb/,,g
+";
+
+cmp 'bar.git/git-daemon-export-ok
+dev/wild1.git/git-daemon-export-ok
+foo.git/git-daemon-export-ok
+gitolite-admin.git/git-daemon-export-ok
+testing.git/git-daemon-export-ok
+';

commit 24b36f11c5c75a808e6903e23fdefa1003a315ed
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 07:49:01 2012 +0530

    (perltidy)

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 5122a05..a80b2db 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -44,8 +44,8 @@ sub trace {
     my $level = shift; return if $ENV{D} < $level;
     my $args = ''; $args = join( ", ", @_ ) if @_;
     my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://;
-    if (not $sub) {
-        $sub = ( caller )[1];
+    if ( not $sub ) {
+        $sub = (caller)[1];
         $sub =~ s(.*/(.*))(($1));
     }
     $sub .= ' ' x ( 32 - length($sub) );
@@ -72,7 +72,7 @@ sub _warn {
 }
 
 sub _die {
-    gl_log("_die:", @_);
+    gl_log( "_die:", @_ );
     if ( $ENV{D} and $ENV{D} >= 3 ) {
         confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
     } elsif ( defined( $ENV{D} ) ) {
@@ -116,7 +116,7 @@ sub _system {
     # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
     # exit with <rc of system()> if it applies, else just "exit 1".
     trace( 2, @_ );
-    gl_log("_system:", @_);
+    gl_log( "_system:", @_ );
     if ( system(@_) != 0 ) {
         trace( 1, "system() failed", @_, "-> $?" );
         if ( $? == -1 ) {
@@ -212,35 +212,35 @@ sub cleanup_conf_line {
 # generate a timestamp.  If a template is passed generate a log file name
 # based on it also
 sub gen_ts_lfn {
-    my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
-    $y += 1900; $m++;               # usual adjustments
-    for ($s, $min, $h, $d, $m) {
+    my ( $s, $min, $h, $d, $m, $y ) = (localtime)[ 0 .. 5 ];
+    $y += 1900; $m++;    # usual adjustments
+    for ( $s, $min, $h, $d, $m ) {
         $_ = "0$_" if $_ < 10;
     }
     my $ts = "$y-$m-$d.$h:$min:$s";
 
     return $ts unless @_;
 
-    my($template) = shift;
+    my ($template) = shift;
     # substitute template parameters and set the logfile name
     $template =~ s/%y/$y/g;
     $template =~ s/%m/$m/g;
     $template =~ s/%d/$d/g;
 
-    return ($ts, $template);
+    return ( $ts, $template );
 }
 
 sub gl_log {
     # the log filename and the timestamp come from the environment.  If we get
     # called even before they are set, we have no choice but to dump to STDERR
     # (and probably call "logger").
-    my $msg = join("\t", @_);
+    my $msg = join( "\t", @_ );
 
     my $ts = $ENV{GL_TS} || gen_ts_lfn();
 
     my $fh;
-    logger_plus_stderr("$ts no GL_LOGFILE env var", "$ts $msg") if not $ENV{GL_LOGFILE};
-    open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr("open log failed: $!", $msg);
+    logger_plus_stderr( "$ts no GL_LOGFILE env var", "$ts $msg" ) if not $ENV{GL_LOGFILE};
+    open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr( "open log failed: $!", $msg );
     print $lfh "$ts\t$msg\n";
     close $lfh;
 }
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index b3aa087..919e33c 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -199,13 +199,13 @@ sub new_wild_repo {
     my ( $repo, $user ) = @_;
     _chdir( $rc{GL_REPO_BASE} );
 
-    trigger('PRE_CREATE', $repo, $user);
+    trigger( 'PRE_CREATE', $repo, $user );
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
     _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
     # XXX git config, daemon, web...
     # XXX pre-create, post-create
-    trigger('POST_CREATE', $repo, $user);
+    trigger( 'POST_CREATE', $repo, $user );
 
     _chdir( $rc{GL_ADMIN_BASE} );
 }
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 04a5e1e..60f0b8d 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -183,9 +183,9 @@ sub role_names {
 
     for my $line (@$lines) {
         if ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
-            my($p, $r) = ($1, $2);
+            my ( $p, $r ) = ( $1, $2 );
             my $u = '';
-            for (split ' ', $3) {
+            for ( split ' ', $3 ) {
                 $_ = "\@$_" if $_ eq 'CREATOR' or $rc{ROLES}{$_};
                 $u .= " $_";
             }
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index c66cafb..dae4412 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -29,7 +29,7 @@ sub update {
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     gl_log( 'update:check', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, '->', $ret );
-    trigger('ACCESS_CHECK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret);
+    trigger( 'ACCESS_CHECK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 89f5fca..eac506d 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -34,7 +34,7 @@ exit 0;
 # XXX lots of stuff from gl-auth-command is missing for now...
 
 sub in_local {
-    if ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/) {
+    if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/ ) {
         print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
         print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
     }
@@ -79,13 +79,13 @@ sub main {
     my $ret = access( $repo, $user, $aa, 'any' );
     trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
     gl_log( 'gitolite-shell:check', $repo, $user, $aa, 'any', '->', $ret );
-    trigger('ACCESS_CHECK', $repo, $user, $aa, 'any', $ret);
+    trigger( 'ACCESS_CHECK', $repo, $user, $aa, 'any', $ret );
     _die $ret if $ret =~ /DENIED/;
 
     $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
-    trigger('PRE_GIT', $repo, $user, $aa, 'any', $verb);
+    trigger( 'PRE_GIT', $repo, $user, $aa, 'any', $verb );
     _system( "git", "shell", "-c", "$verb $repo" );
-    trigger('POST_GIT', $repo, $user, $aa, 'any', $verb);
+    trigger( 'POST_GIT', $repo, $user, $aa, 'any', $verb );
 }
 
 # ----------------------------------------------------------------------

commit d853c58ada07816bbc04e4b739bbe3384ac0849c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 19 07:31:09 2012 +0530

    (!!) trigger mechanism... read below
    
    new triggers:
    
      - PRE_GIT and POST_GIT in gitolite-shell
      - PRE_CREATE and POST_CREATE when a new wild repo is created
      - (POST_COMPILE had already existed)
      - ACCESS_CHECK triggers both in gitolite-shell and the update hook
    
      - trace() learned to print the file name if called from top level and
        a function name is not available
    
    note: trigger was called 'run-all' and only had POST_COMPILE.  The code
    existed in gitolite-shell, but is now moved to Rc.pm.

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 9b43598..5122a05 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -43,7 +43,12 @@ sub trace {
 
     my $level = shift; return if $ENV{D} < $level;
     my $args = ''; $args = join( ", ", @_ ) if @_;
-    my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) );
+    my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://;
+    if (not $sub) {
+        $sub = ( caller )[1];
+        $sub =~ s(.*/(.*))(($1));
+    }
+    $sub .= ' ' x ( 32 - length($sub) );
     say2 "TRACE $level $sub", ( @_ ? shift : () );
     say2( "TRACE $level " . ( " " x 32 ), $_ ) for @_;
 }
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 8b69e3e..b3aa087 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -198,11 +198,15 @@ sub new_repo {
 sub new_wild_repo {
     my ( $repo, $user ) = @_;
     _chdir( $rc{GL_REPO_BASE} );
+
+    trigger('PRE_CREATE', $repo, $user);
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
     _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
     # XXX git config, daemon, web...
     # XXX pre-create, post-create
+    trigger('POST_CREATE', $repo, $user);
+
     _chdir( $rc{GL_ADMIN_BASE} );
 }
 
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index 981051d..d60c3fa 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -31,7 +31,7 @@ sub post_update {
         tsh_try("git checkout -f --quiet master");
     }
     _system("$ENV{GL_BINDIR}/gitolite compile");
-    _system("$ENV{GL_BINDIR}/gitolite run-all POST_COMPILE");
+    _system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
 
     exit 0;
 }
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 9236074..c66cafb 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -10,6 +10,7 @@ package Gitolite::Hooks::Update;
 
 use Exporter 'import';
 
+use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Load;
 
@@ -28,6 +29,7 @@ sub update {
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     gl_log( 'update:check', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, '->', $ret );
+    trigger('ACCESS_CHECK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, $ret);
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 6a7dd32..ff87e1d 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -8,6 +8,7 @@ package Gitolite::Rc;
   glrc
   query_rc
   version
+  trigger
 
   $REMOTE_COMMAND_PATT
   $REF_OR_FILENAME_PATT
@@ -114,10 +115,7 @@ sub glrc {
     }
 }
 
-# ----------------------------------------------------------------------
-# implements 'gitolite query-rc' and 'version'
-# ----------------------------------------------------------------------
-
+# exported functions
 # ----------------------------------------------------------------------
 
 my $all  = 0;
@@ -153,6 +151,30 @@ sub version {
     return $version;
 }
 
+sub trigger {
+    my $rc_section = shift;
+
+    if ( exists $rc{$rc_section} ) {
+        if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
+            _die "$rc_section section in rc file is not a perl list";
+        } else {
+            for my $s ( @{ $rc{$rc_section} } ) {
+
+                # perl-ism; apart from keeping the full path separate from the
+                # simple name, this also protects %rc from change by implicit
+                # aliasing, which would happen if you touched $s itself
+                my $sfp = "$ENV{GL_BINDIR}/commands/$s";
+
+                _warn("skipped command '$s'"), next if not -x $sfp;
+                trace( 2, "command: $s" );
+                _system( $sfp, @_ );    # they better all return with 0 exit codes!
+            }
+        }
+        return;
+    }
+    trace( 2, "'$rc_section' not found in rc" );
+}
+
 # ----------------------------------------------------------------------
 
 =for args
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index a548889..67f0887 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -36,7 +36,7 @@ sub setup {
     setup_gladmin( $admin, $pubkey, $argv );
 
     _system("$ENV{GL_BINDIR}/gitolite compile");
-    _system("$ENV{GL_BINDIR}/gitolite run-all POST_COMPILE");
+    _system("$ENV{GL_BINDIR}/gitolite trigger POST_COMPILE");
 
     hook_repos();    # all of them, just to be sure
 }
diff --git a/src/gitolite b/src/gitolite
index 804e751..7217bb9 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -69,8 +69,8 @@ if ( $command eq 'setup' ) {
     Gitolite::Conf->import;
     compile(@args);
 
-} elsif ( $command eq 'run-all' ) {
-    run_all(@args);
+} elsif ( $command eq 'trigger' ) {
+    trigger(@args);
 
 } elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
     trace( 2, "attempting gitolite command $command" );
@@ -104,27 +104,3 @@ sub run_command {
     _system( $fullpath, @_ );
     exit 0;
 }
-
-sub run_all {
-    my $rc_section = shift;
-
-    if ( exists $rc{$rc_section} ) {
-        if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
-            _warn "$rc_section section in rc file is not a perl list";
-        } else {
-            for my $s ( @{ $rc{$rc_section} } ) {
-
-                # perl-ism; apart from keeping the full path separate from the
-                # simple name, this also protects %rc from change by implicit
-                # aliasing, which would happen if you touched $s itself
-                my $sfp = "$ENV{GL_BINDIR}/commands/$s";
-
-                _warn("skipped command '$s'"), next if not -x $sfp;
-                trace( 2, "command: $s" );
-                _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
-            }
-        }
-        return;
-    }
-    trace( 2, "'$rc_section' not found in rc" );
-}
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 71858cb..89f5fca 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -79,10 +79,13 @@ sub main {
     my $ret = access( $repo, $user, $aa, 'any' );
     trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
     gl_log( 'gitolite-shell:check', $repo, $user, $aa, 'any', '->', $ret );
+    trigger('ACCESS_CHECK', $repo, $user, $aa, 'any', $ret);
     _die $ret if $ret =~ /DENIED/;
 
     $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
-    exec( "git", "shell", "-c", "$verb $repo" );
+    trigger('PRE_GIT', $repo, $user, $aa, 'any', $verb);
+    _system( "git", "shell", "-c", "$verb $repo" );
+    trigger('POST_GIT', $repo, $user, $aa, 'any', $verb);
 }
 
 # ----------------------------------------------------------------------

commit 80b50f3be8929f3ca541f33e677c722cbd499354
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 19:16:31 2012 +0530

    added delegation tests
    
    (although one seems almost the same as the other...)

diff --git a/t/deleg-1.t b/t/deleg-1.t
new file mode 100755
index 0000000..0bff0db
--- /dev/null
+++ b/t/deleg-1.t
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 55";
+
+try "
+    DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
+    DEF SUBCONF_PUSH = SP_1 %2; glt push %1 origin; gsh; /master -> master/
+";
+
+confreset;confadd '
+    @u1r    =   r1a r1b
+    @u2r    =   r2a r2b
+    @u3r    =   r3a r3b
+
+    # the admin repo access was probably like this to start with:
+    repo gitolite-admin
+        RW                              = u1 u2 u3
+        RW+ NAME/                       = admin
+        RW  NAME/conf/fragments/u1r     = u1
+        RW  NAME/conf/fragments/u2r     = u2
+        RW  NAME/conf/fragments/u3r     = u3
+        -   NAME/                       = @all
+
+        subconf "fragments/*.conf"
+';
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "/ABORT/; /legacy delegation/";
+
+mkdir "conf/fragments";
+put   "conf/fragments/u1r.conf", '
+    repo @u1r
+        RW+     =   tester
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "
+        /Initialized empty Git repository in .*/r1a.git//
+        /Initialized empty Git repository in .*/r1b.git//
+";
+
+# u1 push u1r pass
+put   "conf/fragments/u1r.conf", '
+    repo @u1r
+        RW+     =   u5
+';
+try "SUBCONF_PUSH u1 u1; !/FATAL/" or die text();
+
+# u2 main push fail
+confadd '
+    repo @u1r
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u2 u2; /FATAL/;
+        /W VREF/NAME/conf/gitolite.conf gitolite-admin u2 DENIED by VREF/NAME//
+";
+
+try "git reset --hard origin/master; ok";
+
+# u2 push u1r fail
+put   "conf/fragments/u1r.conf", '
+    repo @u1r
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u2 u2; /FATAL/
+        /W VREF/NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by VREF/NAME//
+";
+
+try "git reset --hard origin/master; ok";
+
+# u3 set perms for r2a fail
+put   "conf/fragments/u3r.conf", '
+    repo r2a
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u3 u3;
+        /WARNING: u3r.conf attempting to set access for r2a/
+";
+
+try "git reset --hard origin/master; ok";
+
+# u3 add r2b to u3r fail
+
+put   "conf/fragments/u3r.conf", '
+    @u3r    =   r2b
+    repo @u3r
+        RW+     =   u6
+';
+
+try "SUBCONF_PUSH u3 u3
+        /WARNING: u3r.conf attempting to set access for locally modified \@u3r/
+";
diff --git a/t/deleg-2.t b/t/deleg-2.t
new file mode 100755
index 0000000..b98d144
--- /dev/null
+++ b/t/deleg-2.t
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 56";
+
+try "
+    DEF SP_1 = git add conf ; ok; git commit -m %1; ok; /master.* %1/
+    DEF SUBCONF_PUSH = SP_1 %2; glt push %1 origin; gsh; /master -> master/
+";
+
+confreset;confadd '
+    # group your projects/repos however you want
+    @u1r    =   r1[ab]
+    @u2r    =   r2[ab]
+    @u3r    =   r3[ab]
+
+    # the admin repo access was probably like this to start with:
+    repo gitolite-admin
+        RW                              = u1 u2 u3
+        RW+ NAME/                       = admin
+        RW  NAME/conf/fragments/u1r     = u1
+        RW  NAME/conf/fragments/u2r     = u2
+        RW  NAME/conf/fragments/u3r     = u3
+        -   NAME/                       = @all
+
+    subconf "fragments/*.conf"
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "
+        /ABORT/; /legacy delegation/
+";
+
+try "mkdir -p conf/fragments; ok";
+
+put "conf/fragments/u1r.conf", '
+    repo r1a r1b
+        C       =   @all
+        RW+     =   CREATOR
+    repo @u1r
+        RW+     =   tester
+';
+
+put "conf/fragments/u2r.conf", '
+    repo @u2r
+        C       =   @all
+        RW+     =   CREATOR
+    repo @u2r
+        RW+     =   tester
+';
+
+put "conf/fragments/u3r.conf", '
+    repo @u3r
+        C       =   @all
+        RW+     =   CREATOR
+    repo @u3r
+        RW+     =   tester
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+try "
+        /Initialized empty Git repository in .*/r1a.git//
+        /Initialized empty Git repository in .*/r1b.git//
+";
+
+# u1 push u1r pass
+put   "conf/fragments/u1r.conf", '
+    repo @u1r
+        RW+     =   u5
+';
+try "SUBCONF_PUSH u1 u1; !/FATAL/" or die text();
+
+# u2 main push fail
+confadd '
+    repo @u1r
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u2 u2; /FATAL/;
+        /W VREF/NAME/conf/gitolite.conf gitolite-admin u2 DENIED by VREF/NAME//
+";
+
+try "git reset --hard origin/master; ok";
+
+# u2 push u1r fail
+put   "conf/fragments/u1r.conf", '
+    repo @u1r
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u2 u2; /FATAL/
+        /W VREF/NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by VREF/NAME//
+";
+
+try "git reset --hard origin/master; ok";
+
+# u3 set perms for r2a fail
+put   "conf/fragments/u3r.conf", '
+    repo r2a
+        RW+     =   u6
+';
+try "SUBCONF_PUSH u3 u3;
+        /WARNING: u3r.conf attempting to set access for r2a/
+";
+
+try "git reset --hard origin/master; ok";
+
+# u3 add r2b to u3r fail
+
+put   "conf/fragments/u3r.conf", '
+    @u3r    =   r2b
+    repo @u3r
+        RW+     =   u6
+';
+
+try "SUBCONF_PUSH u3 u3
+        /WARNING: u3r.conf attempting to set access for locally modified \@u3r/
+";

commit 4c1017a529f45a31b1974be195e3392fa7e5c985
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 20:41:02 2012 +0530

    new sugar 'legacy-delegation-abort', enabled by default

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 49ca35d..6a7dd32 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -216,6 +216,7 @@ __DATA__
     SYNTACTIC_SUGAR             =>
         [
             # 'continuation-lines',
+            'legacy-delegation-abort',
         ],
 
     # comment out or uncomment as needed
diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
index 75c015d..69a69b1 100755
--- a/src/syntactic-sugar/keysubdirs-as-groups
+++ b/src/syntactic-sugar/keysubdirs-as-groups
@@ -7,7 +7,7 @@
 # taken as the group name.
 
 sub sugar_script {
-    trace( 2, "running 'keysubdirs-as-groups' sugar script..." );
+    Gitolite::Common::trace( 2, "running 'keysubdirs-as-groups' sugar script..." );
     my $lines = shift;
 
     my @out = @{$lines};
diff --git a/src/syntactic-sugar/legacy-delegation-abort b/src/syntactic-sugar/legacy-delegation-abort
new file mode 100755
index 0000000..1625632
--- /dev/null
+++ b/src/syntactic-sugar/legacy-delegation-abort
@@ -0,0 +1,26 @@
+# vim: syn=perl:
+
+# "sugar script" (syntactic sugar helper) for gitolite3
+
+# Aborts if you appear to be using legacy delegation.
+
+# Note: you cannot fix this using sugar; subconf processing has already
+# happened by the time it gets to this point.
+
+# TODO: supply doc keyword, and that doc should tell people to add this to the
+# end of the main conf file:
+
+#       repo gitolite-admin
+#           -   NAME/       =   @all
+#       subconf "fragments/*.conf"
+
+sub sugar_script {
+    Gitolite::Common::trace( 2, "running 'legacy-delegation-warn' sugar script..." );
+    my $lines = shift;
+
+    my $text = join("\n", @$lines);
+    if ($text =~ m(NAME/conf/fragments/) and $text !~ /^subconf /m) {
+        die "\t**** ABORT ****\nYou may be using legacy delegation; see docs\n";
+    }
+    return $lines;
+}

commit 20fd0d0c194231d99d7595e723e227ca3c18c55b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 18:05:27 2012 +0530

    (minor) test reformatting

diff --git a/t/branch-perms.t b/t/branch-perms.t
index 45ccbc7..4e4c5ff 100755
--- a/t/branch-perms.t
+++ b/t/branch-perms.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # branch permissions test
 # ----------------------------------------------------------------------
 
-try "plan 58";
+try "plan 82";
 
 confreset;confadd '
     @g1 = u1
@@ -53,63 +53,52 @@ try "
                                             /\\+ refs/heads/master aa u2 DENIED by fallthru/
 
     # u3 rewind master succeed
-    git reset --hard HEAD^
-    tc m-508 
-    glt push u3 file://aa +master
-    /\\+ .* master -> master \\(forced update\\)/
+    git reset --hard HEAD^;         ok
+    tc m-508;                       ok
+    glt push u3 file://aa +master;  ok;     /\\+ .* master -> master \\(forced update\\)/
 
     # u4 push master succeed
-    tc f-526 
-    glt push u4 file://aa master
-    /master -> master/
+    tc f-526;                       ok;
+    glt push u4 file://aa master;   ok;     /master -> master/
 
     # u4 rewind master fail
-    git reset --hard HEAD^
-    glt push u4 file://aa +master
-    /\\+ refs/heads/master aa u4 DENIED by fallthru/
+    git reset --hard HEAD^;         ok;
+    glt push u4 file://aa +master;  !ok;    /\\+ refs/heads/master aa u4 DENIED by fallthru/
 
     # u3 and u4 / dev foo -- all 4 fail
-    glt push u3 file://aa dev
-    /W refs/heads/dev aa u3 DENIED by fallthru/
-    glt push u4 file://aa dev
-    /W refs/heads/dev aa u4 DENIED by fallthru/
-    glt push u3 file://aa foo
-    /W refs/heads/foo aa u3 DENIED by fallthru/
-    glt push u4 file://aa foo
-    /W refs/heads/foo aa u4 DENIED by fallthru/
+    glt push u3 file://aa dev;      !ok;    /W refs/heads/dev aa u3 DENIED by fallthru/
+    glt push u4 file://aa dev;      !ok;    /W refs/heads/dev aa u4 DENIED by fallthru/
+    glt push u3 file://aa foo;      !ok;    /W refs/heads/foo aa u3 DENIED by fallthru/
+    glt push u4 file://aa foo;      !ok;    /W refs/heads/foo aa u4 DENIED by fallthru/
 
     # clean up for next set
     glt push u1 -f origin master dev foo
+                                    ok
 
     # u5 push master fail
-    tc l-417 
-    glt push u5 file://aa master
-    /W refs/heads/master aa u5 DENIED by refs/heads/master/
+    tc l-417;                       ok
+    glt push u5 file://aa master;   !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
 
     # u5 rewind dev succeed
     glt push u5 file://aa +dev^:dev
-    /\\+ .* dev\\^ -> dev \\(forced update\\)/
+                                    ok;     /\\+ .* dev\\^ -> dev \\(forced update\\)/
 
     # u5 rewind foo fail
     glt push u5 file://aa +foo^:foo
-    /\\+ refs/heads/foo aa u5 DENIED by fallthru/
+                                    !ok;    /\\+ refs/heads/foo aa u5 DENIED by fallthru/
 
     # u5 tries to push foo; succeeds
-    git checkout foo
-    /Switched to branch 'foo'/
+    git checkout foo;               ok;     /Switched to branch 'foo'/
 
     # u5 push foo succeed
-    tc e-530 
-    glt push u5 file://aa foo
-    /foo -> foo/
+    tc e-530;                       ok;
+    glt push u5 file://aa foo;      ok;     /foo -> foo/
 
     # u1 delete branch dev succeed
-    glt push u1 origin :dev
-    / - \\[deleted\\] *dev/
+    glt push u1 origin :dev;        ok;     / - \\[deleted\\] *dev/
 
     # quietly push it back again
-    glt push u1 origin dev
-    / * \\[new branch\\]      dev -> dev/
+    glt push u1 origin dev;         ok;     / * \\[new branch\\]      dev -> dev/
 
     ";
 
@@ -121,16 +110,13 @@ try "
 try "ADMIN_PUSH set2; !/FATAL/" or die text();
 
 try "
-    cd ../aa
     # u1 tries to delete dev on a new setup
-    /master -> master/
+    cd ../aa;                       ok;     /master -> master/
 
     # u1 delete branch dev fail
-    glt push u1 origin :dev
-    /D refs/heads/dev aa u1 DENIED by fallthru/
+    glt push u1 origin :dev;        !ok;    /D refs/heads/dev aa u1 DENIED by fallthru/
 
     # u4 delete branch dev succeed
-    glt push u4 file://aa :dev
-    / - \\[deleted\\] *dev/
+    glt push u4 file://aa :dev;     ok;     / - \\[deleted\\] *dev/
 
 ";
diff --git a/t/wild-1.t b/t/wild-1.t
index 79085a2..23ecc2d 100755
--- a/t/wild-1.t
+++ b/t/wild-1.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # basic tests
 # ----------------------------------------------------------------------
 
-try "plan 43";
+try "plan 66";
 
 confreset;confadd '
     @prof       =   u1
@@ -31,81 +31,64 @@ try "
 cd ..
 
 # u1 create success
-glt clone u1 file:///foo/u1/a01
-/Initialized empty Git repository in .*/foo/u1/a01.git//
+glt clone u1 file:///foo/u1/a01;        ok;     /Initialized empty Git repository in .*/foo/u1/a01.git//
 
 # u2 create success
-glt clone u2 file:///foo/u2/a02
-/Initialized empty Git repository in .*/foo/u2/a02.git//
+glt clone u2 file:///foo/u2/a02;        ok;     /Initialized empty Git repository in .*/foo/u2/a02.git//
 
 # u4 tries to create u2 repo
-glt clone u4 file:///foo/u2/a12
-/R any foo/u2/a12 u4 DENIED by fallthru/
+glt clone u4 file:///foo/u2/a12;        !ok;    /R any foo/u2/a12 u4 DENIED by fallthru/
 
 # line anchored regexes
-glt clone u4 file:///foo/u4/a1234
-/R any foo/u4/a1234 u4 DENIED by fallthru/
+glt clone u4 file:///foo/u4/a1234;      !ok;    /R any foo/u4/a1234 u4 DENIED by fallthru/
 
 # u4 tries to create his own repo
-glt clone u4 file:///foo/u4/a12
-/Initialized empty Git repository in .*/foo/u4/a12.git//
-/warning: You appear to have cloned an empty repository./
+glt clone u4 file:///foo/u4/a12;        ok;     /Initialized empty Git repository in .*/foo/u4/a12.git//
+                                                /warning: You appear to have cloned an empty repository./
 
 # u4 push success
 cd a12
-tc p-728 p-729 p-730 p-731
-glt push u4 origin master
-/To file:///foo/u4/a12/
-/\\* \\[new branch\\]      master -> master/
+tc p-728 p-729 p-730 p-731;             ok
+glt push u4 origin master;              ok;     /To file:///foo/u4/a12/
+                                                /\\* \\[new branch\\]      master -> master/
 
 # u1 clone success
 cd ..
-glt clone u1 file:///foo/u4/a12 u1a12
-/Cloning into 'u1a12'.../
+glt clone u1 file:///foo/u4/a12 u1a12;  ok;     /Cloning into 'u1a12'.../
 
 # u1 push fail
 cd u1a12
-tc m-778 m-779
-glt push u1 origin
-/W any foo/u4/a12 u1 DENIED by fallthru/
+tc m-778 m-779;                         ok;
+glt push u1 origin;                     !ok;    /W any foo/u4/a12 u1 DENIED by fallthru/
 
 # u2 clone success
 cd ..
-glt clone u2 file:///foo/u4/a12 u2a12
-/Cloning into 'u2a12'.../
+glt clone u2 file:///foo/u4/a12 u2a12;  ok;     /Cloning into 'u2a12'.../
 
 # u2 push success
 cd u2a12
-tc s-708 s-709
-glt push u2 origin
-/To file:///foo/u4/a12/
-/master -> master/
+tc s-708 s-709;                         ok;
+glt push u2 origin;                     ok;     /To file:///foo/u4/a12/
+                                                /master -> master/
 
 # u2 rewind fail
-glt push u2 -f origin master^:master
-/\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
-/error: hook declined to update refs/heads/master/
-/To file:///foo/u4/a12/
-/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
-/error: failed to push some refs to 'file:///foo/u4/a12'/
+glt push u2 -f origin master^:master;   !ok;    /\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
+                                                reject
 
 # u4 pull to sync up
 cd ../a12
-glt pull u4
-/Fast-forward/
-/From file:///foo/u4/a12/
-/master     -> origin/master/
+glt pull u4;                            ok;     /Fast-forward/
+                                                /From file:///foo/u4/a12/
+                                                /master     -> origin/master/
 
 # u4 rewind success
-git reset --hard HEAD^
-glt push u4 -f
-/To file:///foo/u4/a12/
-/\\+ .* master -> master \\(forced update\\)/
+git reset --hard HEAD^;                 ok
+glt push u4 -f;                         ok;     /To file:///foo/u4/a12/
+                                                /\\+ .* master -> master \\(forced update\\)/
 
 # u5 clone fail
 cd ..
-glt clone u5 file:///foo/u4/a12 u5a12
-/R any foo/u4/a12 u5 DENIED by fallthru/
+glt clone u5 file:///foo/u4/a12 u5a12;  !ok;    /R any foo/u4/a12 u5 DENIED by fallthru/
 
 glt perms u4 foo/u4/a12 + READERS u5
 glt perms u4 foo/u4/a12 + WRITERS u6
@@ -119,33 +102,25 @@ WRITERS u6
 
 try "
 # u5 clone success
-glt clone u5 file:///foo/u4/a12 u5a12
-/Cloning into 'u5a12'.../
+glt clone u5 file:///foo/u4/a12 u5a12;  ok;     /Cloning into 'u5a12'.../
 
 # u5 push fail
 cd u5a12
-tc y-743 y-744
-glt push u5
-/W any foo/u4/a12 u5 DENIED by fallthru/
-
+tc y-743 y-744;                         ok
+glt push u5;                            !ok;    /W any foo/u4/a12 u5 DENIED by fallthru/
 
 # u6 clone success
 cd ..
-glt clone u6 file:///foo/u4/a12 u6a12
-/Cloning into 'u6a12'.../
+glt clone u6 file:///foo/u4/a12 u6a12;  ok;     /Cloning into 'u6a12'.../
 
 # u6 push success
 cd u6a12
-tc k-68 k-69
-glt push u6 file:///foo/u4/a12
-/To file:///foo/u4/a12/
-/master -> master/
+tc k-68 k-69;                           ok
+glt push u6 file:///foo/u4/a12;         ok;     /To file:///foo/u4/a12/
+                                                /master -> master/
 
 # u6 rewind fail
 glt push u6 -f file:///foo/u4/a12 master^:master
-/\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
-/error: hook declined to update refs/heads/master/
-/To file:///foo/u4/a12/
-/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
-/error: failed to push some refs to 'file:///foo/u4/a12'/
+                                        !ok;    /\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
+                                                reject
 ";
diff --git a/t/wild-2.t b/t/wild-2.t
index 5695584..2d213c9 100755
--- a/t/wild-2.t
+++ b/t/wild-2.t
@@ -9,7 +9,7 @@ use Gitolite::Test;
 # basic tests
 # ----------------------------------------------------------------------
 
-try "plan 43";
+try "plan 65";
 
 confreset;confadd '
     @prof       =   u1
@@ -30,81 +30,64 @@ try "
 cd ..
 
 # u1 create fail
-glt clone u1 file:///foo/u1/a01
-/R any foo/u1/a01 u1 DENIED by fallthru/
+glt clone u1 file:///foo/u1/a01;        !ok;    /R any foo/u1/a01 u1 DENIED by fallthru/
 
 # u2 create fail
-glt clone u2 file:///foo/u2/a02
-/R any foo/u2/a02 u2 DENIED by fallthru/
+glt clone u2 file:///foo/u2/a02;        !ok;    /R any foo/u2/a02 u2 DENIED by fallthru/
 
 # u4 tries to create u2 repo
-glt clone u4 file:///foo/u2/a12
-/R any foo/u2/a12 u4 DENIED by fallthru/
+glt clone u4 file:///foo/u2/a12;        !ok;    /R any foo/u2/a12 u4 DENIED by fallthru/
 
 # line anchored regexes
-glt clone u4 file:///foo/u4/a1234
-/R any foo/u4/a1234 u4 DENIED by fallthru/
+glt clone u4 file:///foo/u4/a1234;      !ok;    /R any foo/u4/a1234 u4 DENIED by fallthru/
 
 # u4 tries to create his own repo
-glt clone u4 file:///foo/u4/a12
-/Initialized empty Git repository in .*/foo/u4/a12.git//
-/warning: You appear to have cloned an empty repository./
+glt clone u4 file:///foo/u4/a12;        ok;     /Initialized empty Git repository in .*/foo/u4/a12.git//
+                                                /warning: You appear to have cloned an empty repository./
 
 # u4 push success
 cd a12
-tc n-770 n-771 n-772 n-773
-glt push u4 origin master
-/To file:///foo/u4/a12/
-/\\* \\[new branch\\]      master -> master/
+tc n-770 n-771 n-772 n-773;             ok
+glt push u4 origin master;              ok;     /To file:///foo/u4/a12/
+                                                /\\* \\[new branch\\]      master -> master/
 
 # u1 clone success
 cd ..
-glt clone u1 file:///foo/u4/a12 u1a12
-/Cloning into 'u1a12'.../
+glt clone u1 file:///foo/u4/a12 u1a12;  ok;     /Cloning into 'u1a12'.../
 
 # u1 push fail
 cd u1a12
-tc c-442 c-443
-glt push u1
-/W any foo/u4/a12 u1 DENIED by fallthru/
+tc c-442 c-443;                         ok
+glt push u1;                            !ok;    /W any foo/u4/a12 u1 DENIED by fallthru/
 
 # u2 clone success
 cd ..
-glt clone u2 file:///foo/u4/a12 u2a12
-/Cloning into 'u2a12'.../
+glt clone u2 file:///foo/u4/a12 u2a12;  ok;     /Cloning into 'u2a12'.../
 
 # u2 push success
 cd u2a12
-tc e-393 e-394
-glt push u2
-/To file:///foo/u4/a12/
-/master -> master/
+tc e-393 e-394;                         ok;
+glt push u2;                            ok;     /To file:///foo/u4/a12/
+                                                /master -> master/
 
 # u2 rewind fail
-glt push u2 -f origin master^:master
-/\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
-/error: hook declined to update refs/heads/master/
-/To file:///foo/u4/a12/
-/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
-/error: failed to push some refs to 'file:///foo/u4/a12'/
+glt push u2 -f origin master^:master;   !ok;    /\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
+                                                reject
 
 # u4 pull to sync up
 cd ../a12
-glt pull u4
-/Fast-forward/
-/From file:///foo/u4/a12/
-/master     -> origin/master/
+glt pull u4;                            ok;     /Fast-forward/
+                                                /From file:///foo/u4/a12/
+                                                /master     -> origin/master/
 
 # u4 rewind success
-git reset --hard HEAD^
-glt push u4 -f
-/To file:///foo/u4/a12/
-/\\+ .* master -> master \\(forced update\\)/
+git reset --hard HEAD^;                 ok
+glt push u4 -f;                         ok;     /To file:///foo/u4/a12/
+                                                /\\+ .* master -> master \\(forced update\\)/
 
 # u5 clone fail
 cd ..
-glt clone u5 file:///foo/u4/a12 u5a12
-/R any foo/u4/a12 u5 DENIED by fallthru/
+glt clone u5 file:///foo/u4/a12 u5a12;  !ok;    /R any foo/u4/a12 u5 DENIED by fallthru/
 
 # setperm
 glt perms u4 foo/u4/a12 + READERS u5
@@ -120,33 +103,26 @@ WRITERS u6
 
 try "
 # u5 clone success
-glt clone u5 file:///foo/u4/a12 u5a12
-/Cloning into 'u5a12'.../
+glt clone u5 file:///foo/u4/a12 u5a12;  ok;     /Cloning into 'u5a12'.../
 
 # u5 push fail
 cd u5a12
-tc g-809 g-810
-glt push u5
-/W any foo/u4/a12 u5 DENIED by fallthru/
+tc g-809 g-810;                         ok
+glt push u5;                            !ok;    /W any foo/u4/a12 u5 DENIED by fallthru/
 
 # u6 clone success
 cd ..
-glt clone u6 file:///foo/u4/a12 u6a12
-/Cloning into 'u6a12'.../
+glt clone u6 file:///foo/u4/a12 u6a12;  ok;     /Cloning into 'u6a12'.../
 
 # u6 push success
 cd u6a12
 tc f-912 f-913
-glt push u6 file:///foo/u4/a12
-/To file:///foo/u4/a12/
-/master -> master/
+glt push u6 file:///foo/u4/a12;         ok;     /To file:///foo/u4/a12/
+                                                /master -> master/
 
 # u6 rewind fail
 glt push u6 -f file:///foo/u4/a12 master^:master
-/\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
-/error: hook declined to update refs/heads/master/
-/To file:///foo/u4/a12/
-/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
-/error: failed to push some refs to 'file:///foo/u4/a12'/
+                                        !ok;    /\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
+                                                reject
 
 ";

commit 2069156c6ba24bbc29feef0d65426e36e5b591a1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 17:36:51 2012 +0530

    wild repos test #2

diff --git a/t/wild-2.t b/t/wild-2.t
new file mode 100755
index 0000000..5695584
--- /dev/null
+++ b/t/wild-2.t
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 43";
+
+confreset;confadd '
+    @prof       =   u1
+    @TAs        =   u2 u3
+    @students   =   u4 u5 u6
+
+    @gfoo = foo/CREATOR/a[0-9][0-9]
+    repo    @gfoo
+        C   =   @students
+        RW+ =   CREATOR
+        RW  =   WRITERS @TAs
+        R   =   READERS @prof
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+cd ..
+
+# u1 create fail
+glt clone u1 file:///foo/u1/a01
+/R any foo/u1/a01 u1 DENIED by fallthru/
+
+# u2 create fail
+glt clone u2 file:///foo/u2/a02
+/R any foo/u2/a02 u2 DENIED by fallthru/
+
+# u4 tries to create u2 repo
+glt clone u4 file:///foo/u2/a12
+/R any foo/u2/a12 u4 DENIED by fallthru/
+
+# line anchored regexes
+glt clone u4 file:///foo/u4/a1234
+/R any foo/u4/a1234 u4 DENIED by fallthru/
+
+# u4 tries to create his own repo
+glt clone u4 file:///foo/u4/a12
+/Initialized empty Git repository in .*/foo/u4/a12.git//
+/warning: You appear to have cloned an empty repository./
+
+# u4 push success
+cd a12
+tc n-770 n-771 n-772 n-773
+glt push u4 origin master
+/To file:///foo/u4/a12/
+/\\* \\[new branch\\]      master -> master/
+
+# u1 clone success
+cd ..
+glt clone u1 file:///foo/u4/a12 u1a12
+/Cloning into 'u1a12'.../
+
+# u1 push fail
+cd u1a12
+tc c-442 c-443
+glt push u1
+/W any foo/u4/a12 u1 DENIED by fallthru/
+
+# u2 clone success
+cd ..
+glt clone u2 file:///foo/u4/a12 u2a12
+/Cloning into 'u2a12'.../
+
+# u2 push success
+cd u2a12
+tc e-393 e-394
+glt push u2
+/To file:///foo/u4/a12/
+/master -> master/
+
+# u2 rewind fail
+glt push u2 -f origin master^:master
+/\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
+/error: hook declined to update refs/heads/master/
+/To file:///foo/u4/a12/
+/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
+/error: failed to push some refs to 'file:///foo/u4/a12'/
+
+# u4 pull to sync up
+cd ../a12
+glt pull u4
+/Fast-forward/
+/From file:///foo/u4/a12/
+/master     -> origin/master/
+
+# u4 rewind success
+git reset --hard HEAD^
+glt push u4 -f
+/To file:///foo/u4/a12/
+/\\+ .* master -> master \\(forced update\\)/
+
+# u5 clone fail
+cd ..
+glt clone u5 file:///foo/u4/a12 u5a12
+/R any foo/u4/a12 u5 DENIED by fallthru/
+
+# setperm
+glt perms u4 foo/u4/a12 + READERS u5
+glt perms u4 foo/u4/a12 + WRITERS u6
+
+# getperms
+glt perms u4 -l foo/u4/a12
+";
+
+cmp 'READERS u5
+WRITERS u6
+';
+
+try "
+# u5 clone success
+glt clone u5 file:///foo/u4/a12 u5a12
+/Cloning into 'u5a12'.../
+
+# u5 push fail
+cd u5a12
+tc g-809 g-810
+glt push u5
+/W any foo/u4/a12 u5 DENIED by fallthru/
+
+# u6 clone success
+cd ..
+glt clone u6 file:///foo/u4/a12 u6a12
+/Cloning into 'u6a12'.../
+
+# u6 push success
+cd u6a12
+tc f-912 f-913
+glt push u6 file:///foo/u4/a12
+/To file:///foo/u4/a12/
+/master -> master/
+
+# u6 rewind fail
+glt push u6 -f file:///foo/u4/a12 master^:master
+/\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
+/error: hook declined to update refs/heads/master/
+/To file:///foo/u4/a12/
+/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
+/error: failed to push some refs to 'file:///foo/u4/a12'/
+
+";

commit 8dcc051e645614ccd88f17aca1a269395759ef4e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 14:57:13 2012 +0530

    access() with a missing repo
    
    when a real repo (i.e., not a groupname or such) doesn't exist, checking
    any permission other than ^C will give invalid results unless ^C is ok
    for the user in question.
    
    Take a look at this:
    
        repo    foo/CREATOR/a[0-9][0-9]
            C   =   u2 u3
            RW+ =   CREATOR
            R   =   READERS u1
    
    u1 looking for R access on foo/u1/a11 will otherwise result in
    success.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 45c44b1..b8d3c96 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -65,6 +65,14 @@ sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
     load($repo);
 
+    # when a real repo doesn't exist, ^C is a pre-requisite for any other
+    # check to give valid results.
+    if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing($repo) ) {
+        my $iret = access( $repo, $user, '^C', $ref );
+        $iret =~ s/\^C/$aa/;
+        return $iret if $iret =~ /DENIED/;
+    }
+
     my @rules = rules( $repo, $user );
     trace( 2, scalar(@rules) . " rules found" );
     for my $r (@rules) {
@@ -310,12 +318,12 @@ sub user_roles {
     # eg == existing groups (that user is already known to be a member of)
     my %eg = map { $_ => 1 } @eg;
 
-    my %ret = ();
-    my $f   = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+    my %ret   = ();
+    my $f     = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
     my @roles = ();
     if ( -f $f ) {
         my $fh = _open( "<", $f );
-        chomp(@roles = <$fh>);
+        chomp( @roles = <$fh> );
     }
     push @roles, "CREATOR = " . creator($repo);
     for (@roles) {

commit c79f9d23815ebb0d2ed226f5d2a43fcc806d525c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 10:01:07 2012 +0530

    glt learns to deal better with non-git commands

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 2ae512b..71858cb 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -34,8 +34,10 @@ exit 0;
 # XXX lots of stuff from gl-auth-command is missing for now...
 
 sub in_local {
-    print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
-    print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
+    if ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-\w+-pack/) {
+        print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
+        print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
+    }
 }
 
 sub in_http {
diff --git a/t/glt b/t/glt
index 09d4429..b1cf4fe 100755
--- a/t/glt
+++ b/t/glt
@@ -5,19 +5,24 @@ use warnings;
 use FindBin;
 BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
 
-print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
-
 my $cmd  = shift or die "need command";
 my $user = shift or die "need user";
 my $rc;
 
+my %extcmds = (
+    info        => 1,
+    perms       => 1,
+);
+
 $ENV{G3T_USER} = $user;
-if ($cmd eq 'info' ) {
-    $ENV{SSH_ORIGINAL_COMMAND} = $cmd;
+if ($extcmds{$cmd}) {
+    $ENV{SSH_ORIGINAL_COMMAND} = join(" ", $cmd, @ARGV);
     exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
 } elsif ( $cmd eq 'push' ) {
+    print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
     $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV );
 } else {
+    print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
     $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV );
 }
 
diff --git a/t/info.t b/t/info.t
index 7171fbb..22019e3 100755
--- a/t/info.t
+++ b/t/info.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-try 'plan 45';
+try 'plan 39';
 
 try "## info";
 
@@ -30,32 +30,26 @@ try "
                                         /Initialized.*empty.*t3.git/
 ";
 try "
-    glt info u1;                ok;     gsh
-                                        /R W  \t\@t1/
+    glt info u1;                ok;     /R W  \t\@t1/
                                         /R W  \tt1/
                                         /R    \tt2/
                                         !/t3/
                                         /R W  \ttesting/
-    glt info u2;                ok;     gsh
-                                        /R    \t\@t1/
+    glt info u2;                ok;     /R    \t\@t1/
                                         /R    \tt1/
                                         /R W  \tt2/
                                         !/t3/
                                         /R W  \ttesting/
-    glt info u3;                ok;     gsh
-                                        /R W  \tt3/
+    glt info u3;                ok;     /R W  \tt3/
                                         !/\@t1/
                                         !/t[12]/
                                         /R W  \ttesting/
-    glt info u4;                ok;     gsh
-                                        /R    \tt3/
+    glt info u4;                ok;     /R    \tt3/
                                         !/\@t1/
                                         !/t[12]/
                                         /R W  \ttesting/
-    glt info u5;                ok;     gsh
-                                        !/t[123]/
+    glt info u5;                ok;     !/t[123]/
                                         /R W  \ttesting/
-    glt info u6;                ok;     gsh
-                                        !/t[123]/
+    glt info u6;                ok;     !/t[123]/
                                         /R W  \ttesting/
     " or die;

commit e743cab1a3ad8d9918f8a6d7510cd49329978fc0
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 09:50:24 2012 +0530

    perms command done (smoke tested)

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 001c4d7..49ca35d 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -231,6 +231,7 @@ __DATA__
         {
             'help'              =>  1,
             'info'              =>  1,
+            'perms'             =>  1,
         },
 );
 
diff --git a/src/commands/perms b/src/commands/perms
new file mode 100755
index 0000000..e70ca81
--- /dev/null
+++ b/src/commands/perms
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage:  ssh git at host perms -l <repo>
+        ssh git at host perms <repo> - <rolename> <username>
+        ssh git at host perms <repo> + <rolename> <username>
+
+List or set permissions for user-created ("wild") repo.  The first usage shown
+will list the current contents of the permissions file.  The other two will
+change permissions, adding or removing a user from a role.
+
+Examples:
+    ssh git at host perms foo + READERS user1
+    ssh git at host perms foo + READERS user2
+    ssh git at host perms foo + READERS user3
+
+(Note: a legacy mode of piping in the entire permissions text directly is also
+supported.  If you want to use it, don't mix it with the new "+/-" modes).
+=cut
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+
+my $list = 0;
+if ( $ARGV[0] eq '-l' ) {
+    $list++;
+    shift;
+    getperms(@ARGV);    # doesn't return
+}
+
+setperms(@ARGV);
+
+# ----------------------------------------------------------------------
+
+sub getperms {
+    my $repo = shift;
+    _die "repo '$repo' missing" if repo_missing($repo);
+    my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+
+    print slurp($pf) if -f $pf;
+
+    exit 0;
+}
+
+sub setperms {
+    my $repo = shift;
+    _die "repo '$repo' missing" if repo_missing($repo);
+    my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+
+    if ( not @_ ) {
+        # legacy mode; pipe data in
+        @ARGV = ();
+        _print( $pf, <> );
+        exit;
+    }
+
+    _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if @_ != 3;
+    my ( $op, $role, $user ) = @_;
+    _die "Invalid syntax.  Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-';
+    _die "Invalid role '$role'; check the rc file" if not $rc{ROLES}{$role};
+
+    my $text = '';
+    my @text = slurp($pf) if -f $pf;
+
+    my $present = grep { $_ eq "$role $user\n" } @text;
+
+    if ( $op eq '-' ) {
+        if ( not $present ) {
+            _warn "'$role $user' was not present in file";
+        } else {
+            @text = grep { $_ ne "$role $user\n" } @text;
+            _print( $pf, @text );
+        }
+    } else {
+        if ($present) {
+            _warn "'$role $user' already present in file";
+        } else {
+            push @text, "$role $user\n";
+            @text = sort @text;
+            _print( $pf, @text );
+        }
+    }
+}
diff --git a/t/wild-1.t b/t/wild-1.t
new file mode 100755
index 0000000..79085a2
--- /dev/null
+++ b/t/wild-1.t
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 43";
+
+confreset;confadd '
+    @prof       =   u1
+    @TAs        =   u2 u3
+    @students   =   u4 u5 u6
+
+    @gfoo = foo/CREATOR/a[0-9][0-9]
+    repo    @gfoo
+        C   =   @all
+        RW+ =   CREATOR
+        RW  =   WRITERS @TAs
+        R   =   READERS @prof
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+# reasonably complex setup; we'll do everything from one repo though
+cd ..
+
+# u1 create success
+glt clone u1 file:///foo/u1/a01
+/Initialized empty Git repository in .*/foo/u1/a01.git//
+
+# u2 create success
+glt clone u2 file:///foo/u2/a02
+/Initialized empty Git repository in .*/foo/u2/a02.git//
+
+# u4 tries to create u2 repo
+glt clone u4 file:///foo/u2/a12
+/R any foo/u2/a12 u4 DENIED by fallthru/
+
+# line anchored regexes
+glt clone u4 file:///foo/u4/a1234
+/R any foo/u4/a1234 u4 DENIED by fallthru/
+
+# u4 tries to create his own repo
+glt clone u4 file:///foo/u4/a12
+/Initialized empty Git repository in .*/foo/u4/a12.git//
+/warning: You appear to have cloned an empty repository./
+
+# u4 push success
+cd a12
+tc p-728 p-729 p-730 p-731
+glt push u4 origin master
+/To file:///foo/u4/a12/
+/\\* \\[new branch\\]      master -> master/
+
+# u1 clone success
+cd ..
+glt clone u1 file:///foo/u4/a12 u1a12
+/Cloning into 'u1a12'.../
+
+# u1 push fail
+cd u1a12
+tc m-778 m-779
+glt push u1 origin
+/W any foo/u4/a12 u1 DENIED by fallthru/
+
+# u2 clone success
+cd ..
+glt clone u2 file:///foo/u4/a12 u2a12
+/Cloning into 'u2a12'.../
+
+# u2 push success
+cd u2a12
+tc s-708 s-709
+glt push u2 origin
+/To file:///foo/u4/a12/
+/master -> master/
+
+# u2 rewind fail
+glt push u2 -f origin master^:master
+/\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/
+/error: hook declined to update refs/heads/master/
+/To file:///foo/u4/a12/
+/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
+/error: failed to push some refs to 'file:///foo/u4/a12'/
+
+# u4 pull to sync up
+cd ../a12
+glt pull u4
+/Fast-forward/
+/From file:///foo/u4/a12/
+/master     -> origin/master/
+
+# u4 rewind success
+git reset --hard HEAD^
+glt push u4 -f
+/To file:///foo/u4/a12/
+/\\+ .* master -> master \\(forced update\\)/
+
+# u5 clone fail
+cd ..
+glt clone u5 file:///foo/u4/a12 u5a12
+/R any foo/u4/a12 u5 DENIED by fallthru/
+
+glt perms u4 foo/u4/a12 + READERS u5
+glt perms u4 foo/u4/a12 + WRITERS u6
+
+glt perms u4 -l foo/u4/a12
+";
+
+cmp 'READERS u5
+WRITERS u6
+';
+
+try "
+# u5 clone success
+glt clone u5 file:///foo/u4/a12 u5a12
+/Cloning into 'u5a12'.../
+
+# u5 push fail
+cd u5a12
+tc y-743 y-744
+glt push u5
+/W any foo/u4/a12 u5 DENIED by fallthru/
+
+
+# u6 clone success
+cd ..
+glt clone u6 file:///foo/u4/a12 u6a12
+/Cloning into 'u6a12'.../
+
+# u6 push success
+cd u6a12
+tc k-68 k-69
+glt push u6 file:///foo/u4/a12
+/To file:///foo/u4/a12/
+/master -> master/
+
+# u6 rewind fail
+glt push u6 -f file:///foo/u4/a12 master^:master
+/\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/
+/error: hook declined to update refs/heads/master/
+/To file:///foo/u4/a12/
+/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/
+/error: failed to push some refs to 'file:///foo/u4/a12'/
+";

commit 1b31c21440ddf7e68d618a1bf8bee56523ea4b60
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 18 06:44:02 2012 +0530

    wildrepos almost done (except setperms etc)
    
    implementation notes
    
      - new sugar role_names() to prefix an "@" to CREATOR, and any role
        names listed in the rc file.
    
      - invalidate the cache in rules() if the repo was missing.  Without
        this, an auto-create operation succeeds the ^C check and calls
        new_wild_repo(), but then -- due to the cached rules not containing
        a rule for CREATOR, the actual read/write fails.
    
      - treat roles (READERS, WRITERS, etc.) as group names that apply only
        to that particular repo.  Don't add them to %groups, because that
        would screw up caching, but add them in when memberships() is called
        for the user.
    
        This is why the membership call for the user also has a reponame
        tacked on -- i.e., a user's membership list varied depending on
        which repo you're talking about.
    
      - while we're about it, pretend we added "CREATOR = <content of
        gl-creator>" as another "role".  Makes things so much easier dealing
        with "RW+ = CREATOR"
    
      - searching for rules pertaining to foo/CREATOR/bar when looking at
        repo foo/sitaram/bar is done backwards from what g2 used to do.  G2
        used to play tricks with the do-eval'd file using global variables
        so that what you get after the do may not even contain 'CREATOR'.
    
        We go the other way.  We replace sitaram with CREATOR and start
        looking for memberships of *both* foo/sitaram/bar and
        foo/CREATOR/bar.
    
      - this doesn't work (because we don't know *what* to replace) for
        missing repos if GL_USER is not set.  This means that 'gitolite
        access ...' queries (which do not set GL_USER) cannot be used
        reliably for non-existant repos.
    
        Since a ^C check is the only meaningful one for a non-existent repo,
        this means you cannot do that from 'gitolite access'.
    
        'GL_USER=luser gitolite info' will still work though ;-)
    
    all in all, much cleaner and simpler than g2.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 1f3592e..45c44b1 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -229,6 +229,9 @@ sub load_1 {
         $lastuser = $user;
         @cached   = @rules;
 
+        # however if the repo was missing, invalidate the cache
+        $lastrepo = '' if repo_missing($repo);
+
         return @rules;
     }
 
@@ -309,44 +312,60 @@ sub user_roles {
 
     my %ret = ();
     my $f   = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+    my @roles = ();
     if ( -f $f ) {
         my $fh = _open( "<", $f );
-        while (<$fh>) {
-            chomp;
-            # READERS u3 u4 @g1
-            s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/\@//;
-            my ( $role, @members ) = split;
-            # role = READERS, members = u3, u4, @g1
-            if ( not $rc{ROLES}{$role} ) {
-                _warn "role '$role' not allowed, ignoring";
+        chomp(@roles = <$fh>);
+    }
+    push @roles, "CREATOR = " . creator($repo);
+    for (@roles) {
+        # READERS u3 u4 @g1
+        s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
+        my ( $role, @members ) = split;
+        # role = READERS, members = u3, u4, @g1
+        if ( $role ne 'CREATOR' and not $rc{ROLES}{$role} ) {
+            _warn "role '$role' not allowed, ignoring";
+            next;
+        }
+        for my $m (@members) {
+            if ( $m !~ $USERNAME_PATT ) {
+                _warn "ignoring '$m' in perms line";
                 next;
             }
-            for my $m (@members) {
-                if ( $m !~ $USERNAME_PATT ) {
-                    _warn "ignoring '$m' in perms line";
-                    next;
-                }
-                # if user eq u3/u4, or is a member of @g1, he has role READERS
-                $ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
-            }
+            # if user eq u3/u4, or is a member of @g1, he has role READERS
+            $ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
         }
     }
+
     return keys %ret;
 }
 
 sub generic_name {
     my $base  = shift;
     my $base2 = '';
-    my $f     = "$rc{GL_REPO_BASE}/$base.git/gl-creator";
-    if ( -f $f ) {
-        my $creator;
-        chomp( $creator = slurp($f) );
-        ( $base2 = $base ) =~ s(/$creator/)(/CREATOR/);
-        $base2 = '' if $base2 eq $base;    # if there was no change
-    }
+    my $creator;
+
+    # get the creator name.  For not-yet-born repos this is $ENV{GL_USER},
+    # which should be set in all cases that we care about, viz., where we are
+    # checking ^C permissions before new_wild_repo(), and the info command.
+    # In particular, 'gitolite access' can't be used to check ^C perms.
+    $creator = creator($base);
+
+    ( $base2 = $base ) =~ s(/$creator/)(/CREATOR/) if $creator;
+    $base2 = '' if $base2 eq $base;    # if there was no change
+
     return $base2;
 }
 
+sub creator {
+    my $repo = shift;
+    return ( $ENV{GL_USER} || '' ) if repo_missing($repo);
+    my $f       = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
+    my $creator = '';
+    chomp( $creator = slurp($f) ) if -f $f;
+    return $creator;
+}
+
 # ----------------------------------------------------------------------
 # api functions
 # ----------------------------------------------------------------------
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 2ac2dd7..04a5e1e 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -67,6 +67,7 @@ sub sugar {
     $lines = option($lines);       # must come after rw_cdm
     $lines = owner_desc($lines);
     $lines = name_vref($lines);
+    $lines = role_names($lines);
 
     return $lines;
 }
@@ -173,5 +174,30 @@ sub name_vref {
     return \@ret;
 }
 
+sub role_names {
+    my $lines = shift;
+    my @ret;
+
+    # <perm> [<ref>] = <user list containing CREATOR|READERS|WRITERS>
+    #   ->  same but with "@" prepended to rolenames
+
+    for my $line (@$lines) {
+        if ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
+            my($p, $r) = ($1, $2);
+            my $u = '';
+            for (split ' ', $3) {
+                $_ = "\@$_" if $_ eq 'CREATOR' or $rc{ROLES}{$_};
+                $u .= " $_";
+            }
+            $r ||= '';
+            # mind the spaces (or play safe and run cleanup_conf_line again)
+            push @ret, cleanup_conf_line("$p $r = $u");
+        } else {
+            push @ret, $line;
+        }
+    }
+    return \@ret;
+}
+
 1;
 

commit 3c5ae7f26b070e082958b59a039afc7c73b4cd56
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 22:50:45 2012 +0530

    added 'RW+CDM' perm function, including some test code for 'D'

diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 03f46a7..9236074 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -109,26 +109,24 @@ sub args {
     # non-ff push to ref (including ref delete)
     $aa = '+' if $oldsha ne $merge_base;
 
-    # XXX $aa = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
-    # XXX $aa = 'C' if ( $repos{$ENV{GL_REPO}}{CREATE_IS_C} or $repos{'@all'}{CREATE_IS_C} ) and $oldsha eq '0' x 40;
+    $aa = 'D' if ( option( $ENV{GL_REPO}, 'DELETE_IS_D' ) ) and $newsha eq '0' x 40;
+    $aa = 'C' if ( option( $ENV{GL_REPO}, 'CREATE_IS_C' ) ) and $oldsha eq '0' x 40;
 
-    # and now "M" commits.  This presents a bit of a problem.  All the other
-    # accesses (W, +, C, D) were mutually exclusive in some sense.  Sure a W could
-    # be a C or a + could be a D but that's by design.  A merge commit, however,
-    # could still be any of the others (except a "D").
+    # and now "M" commits.  All the other accesses (W, +, C, D) were mutually
+    # exclusive in some sense.  Sure a W could be a C or a + could be a D but
+    # that's by design.  A merge commit, however, could still be any of the
+    # others (except a "D").
 
     # so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
     # effect and this push contains a merge inside)
 
-=for XXX
-    if ( $repos{ $ENV{GL_REPO} }{MERGE_CHECK} or $repos{'@all'}{MERGE_CHECK} ) {
+    if ( option( $ENV{GL_REPO}, 'MERGE_CHECK' ) ) {
         if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
-            warn "ref create/delete ignored for purposes of merge-check\n";
+            _warn "ref create/delete ignored for purposes of merge-check\n";
         } else {
             $aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
         }
     }
-=cut
 
     return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
 }
diff --git a/t/branch-perms.t b/t/branch-perms.t
new file mode 100755
index 0000000..45ccbc7
--- /dev/null
+++ b/t/branch-perms.t
@@ -0,0 +1,136 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# branch permissions test
+# ----------------------------------------------------------------------
+
+try "plan 58";
+
+confreset;confadd '
+    @g1 = u1
+    @g2 = u2
+    @g3 = u3
+    @gaa = aa
+    repo @gaa
+        RW+                 =   @g1
+        RW                  =   @g2
+        RW+     master      =   @g3
+        RW      master      =   u4
+        -       master      =   u5
+        RW+     dev         =   u5
+        RW                  =   u5
+
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+    cd ..;                          ok
+    glt clone u1 file://aa;         ok
+    cd aa;                          ok
+    tc l-995 l-996 l-997 l-998 l-999 l-1000 l-1001 l-1002 l-1003;
+                                    ok;     /master a788db9. l-1003/
+    glt push u1 origin HEAD;        ok;     /To file://aa/
+                                            /\\* \\[new branch\\]      HEAD -> master/
+
+    git branch dev;                 ok
+    git branch foo;                 ok
+
+    # u1 rewind master succeed
+    git reset --hard HEAD^;         ok;     /HEAD is now at 65d5f4a l-1002/
+    tc v-865;                       ok;     /master 3053bb4. v-865/
+    glt push u1 origin +master;     ok;     /\\+ a788db9...3053bb4 master -> master \\(forced update\\)/
+
+    # u2 rewind master fail
+    git reset --hard HEAD^;         ok;     /HEAD is now at 65d5f4a l-1002/
+    tc s-361;                       ok;     /master b331651. s-361/
+    glt push u2 file://aa +master;  !ok;    reject
+                                            /\\+ refs/heads/master aa u2 DENIED by fallthru/
+
+    # u3 rewind master succeed
+    git reset --hard HEAD^
+    tc m-508 
+    glt push u3 file://aa +master
+    /\\+ .* master -> master \\(forced update\\)/
+
+    # u4 push master succeed
+    tc f-526 
+    glt push u4 file://aa master
+    /master -> master/
+
+    # u4 rewind master fail
+    git reset --hard HEAD^
+    glt push u4 file://aa +master
+    /\\+ refs/heads/master aa u4 DENIED by fallthru/
+
+    # u3 and u4 / dev foo -- all 4 fail
+    glt push u3 file://aa dev
+    /W refs/heads/dev aa u3 DENIED by fallthru/
+    glt push u4 file://aa dev
+    /W refs/heads/dev aa u4 DENIED by fallthru/
+    glt push u3 file://aa foo
+    /W refs/heads/foo aa u3 DENIED by fallthru/
+    glt push u4 file://aa foo
+    /W refs/heads/foo aa u4 DENIED by fallthru/
+
+    # clean up for next set
+    glt push u1 -f origin master dev foo
+
+    # u5 push master fail
+    tc l-417 
+    glt push u5 file://aa master
+    /W refs/heads/master aa u5 DENIED by refs/heads/master/
+
+    # u5 rewind dev succeed
+    glt push u5 file://aa +dev^:dev
+    /\\+ .* dev\\^ -> dev \\(forced update\\)/
+
+    # u5 rewind foo fail
+    glt push u5 file://aa +foo^:foo
+    /\\+ refs/heads/foo aa u5 DENIED by fallthru/
+
+    # u5 tries to push foo; succeeds
+    git checkout foo
+    /Switched to branch 'foo'/
+
+    # u5 push foo succeed
+    tc e-530 
+    glt push u5 file://aa foo
+    /foo -> foo/
+
+    # u1 delete branch dev succeed
+    glt push u1 origin :dev
+    / - \\[deleted\\] *dev/
+
+    # quietly push it back again
+    glt push u1 origin dev
+    / * \\[new branch\\]      dev -> dev/
+
+    ";
+
+    confadd '
+        repo @gaa
+            RWD     dev         =   u4
+    ';
+
+try "ADMIN_PUSH set2; !/FATAL/" or die text();
+
+try "
+    cd ../aa
+    # u1 tries to delete dev on a new setup
+    /master -> master/
+
+    # u1 delete branch dev fail
+    glt push u1 origin :dev
+    /D refs/heads/dev aa u1 DENIED by fallthru/
+
+    # u4 delete branch dev succeed
+    glt push u4 file://aa :dev
+    / - \\[deleted\\] *dev/
+
+";

commit 5ae9b4ababc598fd15a659f6b69bde44fadfd4b8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 22:30:02 2012 +0530

    new sugar function to help with RW+CDM

diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index caea1fb..2ac2dd7 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -63,13 +63,38 @@ sub sugar {
 
     # then our stuff:
 
-    $lines = option($lines);
+    $lines = rw_cdm($lines);
+    $lines = option($lines);       # must come after rw_cdm
     $lines = owner_desc($lines);
     $lines = name_vref($lines);
 
     return $lines;
 }
 
+sub rw_cdm {
+    my $lines = shift;
+    my @ret;
+
+    # repo foo <...> RWC = ...
+    #   ->  option CREATE_IS_C = 1
+    # (and similarly DELETE_IS_D and MERGE_CHECK)
+    # but only once per repo of course
+
+    my %seen = ();
+    for my $line (@$lines) {
+        push @ret, $line;
+        if ( $line =~ /^repo / ) {
+            %seen = ();
+        } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
+            my $perms = $1;
+            push @ret, "option DELETE_IS_D = 1" if $perms =~ /D/     and not $seen{D}++;
+            push @ret, "option CREATE_IS_C = 1" if $perms =~ /RW.*C/ and not $seen{C}++;
+            push @ret, "option MERGE_CHECK = 1" if $perms =~ /M/     and not $seen{M}++;
+        }
+    }
+    return \@ret;
+}
+
 sub option {
     my $lines = shift;
     my @ret;

commit a6a666af78d39c971bc34836da1a394faf3dc508
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 21:50:48 2012 +0530

    new option() function in load.pm to quickly test conf options
    
    reminder: these are enabled by 'option foo = bar' keyword in conf and
    apply only to the repo

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 79e53b6..1f3592e 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -7,6 +7,7 @@ package Gitolite::Conf::Load;
   load
   access
   git_config
+  option
   repo_missing
   vrefs
   lister_dispatch
@@ -130,6 +131,14 @@ sub git_config {
     return \%ret;
 }
 
+sub option {
+    my ( $repo, $option ) = @_;
+    $option = "gitolite-options.$option";
+    my $ret = git_config( $repo, "^\Q$option\E\$" );
+    return '' unless %$ret;
+    return $ret->{$option};
+}
+
 sub repo_missing {
     my $repo = shift;
     return not -d "$rc{GL_REPO_BASE}/$repo.git";
@@ -236,7 +245,7 @@ sub load_1 {
 
 sub memberships {
     trace( 3, @_ );
-    my ($type, $base, $repo) = @_;
+    my ( $type, $base, $repo ) = @_;
     my $base2 = '';
 
     my @ret = ( $base, '@all' );
@@ -264,7 +273,7 @@ sub memberships {
         # find the roles this user has when accessing this repo and add those
         # in as groupnames he is a member of.  You need the already existing
         # memberships for this; see below this function for an example
-        push @ret, user_roles($base, $repo, @ret);
+        push @ret, user_roles( $base, $repo, @ret );
     }
 
     @ret = @{ sort_u( \@ret ) };
@@ -293,32 +302,32 @@ sub data_version_mismatch {
 }
 
 sub user_roles {
-    my ($user, $repo, @eg) = @_;
+    my ( $user, $repo, @eg ) = @_;
 
     # eg == existing groups (that user is already known to be a member of)
     my %eg = map { $_ => 1 } @eg;
 
     my %ret = ();
-    my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+    my $f   = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
     if ( -f $f ) {
-        my $fh = _open("<", $f);
+        my $fh = _open( "<", $f );
         while (<$fh>) {
             chomp;
             # READERS u3 u4 @g1
             s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/\@//;
-            my ($role, @members) = split;
+            my ( $role, @members ) = split;
             # role = READERS, members = u3, u4, @g1
-            if (not $rc{ROLES}{$role}) {
+            if ( not $rc{ROLES}{$role} ) {
                 _warn "role '$role' not allowed, ignoring";
                 next;
             }
             for my $m (@members) {
-                if ($m !~ $USERNAME_PATT) {
+                if ( $m !~ $USERNAME_PATT ) {
                     _warn "ignoring '$m' in perms line";
                     next;
                 }
                 # if user eq u3/u4, or is a member of @g1, he has role READERS
-                $ret{'@' . $role} = 1 if $m eq $user or $eg{$m};
+                $ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
             }
         }
     }
@@ -326,9 +335,9 @@ sub user_roles {
 }
 
 sub generic_name {
-    my $base = shift;
+    my $base  = shift;
     my $base2 = '';
-    my $f = "$rc{GL_REPO_BASE}/$base.git/gl-creator";
+    my $f     = "$rc{GL_REPO_BASE}/$base.git/gl-creator";
     if ( -f $f ) {
         my $creator;
         chomp( $creator = slurp($f) );

commit d5ddf6c68df81e53eb1b000ea7402700da76007e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 18:35:26 2012 +0530

    wip test suite

diff --git a/t/0-me-first.t b/t/0-me-first.t
new file mode 100755
index 0000000..afdd414
--- /dev/null
+++ b/t/0-me-first.t
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# initial smoke tests
+# ----------------------------------------------------------------------
+
+try "plan 55";
+
+# basic push admin repo
+confreset;confadd '
+    repo aa
+        RW+     =   u1
+        RW      =   u2 u3
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+                                            /Initialized empty Git repository in .*/aa.git//
+
+    # basic clone
+    cd ..
+    glt clone u1 file://aa u1aa;    ok;     /Cloning into 'u1aa'.../
+                                            /warning: You appear to have cloned an empty repository/
+    ls -ald --time-style=long-iso u1aa;
+                                    ok;     /drwxr-xr-x 3 $ENV{USER} $ENV{USER} 4096 201.-..-.. ..:.. u1aa/
+
+    # basic clone deny
+    glt clone u4 file://aa u4aa;    !ok;    /R any aa u4 DENIED by fallthru/
+    ls -ald u4aa;                   !ok;    /ls: cannot access u4aa: No such file or directory/
+
+    # basic push
+    cd u1aa;                        ok
+    tc z-507;                       ok;     /master .root-commit. 7cf7624. z-507/
+    glt push u1 origin HEAD;        ok;     /To file://aa/
+                                            /\\[new branch\\] *HEAD -> master/
+
+    # basic rewind
+    tc o-866 o-867 o-868;           ok;     /master 2d066fb. o-868/
+    glt push u1 origin HEAD;        ok;     /7cf7624..2d066fb  HEAD -> master/
+    git reset --hard HEAD^;         ok;     /HEAD is now at 8b1456b o-867/
+    tc x-967;                       ok;     /master 284951d. x-967/
+    glt push u1 -f origin HEAD;     ok;     /\\+ 2d066fb...284951d HEAD -> master \\(forced update\\)/
+
+    # log file
+    cat \$(gitolite query-rc GL_LOGFILE);
+                                    ok;     /update:OK/
+                                            /aa\tu1\t\\+\trefs/heads/master/
+                                            /2d066fb4860c29cf321170c17695c6883f3d50e8/
+                                            /284951dfa11d58f99ab76b9f4e4c1ad2f2461236/
+
+    # basic rewind deny
+    cd ..
+    glt clone u2 file://aa u2aa;    ok;     /Cloning into 'u2aa'.../
+    cd u2aa;                        ok
+    tc g-776 g-777 g-778;           ok;     /master 9cbc181. g-778/
+    glt push u2 origin HEAD;        ok;     /284951d..9cbc181  HEAD -> master/
+    git reset --hard HEAD^;         ok;     /HEAD is now at 2edf7fc g-777/
+    tc d-485;                       ok;     /master 1c01d32. d-485/
+    glt push u2 -f origin HEAD;     !ok;    reject
+                                            /\\+ refs/heads/master aa u2 DENIED by fallthru/
+";
diff --git a/t/access.t b/t/access.t
index d63eb19..047193a 100755
--- a/t/access.t
+++ b/t/access.t
@@ -6,7 +6,7 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# test 'gitolite access'
 # ----------------------------------------------------------------------
 
 try "plan 185";
diff --git a/t/basic.t b/t/basic.t
index fddc342..157f320 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -6,14 +6,14 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-# basic tests
+# some more basic tests
 # ----------------------------------------------------------------------
 
 try "
     plan 218
     CHECK_SETUP
 
-    ## subtest 1
+    # subtest 1
     cd ..
     CLONE dev2 file://gitolite-admin ga2
                                 !ok;    gsh
@@ -67,7 +67,7 @@ put "conf/gitolite.conf", "
 ";
 
 try "
-    ## subtest 2
+    # subtest 2
     ADMIN_PUSH t01b
 
     # clone
@@ -124,7 +124,7 @@ put "../gitolite-admin/conf/i1.conf", "
 ";
 
 try "
-    ## subtest 3
+    # subtest 3
     ADMIN_PUSH t01c
 
     cd ..;                      ok
@@ -239,7 +239,7 @@ put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
 ";
 
 try "
-    ## subtest 4
+    # subtest 4
     ADMIN_PUSH t01d
 
     cd ..;                      ok
@@ -266,7 +266,7 @@ put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
 ";
 
 try "
-    ## subtest 5
+    # subtest 5
     ADMIN_PUSH t01e
 
     cd ..;                      ok

commit 44e6bc4bb275fc345339cbe324bfb3953fa63cab
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 21:13:28 2012 +0530

    logging (but see below)
    
    The logging is both for paranoia and parsing/automated processing.  The
    ones you're probably interested in parsing should be easy to pick out
    and are very likely to have tab-delimited fields already.

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 2dd1855..9b43598 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -10,6 +10,8 @@ package Gitolite::Common;
   say2    _die    _system slurp             tsh_lines
           trace           cleanup_conf_line tsh_try
           usage                             tsh_run
+          gen_ts_lfn
+          gl_log
 );
 #>>>
 use Exporter 'import';
@@ -65,6 +67,7 @@ sub _warn {
 }
 
 sub _die {
+    gl_log("_die:", @_);
     if ( $ENV{D} and $ENV{D} >= 3 ) {
         confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
     } elsif ( defined( $ENV{D} ) ) {
@@ -108,6 +111,7 @@ sub _system {
     # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
     # exit with <rc of system()> if it applies, else just "exit 1".
     trace( 2, @_ );
+    gl_log("_system:", @_);
     if ( system(@_) != 0 ) {
         trace( 1, "system() failed", @_, "-> $?" );
         if ( $? == -1 ) {
@@ -200,6 +204,51 @@ sub cleanup_conf_line {
     }
 }
 
+# generate a timestamp.  If a template is passed generate a log file name
+# based on it also
+sub gen_ts_lfn {
+    my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
+    $y += 1900; $m++;               # usual adjustments
+    for ($s, $min, $h, $d, $m) {
+        $_ = "0$_" if $_ < 10;
+    }
+    my $ts = "$y-$m-$d.$h:$min:$s";
+
+    return $ts unless @_;
+
+    my($template) = shift;
+    # substitute template parameters and set the logfile name
+    $template =~ s/%y/$y/g;
+    $template =~ s/%m/$m/g;
+    $template =~ s/%d/$d/g;
+
+    return ($ts, $template);
+}
+
+sub gl_log {
+    # the log filename and the timestamp come from the environment.  If we get
+    # called even before they are set, we have no choice but to dump to STDERR
+    # (and probably call "logger").
+    my $msg = join("\t", @_);
+
+    my $ts = $ENV{GL_TS} || gen_ts_lfn();
+
+    my $fh;
+    logger_plus_stderr("$ts no GL_LOGFILE env var", "$ts $msg") if not $ENV{GL_LOGFILE};
+    open my $lfh, ">>", $ENV{GL_LOGFILE} or logger_plus_stderr("open log failed: $!", $msg);
+    print $lfh "$ts\t$msg\n";
+    close $lfh;
+}
+
+sub logger_plus_stderr {
+    open my $fh, "|-", "logger" or confess "it's really not my day is it...?\n";
+    for ( "FATAL: have errors but logging failed!\n", @_ ) {
+        print STDERR "$_\n";
+        print $fh "$_\n";
+    }
+    exit 1;
+}
+
 # ----------------------------------------------------------------------
 
 # bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh)
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index 2ce4bb5..981051d 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -20,6 +20,7 @@ use warnings;
 
 sub post_update {
     trace( 2, @ARGV );
+    gl_log( 'post-update', @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 1bba84a..03f46a7 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -20,15 +20,18 @@ use warnings;
 
 sub update {
     trace( 2, @ARGV );
+    gl_log( 'update', @ARGV );
     # this is the *real* update hook for gitolite
 
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+    gl_log( 'update:check', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref, '->', $ret );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
+    gl_log( 'update:OK', $ENV{GL_REPO}, $ENV{GL_USER}, $aa, @ARGV );
 
     exit 0;
 }
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index f975b5e..001c4d7 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -28,14 +28,16 @@ our %rc;
 
 # ----------------------------------------------------------------------
 
-# variables that are/could be/should be in the rc file
+# variables that could be overridden by the rc file
 # ----------------------------------------------------------------------
 
-$rc{GL_BINDIR}     = $ENV{GL_BINDIR};
+$rc{GL_BINDIR}    = $ENV{GL_BINDIR};
+$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
+
 $rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
-$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
+$rc{LOG_TEMPLATE}  = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
 
-# variables that should probably never be changed
+# variables that should probably never be changed but someone will want to, I'll bet...
 # ----------------------------------------------------------------------
 
 $REMOTE_COMMAND_PATT  = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$);
@@ -47,6 +49,9 @@ $UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 
 # ----------------------------------------------------------------------
 
+# find the rc file and 'do' it
+# ----------------------------------------------------------------------
+
 my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
@@ -55,13 +60,22 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
-# testing sometimes requires all of it to be overridden silently; use an
-# env var that is highly unlikely to appear in real life :)
+# (testing only) testing sometimes requires all of it to be overridden
+# silently; use an env var that is highly unlikely to appear in real life :)
 do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
+# fix some env vars, setup gitolite internal "env" vars (aka rc vars)
+# ----------------------------------------------------------------------
+
 # fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 
+{
+    my ( $ts, $lfn ) = gen_ts_lfn( $rc{LOG_TEMPLATE} );
+    $rc{GL_LOGFILE} = $ENV{GL_LOGFILE} = $lfn;
+    $rc{GL_TS}      = $ENV{GL_TS}      = $ts;
+}
+
 # ----------------------------------------------------------------------
 
 use strict;
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index eb19a17..a548889 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -97,6 +97,7 @@ sub setup_gladmin {
       and _print( "VERSION", tsh_text() );
 
     _mkdir("conf");
+    _mkdir("logs");
     my $conf;
     {
         local $/ = undef;
diff --git a/src/gitolite b/src/gitolite
index 949f3ef..804e751 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -46,6 +46,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 my ( $command, @args ) = @ARGV;
+gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE};
 args();
 
 # the first two commands need options via @ARGV, as they have their own
diff --git a/src/gitolite-shell b/src/gitolite-shell
index d17af2a..2ae512b 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -50,6 +50,7 @@ sub in_ssh {
 # call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
 # has been setup (even if it's not actually coming via ssh).
 sub main {
+    gl_log( 'gitolite-shell', @ARGV, $ENV{SSH_ORIGINAL_COMMAND} );
     umask $rc{UMASK};
 
     # set up the user
@@ -66,6 +67,7 @@ sub main {
         require Gitolite::Conf::Store;
         Gitolite::Conf::Store->import;
         new_wild_repo( $repo, $user );
+        gl_log( 'gitolite-shell:new_wild_repo', $repo, $user );
     }
 
     # a ref of 'any' signifies that this is a pre-git check, where we don't
@@ -74,6 +76,7 @@ sub main {
     # more information.
     my $ret = access( $repo, $user, $aa, 'any' );
     trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
+    gl_log( 'gitolite-shell:check', $repo, $user, $aa, 'any', '->', $ret );
     _die $ret if $ret =~ /DENIED/;
 
     $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 223d8ed..62e9345 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -26,7 +26,7 @@ try "
     cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6
 ";
 
-_system("gitolite post-compile/ssh-authkeys");
+system("gitolite post-compile/ssh-authkeys");
 
 # basic tests
 # ----------------------------------------------------------------------

commit 5b93dd4b5343cb537931be647919b395c6a7de77
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 16:52:36 2012 +0530

    minor changes to the testing infrastructure

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 331c886..52b3445 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -77,6 +77,7 @@ sub _confargs {
 }
 
 sub confreset {
+    chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
     system( "rm", "-rf", "conf" );
     mkdir("conf");
     system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga");
@@ -93,6 +94,7 @@ sub confreset {
 }
 
 sub confadd {
+    chdir("../gitolite-admin") or die "in `pwd`, could not cd ../g-a";
     my ( $file, $string ) = _confargs(@_);
     put "|cat >> conf/$file", $string;
 }
diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm
index 1a1711f..3281a34 100644
--- a/src/Gitolite/Test/Tsh.pm
+++ b/src/Gitolite/Test/Tsh.pm
@@ -53,7 +53,7 @@ my $cmd;     # the current command
 
 my $testnum;     # current test number, for info in TAP output
 my $testname;    # current test name, for error info to user
-my $line;        # current line number
+my $line;        # current line number and text
 
 my $err_count;   # count of test failures
 my @errors_in;   # list of testnames that errored
@@ -115,7 +115,7 @@ sub tsh {
 # (later) handles single commands
 
 sub try {
-    $rc = $err_count = 0;
+    $line = $rc = $err_count = 0;
     @errors_in = ();
 
     # break up multiline arguments into separate lines
@@ -238,11 +238,13 @@ sub rc_lines {
         my $_ = shift @lines;
         chomp; $_ = trim_ws($_);
 
+        $line++;
+
         # this also sets $testname
         next if is_comment_or_empty($_);
 
         dbg( 2, "L: $_" );
-        $line = $_;    # save line for printing with 'FAIL:'
+        $line .= ": $_";    # save line for printing with 'FAIL:'
 
         # a DEF has to be on a line by itself
         if (/^DEF\s+([-.\w]+)\s*=\s*(\S.*)$/) {
diff --git a/t/z-end.t b/t/z-end.t
new file mode 100755
index 0000000..25edbd9
--- /dev/null
+++ b/t/z-end.t
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try "plan 1; cd $ENV{PWD}; git status -s -uno; !/./ or die" or die "dirty tree";
+try "git log -1 --format='%h %ai %s'";
+put "|cat >> prove.log", text();
+
+
+

commit 941de722da27c0e026ba2ed63aaf88ca6a91a22c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 12:25:38 2012 +0530

    gl-perms handling and roles, first cut
    
    (additional memberships that user has when accessing a specific repo)

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 711b405..79e53b6 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -205,7 +205,7 @@ sub load_1 {
         my @rules = ();
 
         my @repos = memberships( 'repo', $repo );
-        my @users = memberships( 'user', $user );
+        my @users = memberships( 'user', $user, $repo );
         trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
@@ -235,44 +235,109 @@ sub load_1 {
 }
 
 sub memberships {
-    my $type  = shift;
-    my $item  = shift;
-    my $item2 = '';
-    trace( 3, $type, $item );
+    trace( 3, @_ );
+    my ($type, $base, $repo) = @_;
+    my $base2 = '';
 
-    my @ret = ( $item, '@all' );
+    my @ret = ( $base, '@all' );
 
     if ( $type eq 'repo' ) {
-        my $f = "$rc{GL_REPO_BASE}/$item.git/gl-creator";
-        if ( -f $f ) {
-            my $creator;
-            chomp( $creator = slurp($f) );
-            ( $item2 = $item ) =~ s(/$creator/)(/CREATOR/);
-            $item2 = '' if $item2 eq $item;    # no change
-        }
+        # first, if a repo, say, pub/sitaram/project, has a gl-creator file
+        # that says "sitaram", find memberships for pub/CREATOR/project also
+        $base2 = generic_name($base);
+
+        # second, you need to check in %repos also
         for my $i ( keys %repos ) {
-            if ( $item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ ) ) {
+            if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
                 push @ret, $i;
             }
         }
-
     }
 
     for my $i ( keys %groups ) {
-        if ( $item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ ) ) {
+        if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
             push @ret, @{ $groups{$i} };
         }
     }
 
+    if ( $type eq 'user' and $repo ) {
+        # find the roles this user has when accessing this repo and add those
+        # in as groupnames he is a member of.  You need the already existing
+        # memberships for this; see below this function for an example
+        push @ret, user_roles($base, $repo, @ret);
+    }
+
     @ret = @{ sort_u( \@ret ) };
     trace( 3, sort @ret );
     return @ret;
 }
 
+=for example
+
+conf/gitolite.conf:
+    @g1 = u1
+    @g2 = u1
+    # now user is a member of both g1 and g2
+
+gl-perms for repo being accessed:
+    READERS @g1
+
+This should result in @READERS being added to the memberships that u1 has
+(when accessing this repo).  So we send the current list (@g1, @g2) to
+user_roles(), otherwise it has to redo that logic.
+
+=cut
+
 sub data_version_mismatch {
     return $data_version ne glrc('current-data-version');
 }
 
+sub user_roles {
+    my ($user, $repo, @eg) = @_;
+
+    # eg == existing groups (that user is already known to be a member of)
+    my %eg = map { $_ => 1 } @eg;
+
+    my %ret = ();
+    my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
+    if ( -f $f ) {
+        my $fh = _open("<", $f);
+        while (<$fh>) {
+            chomp;
+            # READERS u3 u4 @g1
+            s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/\@//;
+            my ($role, @members) = split;
+            # role = READERS, members = u3, u4, @g1
+            if (not $rc{ROLES}{$role}) {
+                _warn "role '$role' not allowed, ignoring";
+                next;
+            }
+            for my $m (@members) {
+                if ($m !~ $USERNAME_PATT) {
+                    _warn "ignoring '$m' in perms line";
+                    next;
+                }
+                # if user eq u3/u4, or is a member of @g1, he has role READERS
+                $ret{'@' . $role} = 1 if $m eq $user or $eg{$m};
+            }
+        }
+    }
+    return keys %ret;
+}
+
+sub generic_name {
+    my $base = shift;
+    my $base2 = '';
+    my $f = "$rc{GL_REPO_BASE}/$base.git/gl-creator";
+    if ( -f $f ) {
+        my $creator;
+        chomp( $creator = slurp($f) );
+        ( $base2 = $base ) =~ s(/$creator/)(/CREATOR/);
+        $base2 = '' if $base2 eq $base;    # if there was no change
+    }
+    return $base2;
+}
+
 # ----------------------------------------------------------------------
 # api functions
 # ----------------------------------------------------------------------
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index cfbc55b..8b69e3e 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -200,7 +200,7 @@ sub new_wild_repo {
     _chdir( $rc{GL_REPO_BASE} );
     new_repo($repo);
     _print( "$repo.git/gl-creator", $user );
-    # XXX _print("$repo.git/gl-perms", "$rc{WILDREPOS_DEFAULT_PERMS}\n") if $rc{WILDREPOS_DEFAULT_PERMS};
+    _print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
     # XXX git config, daemon, web...
     # XXX pre-create, post-create
     _chdir( $rc{GL_ADMIN_BASE} );
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index b592882..f975b5e 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -179,13 +179,23 @@ __DATA__
 # configuration variables for gitolite
 
 # This file is in perl syntax.  But you do NOT need to know perl to edit it --
-# just mind the commas and make sure the brackets and braces stay matched up!
+# just mind the commas, use single quotes unless you know what you're doing,
+# and make sure the brackets and braces stay matched up!
 
 # (Tip: perl allows a comma after the last item in a list also!)
 
 %RC = (
     UMASK                       =>  0077,
-    GIT_CONFIG_KEYS             =>  "",
+    GIT_CONFIG_KEYS             =>  '',
+
+    # add more roles (like MANAGER, TESTER, ...) here
+    ROLES                       =>
+        {
+            READERS             =>  1,
+            WRITERS             =>  1,
+        },
+    # uncomment (and change) this if you wish
+    # DEFAULT_ROLE_PERMS          =>  'READERS @all',
 
     # comment out or uncomment as needed
     # these will run in sequence during the conf file parse

commit 9650d2fb3febc2fbe50637edacd6f4c7f6f92faa
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 07:40:17 2012 +0530

    (minor)
    
    trace rationalisation plus perltidy again

diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index ac60764..b64f748 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -63,7 +63,7 @@ sub parse {
             my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
             push @validkeys, "gitolite-options\\..*";
             my @matched = grep { $key =~ /^$_$/ } @validkeys;
-            _die "git config $key not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if (@matched < 1);
+            _die "git config $key not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if ( @matched < 1 );
             _die "bad value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 5ab3747..711b405 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -126,6 +126,7 @@ sub git_config {
     # and the final map does this:
     #                 'foo.bar'=>'repo'  ,      'foodbar'=>'repoD'
 
+    trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
     return \%ret;
 }
 
@@ -165,7 +166,7 @@ sub load_1 {
     trace( 3, $repo );
 
     if ( repo_missing($repo) ) {
-        trace( 3, "repo '$repo' missing" );
+        trace( 2, "repo '$repo' missing" );
         return;
     }
     _chdir("$rc{GL_REPO_BASE}/$repo.git");
@@ -237,6 +238,7 @@ sub memberships {
     my $type  = shift;
     my $item  = shift;
     my $item2 = '';
+    trace( 3, $type, $item );
 
     my @ret = ( $item, '@all' );
 
@@ -263,6 +265,7 @@ sub memberships {
     }
 
     @ret = @{ sort_u( \@ret ) };
+    trace( 3, sort @ret );
     return @ret;
 }
 
diff --git a/src/commands/help b/src/commands/help
index acd9a77..211b73a 100755
--- a/src/commands/help
+++ b/src/commands/help
@@ -13,11 +13,11 @@ Prints a list of custom commands available at this gitolite installation.
 =cut
 
 my $user = $ENV{GL_USER} || '';
-print "hello" . ( $user ? " $user" : "") . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
+print "hello" . ( $user ? " $user" : "" ) . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
 
 _chdir("$ENV{GL_BINDIR}/commands");
 
-print "list of " . ($user ? "remote" : "gitolite" ) . " commands available:\n\n";
+print "list of " . ( $user ? "remote" : "gitolite" ) . " commands available:\n\n";
 
 for my $c (`find . -type f|sort`) {
     chomp($c);
diff --git a/src/gitolite b/src/gitolite
index a934d00..949f3ef 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -123,5 +123,7 @@ sub run_all {
                 _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
             }
         }
+        return;
     }
+    trace( 2, "'$rc_section' not found in rc" );
 }

commit 89a1857d56c4bec36757da3ca6aae8067a05ad74
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 17 07:00:17 2012 +0530

    auto-create repo on 'C' perm done

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 939744c..5ab3747 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -7,6 +7,7 @@ package Gitolite::Conf::Load;
   load
   access
   git_config
+  repo_missing
   vrefs
   lister_dispatch
 );
@@ -105,13 +106,13 @@ sub git_config {
       # sort this list of listrefs by the first element in each list ref'd to
       sort { $a->[0] <=> $b->[0] }
       # dereference it (into a list of listrefs)
-      map  { @$_ }
+      map { @$_ }
       # take the value of that entry
       map { $configs{$_} }
       # if it has an entry in %configs
       grep { $configs{$_} }
       # for each "repo" that represents us
-      memberships('repo', $repo);
+      memberships( 'repo', $repo );
 
     # %configs looks like this (for each 'foo' that is in memberships())
     # 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
@@ -128,6 +129,11 @@ sub git_config {
     return \%ret;
 }
 
+sub repo_missing {
+    my $repo = shift;
+    return not -d "$rc{GL_REPO_BASE}/$repo.git";
+}
+
 # ----------------------------------------------------------------------
 
 sub load_common {
@@ -158,6 +164,10 @@ sub load_1 {
     return if $repo =~ /^\@/;
     trace( 3, $repo );
 
+    if ( repo_missing($repo) ) {
+        trace( 3, "repo '$repo' missing" );
+        return;
+    }
     _chdir("$rc{GL_REPO_BASE}/$repo.git");
 
     if ( $repo eq $last_repo ) {
@@ -193,8 +203,8 @@ sub load_1 {
 
         my @rules = ();
 
-        my @repos = memberships('repo', $repo);
-        my @users = memberships('user', $user);
+        my @repos = memberships( 'repo', $repo );
+        my @users = memberships( 'user', $user );
         trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
@@ -224,36 +234,35 @@ sub load_1 {
 }
 
 sub memberships {
-    my $type = shift;
-    my $item = shift;
+    my $type  = shift;
+    my $item  = shift;
     my $item2 = '';
 
     my @ret = ( $item, '@all' );
 
-    if ($type eq 'repo') {
+    if ( $type eq 'repo' ) {
         my $f = "$rc{GL_REPO_BASE}/$item.git/gl-creator";
-        if (-f $f) {
+        if ( -f $f ) {
             my $creator;
-            chomp($creator = slurp($f));
-            ($item2 = $item) =~ s(/$creator/)(/CREATOR/);
-            $item2 = '' if $item2 eq $item; # no change
+            chomp( $creator = slurp($f) );
+            ( $item2 = $item ) =~ s(/$creator/)(/CREATOR/);
+            $item2 = '' if $item2 eq $item;    # no change
         }
-        for my $i (keys %repos) {
-            if ($item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ )) {
+        for my $i ( keys %repos ) {
+            if ( $item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ ) ) {
                 push @ret, $i;
             }
         }
 
     }
 
-    for my $i (keys %groups) {
-        if ($item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ )) {
+    for my $i ( keys %groups ) {
+        if ( $item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ ) ) {
             push @ret, @{ $groups{$i} };
         }
     }
 
-    @ret = @{ sort_u(\@ret) };
-    dbg(\@ret);
+    @ret = @{ sort_u( \@ret ) };
     return @ret;
 }
 
@@ -349,7 +358,7 @@ sub list_memberships {
     my $name = shift;
 
     load_common();
-    my @m = memberships('', $name);
+    my @m = memberships( '', $name );
     return ( sort_u( \@m ) );
 }
 
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 5358172..cfbc55b 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -15,6 +15,7 @@ package Gitolite::Conf::Store;
   expand_list
   new_repos
   new_repo
+  new_wild_repo
   hook_repos
   store
   parse_done
@@ -126,7 +127,7 @@ sub add_rule {
 }
 
 sub add_config {
-    my($n, $key, $value) = @_;
+    my ( $n, $key, $value ) = @_;
 
     $nextseq++;
     for my $repo (@repolist) {
@@ -194,6 +195,17 @@ sub new_repo {
     # XXX ignoring gl-post-init for now
 }
 
+sub new_wild_repo {
+    my ( $repo, $user ) = @_;
+    _chdir( $rc{GL_REPO_BASE} );
+    new_repo($repo);
+    _print( "$repo.git/gl-creator", $user );
+    # XXX _print("$repo.git/gl-perms", "$rc{WILDREPOS_DEFAULT_PERMS}\n") if $rc{WILDREPOS_DEFAULT_PERMS};
+    # XXX git config, daemon, web...
+    # XXX pre-create, post-create
+    _chdir( $rc{GL_ADMIN_BASE} );
+}
+
 sub hook_repos {
     trace(3);
     # all repos, all hooks
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 6bb2970..d17af2a 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -61,6 +61,13 @@ sub main {
     $ENV{GL_REPO} = $repo;
     my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
 
+    # auto-create?
+    if ( repo_missing($repo) and access( $repo, $user, '^C', 'any' ) !~ /DENIED/ ) {
+        require Gitolite::Conf::Store;
+        Gitolite::Conf::Store->import;
+        new_wild_repo( $repo, $user );
+    }
+
     # a ref of 'any' signifies that this is a pre-git check, where we don't
     # yet know the ref that will be eventually pushed (and even that won't
     # apply if it's a read operation).  See the matching code in access() for

commit a014d2ffd5f7cc1ca1e7f80735378dae409913e4
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 21:39:26 2012 +0530

    "memberships()" can now deal with most everything except roles

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 0e5bc4e..939744c 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -111,7 +111,7 @@ sub git_config {
       # if it has an entry in %configs
       grep { $configs{$_} }
       # for each "repo" that represents us
-      memberships($repo);
+      memberships('repo', $repo);
 
     # %configs looks like this (for each 'foo' that is in memberships())
     # 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
@@ -193,8 +193,8 @@ sub load_1 {
 
         my @rules = ();
 
-        my @repos = memberships($repo);
-        my @users = memberships($user);
+        my @repos = memberships('repo', $repo);
+        my @users = memberships('user', $user);
         trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
@@ -224,11 +224,36 @@ sub load_1 {
 }
 
 sub memberships {
+    my $type = shift;
     my $item = shift;
+    my $item2 = '';
 
     my @ret = ( $item, '@all' );
-    push @ret, @{ $groups{$item} } if $groups{$item};
 
+    if ($type eq 'repo') {
+        my $f = "$rc{GL_REPO_BASE}/$item.git/gl-creator";
+        if (-f $f) {
+            my $creator;
+            chomp($creator = slurp($f));
+            ($item2 = $item) =~ s(/$creator/)(/CREATOR/);
+            $item2 = '' if $item2 eq $item; # no change
+        }
+        for my $i (keys %repos) {
+            if ($item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ )) {
+                push @ret, $i;
+            }
+        }
+
+    }
+
+    for my $i (keys %groups) {
+        if ($item eq $i or $item =~ /^$i$/ or $item2 and ( $item2 eq $i or $item2 =~ /^$i$/ )) {
+            push @ret, @{ $groups{$i} };
+        }
+    }
+
+    @ret = @{ sort_u(\@ret) };
+    dbg(\@ret);
     return @ret;
 }
 
@@ -324,7 +349,7 @@ sub list_memberships {
     my $name = shift;
 
     load_common();
-    my @m = memberships($name);
+    my @m = memberships('', $name);
     return ( sort_u( \@m ) );
 }
 

commit f21d17e086849ce858f532ca8dd99c657658ecb0
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 14:29:45 2012 +0530

    git_configs almost done, but
    
    real testing can only happen after wildrepos is finished (specifically,
    when memberships() can return regex repo names also)

diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index 6a02b0d..ac60764 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -60,11 +60,11 @@ sub parse {
             }
         } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
             my ( $key, $value ) = ( $1, $2 );
-            my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) );
+            my @validkeys = split( ' ', ( $rc{GIT_CONFIG_KEYS} || '' ) );
             push @validkeys, "gitolite-options\\..*";
             my @matched = grep { $key =~ /^$_$/ } @validkeys;
-            # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1);
-            # XXX both $key and $value must satisfy a liberal but secure pattern
+            _die "git config $key not allowed\ncheck GIT_CONFIG_KEYS in the rc file" if (@matched < 1);
+            _die "bad value '$value'" if $value =~ $UNSAFE_PATT;
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
             trace( 2, $line );
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 7ff0c6f..0e5bc4e 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -6,6 +6,7 @@ package Gitolite::Conf::Load;
 @EXPORT = qw(
   load
   access
+  git_config
   vrefs
   lister_dispatch
 );
@@ -89,6 +90,44 @@ sub access {
     return "$aa $ref $repo $user DENIED by fallthru";
 }
 
+sub git_config {
+    my ( $repo, $key ) = @_;
+    $key ||= '.';
+
+    load($repo);
+
+    # read comments bottom up
+    my %ret =
+      # and take the second and third elements to make up your new hash
+      map { $_->[1] => $_->[2] }
+      # keep only the ones where the second element matches your key
+      grep { $_->[1] =~ qr($key) }
+      # sort this list of listrefs by the first element in each list ref'd to
+      sort { $a->[0] <=> $b->[0] }
+      # dereference it (into a list of listrefs)
+      map  { @$_ }
+      # take the value of that entry
+      map { $configs{$_} }
+      # if it has an entry in %configs
+      grep { $configs{$_} }
+      # for each "repo" that represents us
+      memberships($repo);
+
+    # %configs looks like this (for each 'foo' that is in memberships())
+    # 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
+    # the first map gets you the value
+    #          [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
+    # the deref gets you
+    #            [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ]
+    # the sort rearranges it (in this case it's already sorted but anyway...)
+    # the grep gets you this, assuming the key is foo.bar (and "." is regex ".')
+    #            [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ]
+    # and the final map does this:
+    #                 'foo.bar'=>'repo'  ,      'foodbar'=>'repoD'
+
+    return \%ret;
+}
+
 # ----------------------------------------------------------------------
 
 sub load_common {
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index e43c45b..5358172 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -5,12 +5,14 @@ package Gitolite::Conf::Store;
 
 @EXPORT = qw(
   add_to_group
-  expand_list
   set_repolist
   parse_refs
   parse_users
   add_rule
+  add_config
   set_subconf
+
+  expand_list
   new_repos
   new_repo
   hook_repos
@@ -40,7 +42,7 @@ my %split_conf;
 
 my @repolist;    # current repo list; reset on each 'repo ...' line
 my $subconf = 'master';
-my $ruleseq = 0;
+my $nextseq = 0;
 my %ignored;
 # XXX you still have to "warn" if this has any entries
 
@@ -60,24 +62,6 @@ sub add_to_group {
     $groups{$lhs} = {} unless $groups{$lhs};
 }
 
-sub expand_list {
-    my @list     = @_;
-    my @new_list = ();
-
-    for my $item (@list) {
-        if ( $item =~ /^@/ and $item ne '@all' )    # nested group
-        {
-            _die "undefined group $item" unless $groups{$item};
-            # add those names to the list
-            push @new_list, sort keys %{ $groups{$item} };
-        } else {
-            push @new_list, $item;
-        }
-    }
-
-    return @new_list;
-}
-
 sub set_repolist {
     @repolist = @_;
 
@@ -119,7 +103,7 @@ sub add_rule {
     _die "bad ref '$ref'"   unless $ref  =~ $REPOPATT_PATT;
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
-    $ruleseq++;
+    $nextseq++;
     for my $repo (@repolist) {
         if ( check_subconf_repo_disallowed( $subconf, $repo ) ) {
             my $repo = $repo;
@@ -128,7 +112,7 @@ sub add_rule {
             next;
         }
 
-        push @{ $repos{$repo}{$user} }, [ $ruleseq, $perm, $ref ];
+        push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
 
         # XXX g2 diff: we're not doing a lint check for usernames versus pubkeys;
         # maybe we can add that later
@@ -141,11 +125,41 @@ sub add_rule {
     }
 }
 
+sub add_config {
+    my($n, $key, $value) = @_;
+
+    $nextseq++;
+    for my $repo (@repolist) {
+        # XXX should we check_subconf_repo_disallowed here?
+        push @{ $configs{$repo} }, [ $nextseq, $key, $value ];
+    }
+}
+
 sub set_subconf {
     $subconf = shift;
     _die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
 }
 
+# ----------------------------------------------------------------------
+
+sub expand_list {
+    my @list     = @_;
+    my @new_list = ();
+
+    for my $item (@list) {
+        if ( $item =~ /^@/ and $item ne '@all' )    # nested group
+        {
+            _die "undefined group $item" unless $groups{$item};
+            # add those names to the list
+            push @new_list, sort keys %{ $groups{$item} };
+        } else {
+            push @new_list, $item;
+        }
+    }
+
+    return @new_list;
+}
+
 sub new_repos {
     trace(3);
     _chdir( $rc{GL_REPO_BASE} );
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index cd828c2..b592882 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -14,6 +14,7 @@ package Gitolite::Rc;
   $REPONAME_PATT
   $REPOPATT_PATT
   $USERNAME_PATT
+  $UNSAFE_PATT
 );
 
 use Exporter 'import';
@@ -42,6 +43,7 @@ $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
 $REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
 $REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
 $USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
+$UNSAFE_PATT          = qr([`~#\$\&()|;<>]);
 
 # ----------------------------------------------------------------------
 
@@ -183,7 +185,7 @@ __DATA__
 
 %RC = (
     UMASK                       =>  0077,
-    GL_GITCONFIG_KEYS           =>  "",
+    GIT_CONFIG_KEYS             =>  "",
 
     # comment out or uncomment as needed
     # these will run in sequence during the conf file parse
diff --git a/src/commands/git-config b/src/commands/git-config
new file mode 100755
index 0000000..2cb8948
--- /dev/null
+++ b/src/commands/git-config
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Getopt::Long;
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage:  gitolite git-config [-n] [-q] [-r] <repo> <key|pattern>
+
+Print git config keys and values for the given repo.  The key is either a full
+key, or, if '-r' is supplied, a regex that is applied to all available keys.
+
+    -q          exit code only (shell truth; 0 is success)
+    -n          suppress trailing newline when used as key (not pattern)
+    -r          treat key as regex pattern (unanchored)
+
+Examples:
+    gitolite git-config repo gitweb.owner
+    gitolite git-config -q repo gitweb.owner
+    gitolite git-config -r repo gitweb
+
+When the key is treated as a pattern, prints one key+value per line, tab
+separated:
+
+    reponame<tab>key<tab>value<newline>
+
+Otherwise the output is just the value.
+
+Finally, see the advanced use section of 'gitolite access -h' -- you can do
+something similar here also:
+
+    gitolite list-phy-repos | gitolite git-config -r % gitweb\\. | cut -f1 > ~/projects.list
+=cut
+
+usage() if not @ARGV;
+
+my ( $help, $nonl, $quiet, $regex ) = (0) x 4;
+GetOptions(
+    'n' => \$nonl,
+    'q' => \$quiet,
+    'r' => \$regex,
+    'h' => \$help,
+) or usage();
+
+my ( $repo, $key ) = @ARGV;
+usage() unless $key;
+
+my $ret = '';
+
+if ( $repo ne '%' and $key ne '%' ) {
+    # single repo, single key; no STDIN
+    $key = "^\Q$key\E\$" unless $regex;
+
+    $ret = git_config( $repo, $key );
+
+    # unlike access, there's nothing to print if we don't find any matching keys
+    exit 1 unless %$ret;
+
+    map { print "$repo\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret unless $quiet;
+    exit 0;
+}
+
+$repo = '' if $repo eq '%';
+$key  = '' if $key  eq '%';
+
+_die "'-q' doesn't go with using a pipe" if $quiet;
+ at ARGV = ();
+while (<>) {
+    my @in = split;
+    my $r  = $repo || shift @in;
+    my $k  = $key || shift @in;
+    $k = "^\Q$k\E\$" unless $regex;
+    $ret = git_config( $r, $k );
+    next unless %$ret;
+    map { print "$r\t$_\t" . $ret->{$_} . "\n" } sort keys %$ret;
+}

commit 45348a4225303ca495067326d80fa678807f687a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 15:47:53 2012 +0530

    access() learned a new trick :)

diff --git a/src/commands/access b/src/commands/access
index ec97046..f42e147 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -10,8 +10,9 @@ use Gitolite::Conf::Load;
 =for usage
 Usage:  gitolite access [-q] <repo> <user> <perm> <ref>
 
-Check access rights for arguments given.  With '-q', returns only an exit code
-(shell truth, not perl truth -- 0 is success, any non-0 is failure).
+Print access rights for arguments given.  The string printed has the word
+DENIED in it if access was denied.  With '-q', returns only an exit code
+(shell truth, not perl truth -- 0 is success).
 
   - repo: mandatory
   - user: mandatory
@@ -19,10 +20,26 @@ Check access rights for arguments given.  With '-q', returns only an exit code
   - ref:  defauts to 'any'.  See notes below
 
 Notes:
-
   - ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine.
     The 'any' ref is special -- it ignores deny rules (see docs for what this
     means and exceptions).
+
+Advanced use (examples only):
+
+    gitolite list-phy-repos | gitolite access % gitweb R | grep -v DENIED | cut -f1 > ~/projects.list
+    # now people can stop thinking gitolite has anything to do with gitweb!
+
+    gitolite list-phy-repos | grep foo |
+        perl -lne 'print "$_ gitweb\n$_ daemon"' |
+        gitolite access % % R | grep -v DENIED | cut -f1 > insecure.repos
+
+For each case where access is not denied, one line is printed like this:
+
+    reponame<tab>username<tab>access rights
+
+This is orders of magnitude faster than running the command multiple times;
+you'll notice if you have more than a hundred or so repos.
+
 =cut
 
 # TODO: deal with "C", call it ^C
@@ -35,19 +52,35 @@ my ( $repo, $user, $aa, $ref ) = @ARGV;
 $aa  ||= '+';
 $ref ||= 'any';
 # XXX the 4th one below might need fine tuning
-_die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT );
-_die "invalid user name" if not( $user and $user =~ $USERNAME_PATT );
-_die "invalid perm"      if not( $aa   and $aa   =~ /^(R|W|\+|C|D|M)$/ );
-_die "invalid ref name"  if not( $ref  and $ref  =~ $REPONAME_PATT );
+_die "invalid perm" if not( $aa and $aa =~ /^(R|W|\+|C|D|M)$/ );
+_die "invalid ref name" if not( $ref and $ref =~ $REPONAME_PATT );
 
 my $ret = '';
 
-$ret = access( $repo, $user, $aa, $ref );
+if ( $repo ne '%' and $user ne '%' ) {
+    # single repo, single user; no STDIN
+    _die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT );
+    _die "invalid user name" if not( $user and $user =~ $USERNAME_PATT );
+    $ret = access( $repo, $user, $aa, $ref );
+
+    if ( $ret =~ /DENIED/ ) {
+        print "$ret\n" unless $quiet;
+        exit 1;
+    }
 
-if ( $ret =~ /DENIED/ ) {
     print "$ret\n" unless $quiet;
-    exit 1;
+    exit 0;
 }
 
-print "$ret\n" unless $quiet;
-exit 0;
+$repo = '' if $repo eq '%';
+$user = '' if $user eq '%';
+
+_die "'-q' doesn't go with using a pipe" if $quiet;
+ at ARGV = ();
+while (<>) {
+    my @in = split;
+    my $r  = $repo || shift @in;
+    my $u  = $user || shift @in;
+    $ret = access( $r, $u, $aa, $ref );
+    print "$r\t$u\t$ret\n";
+}

commit 876b554fb511dd120f206839604615f732ae0b55
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 09:25:39 2012 +0530

    changes to custom command invocation etc.; see below
    
      - 'post-compile' subdir moved under 'commands/' but only for
        sanity; has no real significance now
    
      - new, internal use only, gitolite command run-all, as in
    
            gitolite run-all POST_COMPILE
    
        which runs all the commands in @{ $rc{POST_COMPILE} } in sequence.
        You can sdo this for any section of course, though this is the only
        one in the rc right now.
    
        (Future candidates: PRE_GIT, POST_GIT, PRE_CREATE, POST_CREATE)

diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index 49abdfb..2ce4bb5 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -30,25 +30,7 @@ sub post_update {
         tsh_try("git checkout -f --quiet master");
     }
     _system("$ENV{GL_BINDIR}/gitolite compile");
-
-    # now run optional post-compile features
-    if ( exists $rc{POST_COMPILE} ) {
-        if ( ref( $rc{POST_COMPILE} ) ne 'ARRAY' ) {
-            _warn "bad syntax for specifying post compile scripts; see docs";
-        } else {
-            for my $s ( @{ $rc{POST_COMPILE} } ) {
-
-                # perl-ism; apart from keeping the full path separate from the
-                # simple name, this also protects %rc from change by implicit
-                # aliasing, which would happen if you touched $s itself
-                my $sfp = "$ENV{GL_BINDIR}/post-compile/$s";
-
-                _warn("skipped post-compile script '$s'"), next if not -x $sfp;
-                trace( 2, "post-compile $s" );
-                _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
-            }
-        }
-    }
+    _system("$ENV{GL_BINDIR}/gitolite run-all POST_COMPILE");
 
     exit 0;
 }
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 321db5e..cd828c2 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -196,13 +196,14 @@ __DATA__
     # these will run in sequence after post-update
     POST_COMPILE                =>
         [
-            'ssh-authkeys',
+            'post-compile/ssh-authkeys',
         ],
 
     # comment out or uncomment as needed
     # these are available to remote users
     COMMANDS                    =>
         {
+            'help'              =>  1,
             'info'              =>  1,
         },
 );
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 0d5f664..eb19a17 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -36,7 +36,7 @@ sub setup {
     setup_gladmin( $admin, $pubkey, $argv );
 
     _system("$ENV{GL_BINDIR}/gitolite compile");
-    _system("$ENV{GL_BINDIR}/gitolite post-compile ssh-authkeys") if $pubkey;
+    _system("$ENV{GL_BINDIR}/gitolite run-all POST_COMPILE");
 
     hook_repos();    # all of them, just to be sure
 }
diff --git a/src/commands/help b/src/commands/help
new file mode 100755
index 0000000..acd9a77
--- /dev/null
+++ b/src/commands/help
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+=for usage
+Usage:  gitolite help
+
+Prints a list of custom commands available at this gitolite installation.
+=cut
+
+my $user = $ENV{GL_USER} || '';
+print "hello" . ( $user ? " $user" : "") . ", this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
+
+_chdir("$ENV{GL_BINDIR}/commands");
+
+print "list of " . ($user ? "remote" : "gitolite" ) . " commands available:\n\n";
+
+for my $c (`find . -type f|sort`) {
+    chomp($c);
+    $c =~ s(^./)();
+    next unless -x $c;
+
+    # if it's from a remote client, show only what he is allowed
+    next if $user and not $rc{COMMANDS}{$c};
+
+    print "\t$c\n";
+}
+print "\n";
+
+exit 0;
diff --git a/src/post-compile/ssh-authkeys b/src/commands/post-compile/ssh-authkeys
similarity index 93%
rename from src/post-compile/ssh-authkeys
rename to src/commands/post-compile/ssh-authkeys
index a286a74..6f8f23f 100755
--- a/src/post-compile/ssh-authkeys
+++ b/src/commands/post-compile/ssh-authkeys
@@ -14,7 +14,8 @@ $|++;
 # arguments anyway, it hardly matters.
 
 my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
-_warn("'keydir' not found in '$ab'"), exit if not -d "$ab/keydir";
+trace( 2, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
+my $akdir   = "$ENV{HOME}/.ssh";
 my $akfile  = "$ENV{HOME}/.ssh/authorized_keys";
 my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
 # XXX gl-time not yet coded (GL_PERFLOGT)
@@ -58,11 +59,14 @@ if (@gl_keys) {
 # ----------------------------------------------------------------------
 
 sub sanity {
-    _warn "$akfile missing; creating a new one"                         if not -f $akfile;
     _die "$glshell not found; this should NOT happen..."                if not -f $glshell;
     _die "$glshell found but not readable; this should NOT happen..."   if not -r $glshell;
     _die "$glshell found but not executable; this should NOT happen..." if not -x $glshell;
 
+    _warn "$akdir missing; creating a new one"                          if not -d $akdir;
+    _warn "$akfile missing; creating a new one"                         if not -f $akfile;
+
+    _mkdir($akdir, 0700) if not -d $akfile;
     if ( not -f $akfile ) {
         _print( $akfile, "" );
         chmod 0700, $akfile;
diff --git a/src/gitolite b/src/gitolite
index e62543d..a934d00 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -6,14 +6,13 @@
 =for args
 Usage:  gitolite [sub-command] [options]
 
-The following subcommands are available; they should all respond to '-h' if
-you want further details on each:
+The following built-in subcommands are available; they should all respond to
+'-h' if you want further details on each:
 
     setup                       1st run: initial setup; all runs: hook fixups
     compile                     compile gitolite.conf
 
     query-rc                    get values of rc variables
-    post-compile                run a post-compile command
 
     list-groups                 list all group names in conf
     list-users                  list all users/user groups in conf
@@ -26,6 +25,10 @@ Warnings:
   - list-users is disk bound and could take a while on sites with 1000s of repos
   - list-memberships does not check if the name is known; unknown names come
     back with 2 answers: the name itself and '@all'
+
+In addition, running 'gitolite help' should give you a list of custom commands
+available.  They may or may not respond to '-h', depending on how they were
+written.
 =cut
 
 # ----------------------------------------------------------------------
@@ -65,8 +68,8 @@ if ( $command eq 'setup' ) {
     Gitolite::Conf->import;
     compile(@args);
 
-} elsif ( $command eq 'post-compile' ) {
-    post_compile(@args);
+} elsif ( $command eq 'run-all' ) {
+    run_all(@args);
 
 } elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
     trace( 2, "attempting gitolite command $command" );
@@ -93,36 +96,32 @@ sub args {
 
 # ----------------------------------------------------------------------
 
-=for post_compile
-Usage:  gitolite post-compile [-l] [post-compile-scriptname] [script args...]
-
-    -l          list currently available post-compile scripts
-
-Run a post-compile script (which normally runs from the post-update hook in
-the gitolite-admin repo).
-=cut
-
-sub post_compile {
-    usage() if ( not @_ or $_[0] eq '-h' );
-
-    run_subdir( 'post-compile', @_ );
-}
-
 sub run_command {
-    run_subdir( 'commands', @_ );
-}
-
-sub run_subdir {
-    my $subdir = shift;
-    if ( @_ and $_[0] eq '-l' ) {
-        _chdir("$ENV{GL_BINDIR}/$subdir");
-        map { say2($_) } grep { -x } glob("*");
-        exit 0;
-    }
-
     my $pgm      = shift;
-    my $fullpath = "$ENV{GL_BINDIR}/$subdir/$pgm";
+    my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
     _die "$pgm not found or not executable" if not -x $fullpath;
     _system( $fullpath, @_ );
     exit 0;
 }
+
+sub run_all {
+    my $rc_section = shift;
+
+    if ( exists $rc{$rc_section} ) {
+        if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
+            _warn "$rc_section section in rc file is not a perl list";
+        } else {
+            for my $s ( @{ $rc{$rc_section} } ) {
+
+                # perl-ism; apart from keeping the full path separate from the
+                # simple name, this also protects %rc from change by implicit
+                # aliasing, which would happen if you touched $s itself
+                my $sfp = "$ENV{GL_BINDIR}/commands/$s";
+
+                _warn("skipped command '$s'"), next if not -x $sfp;
+                trace( 2, "command: $s" );
+                _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
+            }
+        }
+    }
+}
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
index 64179b4..e4454ee 100755
--- a/t/ssh-authkeys.t
+++ b/t/ssh-authkeys.t
@@ -11,15 +11,15 @@ $ENV{GL_BINDIR} = "$ENV{PWD}/src";
 my $ak = "$ENV{HOME}/.ssh/authorized_keys";
 my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir";
 
-try "plan 50";
+try "plan 49";
 
-my $pgm = "gitolite post-compile ssh-authkeys";
+my $pgm = "gitolite post-compile/ssh-authkeys";
 
 try "
     # prep
     rm -rf $ak;                 ok
 
-    $pgm;                       ok;    /'keydir' not found/
+    $pgm;                       ok
     mkdir $kd;                  ok
     cd $kd;                     ok
     $pgm;                       ok;     /authorized_keys missing/
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
index 2747185..223d8ed 100755
--- a/t/ssh-basic.t
+++ b/t/ssh-basic.t
@@ -26,7 +26,7 @@ try "
     cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6
 ";
 
-_system("gitolite post-compile ssh-authkeys");
+_system("gitolite post-compile/ssh-authkeys");
 
 # basic tests
 # ----------------------------------------------------------------------

commit efe37fb8a38b9087f02a93332d179f988bd1dcf8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 21:59:36 2012 +0530

    honor umask

diff --git a/src/gitolite-shell b/src/gitolite-shell
index 8437cbd..6bb2970 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -50,6 +50,8 @@ sub in_ssh {
 # call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
 # has been setup (even if it's not actually coming via ssh).
 sub main {
+    umask $rc{UMASK};
+
     # set up the user
     my $user = $ENV{GL_USER} = shift @ARGV;
 

commit db8dc8ca2de12a2cf67dff5a36e471a7b0547a87
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 21:49:47 2012 +0530

    allow trace mode from remote client
    
    just say (for example):
    
        git push git at server:reponame.git1
    
    for trace level 1, and similarly for 2 and 3

diff --git a/src/gitolite-shell b/src/gitolite-shell
index b596ba4..8437cbd 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -77,9 +77,10 @@ sub parse_soc {
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
     $soc ||= 'info';
 
-    if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$) ) {
+    if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git(\d)?)?'$) ) {
         # TODO git archive
-        my ( $verb, $repo ) = ( $1, $2 );
+        my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
+        $ENV{D} = $trace_level if $trace_level;
         _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
         trace( 2, "git command", $soc );
         return ( $verb, $repo );

commit 38cb9bfda9cb6ffd619dee8dbeb7cd59d4e763cf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 21:00:39 2012 +0530

    trace messages rationalised to 3 levels

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 3c29ccf..2dd1855 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -107,8 +107,9 @@ sub _chdir {
 sub _system {
     # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
     # exit with <rc of system()> if it applies, else just "exit 1".
+    trace( 2, @_ );
     if ( system(@_) != 0 ) {
-        say2 "system @_ failed" if $ENV{D};
+        trace( 1, "system() failed", @_, "-> $?" );
         if ( $? == -1 ) {
             die "failed to execute: $!\n" if $ENV{D};
         } elsif ( $? & 127 ) {
@@ -150,7 +151,7 @@ sub dos2unix {
 }
 
 sub ln_sf {
-    trace( 4, @_ );
+    trace( 3, @_ );
     my ( $srcdir, $glob, $dstdir ) = @_;
     for my $hook ( glob("$srcdir/$glob") ) {
         $hook =~ s/$srcdir\///;
@@ -185,8 +186,6 @@ sub cleanup_conf_line {
     my @phy_repos = ();
 
     sub list_phy_repos {
-        trace(3);
-
         # use cached value only if it exists *and* no arg was received (i.e.,
         # receiving *any* arg invalidates cache)
         return \@phy_repos if ( @phy_repos and not @_ );
@@ -196,6 +195,7 @@ sub cleanup_conf_line {
             $repo =~ s(\./(.*)\.git$)($1);
             push @phy_repos, $repo;
         }
+        trace( 2, scalar(@phy_repos) . " physical repos found" );
         return sort_u( \@phy_repos );
     }
 }
@@ -214,7 +214,7 @@ sub cleanup_conf_line {
         $text = `( $cmd ) 2>&1; echo -n RC=\$?`;
         if ( $text =~ s/RC=(\d+)$// ) {
             $rc = $1;
-            trace( 4, $text );
+            trace( 3, $text );
             return ( not $rc );
         }
         die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
@@ -225,7 +225,7 @@ sub cleanup_conf_line {
         local $/ = undef; $text = <$fh>;
         close $fh; warn "pclose failed: $!" if $!;
         $rc = ( $? >> 8 );
-        trace( 4, $text );
+        trace( 3, $text );
         return $text;
     }
 }
diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index a93aa10..6a02b0d 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -23,7 +23,6 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub compile {
-    trace(3);
     _die "'gitolite compile' does not take any arguments" if @_;
 
     _chdir( $rc{GL_ADMIN_BASE} );
@@ -39,7 +38,7 @@ sub compile {
 
 sub parse {
     my $lines = shift;
-    trace( 4, scalar(@$lines) . " lines incoming" );
+    trace( 2, scalar(@$lines) . " lines incoming" );
 
     for my $line (@$lines) {
         # user or repo groups
@@ -68,6 +67,7 @@ sub parse {
             # XXX both $key and $value must satisfy a liberal but secure pattern
             add_config( 1, $key, $value );
         } elsif ( $line =~ /^subconf (\S+)$/ ) {
+            trace( 2, $line );
             set_subconf($1);
         } else {
             _warn "?? $line";
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index 07140bd..3ae7f34 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -22,7 +22,7 @@ my %included = ();
 my %prefixed_groupname = ();
 
 sub explode {
-    trace( 4, @_ );
+    trace( 3, @_ );
     my ( $file, $subconf, $out ) = @_;
 
     # seed the 'seen' list if it's empty
@@ -61,7 +61,7 @@ sub incsub {
     # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly*
     # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) {
 
-    trace( 3, $is_subconf, $include_glob );
+    trace( 2, $is_subconf, $include_glob );
 
     for my $file ( glob($include_glob) ) {
         _warn("included file not found: '$file'"), next unless -f $file;
@@ -93,7 +93,7 @@ sub prefix_groupnames {
     if ($lhs) {
         $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
         $prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs";
-        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
+        trace( 2, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
     }
 
     return $line;
@@ -106,13 +106,13 @@ sub already_included {
     return 0 unless $included{$file_id}++;
 
     _warn("$file already included");
-    trace( 3, "$file already included" );
+    trace( 2, "$file already included" );
     return 1;
 }
 
 sub device_inode {
     my $file = shift;
-    trace( 3, $file, ( stat $file )[ 0, 1 ] );
+    trace( 2, $file, ( stat $file )[ 0, 1 ] );
     return join( "/", ( stat $file )[ 0, 1 ] );
 }
 
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index f2ed36b..7ff0c6f 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -49,9 +49,8 @@ my $last_repo = '';
 
     sub load {
         my $repo = shift or _die "load() needs a reponame";
-        trace( 4, "$repo" );
+        trace( 3, "$repo" );
         if ( $repo ne $loaded_repo ) {
-            trace( 3, "loading $repo..." );
             load_common();
             load_1($repo);
             $loaded_repo = $repo;
@@ -61,15 +60,14 @@ my $last_repo = '';
 
 sub access {
     my ( $repo, $user, $aa, $ref ) = @_;
-    trace( 3, "repo=$repo, user=$user, aa=$aa, ref=$ref" );
     load($repo);
 
     my @rules = rules( $repo, $user );
-    trace( 3, scalar(@rules) . " rules found" );
+    trace( 2, scalar(@rules) . " rules found" );
     for my $r (@rules) {
         my $perm = $r->[1];
         my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
-        trace( 4, "perm=$perm, refex=$refex" );
+        trace( 3, "perm=$perm, refex=$refex" );
 
         # skip 'deny' rules if the ref is not (yet) known
         next if $perm eq '-' and $ref eq 'any';
@@ -77,7 +75,7 @@ sub access {
         # rule matches if ref matches or ref is any (see gitolite-shell)
         next unless $ref =~ /^$refex/ or $ref eq 'any';
 
-        trace( 3, "DENIED by $refex" ) if $perm eq '-';
+        trace( 2, "DENIED by $refex" ) if $perm eq '-';
         return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-';
 
         # $perm can be RW\+?(C|D|CD|DC)?M?.  $aa can be W, +, C or D, or
@@ -87,7 +85,7 @@ sub access {
         # as far as *this* ref is concerned we're ok
         return $refex if ( $perm =~ /$aaq/ );
     }
-    trace( 3, "DENIED by fallthru" );
+    trace( 2, "DENIED by fallthru" );
     return "$aa $ref $repo $user DENIED by fallthru";
 }
 
@@ -105,7 +103,6 @@ sub load_common {
         return;
     }
 
-    trace(4);
     my $cc = "conf/gitolite.conf-compiled.pm";
 
     _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
@@ -120,7 +117,7 @@ sub load_common {
 sub load_1 {
     my $repo = shift;
     return if $repo =~ /^\@/;
-    trace( 4, $repo );
+    trace( 3, $repo );
 
     _chdir("$rc{GL_REPO_BASE}/$repo.git");
 
@@ -151,7 +148,7 @@ sub load_1 {
 
     sub rules {
         my ( $repo, $user ) = @_;
-        trace( 4, "repo=$repo, user=$user" );
+        trace( 3, "repo=$repo, user=$user" );
 
         return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
 
@@ -159,7 +156,7 @@ sub load_1 {
 
         my @repos = memberships($repo);
         my @users = memberships($user);
-        trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
+        trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
         for my $r (@repos) {
             for my $u (@users) {
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 33dc003..e43c45b 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -144,7 +144,6 @@ sub add_rule {
 sub set_subconf {
     $subconf = shift;
     _die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
-    trace( 1, $subconf );
 }
 
 sub new_repos {
@@ -167,7 +166,7 @@ sub new_repos {
 
 sub new_repo {
     my $repo = shift;
-    trace( 4, $repo );
+    trace( 3, $repo );
 
     # XXX ignoring UMASK for now
 
@@ -221,6 +220,7 @@ sub parse_done {
 sub check_subconf_repo_disallowed {
     # trying to set access for $repo (='foo')...
     my ( $subconf, $repo ) = @_;
+    trace( 2, $subconf, $repo );
 
     # processing the master config, not a subconf
     return 0 if $subconf eq 'master';
@@ -234,14 +234,14 @@ sub check_subconf_repo_disallowed {
       sort keys %{ $groups{"\@$subconf"} };
     return 0 if @matched > 0;
 
-    trace( 3, "disallowed: $subconf for $repo" );
+    trace( 2, "-> disallowed" );
     return 1;
 }
 
 sub store_1 {
     # warning: writes and *deletes* it from %repos and %configs
     my ($repo) = shift;
-    trace( 4, $repo );
+    trace( 3, $repo );
     return unless $repos{$repo} and -d "$repo.git";
 
     my ( %one_repo, %one_config );
@@ -267,7 +267,7 @@ sub store_1 {
 }
 
 sub store_common {
-    trace(4);
+    trace(3);
     my $cc = "conf/gitolite.conf-compiled.pm";
     my $compiled_fh = _open( ">", "$cc.new" );
 
@@ -301,7 +301,7 @@ sub store_common {
 
     sub hook_1 {
         my $repo = shift;
-        trace( 4, $repo );
+        trace( 3, $repo );
 
         # reset the gitolite supplied hooks, in case someone fiddled with
         # them, but only once per run
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index efd4838..49abdfb 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -19,7 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub post_update {
-    trace( 3, @ARGV );
+    trace( 2, @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
@@ -44,6 +44,7 @@ sub post_update {
                 my $sfp = "$ENV{GL_BINDIR}/post-compile/$s";
 
                 _warn("skipped post-compile script '$s'"), next if not -x $sfp;
+                trace( 2, "post-compile $s" );
                 _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
             }
         }
@@ -56,7 +57,6 @@ sub post_update {
     my $text = '';
 
     sub post_update_hook {
-        trace(1);
         if ( not $text ) {
             local $/ = undef;
             $text = <DATA>;
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index da089b5..1bba84a 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -19,13 +19,13 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub update {
-    trace( 3, @ARGV );
+    trace( 2, @ARGV );
     # this is the *real* update hook for gitolite
 
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
-    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" );
+    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     _die $ret if $ret =~ /DENIED/;
 
     check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
@@ -37,7 +37,6 @@ sub check_vrefs {
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
     my $name_seen = 0;
     for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
-        trace( 1, "vref=$vref" );
         if ( $vref =~ m(^VREF/NAME/) ) {
             # this one is special; we process it right here, and only once
             next if $name_seen++;
@@ -76,7 +75,6 @@ sub check_vref {
     my $text = '';
 
     sub update_hook {
-        trace(1);
         if ( not $text ) {
             local $/ = undef;
             $text = <DATA>;
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 2da9700..321db5e 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -76,19 +76,15 @@ my $glrc_default_text = '';
 sub glrc {
     my $cmd = shift;
     if ( $cmd eq 'default-filename' ) {
-        trace( 1, "..should happen only on first run" );
         return "$ENV{HOME}/.gitolite.rc";
     } elsif ( $cmd eq 'default-text' ) {
-        trace( 1, "..should happen only on first run" );
         return $glrc_default_text if $glrc_default_text;
         _die "rc file default text not set; this should not happen!";
     } elsif ( $cmd eq 'filename' ) {
         # where is the rc file?
-        trace(4);
 
         # search $HOME first
         return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
-        trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
 
         # XXX for fedora, we can add the following line, but I would really prefer
         # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
@@ -112,7 +108,6 @@ my $all  = 0;
 my $nonl = 0;
 
 sub query_rc {
-    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
 
     my @vars = args();
 
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index c0cab3e..0d5f664 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -73,13 +73,11 @@ sub args {
 }
 
 sub setup_glrc {
-    trace(1);
     _print( glrc('default-filename'), glrc('default-text') ) if not glrc('filename');
 }
 
 sub setup_gladmin {
     my ( $admin, $pubkey, $argv ) = @_;
-    trace( 1, $admin || '<no admin name given>' );
     _die "no existing conf file found, '-a' required"
       if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
 
diff --git a/src/gitolite b/src/gitolite
index dd41495..e62543d 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -69,6 +69,7 @@ if ( $command eq 'setup' ) {
     post_compile(@args);
 
 } elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
+    trace( 2, "attempting gitolite command $command" );
     run_command( $command, @args );
 
 } elsif ( $command eq 'list-phy-repos' ) {
@@ -76,6 +77,7 @@ if ( $command eq 'setup' ) {
     print "$_\n" for ( @{ list_phy_repos(@args) } );
 
 } elsif ( $command =~ /^list-/ ) {
+    trace( 2, "attempting lister command $command" );
     require Gitolite::Conf::Load;
     Gitolite::Conf::Load->import;
     my $fn = lister_dispatch($command);
diff --git a/src/gitolite-shell b/src/gitolite-shell
index 6668f6a..b596ba4 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -64,7 +64,7 @@ sub main {
     # apply if it's a read operation).  See the matching code in access() for
     # more information.
     my $ret = access( $repo, $user, $aa, 'any' );
-    trace( 1, "access($repo, $user, $aa, 'any') -> $ret" );
+    trace( 1, "access($repo, $user, $aa, 'any')", "-> $ret" );
     _die $ret if $ret =~ /DENIED/;
 
     $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
@@ -81,6 +81,7 @@ sub parse_soc {
         # TODO git archive
         my ( $verb, $repo ) = ( $1, $2 );
         _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
+        trace( 2, "git command", $soc );
         return ( $verb, $repo );
     }
 
@@ -91,6 +92,7 @@ sub parse_soc {
 
     my @words = split ' ', $soc;
     if ( $rc{COMMANDS}{ $words[0] } ) {
+        trace( 2, "gitolite command", $soc );
         _system( "gitolite", @words );
         exit 0;
     }
diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
index 83a9b77..75c015d 100755
--- a/src/syntactic-sugar/keysubdirs-as-groups
+++ b/src/syntactic-sugar/keysubdirs-as-groups
@@ -7,6 +7,7 @@
 # taken as the group name.
 
 sub sugar_script {
+    trace( 2, "running 'keysubdirs-as-groups' sugar script..." );
     my $lines = shift;
 
     my @out = @{$lines};

commit 8714b77eae6b80287b36d532e1b16bb7045a80e6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 20:04:30 2012 +0530

    (perltidy)

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 6fcfcc6..3c29ccf 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -77,8 +77,8 @@ sub _die {
 sub usage {
     _warn(shift) if @_;
     my ( $script, $function ) = ( caller(1) )[ 1, 3 ];
-    if (not $script) {
-        $script = ( caller ) [1];
+    if ( not $script ) {
+        $script   = (caller)[1];
         $function = 'usage';
     }
     dbg( "u s a g e", $script, $function );
@@ -115,7 +115,7 @@ sub _system {
             die "child died with signal " . ( $? & 127 ) . "\n" if $ENV{D};
         } else {
             die "child exited with value " . ( $? >> 8 ) . "\n" if $ENV{D};
-            exit ( $? >> 8 );
+            exit( $? >> 8 );
         }
         exit 1;
     }
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index f77e89d..07140bd 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -51,8 +51,8 @@ sub incsub {
     _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master';
 
     _die "invalid include/subconf file/glob '$include_glob'"
-        unless $include_glob =~ /^"(.+)"$/
-            or $include_glob =~ /^'(.+)'$/;
+      unless $include_glob =~ /^"(.+)"$/
+          or $include_glob =~ /^'(.+)'$/;
     $include_glob = $1;
 
     # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 3d73ca7..f2ed36b 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -67,8 +67,8 @@ sub access {
     my @rules = rules( $repo, $user );
     trace( 3, scalar(@rules) . " rules found" );
     for my $r (@rules) {
-        my $perm  = $r->[1];
-        my $refex = $r->[2];    $refex =~ s(/USER/)(/$user/);
+        my $perm = $r->[1];
+        my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
         trace( 4, "perm=$perm, refex=$refex" );
 
         # skip 'deny' rules if the ref is not (yet) known
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index fef1746..33dc003 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -116,7 +116,7 @@ sub parse_users {
 
 sub add_rule {
     my ( $perm, $ref, $user ) = @_;
-    _die "bad ref '$ref'" unless $ref =~ $REPOPATT_PATT;
+    _die "bad ref '$ref'"   unless $ref  =~ $REPOPATT_PATT;
     _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
     $ruleseq++;
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index f276fc2..2da9700 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -53,9 +53,9 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
-    # testing sometimes requires all of it to be overridden silently; use an
-    # env var that is highly unlikely to appear in real life :)
-    do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
+# testing sometimes requires all of it to be overridden silently; use an
+# env var that is highly unlikely to appear in real life :)
+do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
 
 # fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 3b5f37a..c0cab3e 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -50,9 +50,9 @@ sub args {
     my $argv   = join( " ", @ARGV );
 
     GetOptions(
-        'admin|a=s'     => \$admin,
-        'pubkey|pk=s'   => \$pubkey,
-        'help|h'        => \$help,
+        'admin|a=s'   => \$admin,
+        'pubkey|pk=s' => \$pubkey,
+        'help|h'      => \$help,
     ) or usage();
 
     usage() if $help or ($pubkey and $admin);
@@ -79,9 +79,9 @@ sub setup_glrc {
 
 sub setup_gladmin {
     my ( $admin, $pubkey, $argv ) = @_;
-    trace( 1, $admin || '<no admin name given>');
+    trace( 1, $admin || '<no admin name given>' );
     _die "no existing conf file found, '-a' required"
-        if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
+      if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
 
     # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
     # $rc{GL_REPO_BASE}/gitolite-admin.git
@@ -96,7 +96,7 @@ sub setup_gladmin {
     _chdir( $rc{GL_ADMIN_BASE} );
 
     tsh_try("cd \$GL_BINDIR; git describe --tags --long --dirty=-dt 2>/dev/null")
-        and _print("VERSION", tsh_text());
+      and _print( "VERSION", tsh_text() );
 
     _mkdir("conf");
     my $conf;
diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 5654477..331c886 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -25,7 +25,7 @@ BEGIN {
     *{'try'}  = \&Tsh::try;
     *{'put'}  = \&Tsh::put;
     *{'text'} = \&Tsh::text;
-    *{'cmp'} = \&Tsh::cmp;
+    *{'cmp'}  = \&Tsh::cmp;
 }
 
 use strict;
@@ -72,12 +72,12 @@ sub dump {
 }
 
 sub _confargs {
-    return @_ if ($_[1]);
+    return @_ if ( $_[1] );
     return 'gitolite.conf', $_[0];
 }
 
 sub confreset {
-    system("rm", "-rf", "conf");
+    system( "rm", "-rf", "conf" );
     mkdir("conf");
     system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga");
     system("mv ~/repositories/testing.git        ~/repositories/.te");
@@ -93,7 +93,7 @@ sub confreset {
 }
 
 sub confadd {
-    my ($file, $string) = _confargs(@_);
+    my ( $file, $string ) = _confargs(@_);
     put "|cat >> conf/$file", $string;
 }
 
diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm
index 491ee59..1a1711f 100644
--- a/src/Gitolite/Test/Tsh.pm
+++ b/src/Gitolite/Test/Tsh.pm
@@ -473,13 +473,13 @@ sub fail {
 sub cmp {
     # compare input string with text()
     my $text = text();
-    my $in = shift;
+    my $in   = shift;
 
-    if ($text eq $in) {
+    if ( $text eq $in ) {
         ok();
     } else {
-        fail('cmp failed', '');
-        dbg(4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n");
+        fail( 'cmp failed', '' );
+        dbg( 4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n" );
     }
 }
 
diff --git a/src/commands/access b/src/commands/access
index f59ac6b..ec97046 100755
--- a/src/commands/access
+++ b/src/commands/access
@@ -44,7 +44,7 @@ my $ret = '';
 
 $ret = access( $repo, $user, $aa, $ref );
 
-if ($ret =~ /DENIED/) {
+if ( $ret =~ /DENIED/ ) {
     print "$ret\n" unless $quiet;
     exit 1;
 }
diff --git a/src/commands/info b/src/commands/info
index 726b0d8..65e7f30 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -19,7 +19,7 @@ usage() if @ARGV;
 my $user = $ENV{GL_USER} or _die "GL_USER not set";
 my $ref = 'any';
 
-print "hello $user, this is gitolite3 " . version() . " on git " . substr(`git --version`, 12) . "\n";
+print "hello $user, this is gitolite3 " . version() . " on git " . substr( `git --version`, 12 ) . "\n";
 
 my $lr = lister_dispatch('list-repos');
 my $lm = lister_dispatch('list-members');
@@ -27,7 +27,7 @@ my $lm = lister_dispatch('list-members');
 for ( @{ $lr->() } ) {
     my $perm = '';
     for my $aa (qw(R W ^C)) {
-        my $ret = access($_, $user, $aa, $ref);
+        my $ret = access( $_, $user, $aa, $ref );
         $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
     }
     next unless $perm =~ /\S/;
diff --git a/src/gitolite b/src/gitolite
index c265bd5..dd41495 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -103,11 +103,11 @@ the gitolite-admin repo).
 sub post_compile {
     usage() if ( not @_ or $_[0] eq '-h' );
 
-    run_subdir('post-compile', @_);
+    run_subdir( 'post-compile', @_ );
 }
 
 sub run_command {
-    run_subdir('commands', @_);
+    run_subdir( 'commands', @_ );
 }
 
 sub run_subdir {
diff --git a/src/gitolite-shell b/src/gitolite-shell
index c291bc9..6668f6a 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -54,7 +54,7 @@ sub main {
     my $user = $ENV{GL_USER} = shift @ARGV;
 
     # set up the repo and the attempted access
-    my ( $verb, $repo ) = parse_soc();  # returns only for git commands
+    my ( $verb, $repo ) = parse_soc();    # returns only for git commands
     sanity($repo);
     $ENV{GL_REPO} = $repo;
     my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
@@ -79,9 +79,9 @@ sub parse_soc {
 
     if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$) ) {
         # TODO git archive
-        my($verb, $repo) = ($1, $2);
+        my ( $verb, $repo ) = ( $1, $2 );
         _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
-        return ($verb, $repo);
+        return ( $verb, $repo );
     }
 
     # after this we should not return; caller expects us to handle it all here
@@ -90,8 +90,8 @@ sub parse_soc {
     _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
 
     my @words = split ' ', $soc;
-    if ($rc{COMMANDS}{$words[0]}) {
-        _system("gitolite", @words);
+    if ( $rc{COMMANDS}{ $words[0] } ) {
+        _system( "gitolite", @words );
         exit 0;
     }
 
diff --git a/src/post-compile/ssh-authkeys b/src/post-compile/ssh-authkeys
index 5e5ad4b..a286a74 100755
--- a/src/post-compile/ssh-authkeys
+++ b/src/post-compile/ssh-authkeys
@@ -13,7 +13,7 @@ $|++;
 # can be called directly, or as a post-update hook.  Since it ignores
 # arguments anyway, it hardly matters.
 
-my $ab      = `gitolite query-rc -n GL_ADMIN_BASE`;
+my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
 _warn("'keydir' not found in '$ab'"), exit if not -d "$ab/keydir";
 my $akfile  = "$ENV{HOME}/.ssh/authorized_keys";
 my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
index d16dc78..83a9b77 100755
--- a/src/syntactic-sugar/keysubdirs-as-groups
+++ b/src/syntactic-sugar/keysubdirs-as-groups
@@ -9,21 +9,21 @@
 sub sugar_script {
     my $lines = shift;
 
-    my @out  = @{ $lines };
+    my @out = @{$lines};
     unshift @out, groupnames();
 
     return \@out;
 }
 
 sub groupnames {
-    my @out = ();
+    my @out     = ();
     my %members = ();
     for my $pk (`find ../keydir/ -name "*.pub"`) {
         next unless $pk =~ m(.*/([^/]+)/([^/]+)\.pub$);
         next if $1 eq 'keydir';
         $members{$1} .= " $2";
     }
-    for my $m (sort keys %members) {
+    for my $m ( sort keys %members ) {
         push @out, "\@$m =" . $members{$m};
     }
 

commit afcd974afab9cdd9da23107723ca62d7f52293d0
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 19:14:40 2012 +0530

    record and maintain a 'version' (for info and elsewhere)

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 139d6e3..f276fc2 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -7,6 +7,7 @@ package Gitolite::Rc;
   %rc
   glrc
   query_rc
+  version
 
   $REMOTE_COMMAND_PATT
   $REF_OR_FILENAME_PATT
@@ -102,7 +103,7 @@ sub glrc {
 }
 
 # ----------------------------------------------------------------------
-# implements 'gitolite query-rc'
+# implements 'gitolite query-rc' and 'version'
 # ----------------------------------------------------------------------
 
 # ----------------------------------------------------------------------
@@ -131,6 +132,16 @@ sub query_rc {
     exit 1;
 }
 
+sub version {
+    my $version = '';
+    $version = '(unknown)';
+    for ("$rc{GL_ADMIN_BASE}/VERSION") {
+        $version = slurp($_) if -r $_;
+    }
+    chomp($version);
+    return $version;
+}
+
 # ----------------------------------------------------------------------
 
 =for args
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 20143f0..3b5f37a 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -95,6 +95,9 @@ sub setup_gladmin {
     _mkdir( $rc{GL_ADMIN_BASE} );
     _chdir( $rc{GL_ADMIN_BASE} );
 
+    tsh_try("cd \$GL_BINDIR; git describe --tags --long --dirty=-dt 2>/dev/null")
+        and _print("VERSION", tsh_text());
+
     _mkdir("conf");
     my $conf;
     {
diff --git a/src/commands/info b/src/commands/info
index 6672aeb..726b0d8 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -19,6 +19,8 @@ usage() if @ARGV;
 my $user = $ENV{GL_USER} or _die "GL_USER not set";
 my $ref = 'any';
 
+print "hello $user, this is gitolite3 " . version() . " on git " . substr(`git --version`, 12) . "\n";
+
 my $lr = lister_dispatch('list-repos');
 my $lm = lister_dispatch('list-members');
 

commit 5e2563bb8cdc534e520fa07c540a6e40019d163b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 18:16:16 2012 +0530

    setup was over-engineered...

diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index 09930bd..20143f0 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -4,20 +4,12 @@ package Gitolite::Setup;
 # ----------------------------------------------------------------------
 
 =for args
-Usage:  gitolite setup [<at least one option>]
+Usage:  gitolite setup [<option>]
 
-    -a, --admin <name>          admin user name
-    -pk --pubkey <file>         pubkey file name
-    -f, --fixup-hooks           fixup hooks
+    -pk, --pubkey <file>        pubkey file name
 
-Setup (first run only), then compile conf and fixup hooks.
-
-First run:
-    -a      required
-    -pk     required for ssh mode install
-
-Later runs:
-    no options required; but '-f' can be specified for clarity
+Setup gitolite, compile conf, and fixup hooks.  The pubkey is required on the
+first run.
 =cut
 
 # ----------------------------------------------------------------------
@@ -40,12 +32,8 @@ use warnings;
 
 sub setup {
     my ( $admin, $pubkey, $argv ) = args();
-    # first time
-    if ( first_run() ) {
-        trace( 1, "..should happen only on first run" );
-        setup_glrc();
-        setup_gladmin( $admin, $pubkey, $argv );
-    }
+    setup_glrc();
+    setup_gladmin( $admin, $pubkey, $argv );
 
     _system("$ENV{GL_BINDIR}/gitolite compile");
     _system("$ENV{GL_BINDIR}/gitolite post-compile ssh-authkeys") if $pubkey;
@@ -55,36 +43,30 @@ sub setup {
 
 # ----------------------------------------------------------------------
 
-sub first_run {
-    # if the rc file could not be found, it's *definitely* a first run!
-    return not glrc('filename');
-}
-
 sub args {
     my $admin  = '';
     my $pubkey = '';
-    my $fixup  = 0;
     my $help   = 0;
     my $argv   = join( " ", @ARGV );
 
     GetOptions(
         'admin|a=s'     => \$admin,
         'pubkey|pk=s'   => \$pubkey,
-        'fixup-hooks|f' => \$fixup,
         'help|h'        => \$help,
     ) or usage();
 
-    usage() if $help;
-    usage("first run requires '-a'")     if first_run() and not($admin);
-    _warn("not setting up ssh...")       if first_run() and $admin and not $pubkey;
-    _warn("first run, ignoring '-f'...") if first_run() and $fixup;
-    _warn("not first run, ignoring '-a' / '-pk'...") if not first_run() and ( $admin or $pubkey );
+    usage() if $help or ($pubkey and $admin);
 
     if ($pubkey) {
         $pubkey =~ /\.pub$/ or _die "$pubkey name does not end in .pub";
+        $pubkey =~ /\@/    and _die "$pubkey name contains '\@'";
         tsh_try("cat $pubkey")              or _die "$pubkey not a readable file";
         tsh_lines() == 1                    or _die "$pubkey must have exactly one line";
         tsh_try("ssh-keygen -l -f $pubkey") or _die "$pubkey does not seem to be a valid ssh pubkey file";
+
+        $admin = $pubkey;
+        $admin =~ s(.*/)();
+        $admin =~ s/\.pub$//;
     }
 
     return ( $admin || '', $pubkey || '', $argv );
@@ -92,23 +74,21 @@ sub args {
 
 sub setup_glrc {
     trace(1);
-    _print( glrc('default-filename'), glrc('default-text') );
+    _print( glrc('default-filename'), glrc('default-text') ) if not glrc('filename');
 }
 
 sub setup_gladmin {
     my ( $admin, $pubkey, $argv ) = @_;
-    trace( 1, $admin );
+    trace( 1, $admin || '<no admin name given>');
+    _die "no existing conf file found, '-a' required"
+        if not $admin and not -f "$rc{GL_ADMIN_BASE}/conf/gitolite.conf";
 
     # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
     # $rc{GL_REPO_BASE}/gitolite-admin.git
 
     # grab the pubkey content before we chdir() away
-
     my $pubkey_content = '';
-    if ($pubkey) {
-        $pubkey_content = slurp($pubkey);
-        $pubkey =~ s(.*/)();    # basename
-    }
+    $pubkey_content = slurp($pubkey) if $pubkey;
 
     # set up the admin files in admin-base
 
@@ -123,11 +103,11 @@ sub setup_gladmin {
     }
     $conf =~ s/%ADMIN/$admin/g;
 
-    _print( "conf/gitolite.conf", $conf );
+    _print( "conf/gitolite.conf", $conf ) if not -f "conf/gitolite.conf";
 
     if ($pubkey) {
         _mkdir("keydir");
-        _print( "keydir/$pubkey", $pubkey_content );
+        _print( "keydir/$admin.pub", $pubkey_content );
     }
 
     # set up the admin repo in repo-base
@@ -136,7 +116,7 @@ sub setup_gladmin {
     _mkdir( $rc{GL_REPO_BASE} );
     _chdir( $rc{GL_REPO_BASE} );
 
-    new_repo("gitolite-admin");
+    new_repo("gitolite-admin") if not -d "gitolite-admin.git";
 
     # commit the admin files to the admin repo
 

commit 9780ddab9de2856e005e699671606a78bc45ba8b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 16:00:15 2012 +0530

    (!!) personal branches -- 1 line of code, 50 lines of test!
    
    (and by the way even in g2 this was not so easy as just ONE line of
    code!)

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 9b38880..3d73ca7 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -68,7 +68,7 @@ sub access {
     trace( 3, scalar(@rules) . " rules found" );
     for my $r (@rules) {
         my $perm  = $r->[1];
-        my $refex = $r->[2];
+        my $refex = $r->[2];    $refex =~ s(/USER/)(/$user/);
         trace( 4, "perm=$perm, refex=$refex" );
 
         # skip 'deny' rules if the ref is not (yet) known
diff --git a/t/personal-branches.t b/t/personal-branches.t
new file mode 100755
index 0000000..04e911f
--- /dev/null
+++ b/t/personal-branches.t
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 39";
+
+confreset;confadd '
+    @admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    repo testing
+        RW+     =   @all
+
+    @g1 = t1
+    repo @g1
+        R               =   u2
+        RW              =   u3
+        RW+             =   u4
+        RW  a/USER/     =   @all
+        RW+ p/USER/     =   u1 u6
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+    gitolite access t1 u1;                              ok;     /refs/heads/p/u1//; !/DENIED/
+    gitolite access t1 u5;                              !ok;    /\\+ any t1 u5 DENIED by fallthru/
+    gitolite access \@g1 u5 W;                          ok;     /refs/heads/a/u5//; !/DENIED/
+
+    gitolite access t1 u1 W refs/heads/a/user1/foo;     !ok;    /W refs/heads/a/user1/foo t1 u1 DENIED by fallthru/
+    gitolite access \@g1 u1 + refs/heads/a/user1/foo;   !ok;    /\\+ refs/heads/a/user1/foo \@g1 u1 DENIED by fallthru/
+    gitolite access t1 u1 W refs/heads/p/user1/foo;     !ok;    /W refs/heads/p/user1/foo t1 u1 DENIED by fallthru/
+    gitolite access \@g1 u1 + refs/heads/p/user1/foo;   !ok;    /\\+ refs/heads/p/user1/foo \@g1 u1 DENIED by fallthru/
+
+    gitolite access \@g1 u1 W refs/heads/a/u1/foo;      ok;     /refs/heads/a/u1//; !/DENIED/
+    gitolite access t1 u1 + refs/heads/a/u1/foo;        !ok;    /\\+ refs/heads/a/u1/foo t1 u1 DENIED by fallthru/
+    gitolite access \@g1 u1 W refs/heads/p/u1/foo;      ok;     /refs/heads/p/u1//; !/DENIED/
+    gitolite access t1 u1 + refs/heads/p/u1/foo;        ok;     /refs/heads/p/u1//; !/DENIED/
+
+    gitolite access \@g1 u1 W refs/heads/p/u2/foo;      !ok;    /W refs/heads/p/u2/foo \@g1 u1 DENIED by fallthru/
+    gitolite access t1 u1 + refs/heads/p/u2/foo;        !ok;    /\\+ refs/heads/p/u2/foo t1 u1 DENIED by fallthru/
+";

commit 876f6517f5a9605ab3ebdfedbe2e2ed93a242acf
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 15:05:51 2012 +0530

    (testing help) allow a *testing* rc to override the normal one

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 2a51a55..139d6e3 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -52,6 +52,10 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
+    # testing sometimes requires all of it to be overridden silently; use an
+    # env var that is highly unlikely to appear in real life :)
+    do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
+
 # fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
 $ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
 

commit 8b8d3ef484aee01e73134ae3a6ce7ac4865bd27c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 10:40:50 2012 +0530

    new test 'listers'

diff --git a/t/listers.t b/t/listers.t
new file mode 100755
index 0000000..84add2f
--- /dev/null
+++ b/t/listers.t
@@ -0,0 +1,147 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try 'plan 30';
+
+try "## info";
+
+confreset;confadd '
+    @oss = git gitolite gitolite3
+    @prop = cc p4
+    @crypto = alice bob carol
+    @dilbert = alice wally ashok
+
+    repo    @oss
+        RW              =   u1 @crypto
+        R               =   u2 @dilbert
+    repo    @prop
+        RW  =               u2 @dilbert
+        R   =               u1
+    repo    t3
+                    RW  =   u3
+                    R   =   u4
+';
+
+try "ADMIN_PUSH info; !/FATAL/" or die text();
+try "
+                                        /Initialized.*empty.*cc.git/
+                                        /Initialized.*empty.*p4.git/
+                                        /Initialized.*empty.*git.git/
+                                        /Initialized.*empty.*gitolite.git/
+                                        /Initialized.*empty.*gitolite3.git/
+                                        /Initialized.*empty.*t3.git/
+";
+
+try "gitolite list-groups"; cmp
+'@crypto
+ at dilbert
+ at oss
+ at prop
+';
+
+try "gitolite list-users"; cmp
+'@all
+ at crypto
+ at dilbert
+admin
+u1
+u2
+u3
+u4
+';
+try "gitolite list-repos"; cmp
+'@oss
+ at prop
+gitolite-admin
+t3
+testing
+';
+
+try "gitolite list-phy-repos"; cmp
+'cc
+git
+gitolite
+gitolite-admin
+gitolite3
+p4
+t3
+testing
+';
+
+try "gitolite list-memberships alice"; cmp
+'@all
+ at crypto
+ at dilbert
+alice
+';
+
+try "gitolite list-memberships ashok"; cmp
+'@all
+ at dilbert
+ashok
+';
+
+try "gitolite list-memberships carol"; cmp
+'@all
+ at crypto
+carol
+';
+
+try "gitolite list-memberships git"; cmp
+'@all
+ at oss
+git
+';
+
+try "gitolite list-memberships gitolite"; cmp
+'@all
+ at oss
+gitolite
+';
+
+try "gitolite list-memberships gitolite3"; cmp
+'@all
+ at oss
+gitolite3
+';
+
+try "gitolite list-memberships cc"; cmp
+'@all
+ at prop
+cc
+';
+
+try "gitolite list-memberships p4"; cmp
+'@all
+ at prop
+p4
+';
+
+try "gitolite list-members \@crypto"; cmp
+'alice
+bob
+carol
+';
+
+try "gitolite list-members \@dilbert"; cmp
+'alice
+ashok
+wally
+';
+
+try "gitolite list-members \@oss"; cmp
+'git
+gitolite
+gitolite3
+';
+
+try "gitolite list-members \@prop"; cmp
+'cc
+p4
+';
+

commit 446bd19de733e2abeee91d6357f77b8a13a678c9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 10:40:27 2012 +0530

    tsh/test learn the cmp() function to make full output compares easier

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index df4cc89..5654477 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -11,6 +11,7 @@ package Gitolite::Test;
   dump
   confreset
   confadd
+  cmp
 );
 #>>>
 use Exporter 'import';
@@ -24,6 +25,7 @@ BEGIN {
     *{'try'}  = \&Tsh::try;
     *{'put'}  = \&Tsh::put;
     *{'text'} = \&Tsh::text;
+    *{'cmp'} = \&Tsh::cmp;
 }
 
 use strict;
diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm
index 41b4d12..491ee59 100644
--- a/src/Gitolite/Test/Tsh.pm
+++ b/src/Gitolite/Test/Tsh.pm
@@ -17,7 +17,7 @@ package Tsh;
 
 use Exporter 'import';
 @EXPORT = qw(
-  try run AUTOLOAD
+  try run cmp AUTOLOAD
   rc error_count text lines error_list put
   cd tsh_tempdir
 
@@ -470,6 +470,19 @@ sub fail {
     exit( $rc || 74 );
 }
 
+sub cmp {
+    # compare input string with text()
+    my $text = text();
+    my $in = shift;
+
+    if ($text eq $in) {
+        ok();
+    } else {
+        fail('cmp failed', '');
+        dbg(4, "\n\ntext = <<<$text>>>, in = <<<$in>>>\n\n");
+    }
+}
+
 sub expect {
     my ( $patt, $msg ) = @_;
     $msg =~ s/^\s+// if $msg;

commit fb332a6c761661373562682afc27882521608be5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 09:28:07 2012 +0530

    (!!) neat little 'access' command...
    
    ...makes it sooo much eaier to check access rights from external scripts

diff --git a/src/commands/access b/src/commands/access
new file mode 100755
index 0000000..f59ac6b
--- /dev/null
+++ b/src/commands/access
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage:  gitolite access [-q] <repo> <user> <perm> <ref>
+
+Check access rights for arguments given.  With '-q', returns only an exit code
+(shell truth, not perl truth -- 0 is success, any non-0 is failure).
+
+  - repo: mandatory
+  - user: mandatory
+  - perm: defauts to '+'.  Valid values: R, W, +, C, D, M
+  - ref:  defauts to 'any'.  See notes below
+
+Notes:
+
+  - ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine.
+    The 'any' ref is special -- it ignores deny rules (see docs for what this
+    means and exceptions).
+=cut
+
+# TODO: deal with "C", call it ^C
+
+usage() if not @ARGV or $ARGV[0] eq '-h';
+my $quiet = 0;
+if ( $ARGV[0] eq '-q' ) { $quiet = 1; shift @ARGV; }
+
+my ( $repo, $user, $aa, $ref ) = @ARGV;
+$aa  ||= '+';
+$ref ||= 'any';
+# XXX the 4th one below might need fine tuning
+_die "invalid repo name" if not( $repo and $repo =~ $REPONAME_PATT );
+_die "invalid user name" if not( $user and $user =~ $USERNAME_PATT );
+_die "invalid perm"      if not( $aa   and $aa   =~ /^(R|W|\+|C|D|M)$/ );
+_die "invalid ref name"  if not( $ref  and $ref  =~ $REPONAME_PATT );
+
+my $ret = '';
+
+$ret = access( $repo, $user, $aa, $ref );
+
+if ($ret =~ /DENIED/) {
+    print "$ret\n" unless $quiet;
+    exit 1;
+}
+
+print "$ret\n" unless $quiet;
+exit 0;
diff --git a/t/access.t b/t/access.t
new file mode 100755
index 0000000..d63eb19
--- /dev/null
+++ b/t/access.t
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "plan 185";
+
+confreset;confadd '
+    @admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    repo testing
+        RW+     =   @all
+
+    @g1 = t1
+    repo @g1
+        R       =   u2
+        RW      =   u3
+        RW+     =   u4
+';
+
+try "ADMIN_PUSH set1; !/FATAL/" or die text();
+
+try "
+
+    gitolite access -q t1 u1;   !ok;    !/./
+    gitolite access -q t1 u1 R; !ok;    !/./
+    gitolite access -q t1 u1 W; !ok;    !/./
+    gitolite access -q t1 u1 +; !ok;    !/./
+    gitolite access -q t1 u2;   !ok;    !/./
+    gitolite access -q t1 u2 R; ok;     !/./
+    gitolite access -q t1 u2 W; !ok;    !/./
+    gitolite access -q t1 u2 +; !ok;    !/./
+    gitolite access -q t1 u3;   !ok;    !/./
+    gitolite access -q t1 u3 R; ok;     !/./
+    gitolite access -q t1 u3 W; ok;     !/./
+    gitolite access -q t1 u3 +; !ok;    !/./
+    gitolite access -q t1 u4;   ok;     !/./
+    gitolite access -q t1 u4 R; ok;     !/./
+    gitolite access -q t1 u4 W; ok;     !/./
+    gitolite access -q t1 u4 +; ok;     !/./
+
+    gitolite access t1 u1;      !ok;    /\\+ any t1 u1 DENIED by fallthru/
+    gitolite access t1 u1 R;    !ok;    /R any t1 u1 DENIED by fallthru/
+    gitolite access t1 u1 W;    !ok;    /W any t1 u1 DENIED by fallthru/
+    gitolite access t1 u1 +;    !ok;    /\\+ any t1 u1 DENIED by fallthru/
+    gitolite access t1 u2;      !ok;    /\\+ any t1 u2 DENIED by fallthru/
+    gitolite access t1 u2 R;    ok;     /refs/\.\*/
+    gitolite access t1 u2 W;    !ok;    /W any t1 u2 DENIED by fallthru/
+    gitolite access t1 u2 +;    !ok;    /\\+ any t1 u2 DENIED by fallthru/
+    gitolite access t1 u3;      !ok;    /\\+ any t1 u3 DENIED by fallthru/
+    gitolite access t1 u3 R;    ok;     /refs/\.\*/
+    gitolite access t1 u3 W;    ok;     /refs/\.\*/
+    gitolite access t1 u3 +;    !ok;    /\\+ any t1 u3 DENIED by fallthru/
+    gitolite access t1 u4;      ok;     /refs/\.\*/
+    gitolite access t1 u4 R;    ok;     /refs/\.\*/
+    gitolite access t1 u4 W;    ok;     /refs/\.\*/
+    gitolite access t1 u4 +;    ok;     /refs/\.\*/
+
+";
+
+confreset;confadd '
+    @admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    @g1 = u1
+    @g2 = u2
+    @g3 = u3
+    @gaa = aa
+    repo @gaa
+        RW+                 =   @g1
+        RW                  =   @g2
+        RW+     master      =   @g3
+        RW      master      =   u4
+        -       master      =   u5
+        RW+     dev         =   u5
+        RW                  =   u5
+';
+
+try "ADMIN_PUSH set2; !/FATAL/" or die text();
+
+try "
+    gitolite access \@gaa \@g1 + any ;                  ok;     /refs/.*/; !/DENIED/
+    gitolite access aa \@g1 + refs/heads/master ;       ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa \@g1 + refs/heads/next ;      ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa \@g1 W refs/heads/next ;      ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa u1 + refs/heads/dev ;         ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa u1 + refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
+    gitolite access aa u1 W refs/heads/next ;           ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa \@g2 + refs/heads/master ;    !ok;    /\\+ refs/heads/master \@gaa \@g2 DENIED by fallthru/
+    gitolite access \@gaa \@g2 + refs/heads/next ;      !ok;    /\\+ refs/heads/next \@gaa \@g2 DENIED by fallthru/
+    gitolite access aa \@g2 W refs/heads/master ;       ok;     /refs/.*/; !/DENIED/
+    gitolite access aa u2 + any ;                       !ok;    /\\+ any aa u2 DENIED by fallthru/
+    gitolite access \@gaa u2 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u2 DENIED by fallthru/
+    gitolite access \@gaa u2 W refs/heads/master ;      ok;     /refs/.*/; !/DENIED/
+    gitolite access \@gaa \@g3 + refs/heads/master ;    ok;     /refs/heads/master/; !/DENIED/
+    gitolite access \@gaa \@g3 W refs/heads/next ;      !ok;    /W refs/heads/next \@gaa \@g3 DENIED by fallthru/
+    gitolite access \@gaa \@g3 W refs/heads/dev ;       !ok;    /W refs/heads/dev \@gaa \@g3 DENIED by fallthru/
+    gitolite access aa u3 + refs/heads/dev ;            !ok;    /\\+ refs/heads/dev aa u3 DENIED by fallthru/
+    gitolite access aa u3 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u3 DENIED by fallthru/
+    gitolite access \@gaa u4 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u4 DENIED by fallthru/
+    gitolite access \@gaa u4 W refs/heads/master ;      ok;     /refs/heads/master/; !/DENIED/
+    gitolite access aa u4 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u4 DENIED by fallthru/
+    gitolite access \@gaa u4 W refs/heads/next ;        !ok;    /W refs/heads/next \@gaa u4 DENIED by fallthru/
+    gitolite access \@gaa u5 R any ;                    ok;     /refs/heads/dev/; !/DENIED/
+    gitolite access aa u5 R any ;                       ok;     /refs/heads/dev/; !/DENIED/
+    gitolite access \@gaa u5 + refs/heads/dev ;         ok;     /refs/heads/dev/; !/DENIED/
+    gitolite access \@gaa u5 + refs/heads/master ;      !ok;    /\\+ refs/heads/master \@gaa u5 DENIED by refs/heads/master/
+    gitolite access aa u5 + refs/heads/next ;           !ok;    /\\+ refs/heads/next aa u5 DENIED by fallthru/
+    gitolite access \@gaa u5 R refs/heads/dev ;         ok;     /refs/heads/dev/; !/DENIED/
+    gitolite access \@gaa u5 R refs/heads/master ;      !ok;    /R refs/heads/master \@gaa u5 DENIED by refs/heads/master/
+    gitolite access \@gaa u5 R refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
+    gitolite access aa u5 W refs/heads/dev ;            ok;     /refs/heads/dev/; !/DENIED/
+    gitolite access aa u5 W refs/heads/master ;         !ok;    /W refs/heads/master aa u5 DENIED by refs/heads/master/
+    gitolite access \@gaa u5 W refs/heads/next ;        ok;     /refs/.*/; !/DENIED/
+";
+
+confreset;confadd '
+    @admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    @gr1 = r1
+    repo @gr1
+        RW  refs/heads/v[0-9]   = u1
+        RW  refs/heads          = tester
+
+    @gr2 = r2
+    repo @gr2
+        RW  refs/heads/v[0-9]   = u1
+        -   refs/heads/v[0-9]   = tester
+        RW  refs/heads          = tester
+';
+
+try "ADMIN_PUSH set3; !/FATAL/" or die text();
+
+try "
+    gitolite access \@gr2 tester W refs/heads/v1;       !ok;    /W refs/heads/v1 \@gr2 tester DENIED by refs/heads/v\\[0-9\\]/
+    gitolite access \@gr1 tester W refs/heads/v1;       ok;     /refs/heads/; !/DENIED/
+    gitolite access r1 tester W refs/heads/v1;          ok;     /refs/heads/; !/DENIED/
+    gitolite access r2 tester W refs/heads/v1;          !ok;    /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
+    gitolite access r2 tester W refs/heads/va;          ok;     /refs/heads/; !/DENIED/
+";
+

commit 9a8a86306b9976476ef59564e3db4062fdbbab15
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 09:15:02 2012 +0530

    _system() is less verbose
    
    otherwise things like 'gitolite access' print extra junk that is
    confusing.

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 2260e41..6fcfcc6 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -105,15 +105,19 @@ sub _chdir {
 }
 
 sub _system {
+    # run system(), catch errors.  Be verbose only if $ENV{D} exists.  If not,
+    # exit with <rc of system()> if it applies, else just "exit 1".
     if ( system(@_) != 0 ) {
-        say2 "system @_ failed";
+        say2 "system @_ failed" if $ENV{D};
         if ( $? == -1 ) {
-            die "failed to execute: $!\n";
+            die "failed to execute: $!\n" if $ENV{D};
         } elsif ( $? & 127 ) {
-            die "child died with signal " . ( $? & 127 ) . "\n";
+            die "child died with signal " . ( $? & 127 ) . "\n" if $ENV{D};
         } else {
-            die "child exited with value " . ( $? >> 8 ) . "\n";
+            die "child exited with value " . ( $? >> 8 ) . "\n" if $ENV{D};
+            exit ( $? >> 8 );
         }
+        exit 1;
     }
 }
 

commit 6624d35cf99f9471e7d5061ff1bb42077ae8ea0b
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 06:07:41 2012 +0530

    info command deals with groups

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 1759214..9b38880 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -119,6 +119,7 @@ sub load_common {
 
 sub load_1 {
     my $repo = shift;
+    return if $repo =~ /^\@/;
     trace( 4, $repo );
 
     _chdir("$rc{GL_REPO_BASE}/$repo.git");
diff --git a/src/commands/info b/src/commands/info
index fe52837..6672aeb 100755
--- a/src/commands/info
+++ b/src/commands/info
@@ -19,13 +19,23 @@ usage() if @ARGV;
 my $user = $ENV{GL_USER} or _die "GL_USER not set";
 my $ref = 'any';
 
-my $fn = lister_dispatch('list-repos');
+my $lr = lister_dispatch('list-repos');
+my $lm = lister_dispatch('list-members');
 
-for ( @{ $fn->() } ) {
+for ( @{ $lr->() } ) {
     my $perm = '';
     for my $aa (qw(R W ^C)) {
         my $ret = access($_, $user, $aa, $ref);
         $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
     }
-    print "$perm\t$_\n" if $perm =~ /\S/;
+    next unless $perm =~ /\S/;
+    if (/^\@/) {
+        print "\n$perm\t$_\n";
+        for ( @{ $lm->($_) } ) {
+            print "$perm\t$_\n";
+        }
+        print "\n";
+    } else {
+        print "$perm\t$_\n";
+    }
 }
diff --git a/t/info.t b/t/info.t
index 967f4af..7171fbb 100755
--- a/t/info.t
+++ b/t/info.t
@@ -6,12 +6,13 @@ use warnings;
 use lib "src";
 use Gitolite::Test;
 
-try 'plan 35';
+try 'plan 45';
 
 try "## info";
 
 confreset;confadd '
-    repo    t1
+    @t1 = t1
+    repo    @t1
         RW              =   u1
         R               =   u2
     repo    t2
@@ -30,23 +31,31 @@ try "
 ";
 try "
     glt info u1;                ok;     gsh
-                                        /R W  *\tt1/
-                                        /R  *\tt2/
+                                        /R W  \t\@t1/
+                                        /R W  \tt1/
+                                        /R    \tt2/
                                         !/t3/
-                                        / R W  *\ttesting/
+                                        /R W  \ttesting/
     glt info u2;                ok;     gsh
-                                        /R  *\tt1/
-                                        /R W  *\tt2/
+                                        /R    \t\@t1/
+                                        /R    \tt1/
+                                        /R W  \tt2/
                                         !/t3/
-                                        / R W  *\ttesting/
+                                        /R W  \ttesting/
     glt info u3;                ok;     gsh
-                                        /R W  *\tt3/
-                                        !/t1/
-                                        !/t2/
-                                        / R W  *\ttesting/
+                                        /R W  \tt3/
+                                        !/\@t1/
+                                        !/t[12]/
+                                        /R W  \ttesting/
     glt info u4;                ok;     gsh
-                                        /R  *\tt3/
-                                        !/t1/
-                                        !/t2/
-                                        / R W  *\ttesting/
+                                        /R    \tt3/
+                                        !/\@t1/
+                                        !/t[12]/
+                                        /R W  \ttesting/
+    glt info u5;                ok;     gsh
+                                        !/t[123]/
+                                        /R W  \ttesting/
+    glt info u6;                ok;     gsh
+                                        !/t[123]/
+                                        /R W  \ttesting/
     " or die;

commit 5ebb981efabd201b2bffde1edf88d6f19ff806e7
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 05:28:56 2012 +0530

    new sugar -- keysubdirs as groups.  TODO: add appropriate commented entry to Gitolite::Rc.pm also

diff --git a/src/syntactic-sugar/keysubdirs-as-groups b/src/syntactic-sugar/keysubdirs-as-groups
new file mode 100755
index 0000000..d16dc78
--- /dev/null
+++ b/src/syntactic-sugar/keysubdirs-as-groups
@@ -0,0 +1,31 @@
+# vim: syn=perl:
+
+# "sugar script" (syntactic sugar helper) for gitolite3
+
+# Enabling this script in the rc file allows you to use subdirectories in
+# keydir as group names.  The last component other than keydir itself will be
+# taken as the group name.
+
+sub sugar_script {
+    my $lines = shift;
+
+    my @out  = @{ $lines };
+    unshift @out, groupnames();
+
+    return \@out;
+}
+
+sub groupnames {
+    my @out = ();
+    my %members = ();
+    for my $pk (`find ../keydir/ -name "*.pub"`) {
+        next unless $pk =~ m(.*/([^/]+)/([^/]+)\.pub$);
+        next if $1 eq 'keydir';
+        $members{$1} .= " $2";
+    }
+    for my $m (sort keys %members) {
+        push @out, "\@$m =" . $members{$m};
+    }
+
+    return @out;
+}

commit 4e25a8acd130cc39c2d5eed777feb66b8bb1235f
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 15 05:07:37 2012 +0530

    ssh-basic tests (and that's all we will ever do; see below)
    
    ssh tests are meant to ensure that basic authENTICATION is happening.
    AuthORISATION is checked all over the rest of the test suite and these
    two are quite orthogonal operations so there is no need to test all of
    authZ with ssh.

diff --git a/t/keys/admin b/t/keys/admin
new file mode 100644
index 0000000..676a711
--- /dev/null
+++ b/t/keys/admin
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA0/X7uwd7xOvC3UTZaAnFOR5xqhdgcyc8vk3d1bXXthiuUSmq
+5t4uhS9qj0ismcPX0YRNhSDElotG1KSWp8DceJpR2c7GYmELpqoE7ebVPnEBKY0c
+PX+G9KZNgbJyyx35QlpJmlO+LimM0oO8XarRphn3kc0jCTLKaI5ndEjGDHH4fsOe
+wlryW7a0RR/brMNiJYYzy5ADCOkAnyXQmbrQW4nROCPqFojBFTEBLpe8EuODlJXE
+mRHpa1k+k/grFgp/c1xbOrjox127LZT4vkLf5+lSwkhq8oyzWHUrQ+rUJZssi1LC
+GEZSudhX5reqksmIlTwX0GPIIwSwZFNVxhj51wIDAQABAoIBAFFLlzE0vZPZmPOk
+5H2ywaIWuyGxtZx1ACc9VkgRZprA/JrEkHfb35vVg9lQ1mJjavNA+zqERuI2qQQF
+3IKaxfS7u4j+dbhl4EIcE6frUP6R+RAmvx4XO3u6DSAhgUXGSUPZvUEjvV2XMhvL
+ywNh8Ob0LrANLdLpWBiiBavj/ZHnsVZj7y+39GKEtLm8BbsH8L7GanPl4uQDNruR
+Ef2L2pSEqml+Iva6yk5a22U293JCVZ/aqDdGvrnbjkaxqin5jBgL47D91A3xo4SW
+zFZkJR5SDYnFLdwnwTPvxSA9IXj6TZ2ZdnTNvI5Utpd0Wy5N4GlfXS3TBPBD4kP+
++pS1vgECgYEA/D7k1IyOM2hGamHc0qG+sIxMHge6rJE8pYtW2suB2KX0bJvAeBY8
+eHM+pPFMbjjZHlU7qB9qmGbZ++ZVlEv/SqqHizvwLzth0P4MJ6/UjAcmAdEUclV0
+YSzMRFk/g0J7aMlJb5NXgA5xlArHLsmV3Zc+QwFPECgzz5LHwD5nTMcCgYEA1x2Y
+qb7FXwcQPDcs4Qwux12tCUaExQMZbNoLHWrXw7ktHRfR9sI8TcEErLzc2wnZbdyZ
+av/GYSTR9UCeq12kUhmTL4SNryktHKNKEfDEvR7wK6sa9iffzZH8FDe5JktLXnu7
+Oe5SNfLDHA5X7qQefz8s2658+xVJmdp+uasNOnECgYEAlzBXVbJ9VQCuG/tWMQVz
+VzxwLxuw3tgagprWz0NlK2ak7ygXn6KsUgG5TYG3ruTx9gVeQXG7IWecRiiTqNQ4
+SxeVMHYXiyfLhEmRHYR9IAT02efomnLv04LXWCwqLlF9yJvFIVQuAPonR3WCV1/K
+LMwHLIAvVF7UVxkCEw8UOWcCgYEAwiUH/0sZvuYVFQOHEaV5Ip286b4nXdeqPr+b
+gHVJPnAF81foO5iZ7GLj4TKi8V02SxzpqdQmKs6cX4huq6LcBuzmFeDALvIusMX+
+t6phJX6irAbFUpwyNMoog+a2x4T1BNUO6P3aXK44wT2AxvSAQb+2sJ4OVl2kC6NS
+9CcYzUECgYEAzqbWlJIbjJx5620Q95bu+lpessOUtoVcYryO6dQNZOiis8aDZjJk
+RKdB9qhJlcUeFqnvJS5L5gohaJoyx0wMSYVB7MrO6kAIMHo/OriEQ6CTsQOTqa8n
+q4cV9Wuk9pRb1b/x5eXCcHe8P7wBt8KKIh/VBB4aDyeIwGbO0NODYr4=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/admin.pub b/t/keys/admin.pub
new file mode 100644
index 0000000..b50a5b9
--- /dev/null
+++ b/t/keys/admin.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDT9fu7B3vE68LdRNloCcU5HnGqF2BzJzy+Td3Vtde2GK5RKarm3i6FL2qPSKyZw9fRhE2FIMSWi0bUpJanwNx4mlHZzsZiYQumqgTt5tU+cQEpjRw9f4b0pk2BsnLLHflCWkmaU74uKYzSg7xdqtGmGfeRzSMJMspojmd0SMYMcfh+w57CWvJbtrRFH9usw2IlhjPLkAMI6QCfJdCZutBbidE4I+oWiMEVMQEul7wS44OUlcSZEelrWT6T+CsWCn9zXFs6uOjHXbstlPi+Qt/n6VLCSGryjLNYdStD6tQlmyyLUsIYRlK52Ffmt6qSyYiVPBfQY8gjBLBkU1XGGPnX g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/config b/t/keys/config
new file mode 100644
index 0000000..fc75580
--- /dev/null
+++ b/t/keys/config
@@ -0,0 +1,20 @@
+host *
+    stricthostkeychecking no
+host admin
+        identityfile ~/.ssh/admin
+
+host u? admin
+	user %USER
+	hostname localhost
+host u1
+	identityfile ~/.ssh/u1
+host u2
+	identityfile ~/.ssh/u2
+host u3
+	identityfile ~/.ssh/u3
+host u4
+	identityfile ~/.ssh/u4
+host u5
+	identityfile ~/.ssh/u5
+host u6
+	identityfile ~/.ssh/u6
diff --git a/t/keys/u1 b/t/keys/u1
new file mode 100644
index 0000000..828d1c3
--- /dev/null
+++ b/t/keys/u1
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAuB8HmJ5UX30xFktmvlLgSrzsGIzSiYiAYH8eU6epJGr/xjYD
+9GE6G9EcL+/NTc0ziPhIUtVm+h+kFEHtQ1VeOFEuVuWAukPNXRFDrYOMfeR4U1h7
+olT58U3+IlctreKavXr+7bPPhbhvdB7FPM8xusaPfwT6BG4sTqHyOPL5a3eXW7Sp
+tuVD87KNHKo3skWcbb78EVQt7MfCMTam6X2O4Y0y1da51kOht6qRaeJHoaAjXB4V
+pY9lJSQA+Km5uYu+FIwn89z7P4j2aUNZFq2UOSwuViekSR6lfHvortyh8YFrSRUs
+Q4berZUICDp0Ek2BiJirWiPXAp9uxwXms9UbvQIDAQABAoIBAAUTnfMAUpU7b3IM
+7C1NPa/x25SltVxjbh67AowN8GT3qku9y4geciq4Lk3ID+IYSVZ6egwGpEs7Ohvw
+4Wjc3rcwzdVJiK4aFnx9cF9FZEdIWGT76JTGQQn9O4eY3cKQn/GfhY3qSkuGlVQf
+URLnJ5jdxrEa4wXiP8h/QJ1/XY8v9en0uWf/18fMHyzImpd8dqgtTdomSQLjM2ie
+1k9Fllh/5Q6e1Kd5mn5QP1QPC65Z78IskOot9Wrg0sk19sWiFMsopgJkQg66BlnM
+Fj8A4HPS8Yylt0oXJ5yytUYfvZNDRANZRiAz9mfgQw9oapJLnCLt3Awi5bcKJIVk
+/nTccEECgYEA8lSPFeTyzid0sIextXjjHvV4riSZKAHAa5M5DfsPkXLQfbEF4zog
+FLZ6c85jUCk7/vTzDRXr38XKaGbAg/1M3UJk7lS9shh1Zdo0+Hshq7rNICNLKz5H
+/u1/0gkxqqJcpXaoOHYN1hM7HJrP3uZFDIA5ZrB7JD20YDCQ/2PWrS0CgYEAwoHi
+qEKKXOol7FIiG1upTjPaWMTmxtmSs2/pobMVf+ZGDMPts0pbqDegXruaNFUrUQOG
+9yvrjA3a2QaKt7R5eDvHAafeMeu0WcHaqUWtIueTtEuduK4fXsCGONBPEGJu+Ct/
+BOCV/W8MAmzIOvffT7Jg1HWsZyAstoikPi1w4tECgYEAuvVmFxw1/7sNGgz2m+2S
+PJZh7uipiOYhEF3bTN//mNWd6PskcbSsf45xVttKX9QQR5mv0s6w1koA6R8tNCe+
+n43T1NRoLfkUyenZqENHLPjHvR29prU8Un/ld6REP0NYewfarQTXk+vuVRlTesLp
+TsW2g3Vw6/r3KKcPlxntzFkCgYEAqqkH1BY+DHQtPgJ6hoKQNFtuswBgdAymmOYS
+mZvlu0iyIbUvNGaDsT7NaRE1pcEstnJf0zMoAsSNRmpk//ZLteDNJXjCjg5/OVnL
+n0XROZTylfjatBWi1KIbonGzTW7warLPSdo8ABeU8/O6Y3Lk7qpWJ1PwJrOmR6nw
+YdXA/GECgYEAsSokOABGGLQ2zOmDYPDKrRenLho8cSe4g/CF5b6x5MXfJvvBfI/M
+hOS/2AFj7UkOSVM9B9QH607F67YJuBPNGvciCatqY/Bcs6ViuuyqG9U4O13OrrmA
+oG8Cho3Zy2v3udaLJ6O5JYcpX+hdvkwoG5j/8hLrzkD/3AA12z6QnV0=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u1.pub b/t/keys/u1.pub
new file mode 100644
index 0000000..264c1f0
--- /dev/null
+++ b/t/keys/u1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4HweYnlRffTEWS2a+UuBKvOwYjNKJiIBgfx5Tp6kkav/GNgP0YTob0Rwv781NzTOI+EhS1Wb6H6QUQe1DVV44US5W5YC6Q81dEUOtg4x95HhTWHuiVPnxTf4iVy2t4pq9ev7ts8+FuG90HsU8zzG6xo9/BPoEbixOofI48vlrd5dbtKm25UPzso0cqjeyRZxtvvwRVC3sx8IxNqbpfY7hjTLV1rnWQ6G3qpFp4kehoCNcHhWlj2UlJAD4qbm5i74UjCfz3Ps/iPZpQ1kWrZQ5LC5WJ6RJHqV8e+iu3KHxgWtJFSxDht6tlQgIOnQSTYGImKtaI9cCn27HBeaz1Ru9 g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/u2 b/t/keys/u2
new file mode 100644
index 0000000..02486a6
--- /dev/null
+++ b/t/keys/u2
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpgIBAAKCAQEA4/t3WV4q98MlYV9Jbvbf7gE2Dzit6dD8xHsWBh2wTmggQM9I
+2RsPp1tTIoOpt4YdlTzV41577BKLVMvqG7AxnVljC7+m7PrT2YmxEGrrbcrHebHu
++pwh0lkN/CIz6MHjWzLbFiRoHhKh9LyfSiKxCgJ3dyjFgAnA+wrXkwIfPRG8qOLk
+sb1KUsMdfYm0OUEC/mHU2dsIS5HIY6xhy5Q5BXWgkfYUK7LL7Eyw6ffLhvq4U9tv
+Qv+u64Qowew4BY4B8rvyb73iEGcInIF2JRY0jVC8yJFejTJo2KKlXxu8MyAtAhth
+cHE6gA7xmn1d8TF7sxHDNqprvD9mLTjUVdz1owIDAQABAoIBAQDEGaWLZYioHV+l
+5gSQQiJT4w7RAPv3RyBlEUrcb+UbTE2R8brDpJdOaSuVYJM3nVEM8Ys5TChj43+d
+rNjugBvtMNoVXQEEjqxzThDUAmQHyIjUkMzzHCGrgZaZ7gGgkEY0SAZTgXVdiMFu
+dmC9sCGAbqa8BIH9pGYuiiDr/sNIDr3ekyqyuSA0Aa2JIrEx1TKFXF5JtGseU9S6
+bUfesCpoWGyVdulr3NbsLM26eDCsZo45OF5QdpTtU99xc9K4gsOre0ZtqEJMVGlR
+2nDQILCroH93GabSIW+fiUZD2lO9BxXAM0NA730ODQWyM8IgoWrqxGUuFz40l7X1
+teB7yRwBAoGBAPkkF//ZaEUUyCX6+a6TjaPQ1atemQCxHtIm1phfIM/u2uANqeOP
+Z+N3dM0TL53lRx/xbT0dO4sfF73XcF0sYkQj9rep28Q04W8Z6iC/I+jQAJJ0bYl5
+skUGTECxIPLmKOciQF7N5PUGvxhI4oq/0vgDYFc+NW18mow1Z66H1PkjAoGBAOpC
+P9EJXEFKZY1lK6ZL6wvJ6tJaPWAuiQlkOdj9wLvvzywQPlEdBJ1k0q+ndAqpR6WJ
+eTlkP/bzDpLRi+l4PEVTsFlpxjcfVn19RRabsKTNIS0usl1el/uQP7u4rRimBCz5
+MO72GD4ARM5CgMZZMGU5AXmxKEM9feTcUNss3RmBAoGBALTYsmMRmVKr5y1KpPtI
+OER1TuR6Ym3SJCE/9/3a76KAK3j/8hYw/qRrDenex23CBIL3aOg31AUEqOMxA2te
+0GXOBUUEk3Y1PH69POpQVOymMAQfZ3OnVvQrwiYjbVtkHsTIZBltM4l5QDWMkoVN
+AQLu0HwDuBylmjm0enKCPuIpAoGBAObd257bprwB4gtzhY0ijMbVfENLA+nictN6
+nzgm/Oc68+XtLD0sZ/vl/W13jnljU2TlEz9oeVGbQOWY9lZlVKDOVaIJCHwSul56
+MriRP4lrUCMDPm2eaBJYmzcaTh1YoAzimUMn7cRM54KPL/JKu9NGVxnjala6J3SB
+XH5kvJIBAoGBAITZe45tIbVkGjK3jTjgBfaqFfhyws4L3QgRbjqpy2h9nLEGHMYV
+RkIt6bCkw6cy+8TnYB0iG9lcGP1S5E4C6zpTdldi/5tGl7uu9qfEz4hxRpRbJ4Q0
+nwZzUkgBaLxAiLHfntBpwk16UvHkCpo5hiKzSLzxfReXzQPUUudGRnsG
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u2.pub b/t/keys/u2.pub
new file mode 100644
index 0000000..916dcf5
--- /dev/null
+++ b/t/keys/u2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDj+3dZXir3wyVhX0lu9t/uATYPOK3p0PzEexYGHbBOaCBAz0jZGw+nW1Mig6m3hh2VPNXjXnvsEotUy+obsDGdWWMLv6bs+tPZibEQauttysd5se76nCHSWQ38IjPoweNbMtsWJGgeEqH0vJ9KIrEKAnd3KMWACcD7CteTAh89Ebyo4uSxvUpSwx19ibQ5QQL+YdTZ2whLkchjrGHLlDkFdaCR9hQrssvsTLDp98uG+rhT229C/67rhCjB7DgFjgHyu/JvveIQZwicgXYlFjSNULzIkV6NMmjYoqVfG7wzIC0CG2FwcTqADvGafV3xMXuzEcM2qmu8P2YtONRV3PWj g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/u3 b/t/keys/u3
new file mode 100644
index 0000000..163bdef
--- /dev/null
+++ b/t/keys/u3
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArK6uQkgoAnS+w6vs4DmvdOfcGdFUzh3ivHAXHug7sjjKGUB2
+KwcXNwcmJPtyNXM0BJ9ZoOJSqJo5cWLMl7Qn1HBubuyM02Id9EqeJu1Ytn/i1XFY
+NMp8fj4wma3LZTdeOy3ockbefkw6VQUq3cheXIeaVpT91jsuyIaE0ejWkjGMu596
+zXFQZYTSO28TIYghRvhXGq5W9hJ3V2k+ZAQ/AsWQqjK4XruICi+RGRpKj59ECI7V
+zIrq4MUEti+3LldaY6tyYsTGQLtFJYu+l/ZavkrM979cPOxLeoi8ZHPTPKh6asdr
+ZXA2J6++IyIgNy2Q4sNxTmkIlU/EDysvTTGdMwIDAQABAoIBABF+NJr0UlFFYFnU
+Hc/tKBAQuORIp22l62Upeb4gyoNYa2i5df8P3dMuPzf53Oz7OabKObspkjQQQ4dv
++cfYcTx9E0LbZby4MM6hjHnnC1iZhfIXZFccuBXV2PiIeZVMUZhvIyAIe9uRf0tD
+lb8X4C9BcWoZ98ju/+NCdUwKaUov3jXc32hmsAhK+dtqBE+lwccX9keJQcncf+6j
++2znpDAJdaF70dM0DidbkmXmOTgH6LjjqrYRTAUnVsvUyDsr5YhopwOHs0E95YB4
+RzO7V/DA8H7DQ+XE20Iqp2i6dSbRQQPxbQLvJG4BaITIqb1L8/WJ6N75O/uns4rE
+Y8WVwWECgYEA1gDDP70DqvVMu6KDvn18pvfxzxJ3jccYcBZITV/E6xgj1AbH7fdA
+0iwvG6jQ26DTUBfLBquZ2fCCyI44OUpZ8GZu/wYBn5CQjYtr1vJ7zHKM3dVf+POT
+cGgOVDIHopCCX5Dwb74OpbkTV4g/WxouClvz0Ovobq8NncSkJJkyGEMCgYEAzpIF
+oj2AVMWDd6NL+P8e+QXPYEvJ/trAHoAteI83Eof4ZOC/sBr7Gf3c6nM7ZJbCXGhJ
+NCCr4teHJMg9DThQ1KxKn9qEf9dWiAE+HCouaZU98Z0UcLQ+tgPeBh/Dw7rTF3M2
+7A/LtLYbg1MoPCZNj6V4qGjmNWCy2lku1ZGBUFECgYBD/4oKvqxjrf3rwP/Lj2QE
+SdRzz5JdYl3Jf8sJityvNsRropv0aRQXtCJjz4hNwRRj5quEOxJvxZRI1afXzGA3
+mtS6A9aQNQc5couZiQL9O4i3FA2itQKsPOQQrLTwWqqSYyOC3gkZb21N6uT2taLb
+d8xJHiyEvuq8rrbZSjQ4sQKBgEkUDZws58aVrYHYqlrnXny4mnm1tjtMBiWEMRHy
+kIgkxDJj9EyH7wdt8QacR4m5b/8jAarIWCbDGtNfZ4HSx33FigztUGytsLYiwmdS
+YOMHYkeky4NnsLvRuG0wNaB76ovkPazblbRTrH4UICrPXicQYhQqMC74C64FWPVD
+KZ1RAoGAE/vKCHCzPegTT9gr2aaIrhLPUUZboOF4gHajYr9Scr176nJhpIvP/0Y2
+yQtqfas5lID8ouqXb+oL0Q4Yi00hdu+TRYQHm3M+2UL7wgffR5H2vfhk24UUDfV5
+0qQjNKp3pNUWZdiZ2J+RGUszXt0THfWbWI/ntwnG5QchqXCqYEA=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u3.pub b/t/keys/u3.pub
new file mode 100644
index 0000000..e97645c
--- /dev/null
+++ b/t/keys/u3.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsrq5CSCgCdL7Dq+zgOa9059wZ0VTOHeK8cBce6DuyOMoZQHYrBxc3ByYk+3I1czQEn1mg4lKomjlxYsyXtCfUcG5u7IzTYh30Sp4m7Vi2f+LVcVg0ynx+PjCZrctlN147LehyRt5+TDpVBSrdyF5ch5pWlP3WOy7IhoTR6NaSMYy7n3rNcVBlhNI7bxMhiCFG+Fcarlb2EndXaT5kBD8CxZCqMrheu4gKL5EZGkqPn0QIjtXMiurgxQS2L7cuV1pjq3JixMZAu0Uli76X9lq+Ssz3v1w87Et6iLxkc9M8qHpqx2tlcDYnr74jIiA3LZDiw3FOaQiVT8QPKy9NMZ0z g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/u4 b/t/keys/u4
new file mode 100644
index 0000000..a669e34
--- /dev/null
+++ b/t/keys/u4
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2Wg3bl7T0C8VuR8HdbAqmwvQH4/T/maaqlQeJqcATRgWQNDv
+VthEasW5Kx8DzcSVRWS0cJ5EpTLGvrs84aXgdvg6TwFZKO/ujrkFDZmw9hd4I/Dv
+0dp/Y7himS8vAvnnWfYyqJBiX4plEx9Kg8oWHwy8KK3HHKoO8jAWAHOWO3GMB8BV
+128VLovGB6Sp99GrgltzP9UhNRHt/doa3ve8+fDk782SandVTTR9MNLt7qhMSOKr
+bmoFWtL36W5hmVjFGTZC1ZNIU6PiqygUqPtyAYblwv/nZ35s72KmxP7Seag3SsuZ
+UPnRjM4PZ+7YE7tcPTOUxcct8xuTZXgjh2WiNwIDAQABAoIBADSrR8qIVJ452fRo
+LQF49UlsmjYbPQuDxfJ/wHIywSLsM+/t7h3G9QQ89Hga4mwGNPeDxycFYLH41Cc+
+6yfrbK7FwjKDrBr7zXpsHmpGEpX755Ile6QGYBhDgjeEM8pvynmD6I/nsr1cpNH2
+IbI90hAhoK/mMbejB03rEll3pyytCgEvJQsu847dTNTZN+PHsumPr9HERi/dDrbw
+JrDSH2tbOvYw4UW9HLFfZAB+IgMo34WXf6kcUY69wWQbxnv1e7KHnYkxIuLCGZ6x
+Lc9APM97f4atlGt+mAtqK97CJB1AqoIE3KpPq1x+gpFSBNEE+9U9KnItuSqF3mG4
+vq/VjIkCgYEA+ASubOgzJaAldNz3dinLYK6MLB+V1R+RpkDc3+OgfF5mgg11ZtpE
+/DO1Ndpf5dOYB4JYGfhC9+xomHGsQmbOsIUprWaAR/xaO6Bgxw7cxkOBIadsrdwu
+MWJ6h3gN72zuhKPERpUVVH7ZH5KQfZwxEMgquUkGdEwumU4rMXeCFqsCgYEA4GdX
+qI/5UNXEPnXk3dDWNzNJO7gRoonU228IM18qz5ZKMRIKp6Sq+wIuO8+aQMr2K0rO
+RMQk+lLOZ17omaWh9ysDPzTCWRrjOiDOnYWDPCclTeqSnEk8uuWyppDGNXgQ6osM
+LnwJSeP8+1EDgkKf5zoiQfcyBc0v8PSRSdTfEqUCgYEA2PqHeqHd9UHY4xdZq1e/
+JLMv0H5Ff/GhY7iFQ54JziRsO8T4e+Xiyl2WYCnPEer+qzseRoIKXInHq+5uzJzS
+oF2va5MsEU41xsp1QFDBVvbBpyapDqV9CBlmptOiJV/Af+wiD7nnskdTPqrjm/Ck
+gFEOB5Fagy4O6nIXmaw69AcCgYEAhVSBndKlZKUOa7oqmKzLipK7UXNFbxiL0zE+
+Yx+JVTvLqyo4EHFjca5TABCSayrsZr6UngEYo27t2jdm5lumRzBURoq3aq/yEIiL
+msZIOkZcANZ988QEBFwT8KmWSxCipGinfTsPXcrLdhslhZDGZ2GAF0ejfhTzBiyZ
+4o9LV00CgYBf46Z2M1fWI6Tc8HUKr9WoyUmTmUtHUFPt9IZ2CKm2czoltpku7cC7
+ztlt4LmVPKv1UUavbC/nCz6s1ylOkY0rdm45FSYXKDYPMQqzQix1jG4GZJjM9En+
+M848LupnHBFsmHyQqyyRlsg1gb+wtvAw7Y44wcfAhGbiAT5DVX+zxg==
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u4.pub b/t/keys/u4.pub
new file mode 100644
index 0000000..06f3648
--- /dev/null
+++ b/t/keys/u4.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZaDduXtPQLxW5Hwd1sCqbC9Afj9P+ZpqqVB4mpwBNGBZA0O9W2ERqxbkrHwPNxJVFZLRwnkSlMsa+uzzhpeB2+DpPAVko7+6OuQUNmbD2F3gj8O/R2n9juGKZLy8C+edZ9jKokGJfimUTH0qDyhYfDLworcccqg7yMBYAc5Y7cYwHwFXXbxUui8YHpKn30auCW3M/1SE1Ee392hre97z58OTvzZJqd1VNNH0w0u3uqExI4qtuagVa0vfpbmGZWMUZNkLVk0hTo+KrKBSo+3IBhuXC/+dnfmzvYqbE/tJ5qDdKy5lQ+dGMzg9n7tgTu1w9M5TFxy3zG5NleCOHZaI3 g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/u5 b/t/keys/u5
new file mode 100644
index 0000000..ed65131
--- /dev/null
+++ b/t/keys/u5
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA41A0bY6+0akNSJlR2PeRATNtncARXVOUar7CNaxwPqVXQR1+
+TmU9evmIEkRLf0kFAa7L3QDFroFu4sDiSJjvfYIkHdoxO4Fk128PJBhObIXaarKc
+UBIZ29/8I3dTedq5CY/YHL/AjaT+0VktWX7YwigvGDamrXAKqjnW1mWKp6TNj8bp
+vppBvj3yvdYoOLYvCs/SKfKdlayngiihTbKELPcRu8NzYxHiF2b/u0t31evdP0fA
+/apAslKhHdCm8ScZJuyIdztcogBFE5dGm0h3qlbJKvKT8JmTcgQ5TBBcmqPVmUkm
+nPvIzd2G7D1smbExG7LnqekHcS9dvaPnF0B3oQIDAQABAoIBAGl1e21crXDN0mjd
+INjdOnvpJTDru+KldRT0/VszbjvSL6H5EfFDDPvxqsx2vOQHt3fpZZFZ21yzlgND
+Y3g0499BsonbAb5OsL82OjsPv8qfaw7XYKfRTgfxaaP2p1bAP9qMzsG/wJC2fLYZ
+fm2n6N5jED5WlIugkIIbJW4AXAycDB2ZU0xtPTHJ8nrSj1otDCsD2VbbVHXN7H6g
+HrTqP4RqD+uVDIxSrXllz4Udwe/I1wUrvY9HjH2qS6Liqb5kw8SOf4a2et18+KB2
+NNk6ZEAlmOx1ddC2eZPxm8XxAxdSwTggcwvtixCeoXR53OwTZAZ8BBzwusrMklZm
+/n5zPWUCgYEA88Lf4i0WFu9h4FnQ6qxCAKDS1XyRpCYt5cyTpZPMV3LuUV0DPS3K
++EZeU689BSOiwav8omCtrrrOHtE9fHjOH85Gd4IRyOwUQWI7RLpFiI8Lbs4tMbP5
+k1UOzzTji++N47XfTTQVdwHbx4jac80LVavbRd3AKd6oRjc/gQOpgasCgYEA7rnr
+uoWqT76xVNLmE+g93x33hzES2HitgjHzvm5B2Cu6fwlXi7cSnLEWVn7W5QXEuhek
+6uhq4NC0ahL/qAyJsFhVve32qrR7yacDVlMWZ5iQPfqtvS9CsxyafADBWsgeN4L1
+5oqQofNlh0l+UvMFp8INNbKfxTOPKCMfGxIEd+MCgYEA7UsJky380PrbtwD4JVrn
+LaFhXL3VMYyRJaFPIeKNC5wwbzgyjP3lFme6L5Dpv/T+3bZFSvT+XpgvS0S5rFAV
+qFSvuGsAUS2wUi4EMFV8lwFZSdafnEDtdgVZU1DTKkhbQg6sgIVxV9aRUt7gedZj
+cFTKMms6RAgim6fww/ECs90CgYBdI1pt9jJhVHPZNUMgpy5ke0uUijfhDwwazKRd
+OqUj0sO7RojKcM2pJoohivEKf3qmZA0qvSzds2+AJxNpnCKoE364UDw5k5rsLOXn
+axlFp8c29zOLqQGr4c//60eExKjNXaHUpWESXmTRKIJJmJkvP01qEtu0043ZygIb
+zKbDowKBgHhacDR3aat3kkaTp/4AhekhDhMgZ1u2NIi0ycsA6zRlmFucHMwtKCM0
+VMuu40D9N1lTmcdndNKkTJx6CpbS7g/y0ctkyGwgFdmTjjHYl1nATt9G2oRzKx6I
+nQ+sBUwBPieIqEmiFh+KAsDox6plrtvhSSwp8FYLWBJXZHlLJIsd
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u5.pub b/t/keys/u5.pub
new file mode 100644
index 0000000..96a0045
--- /dev/null
+++ b/t/keys/u5.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDjUDRtjr7RqQ1ImVHY95EBM22dwBFdU5RqvsI1rHA+pVdBHX5OZT16+YgSREt/SQUBrsvdAMWugW7iwOJImO99giQd2jE7gWTXbw8kGE5shdpqspxQEhnb3/wjd1N52rkJj9gcv8CNpP7RWS1ZftjCKC8YNqatcAqqOdbWZYqnpM2Pxum+mkG+PfK91ig4ti8Kz9Ip8p2VrKeCKKFNsoQs9xG7w3NjEeIXZv+7S3fV690/R8D9qkCyUqEd0KbxJxkm7Ih3O1yiAEUTl0abSHeqVskq8pPwmZNyBDlMEFyao9WZSSac+8jN3YbsPWyZsTEbsuep6QdxL129o+cXQHeh g3 at sita-lt.atc.tcs.com
diff --git a/t/keys/u6 b/t/keys/u6
new file mode 100644
index 0000000..86deee7
--- /dev/null
+++ b/t/keys/u6
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxyRjRT8RoSDnAnbZdrTjXhBMrkfNfolWFqc3qAjAo8Tmp7Ns
+n7R+KCl31RDkC9u4ll4AOfF9bIdP9ovDVvXoTMoiOdPTYqsnaMBBpDH04vxID2U5
+rEOhSzbvTazzaSlYNFED2a9bMfULuF6WxchFPBQGhTZaC8cDAkS5YYXvjHXAn1GN
+kpv0k5qltD8vjJoM7Mg5JLm84WejPQ5CmwOXqsQj69ZUZEyA8J3oqWVYkEhpYkbK
+IuzaHkrTC0PUQJ8MS3wM8358bWp53zYFJMkyL1a/zJQPOMaDiFTXXj1Uue/5ZkIM
+/fkkOMLbaCgEFCMbL8HkVEq4Q0dLhJKl1IdzbQIDAQABAoIBACQd91shuyLMAtmx
+kHM1D1+J+T5Ki3x9j/1/ylpRbA7HsUWNBxBX/eFu0+ryq0lzSiELX2Mi5yp9yATh
+CEaHRuBWcKqoPlhQzk7zP3R2EwHv22nfY/xYL7KiffhKe8MA2pxybQ5X/WQsGzoO
+/a1VSylAQIZ8ewxTxbntmOmVDwMcLZdY5X4NAmzUw1lFD2TVOlHyhjvhgIzhO1tp
+fBvsX3OL+Pk0tTQebuhoqEy2R01BLc+0ZFAKAFZpZgualqZqjyIU0isQsEo+EK4s
+Pn7Ccxy+156aWAEdgD5Aj7oMn3zA9YIt/JoX8muaceUOj3E7B5ZKBtYZkFPVnKO9
+rPMNTIkCgYEA/v5IeFDZjcAMLWqhuPekcUQYMXc+I0ZTn7vT3aksWxhARG96CPdG
+HNLMw4y8yZLnCAsdKUJGtrVcn4y4K0FzUYn0tDm9cmtFoFcQvLPp4mR1kB84urIG
+7qEwv4djeLhl0cYsqMpyl7qflbTVwKa09Fy7HX9ZFL58nXVX63uRXlMCgYEAx+2o
+L/GKkKgRPpCudsGoplECjzqP4SWHXaYVOupVzpnvxFKVcpBl6Un6p6U9V6W16k2C
+XUg9XqFdleawfAwGQ2D/Ip/u4r8GKOphv0QCjO88ld15Ky9mr9Sk4/Z2EAMUrmKg
+CS4hGBF7VIeA3FnzJwLkYL0+WQKpKBt/zojQLz8CgYEAnmtEgttYDeTeq+iviMbx
+9xyjGzhF9oxer8J1oiTUVdP/OYU4gBGAEbA1Xtg1AdauiiS9fUCbxi9u2AEI+naz
+OllHGiE1Pby/iRoOX+42xFw9XcjH6dVo0SB7tMJcXkfRmj5QyJzeDL35H301v3bS
+vW5PIchYg7bEnN6mPLqMWdkCgYBVWaX1Yb5v5vAFr6prVF11MxxOnQeTbHwPhLmH
+f0bGfn0XaNIYKID5SPXS3/4CDuJMdm5y+EYKwgS729H4AwIhfaUt2O0Yq8gra3Pz
+PUuBcxiAOh5iS0ghRDxofW0FhOstTzlW8fR62+u0uGxQpa3iN5/blK6rPTGNx7+W
+Il4N7QKBgHRTxUZraVt3n0kJ4+tdc+F8XK1wLyVC9PmtW+28tiyx14k8o8H/6W94
+r3lyI13sUnvW6zU0VMAALQujRcJDLac/zSUdFGmgU3P3QatU5yUm0Xpt1uFM49Zw
+xZLBTDJxe2Qrc3Z3SKReRqxLz6tTU1soH7TVw/S36tBovKjTw7Cc
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u6.pub b/t/keys/u6.pub
new file mode 100644
index 0000000..de5b06b
--- /dev/null
+++ b/t/keys/u6.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHJGNFPxGhIOcCdtl2tONeEEyuR81+iVYWpzeoCMCjxOans2yftH4oKXfVEOQL27iWXgA58X1sh0/2i8NW9ehMyiI509NiqydowEGkMfTi/EgPZTmsQ6FLNu9NrPNpKVg0UQPZr1sx9Qu4XpbFyEU8FAaFNloLxwMCRLlhhe+MdcCfUY2Sm/STmqW0Py+MmgzsyDkkubzhZ6M9DkKbA5eqxCPr1lRkTIDwneipZViQSGliRsoi7NoeStMLQ9RAnwxLfAzzfnxtannfNgUkyTIvVr/MlA84xoOIVNdePVS57/lmQgz9+SQ4wttoKAQUIxsvweRUSrhDR0uEkqXUh3Nt g3 at sita-lt.atc.tcs.com
diff --git a/t/ssh-basic.t b/t/ssh-basic.t
new file mode 100755
index 0000000..2747185
--- /dev/null
+++ b/t/ssh-basic.t
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Common;
+use Gitolite::Test;
+
+my $bd = `gitolite query-rc -n GL_BINDIR`;
+my $h  = $ENV{HOME};
+my $ab = `gitolite query-rc -n GL_ADMIN_BASE`;
+umask 0077;
+
+try "
+    plan 26
+
+    # reset stuff
+    rm -f $h/.ssh/authorized_keys;          ok or die 1
+
+    cp $bd/../t/keys/u[1-6]* $h/.ssh;       ok or die 2
+    cp $bd/../t/keys/admin*  $h/.ssh;       ok or die 3
+    cp $bd/../t/keys/config  $h/.ssh;       ok or die 4
+
+    mkdir                  $ab/keydir;      ok or die 5
+    cp $bd/../t/keys/*.pub $ab/keydir;      ok or die 6
+";
+
+_system("gitolite post-compile ssh-authkeys");
+
+# basic tests
+# ----------------------------------------------------------------------
+
+confreset; confadd '
+    @g1 = u1
+    @g2 = u2
+    repo foo
+        RW = @g1 u3
+        R  = @g2 u4
+';
+
+try "ADMIN_PUSH set3; !/FATAL/" or die text();
+
+try "
+    ssh u1 info;                ok;     /R W  \tfoo/
+    ssh u2 info;                ok;     /R    \tfoo/
+    ssh u3 info;                ok;     /R W  \tfoo/
+    ssh u4 info;                ok;     /R    \tfoo/
+    ssh u5 info;                ok;     !/foo/
+    ssh u6 info;                ok;     !/foo/
+"

commit 141b2ce8970cab434e562b4017ca82beb7438bc5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 14 15:30:05 2012 +0530

    more tests

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index e78fe88..df4cc89 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -77,6 +77,11 @@ sub _confargs {
 sub confreset {
     system("rm", "-rf", "conf");
     mkdir("conf");
+    system("mv ~/repositories/gitolite-admin.git ~/repositories/.ga");
+    system("mv ~/repositories/testing.git        ~/repositories/.te");
+    system("find ~/repositories -name '*.git' |xargs rm -rf");
+    system("mv ~/repositories/.ga ~/repositories/gitolite-admin.git");
+    system("mv ~/repositories/.te ~/repositories/testing.git       ");
     put "conf/gitolite.conf", '
         repo    gitolite-admin
             RW+     =   admin
diff --git a/t/include-subconf.t b/t/include-subconf.t
new file mode 100755
index 0000000..7bb5b04
--- /dev/null
+++ b/t/include-subconf.t
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try 'plan 37';
+
+confreset; confadd '
+    include "i1.conf"
+    @i2 = b1
+    subconf "i2.conf"
+    include "i1.conf"
+';
+confadd 'i1.conf', '
+    @g1 = a1 a2
+    repo foo
+        RW = u1
+
+    include "j1.conf"
+';
+confadd 'i2.conf', '
+    @g2 = b1 b2
+    repo bar b1 b2 i1 i2 @i1 @i2 @g2
+        RW = u2
+';
+confadd 'j1.conf', '
+    @h2 = c1 c2
+    repo baz
+        RW = u3
+';
+
+try "ADMIN_PUSH set2; !/FATAL/" or die text();
+
+try "
+                                        /i1.conf already included/
+	                                /i2.conf attempting to set access for \@i1, b2, bar, i1, locally modified \@g2/
+                                        !/attempting to set access.*i2/
+                                        /Initialized.*empty.*baz.git/
+                                        /Initialized.*empty.*foo.git/
+                                        /Initialized.*empty.*b1.git/
+                                        /Initialized.*empty.*i2.git/
+                                        !/Initialized.*empty.*b2.git/
+                                        !/Initialized.*empty.*i1.git/
+                                        !/Initialized.*empty.*bar.git/
+";
+
+confreset;confadd '
+    @g2 = i1 i2 i3
+    subconf "g2.conf"
+';
+confadd 'g2.conf', '
+    @g2 = g2 h2 i2
+    repo @g2
+        RW = u1
+';
+
+try "ADMIN_PUSH set3; !/FATAL/" or die text();
+try "
+                                        /g2.conf attempting to set access for locally modified \@g2/
+                                        !/Initialized.*empty/
+";
+
+confreset;confadd '
+    @g2 = i1 i2 i3
+    subconf "g2.conf"
+';
+confadd 'g2.conf', '
+    subconf master
+    @g2 = g2 h2 i2
+    repo @g2
+        RW = u1
+';
+
+try "
+    ADMIN_PUSH set3;           ok;     /FATAL: subconf g2 attempting to run 'subconf'/
+";
diff --git a/t/info.t b/t/info.t
new file mode 100755
index 0000000..967f4af
--- /dev/null
+++ b/t/info.t
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try 'plan 35';
+
+try "## info";
+
+confreset;confadd '
+    repo    t1
+        RW              =   u1
+        R               =   u2
+    repo    t2
+        RW  =               u2
+        R   =               u1
+    repo    t3
+                    RW  =   u3
+                    R   =   u4
+';
+
+try "ADMIN_PUSH info; !/FATAL/" or die text();
+try "
+                                        /Initialized.*empty.*t1.git/
+                                        /Initialized.*empty.*t2.git/
+                                        /Initialized.*empty.*t3.git/
+";
+try "
+    glt info u1;                ok;     gsh
+                                        /R W  *\tt1/
+                                        /R  *\tt2/
+                                        !/t3/
+                                        / R W  *\ttesting/
+    glt info u2;                ok;     gsh
+                                        /R  *\tt1/
+                                        /R W  *\tt2/
+                                        !/t3/
+                                        / R W  *\ttesting/
+    glt info u3;                ok;     gsh
+                                        /R W  *\tt3/
+                                        !/t1/
+                                        !/t2/
+                                        / R W  *\ttesting/
+    glt info u4;                ok;     gsh
+                                        /R  *\tt3/
+                                        !/t1/
+                                        !/t2/
+                                        / R W  *\ttesting/
+    " or die;
diff --git a/t/m-explode.t b/t/m-explode.t
deleted file mode 100755
index aef337f..0000000
--- a/t/m-explode.t
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/perl
-use strict;
-use warnings;
-use 5.10.0;
-
-use Test;
-BEGIN { plan tests =>
-    2
-}
-
-use lib "$ENV{PWD}/src";
-use Gitolite::Test;
-use Gitolite::Conf::Explode;
-
-my @out;
-my @out2;
-
-warn "
-        <<< expect a couple of warnings about already included files >>>
-";
-
-# test 1 -- space normalisation
-
-    put "foo", "
-        foo line 1          
-                                foo=line 2
-
-
-        foo      3
-    ";
-    @out = ();
-    explode("foo", 'master', \@out);
-    @out2 = (
-        'foo line 1',
-        'foo = line 2',
-        'foo 3',
-    );
-
-    ok(@out ~~ @out2);
-
-# test 2 -- include/subconf processing
-
-    put "foo", "
-        foo line 1
-        \@fog=line 2
-            include                         \"bar.conf\"
-
-        foo line=5
-            subconf \"subs/baz.conf\"
-            include                         \"bar.conf\"
-        foo line=7
-            include \"bazup.conf\"
-    ";
-
-    put "bar.conf", "
-        \@brg=line 1
-
-        bar line 3
-    ";
-
-    mkdir("subs");
-
-    put "subs/baz.conf", "
-        \@bzg         =           line 1
-
-            include \"subs/baz2.conf\"
-
-        baz=line 3
-    ";
-
-    put "subs/baz2.conf", "
-        baz2 line 1
-        baz2 line 2
-            include \"bazup.conf\"
-        baz2 line 4
-    ";
-
-    put "bazup.conf", "
-        whatever...
-    ";
-
-    @out = ();
-    explode("foo", 'master', \@out);
-
-    @out2 = (
-        'foo line 1',
-        '@fog = line 2',
-        '@brg = line 1',
-        'bar line 3',
-        'foo line = 5',
-        'subconf baz',
-        '@baz.bzg = line 1',
-        'baz2 line 1',
-        'baz2 line 2',
-        'whatever...',
-        'baz2 line 4',
-        'baz = line 3',
-        'subconf master',
-        'foo line = 7'
-    );
-
-    ok(@out ~~ @out2);

commit 89cc3a303d1b71beb5075fed55d9ffae053b8954
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 14 15:29:44 2012 +0530

    Test.pm learned confreset() and confadd()

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index f7b4544..e78fe88 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -9,12 +9,16 @@ package Gitolite::Test;
   put
   text
   dump
+  confreset
+  confadd
 );
 #>>>
 use Exporter 'import';
 use File::Path qw(mkpath);
 use Carp qw(carp cluck croak confess);
 
+use Gitolite::Common;
+
 BEGIN {
     require Gitolite::Test::Tsh;
     *{'try'}  = \&Tsh::try;
@@ -65,4 +69,25 @@ sub dump {
     }
 }
 
+sub _confargs {
+    return @_ if ($_[1]);
+    return 'gitolite.conf', $_[0];
+}
+
+sub confreset {
+    system("rm", "-rf", "conf");
+    mkdir("conf");
+    put "conf/gitolite.conf", '
+        repo    gitolite-admin
+            RW+     =   admin
+        repo    testing
+            RW+     =   @all
+';
+}
+
+sub confadd {
+    my ($file, $string) = _confargs(@_);
+    put "|cat >> conf/$file", $string;
+}
+
 1;

commit 356ff2b7572672f315f273116d60c556589331fd
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Wed Mar 14 14:11:43 2012 +0530

    store got a few more validations
    
    (a full scan of all input data is pending; this is just for diagnostics)

diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 154b44e..fef1746 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -49,6 +49,7 @@ my %ignored;
 sub add_to_group {
     my ( $lhs, @rhs ) = @_;
     _die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT;
+    map { _die "bad expansion '$_'" unless $_ =~ $REPOPATT_PATT } @rhs;
 
     # store the group association, but overload it to keep track of when
     # the group was *first* created by using $subconf as the *value*
@@ -115,6 +116,8 @@ sub parse_users {
 
 sub add_rule {
     my ( $perm, $ref, $user ) = @_;
+    _die "bad ref '$ref'" unless $ref =~ $REPOPATT_PATT;
+    _die "bad user '$user'" unless $user =~ $USERNAME_PATT;
 
     $ruleseq++;
     for my $repo (@repolist) {
@@ -140,6 +143,7 @@ sub add_rule {
 
 sub set_subconf {
     $subconf = shift;
+    _die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
     trace( 1, $subconf );
 }
 

commit 7f8020adc5709690e74f0d07838022e43533c272
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 12 20:54:30 2012 +0530

    'info' command, plus lots more changes:
    
      - usage() gets a little smarter; it now knows what function it was called
        from and tries to find a '=for function_name' chunk of data in the script
    
      - the various list-* functions now work off a dispatcher in Load.pm
      - (...and they all use the new usage() magic to print their helps!)
    
      - src/gitolite got a lot leaner due to this dispatcher
    
      - src/gitolite-shell became a lot more easier to read/flow
    
      - rc acquired '{COMMANDS}', which gitolite-shell now refers to
      - comments in the default rc file changed a bit
      - rc got a new REMOTE_COMMAND_PATT (in place of ADC_CMD_ARGS_PATT)
    
    the rest is perltidy and stuff like that

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index eb4b6f1..2260e41 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -40,10 +40,10 @@ sub trace {
     return unless defined( $ENV{D} );
 
     my $level = shift; return if $ENV{D} < $level;
-    my $args  = ''; $args = join( ", ", @_ ) if @_;
-    my $sub   = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) );
-    say2 "TRACE $level $sub", (@_ ? shift : ());
-    say2("TRACE $level " . (" " x 32), $_)for @_;
+    my $args = ''; $args = join( ", ", @_ ) if @_;
+    my $sub = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) );
+    say2 "TRACE $level $sub", ( @_ ? shift : () );
+    say2( "TRACE $level " . ( " " x 32 ), $_ ) for @_;
 }
 
 sub dbg {
@@ -75,13 +75,17 @@ sub _die {
 }
 
 sub usage {
-    my ($warn, $section) = @_;
-    _warn($warn) if $warn;
-    $section ||= 'usage';
-    my $scriptname = ( caller() )[1];
-    my $script     = slurp($scriptname);
-    $script =~ /^=for $section(.*?)^=cut/sm;
-    say2( $1 ? $1 : "...no usage message in $scriptname" );
+    _warn(shift) if @_;
+    my ( $script, $function ) = ( caller(1) )[ 1, 3 ];
+    if (not $script) {
+        $script = ( caller ) [1];
+        $function = 'usage';
+    }
+    dbg( "u s a g e", $script, $function );
+    $function =~ s/.*:://;
+    my $code = slurp($script);
+    $code =~ /^=for $function(.*?)^=cut/sm;
+    say2( $1 ? $1 : "...no usage message in $script" );
     exit 1;
 }
 
@@ -154,8 +158,8 @@ sub ln_sf {
 sub sort_u {
     my %uniq;
     my $listref = shift;
-    return [] unless @{ $listref };
-    undef @uniq{ @{ $listref } }; # expect a listref
+    return [] unless @{$listref};
+    undef @uniq{ @{$listref} };    # expect a listref
     my @sort_u = sort keys %uniq;
     return \@sort_u;
 }
@@ -177,7 +181,6 @@ sub cleanup_conf_line {
     my @phy_repos = ();
 
     sub list_phy_repos {
-        _die "'gitolite list_phy_repos' takes no arguments" if @ARGV;
         trace(3);
 
         # use cached value only if it exists *and* no arg was received (i.e.,
@@ -189,7 +192,7 @@ sub cleanup_conf_line {
             $repo =~ s(\./(.*)\.git$)($1);
             push @phy_repos, $repo;
         }
-        return sort_u(\@phy_repos);
+        return sort_u( \@phy_repos );
     }
 }
 
diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index 6fcc0cf..a93aa10 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -24,12 +24,12 @@ use warnings;
 
 sub compile {
     trace(3);
-    # XXX assume we're in admin-base/conf
+    _die "'gitolite compile' does not take any arguments" if @_;
 
     _chdir( $rc{GL_ADMIN_BASE} );
     _chdir("conf");
 
-    parse(sugar('gitolite.conf'));
+    parse( sugar('gitolite.conf') );
 
     # the order matters; new repos should be created first, to give store a
     # place to put the individual gl-conf files
@@ -39,7 +39,7 @@ sub compile {
 
 sub parse {
     my $lines = shift;
-    trace(4, scalar(@$lines) . " lines incoming");
+    trace( 4, scalar(@$lines) . " lines incoming" );
 
     for my $line (@$lines) {
         # user or repo groups
diff --git a/src/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
index a821dc9..f77e89d 100644
--- a/src/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -28,7 +28,7 @@ sub explode {
     # seed the 'seen' list if it's empty
     $included{ device_inode("conf/gitolite.conf") }++ unless %included;
 
-    my $fh    = _open( "<", $file );
+    my $fh = _open( "<", $file );
     while (<$fh>) {
         my $line = cleanup_conf_line($_);
         next unless $line =~ /\S/;
diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 625d7eb..1759214 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -7,12 +7,7 @@ package Gitolite::Conf::Load;
   load
   access
   vrefs
-
-  list_groups
-  list_users
-  list_repos
-  list_memberships
-  list_members
+  lister_dispatch
 );
 
 use Exporter 'import';
@@ -25,8 +20,6 @@ use warnings;
 
 # ----------------------------------------------------------------------
 
-my $subconf = 'master';
-
 # our variables, because they get loaded by a 'do'
 our $data_version = '';
 our %repos;
@@ -36,6 +29,16 @@ our %configs;
 our %one_config;
 our %split_conf;
 
+my $subconf = 'master';
+
+my %listers = (
+    'list-groups'      => \&list_groups,
+    'list-users'       => \&list_users,
+    'list-repos'       => \&list_repos,
+    'list-memberships' => \&list_memberships,
+    'list-members'     => \&list_members,
+);
+
 # helps maintain the "cache" in both "load_common" and "load_1"
 my $last_repo = '';
 
@@ -118,7 +121,7 @@ sub load_1 {
     my $repo = shift;
     trace( 4, $repo );
 
-    _chdir( "$rc{GL_REPO_BASE}/$repo.git" );
+    _chdir("$rc{GL_REPO_BASE}/$repo.git");
 
     if ( $repo eq $last_repo ) {
         $repos{$repo} = $one_repo{$repo};
@@ -143,13 +146,13 @@ sub load_1 {
 {
     my $lastrepo = '';
     my $lastuser = '';
-    my @cached = ();
+    my @cached   = ();
 
     sub rules {
         my ( $repo, $user ) = @_;
         trace( 4, "repo=$repo, user=$user" );
 
-        return @cached if ($lastrepo eq $repo and $lastuser eq $user and @cached);
+        return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
 
         my @rules = ();
 
@@ -167,7 +170,7 @@ sub load_1 {
 
         $lastrepo = $repo;
         $lastuser = $user;
-        @cached = @rules;
+        @cached   = @rules;
 
         return @rules;
     }
@@ -175,7 +178,7 @@ sub load_1 {
     sub vrefs {
         my ( $repo, $user ) = @_;
         # fill the cache if needed
-        rules($repo, $user) unless ($lastrepo eq $repo and $lastuser eq $user and @cached);
+        rules( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached );
 
         my %seen;
         my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
@@ -200,15 +203,22 @@ sub data_version_mismatch {
 # api functions
 # ----------------------------------------------------------------------
 
-# list all groups
-sub list_groups {
-    die "
+sub lister_dispatch {
+    my $command = shift;
+
+    my $fn = $listers{$command} or _die "unknown gitolite sub-command";
+    return $fn;
+}
+
+=for list_groups
 Usage:  gitolite list-groups
 
   - lists all group names in conf
   - no options, no flags
+=cut
 
-" if @ARGV;
+sub list_groups {
+    usage() if @_;
 
     load_common();
 
@@ -219,18 +229,18 @@ Usage:  gitolite list-groups
     return ( sort_u( \@g ) );
 }
 
-sub list_users {
-    my $count = 0;
-    my $total = 0;
-
-    die "
+=for list_users
 Usage:  gitolite list-users
 
   - lists all users/user groups in conf
   - no options, no flags
   - WARNING: may be slow if you have thousands of repos
+=cut
 
-" if @ARGV;
+sub list_users {
+    usage() if @_;
+    my $count = 0;
+    my $total = 0;
 
     load_common();
 
@@ -242,19 +252,19 @@ Usage:  gitolite list-users
         $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
         push @u, map { keys %{$_} } values %one_repo;
     }
-    print STDERR "\n";
+    print STDERR "\n" if $count >= 100;
     return ( sort_u( \@u ) );
 }
 
-sub list_repos {
-
-    die "
+=for list_repos
 Usage:  gitolite list-repos
 
   - lists all repos/repo groups in conf
   - no options, no flags
+=cut
 
-" if @ARGV;
+sub list_repos {
+    usage() if @_;
 
     load_common();
 
@@ -264,34 +274,34 @@ Usage:  gitolite list-repos
     return ( sort_u( \@r ) );
 }
 
-sub list_memberships {
-
-    die "
+=for list_memberships
 Usage:  gitolite list-memberships <name>
 
   - list all groups a name is a member of
   - takes one user/repo name
+=cut
 
-" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_;
+sub list_memberships {
+    usage() if @_ and $_[0] eq '-h' or not @_;
 
-    my $name = ( @_ ? shift @_ : shift @ARGV );
+    my $name = shift;
 
     load_common();
     my @m = memberships($name);
     return ( sort_u( \@m ) );
 }
 
-sub list_members {
-
-    die "
+=for list_members
 Usage:  gitolite list-members <group name>
 
   - list all members of a group
   - takes one group name
+=cut
 
-" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_;
+sub list_members {
+    usage() if @_ and $_[0] eq '-h' or not @_;
 
-    my $name = ( @_ ? shift @_ : shift @ARGV );
+    my $name = shift;
 
     load_common();
 
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index c513669..154b44e 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -207,9 +207,8 @@ sub store {
 }
 
 sub parse_done {
-    for my $ig (sort keys %ignored)
-    {
-        _warn "$ig.conf attempting to set access for " . join (", ", sort keys %{ $ignored{$ig} });
+    for my $ig ( sort keys %ignored ) {
+        _warn "$ig.conf attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
     }
 }
 
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 30dcfc0..caea1fb 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -3,7 +3,7 @@
 package SugarBox;
 
 sub run_sugar_script {
-    my ($ss, $lref) = @_;
+    my ( $ss, $lref ) = @_;
     do $ss if -x $ss;
     $lref = sugar_script($lref);
     return $lref;
@@ -35,7 +35,7 @@ sub sugar {
     # gets a filename, returns a listref
 
     my @lines = ();
-    explode(shift, 'master', \@lines);
+    explode( shift, 'master', \@lines );
 
     my $lines;
     $lines = \@lines;
@@ -43,11 +43,11 @@ sub sugar {
     # run through the sugar stack one by one
 
     # first, user supplied sugar:
-    if (exists $rc{SYNTACTIC_SUGAR}) {
-        if (ref($rc{SYNTACTIC_SUGAR}) ne 'ARRAY') {
+    if ( exists $rc{SYNTACTIC_SUGAR} ) {
+        if ( ref( $rc{SYNTACTIC_SUGAR} ) ne 'ARRAY' ) {
             _warn "bad syntax for specifying sugar scripts; see docs";
         } else {
-            for my $s (@{ $rc{SYNTACTIC_SUGAR} }) {
+            for my $s ( @{ $rc{SYNTACTIC_SUGAR} } ) {
 
                 # perl-ism; apart from keeping the full path separate from the
                 # simple name, this also protects %rc from change by implicit
@@ -55,7 +55,7 @@ sub sugar {
                 my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s";
 
                 _warn("skipped sugar script '$s'"), next if not -x $sfp;
-                $lines = SugarBox::run_sugar_script($sfp, $lines);
+                $lines = SugarBox::run_sugar_script( $sfp, $lines );
                 $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
             }
         }
diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index ab94e23..efd4838 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -19,7 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub post_update {
-    trace(3, @ARGV);
+    trace( 3, @ARGV );
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
@@ -32,11 +32,11 @@ sub post_update {
     _system("$ENV{GL_BINDIR}/gitolite compile");
 
     # now run optional post-compile features
-    if (exists $rc{POST_COMPILE}) {
-        if (ref($rc{POST_COMPILE}) ne 'ARRAY') {
+    if ( exists $rc{POST_COMPILE} ) {
+        if ( ref( $rc{POST_COMPILE} ) ne 'ARRAY' ) {
             _warn "bad syntax for specifying post compile scripts; see docs";
         } else {
-            for my $s (@{ $rc{POST_COMPILE} }) {
+            for my $s ( @{ $rc{POST_COMPILE} } ) {
 
                 # perl-ism; apart from keeping the full path separate from the
                 # simple name, this also protects %rc from change by implicit
@@ -44,7 +44,7 @@ sub post_update {
                 my $sfp = "$ENV{GL_BINDIR}/post-compile/$s";
 
                 _warn("skipped post-compile script '$s'"), next if not -x $sfp;
-                _system($sfp, @ARGV);   # they better all return with 0 exit codes!
+                _system( $sfp, @ARGV );    # they better all return with 0 exit codes!
             }
         }
     }
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index cc13465..da089b5 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -28,32 +28,32 @@ sub update {
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" );
     _die $ret if $ret =~ /DENIED/;
 
-    check_vrefs($ref, $oldsha, $newsha, $oldtree, $newtree, $aa);
+    check_vrefs( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
 
     exit 0;
 }
 
 sub check_vrefs {
-    my($ref, $oldsha, $newsha, $oldtree, $newtree, $aa) = @_;
+    my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = @_;
     my $name_seen = 0;
-    for my $vref ( vrefs($ENV{GL_REPO}, $ENV{GL_USER}) ) {
-        trace(1, "vref=$vref");
-        if ($vref =~ m(^VREF/NAME/)) {
+    for my $vref ( vrefs( $ENV{GL_REPO}, $ENV{GL_USER} ) ) {
+        trace( 1, "vref=$vref" );
+        if ( $vref =~ m(^VREF/NAME/) ) {
             # this one is special; we process it right here, and only once
             next if $name_seen++;
 
             for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
-                check_vref($aa, $ref);
+                check_vref( $aa, $ref );
             }
         } else {
-            my($dummy, $pgm, @args) = split '/', $vref;
+            my ( $dummy, $pgm, @args ) = split '/', $vref;
             $pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
             -x $pgm or die "$vref: helper program missing or unexecutable\n";
 
             open( my $fh, "-|", $pgm, @_, $vref, @args ) or die "$vref: can't spawn helper program: $!\n";
             while (<$fh>) {
                 my ( $ref, $deny_message ) = split( ' ', $_, 2 );
-                check_vref($aa, $ref, $deny_message);
+                check_vref( $aa, $ref, $deny_message );
             }
             close($fh) or die $!
               ? "Error closing sort pipe: $!"
@@ -63,13 +63,13 @@ sub check_vrefs {
 }
 
 sub check_vref {
-    my ($aa, $ref, $deny_message) = @_;
+    my ( $aa, $ref, $deny_message ) = @_;
 
     my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
     _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
-        if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
-    trace( 1, "remember, fallthru is success here!") if $ret =~ /by fallthru/;
+      if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
+    trace( 1, "remember, fallthru is success here!" ) if $ret =~ /by fallthru/;
 }
 
 {
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index be82ab2..2a51a55 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -8,7 +8,7 @@ package Gitolite::Rc;
   glrc
   query_rc
 
-  $ADC_CMD_ARGS_PATT
+  $REMOTE_COMMAND_PATT
   $REF_OR_FILENAME_PATT
   $REPONAME_PATT
   $REPOPATT_PATT
@@ -36,7 +36,7 @@ $rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
 # variables that should probably never be changed
 # ----------------------------------------------------------------------
 
-$ADC_CMD_ARGS_PATT    = qr(^[0-9a-zA-Z._\@/+:-]*$);
+$REMOTE_COMMAND_PATT  = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$);
 $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
 $REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
 $REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
@@ -101,26 +101,9 @@ sub glrc {
 # implements 'gitolite query-rc'
 # ----------------------------------------------------------------------
 
-=for usage
-
-Usage:  gitolite query-rc -a
-        gitolite query-rc [-n] <list of rc variables>
-
-    -a          print all variables and values
-    -n          do not append a newline
-
-Example:
-
-    gitolite query-rc GL_ADMIN_BASE GL_UMASK
-    # prints "/home/git/.gitolite<tab>0077" or similar
-
-    gitolite query-rc -a
-    # prints all known variables and values, one per line
-=cut
-
 # ----------------------------------------------------------------------
 
-my $all = 0;
+my $all  = 0;
 my $nonl = 0;
 
 sub query_rc {
@@ -130,18 +113,38 @@ sub query_rc {
 
     no strict 'refs';
 
-    if ( $all ) {
-        for my $e (sort keys %rc) {
-            print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n";
+    if ($all) {
+        for my $e ( sort keys %rc ) {
+            print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
         }
-        return;
+        exit 0;
     }
 
-    print join( "\t", map { $rc{$_} || '' } @vars ) . ($nonl ? '' : "\n") if @vars;
+    my @res = map { $rc{$_} } grep { $rc{$_} } @vars;
+    print join( "\t", @res ) . ( $nonl ? '' : "\n" ) if @res;
+    # shell truth
+    exit 0 if @res;
+    exit 1;
 }
 
 # ----------------------------------------------------------------------
 
+=for args
+Usage:  gitolite query-rc -a
+        gitolite query-rc [-n] <list of rc variables>
+
+    -a          print all variables and values
+    -n          do not append a newline
+
+Example:
+
+    gitolite query-rc GL_ADMIN_BASE UMASK
+    # prints "/home/git/.gitolite<tab>0077" or similar
+
+    gitolite query-rc -a
+    # prints all known variables and values, one per line
+=cut
+
 sub args {
     my $help = 0;
 
@@ -163,30 +166,35 @@ sub args {
 __DATA__
 # configuration variables for gitolite
 
-# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
-
-# This file is in perl syntax.
+# This file is in perl syntax.  But you do NOT need to know perl to edit it --
+# just mind the commas and make sure the brackets and braces stay matched up!
 
-# However, you do NOT need to know perl to edit it; it should be fairly
-# self-explanatory and easy to maintain.  Just mind the commas (perl is quite
-# happy to have an extra one at the end of the last item in any list, by the
-# way!).  And make sure the brackets and braces stay matched up!
+# (Tip: perl allows a comma after the last item in a list also!)
 
 %RC = (
     UMASK                       =>  0077,
     GL_GITCONFIG_KEYS           =>  "",
 
     # comment out or uncomment as needed
+    # these will run in sequence during the conf file parse
     SYNTACTIC_SUGAR             =>
         [
             # 'continuation-lines',
         ],
 
     # comment out or uncomment as needed
+    # these will run in sequence after post-update
     POST_COMPILE                =>
         [
             'ssh-authkeys',
         ],
+
+    # comment out or uncomment as needed
+    # these are available to remote users
+    COMMANDS                    =>
+        {
+            'info'              =>  1,
+        },
 );
 
 # ------------------------------------------------------------------------------
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index d335147..09930bd 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -3,14 +3,15 @@ package Gitolite::Setup;
 # implements 'gitolite setup'
 # ----------------------------------------------------------------------
 
-=for usage
+=for args
 Usage:  gitolite setup [<at least one option>]
 
-
     -a, --admin <name>          admin user name
     -pk --pubkey <file>         pubkey file name
     -f, --fixup-hooks           fixup hooks
 
+Setup (first run only), then compile conf and fixup hooks.
+
 First run:
     -a      required
     -pk     required for ssh mode install
diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index f950fb3..f7b4544 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -17,8 +17,8 @@ use Carp qw(carp cluck croak confess);
 
 BEGIN {
     require Gitolite::Test::Tsh;
-    *{'try'} = \&Tsh::try;
-    *{'put'} = \&Tsh::put;
+    *{'try'}  = \&Tsh::try;
+    *{'put'}  = \&Tsh::put;
     *{'text'} = \&Tsh::text;
 }
 
diff --git a/src/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm
index b4b3b41..41b4d12 100644
--- a/src/Gitolite/Test/Tsh.pm
+++ b/src/Gitolite/Test/Tsh.pm
@@ -259,8 +259,7 @@ sub rc_lines {
             $cmd = shift @cmds;
 
             # is the current command a "testing" command?
-            my $testing_cmd =
-              ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) );
+            my $testing_cmd = ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) );
 
             # warn if the previous command failed but rc is not being checked
             if ( $rc and not $testing_cmd ) {
diff --git a/src/VREF/COUNT b/src/VREF/COUNT
index f61ab57..d5c3982 100755
--- a/src/VREF/COUNT
+++ b/src/VREF/COUNT
@@ -1,4 +1,5 @@
 #!/bin/bash
+# TODO: convert to perl!
 
 # gitolite VREF to count number of changed/new files in a push
 
diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE
index e61acc6..2115a5c 100755
--- a/src/VREF/FILETYPE
+++ b/src/VREF/FILETYPE
@@ -1,4 +1,5 @@
 #!/bin/bash
+# TODO: convert to perl!
 
 # gitolite VREF to find autogenerated files
 
diff --git a/src/commands/info b/src/commands/info
new file mode 100755
index 0000000..fe52837
--- /dev/null
+++ b/src/commands/info
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+=for usage
+Usage:  gitolite info
+
+  - list all repos/repo groups you can access
+  - no options, no flags
+=cut
+
+usage() if @ARGV;
+
+my $user = $ENV{GL_USER} or _die "GL_USER not set";
+my $ref = 'any';
+
+my $fn = lister_dispatch('list-repos');
+
+for ( @{ $fn->() } ) {
+    my $perm = '';
+    for my $aa (qw(R W ^C)) {
+        my $ret = access($_, $user, $aa, $ref);
+        $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
+    }
+    print "$perm\t$_\n" if $perm =~ /\S/;
+}
diff --git a/src/gitolite b/src/gitolite
index d8c82af..c265bd5 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -3,7 +3,7 @@
 # all gitolite CLI tools run as sub-commands of this command
 # ----------------------------------------------------------------------
 
-=for usage
+=for args
 Usage:  gitolite [sub-command] [options]
 
 The following subcommands are available; they should all respond to '-h' if
@@ -11,14 +11,16 @@ you want further details on each:
 
     setup                       1st run: initial setup; all runs: hook fixups
     compile                     compile gitolite.conf
+
     query-rc                    get values of rc variables
+    post-compile                run a post-compile command
+
     list-groups                 list all group names in conf
     list-users                  list all users/user groups in conf
     list-repos                  list all repos/repo groups in conf
     list-phy-repos              list all repos actually on disk
     list-memberships            list all groups a name is a member of
     list-members                list all members of a group
-    post-compile                run a post-compile command
 
 Warnings:
   - list-users is disk bound and could take a while on sites with 1000s of repos
@@ -40,66 +42,56 @@ use warnings;
 
 # ----------------------------------------------------------------------
 
+my ( $command, @args ) = @ARGV;
 args();
 
-# ----------------------------------------------------------------------
+# the first two commands need options via @ARGV, as they have their own
+# GetOptions calls and older perls don't have 'GetOptionsFromArray'
+
+if ( $command eq 'setup' ) {
+    shift @ARGV;
+    require Gitolite::Setup;
+    Gitolite::Setup->import;
+    setup();
+
+} elsif ( $command eq 'query-rc' ) {
+    shift @ARGV;
+    query_rc();     # doesn't return
+
+# the rest don't need @ARGV per se
+
+} elsif ( $command eq 'compile' ) {
+    require Gitolite::Conf;
+    Gitolite::Conf->import;
+    compile(@args);
+
+} elsif ( $command eq 'post-compile' ) {
+    post_compile(@args);
+
+} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
+    run_command( $command, @args );
+
+} elsif ( $command eq 'list-phy-repos' ) {
+    _chdir( $rc{GL_REPO_BASE} );
+    print "$_\n" for ( @{ list_phy_repos(@args) } );
+
+} elsif ( $command =~ /^list-/ ) {
+    require Gitolite::Conf::Load;
+    Gitolite::Conf::Load->import;
+    my $fn = lister_dispatch($command);
+    print "$_\n" for ( @{ $fn->(@args) } );
+
+} else {
+    _die "unknown gitolite sub-command";
+}
 
 sub args {
-    my ( $command, @args ) = @ARGV;
     usage() if not $command or $command eq '-h';
-
-    if ( $command eq 'setup' ) {
-        shift @ARGV;
-        require Gitolite::Setup;
-        Gitolite::Setup->import;
-        setup();
-    } elsif ( $command eq 'compile' ) {
-        shift @ARGV;
-        _die "'gitolite compile' does not take any arguments" if @ARGV;
-        require Gitolite::Conf;
-        Gitolite::Conf->import;
-        compile();
-    } elsif ( $command eq 'query-rc' ) {
-        shift @ARGV;
-        query_rc();
-    } elsif ( $command eq 'list-groups' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_groups() } );
-    } elsif ( $command eq 'list-users' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_users() } );
-    } elsif ( $command eq 'list-repos' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_repos() } );
-    } elsif ( $command eq 'list-phy-repos' ) {
-        shift @ARGV;
-        _chdir( $rc{GL_REPO_BASE} );
-        print "$_\n" for ( @{ list_phy_repos() } );
-    } elsif ( $command eq 'list-memberships' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_memberships() } );
-    } elsif ( $command eq 'list-members' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_members() } );
-    } elsif ( $command eq 'post-compile' ) {
-        shift @ARGV;
-        post_compile();
-    } else {
-        _die "unknown gitolite sub-command";
-    }
 }
 
-=for post-compile
+# ----------------------------------------------------------------------
+
+=for post_compile
 Usage:  gitolite post-compile [-l] [post-compile-scriptname] [script args...]
 
     -l          list currently available post-compile scripts
@@ -109,17 +101,26 @@ the gitolite-admin repo).
 =cut
 
 sub post_compile {
-    usage('', 'post-compile') if (@ARGV and $ARGV[0] eq '-h');
+    usage() if ( not @_ or $_[0] eq '-h' );
+
+    run_subdir('post-compile', @_);
+}
+
+sub run_command {
+    run_subdir('commands', @_);
+}
 
-    if (@ARGV and $ARGV[0] eq '-l') {
-        _chdir("$ENV{GL_BINDIR}/post-compile");
+sub run_subdir {
+    my $subdir = shift;
+    if ( @_ and $_[0] eq '-l' ) {
+        _chdir("$ENV{GL_BINDIR}/$subdir");
         map { say2($_) } grep { -x } glob("*");
         exit 0;
     }
 
-    my $pgm = shift @ARGV;
-    my $fullpath = "$ENV{GL_BINDIR}/post-compile/$pgm";
+    my $pgm      = shift;
+    my $fullpath = "$ENV{GL_BINDIR}/$subdir/$pgm";
     _die "$pgm not found or not executable" if not -x $fullpath;
-    _system($fullpath, @ARGV);
+    _system( $fullpath, @_ );
     exit 0;
 }
diff --git a/src/gitolite-shell b/src/gitolite-shell
index d7f6a19..c291bc9 100755
--- a/src/gitolite-shell
+++ b/src/gitolite-shell
@@ -13,38 +13,89 @@ use Gitolite::Conf::Load;
 
 use strict;
 use warnings;
-print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
-print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
+
+# the main() sub expects ssh-ish things; set them up...
+if ( exists $ENV{G3T_USER} ) {
+    in_local();    # file:// masquerading as ssh:// for easy testing
+} elsif ( exists $ENV{SSH_CONNECTION} ) {
+    in_ssh();
+} elsif ( exists $ENV{REQUEST_URI} ) {
+    in_http();
+} else {
+    _die "who the *heck* are you?";
+}
+
+main();
+
+exit 0;
 
 # ----------------------------------------------------------------------
 
 # XXX lots of stuff from gl-auth-command is missing for now...
 
-# set up the user
-my $user = $ENV{GL_USER} = shift;
+sub in_local {
+    print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
+    print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
+}
+
+sub in_http {
+    _die 'http not yet implemented...';
+}
+
+sub in_ssh {
+}
+
+# ----------------------------------------------------------------------
 
-# set up the repo and the attempted access
-my ( $verb, $repo ) = split_soc();
-sanity($repo);
-$ENV{GL_REPO} = $repo;
-my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
+# call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
+# has been setup (even if it's not actually coming via ssh).
+sub main {
+    # set up the user
+    my $user = $ENV{GL_USER} = shift @ARGV;
 
-# a ref of 'any' signifies that this is a pre-git check, where we don't
-# yet know the ref that will be eventually pushed (and even that won't apply
-# if it's a read operation).  See the matching code in access() for more.
-my $ret = access( $repo, $user, $aa, 'any' );
-trace( 1, "access($repo, $user, $aa, 'any') -> $ret" );
-_die $ret if $ret =~ /DENIED/;
+    # set up the repo and the attempted access
+    my ( $verb, $repo ) = parse_soc();  # returns only for git commands
+    sanity($repo);
+    $ENV{GL_REPO} = $repo;
+    my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
 
-$repo = "'$rc{GL_REPO_BASE}/$repo.git'";
-exec( "git", "shell", "-c", "$verb $repo" );
+    # a ref of 'any' signifies that this is a pre-git check, where we don't
+    # yet know the ref that will be eventually pushed (and even that won't
+    # apply if it's a read operation).  See the matching code in access() for
+    # more information.
+    my $ret = access( $repo, $user, $aa, 'any' );
+    trace( 1, "access($repo, $user, $aa, 'any') -> $ret" );
+    _die $ret if $ret =~ /DENIED/;
+
+    $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
+    exec( "git", "shell", "-c", "$verb $repo" );
+}
 
 # ----------------------------------------------------------------------
 
-sub split_soc {
+sub parse_soc {
     my $soc = $ENV{SSH_ORIGINAL_COMMAND};
-    return ( $1, $2 ) if $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$);
-    _die "unknown command: $soc";
+    $soc ||= 'info';
+
+    if ( $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$) ) {
+        # TODO git archive
+        my($verb, $repo) = ($1, $2);
+        _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
+        return ($verb, $repo);
+    }
+
+    # after this we should not return; caller expects us to handle it all here
+    # and exit out
+
+    _die "suspicious characters loitering about '$soc'" if $soc !~ $REMOTE_COMMAND_PATT;
+
+    my @words = split ' ', $soc;
+    if ($rc{COMMANDS}{$words[0]}) {
+        _system("gitolite", @words);
+        exit 0;
+    }
+
+    _die "unknown git/gitolite command: $soc";
 }
 
 sub sanity {
diff --git a/src/post-compile/ssh-authkeys b/src/post-compile/ssh-authkeys
index c45e388..5e5ad4b 100755
--- a/src/post-compile/ssh-authkeys
+++ b/src/post-compile/ssh-authkeys
@@ -89,7 +89,7 @@ sub fp {
         return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
     } else {
         # one or more actual keys
-        return map { fp_line($_) } grep { !/^#/ and /\S/ } ($in, @_);
+        return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ );
     }
 }
 
diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines
index 1d25379..3c28f20 100755
--- a/src/syntactic-sugar/continuation-lines
+++ b/src/syntactic-sugar/continuation-lines
@@ -18,10 +18,10 @@
 sub sugar_script {
     my $lines = shift;
 
-    my @out = ();
+    my @out  = ();
     my $keep = '';
     for my $l (@$lines) {
-        if ($l =~ s/\\$//) {
+        if ( $l =~ s/\\$// ) {
             $keep .= $l;
         } else {
             $l = $keep . $l if $keep;
diff --git a/t/glt b/t/glt
index 45e7b19..09d4429 100755
--- a/t/glt
+++ b/t/glt
@@ -12,7 +12,10 @@ my $user = shift or die "need user";
 my $rc;
 
 $ENV{G3T_USER} = $user;
-if ( $cmd eq 'push' ) {
+if ($cmd eq 'info' ) {
+    $ENV{SSH_ORIGINAL_COMMAND} = $cmd;
+    exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
+} elsif ( $cmd eq 'push' ) {
     $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV );
 } else {
     $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV );

commit 0aeb0cd5e2e37387a7fff5e11ff3012eb5b65646
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 21:33:15 2012 +0530

    ssh-authkeys done!

diff --git a/src/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
index ef8e522..ab94e23 100644
--- a/src/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -19,7 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub post_update {
-    trace(3);
+    trace(3, @ARGV);
     # this is the *real* post_update hook for gitolite
 
     tsh_try("git ls-tree --name-only master");
@@ -31,6 +31,24 @@ sub post_update {
     }
     _system("$ENV{GL_BINDIR}/gitolite compile");
 
+    # now run optional post-compile features
+    if (exists $rc{POST_COMPILE}) {
+        if (ref($rc{POST_COMPILE}) ne 'ARRAY') {
+            _warn "bad syntax for specifying post compile scripts; see docs";
+        } else {
+            for my $s (@{ $rc{POST_COMPILE} }) {
+
+                # perl-ism; apart from keeping the full path separate from the
+                # simple name, this also protects %rc from change by implicit
+                # aliasing, which would happen if you touched $s itself
+                my $sfp = "$ENV{GL_BINDIR}/post-compile/$s";
+
+                _warn("skipped post-compile script '$s'"), next if not -x $sfp;
+                _system($sfp, @ARGV);   # they better all return with 0 exit codes!
+            }
+        }
+    }
+
     exit 0;
 }
 
@@ -64,5 +82,5 @@ use Gitolite::Hooks::PostUpdate;
 # gitolite post-update hook (only for the admin repo)
 # ----------------------------------------------------------------------
 
-post_update(@ARGV);     # is not expected to return
+post_update();          # is not expected to return
 exit 1;                 # so if it does, something is wrong
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index dc94e97..cc13465 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -19,7 +19,7 @@ use warnings;
 # ----------------------------------------------------------------------
 
 sub update {
-    trace( 3, @_ );
+    trace( 3, @ARGV );
     # this is the *real* update hook for gitolite
 
     my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
@@ -150,5 +150,5 @@ use Gitolite::Hooks::Update;
 # gitolite update hook
 # ----------------------------------------------------------------------
 
-update(@ARGV);          # is not expected to return
+update();               # is not expected to return
 exit 1;                 # so if it does, something is wrong
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index f526fde..be82ab2 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -165,19 +165,28 @@ __DATA__
 
 # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
 
-# this file is in perl syntax.  However, you do NOT need to know perl to edit
-# it; it should be fairly self-explanatory and easy to maintain.  Just mind
-# the commas, make sure the brackets and braces stay matched up!
+# This file is in perl syntax.
+
+# However, you do NOT need to know perl to edit it; it should be fairly
+# self-explanatory and easy to maintain.  Just mind the commas (perl is quite
+# happy to have an extra one at the end of the last item in any list, by the
+# way!).  And make sure the brackets and braces stay matched up!
 
 %RC = (
     UMASK                       =>  0077,
     GL_GITCONFIG_KEYS           =>  "",
 
-    # uncomment as needed
+    # comment out or uncomment as needed
     SYNTACTIC_SUGAR             =>
         [
             # 'continuation-lines',
-        ]
+        ],
+
+    # comment out or uncomment as needed
+    POST_COMPILE                =>
+        [
+            'ssh-authkeys',
+        ],
 );
 
 # ------------------------------------------------------------------------------
diff --git a/src/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
index b42e0fc..d335147 100644
--- a/src/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -47,6 +47,7 @@ sub setup {
     }
 
     _system("$ENV{GL_BINDIR}/gitolite compile");
+    _system("$ENV{GL_BINDIR}/gitolite post-compile ssh-authkeys") if $pubkey;
 
     hook_repos();    # all of them, just to be sure
 }
diff --git a/src/gitolite b/src/gitolite
index 0457cc2..d8c82af 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -6,7 +6,8 @@
 =for usage
 Usage:  gitolite [sub-command] [options]
 
-The following subcommands are available; they should all respond to '-h':
+The following subcommands are available; they should all respond to '-h' if
+you want further details on each:
 
     setup                       1st run: initial setup; all runs: hook fixups
     compile                     compile gitolite.conf
@@ -17,6 +18,7 @@ The following subcommands are available; they should all respond to '-h':
     list-phy-repos              list all repos actually on disk
     list-memberships            list all groups a name is a member of
     list-members                list all members of a group
+    post-compile                run a post-compile command
 
 Warnings:
   - list-users is disk bound and could take a while on sites with 1000s of repos
@@ -89,7 +91,35 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_members() } );
+    } elsif ( $command eq 'post-compile' ) {
+        shift @ARGV;
+        post_compile();
     } else {
         _die "unknown gitolite sub-command";
     }
 }
+
+=for post-compile
+Usage:  gitolite post-compile [-l] [post-compile-scriptname] [script args...]
+
+    -l          list currently available post-compile scripts
+
+Run a post-compile script (which normally runs from the post-update hook in
+the gitolite-admin repo).
+=cut
+
+sub post_compile {
+    usage('', 'post-compile') if (@ARGV and $ARGV[0] eq '-h');
+
+    if (@ARGV and $ARGV[0] eq '-l') {
+        _chdir("$ENV{GL_BINDIR}/post-compile");
+        map { say2($_) } grep { -x } glob("*");
+        exit 0;
+    }
+
+    my $pgm = shift @ARGV;
+    my $fullpath = "$ENV{GL_BINDIR}/post-compile/$pgm";
+    _die "$pgm not found or not executable" if not -x $fullpath;
+    _system($fullpath, @ARGV);
+    exit 0;
+}
diff --git a/src/post-compile/ssh-authkeys b/src/post-compile/ssh-authkeys
new file mode 100755
index 0000000..c45e388
--- /dev/null
+++ b/src/post-compile/ssh-authkeys
@@ -0,0 +1,129 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use File::Temp qw(tempfile);
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+$|++;
+
+# can be called directly, or as a post-update hook.  Since it ignores
+# arguments anyway, it hardly matters.
+
+my $ab      = `gitolite query-rc -n GL_ADMIN_BASE`;
+_warn("'keydir' not found in '$ab'"), exit if not -d "$ab/keydir";
+my $akfile  = "$ENV{HOME}/.ssh/authorized_keys";
+my $glshell = `gitolite query-rc -n GL_BINDIR` . "/gitolite-shell";
+# XXX gl-time not yet coded (GL_PERFLOGT)
+my $auth_options = auth_options();
+
+sanity();
+
+# ----------------------------------------------------------------------
+
+_chdir($ab);
+
+# old data
+my $old_ak = slurp($akfile);
+my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
+chomp(@non_gl);
+my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
+# die 1;
+
+# pubkey files
+chomp( my @pubkeys = `find keydir -type f -name "*.pub" | sort` );
+my @gl_keys = ();
+for my $f (@pubkeys) {
+    my $fp = fp($f);
+    if ( $seen{$fp} ) {
+        _warn "$f duplicates $seen{$fp}, sshd will ignore it";
+    } else {
+        $seen{$fp} = $f;
+    }
+    push @gl_keys, grep { /./ } optionise($f);
+}
+
+# dump it out
+if (@gl_keys) {
+    my $out = join( "\n", map { my $_ = $_; chomp($_); $_ } @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
+
+    my $ak = slurp($akfile);
+    _die "$akfile changed between start and end of this program!" if $ak ne $old_ak;
+    _print( $akfile, $out );
+}
+
+# ----------------------------------------------------------------------
+
+sub sanity {
+    _warn "$akfile missing; creating a new one"                         if not -f $akfile;
+    _die "$glshell not found; this should NOT happen..."                if not -f $glshell;
+    _die "$glshell found but not readable; this should NOT happen..."   if not -r $glshell;
+    _die "$glshell found but not executable; this should NOT happen..." if not -x $glshell;
+
+    if ( not -f $akfile ) {
+        _print( $akfile, "" );
+        chmod 0700, $akfile;
+    }
+}
+
+sub auth_options {
+    my $auth_options = `gitolite query-rc AUTH_OPTIONS`;
+    chomp($auth_options);
+    $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
+
+    return $auth_options;
+}
+
+sub fp {
+    # input: see below
+    # output: a (list of) FPs
+    my $in = shift || '';
+    if ( $in =~ /\.pub$/ ) {
+        # single pubkey file
+        return fp_file($in);
+    } elsif ( -f $in ) {
+        # an authkeys file
+        return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
+    } else {
+        # one or more actual keys
+        return map { fp_line($_) } grep { !/^#/ and /\S/ } ($in, @_);
+    }
+}
+
+sub fp_file {
+    my $f  = shift;
+    my $fp = `ssh-keygen -l -f $f`;
+    chomp($fp);
+    _die "fingerprinting failed for $f" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/;
+    $fp = $1;
+    return $fp;
+}
+
+sub fp_line {
+    my ( $fh, $fn ) = tempfile();
+    print $fh shift;
+    close $fh;
+    my $fp = fp_file($fn);
+    unlink $fn;
+    return $fp;
+}
+
+sub optionise {
+    my $f = shift;
+
+    my $user = $f;
+    $user =~ s(.*/)();                # foo/bar/baz.pub -> baz.pub
+    $user =~ s/(\@[^.]+)?\.pub$//;    # baz.pub, baz at home.pub -> baz
+
+    my @line = slurp($f);
+    if ( @line != 1 ) {
+        _warn "$f does not contain exactly 1 line; ignoring";
+        return '';
+    }
+    chomp(@line);
+    return "command=\"$glshell $user\",$auth_options $line[0]";
+}
+
diff --git a/t/reset b/t/reset
index 8c5dcbf..cfdc3db 100755
--- a/t/reset
+++ b/t/reset
@@ -2,6 +2,10 @@
 use strict;
 use warnings;
 
+BEGIN {
+    unlink "$ENV{HOME}/.ssh/authorized_keys";
+}
+
 # this is hardcoded; change it if needed
 use lib "src";
 use Gitolite::Test;
diff --git a/t/ssh-authkeys.t b/t/ssh-authkeys.t
new file mode 100755
index 0000000..64179b4
--- /dev/null
+++ b/t/ssh-authkeys.t
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+$ENV{GL_BINDIR} = "$ENV{PWD}/src";
+
+my $ak = "$ENV{HOME}/.ssh/authorized_keys";
+my $kd = `gitolite query-rc -n GL_ADMIN_BASE` . "/keydir";
+
+try "plan 50";
+
+my $pgm = "gitolite post-compile ssh-authkeys";
+
+try "
+    # prep
+    rm -rf $ak;                 ok
+
+    $pgm;                       ok;    /'keydir' not found/
+    mkdir $kd;                  ok
+    cd $kd;                     ok
+    $pgm;                       ok;     /authorized_keys missing/
+                                        /creating/
+    wc < $ak;                   ok;     /0 *0 *0/
+    # some gl keys
+    ssh-keygen -N '' -q -f alice -C alice
+    ssh-keygen -N '' -q -f bob   -C bob
+    ssh-keygen -N '' -q -f carol -C carol
+    ssh-keygen -N '' -q -f dave  -C dave
+    ssh-keygen -N '' -q -f eve   -C eve
+    rm alice bob carol dave eve
+    ls -a;                      ok;     /alice.pub/; /bob.pub/; /carol.pub/; /dave.pub/; /eve.pub/
+    $pgm;                       ok;
+    wc    < $ak;                ok;     /^ *7 .*/;
+    grep gitolite $ak;          ok;     /start/
+                                        /end/
+
+    # some normal keys
+    mv alice.pub $ak;           ok
+    cat carol.pub >> $ak;       ok
+    $pgm;                       ok;     /carol.pub duplicates.*non-gitolite key/
+    wc < $ak;                   ok;     /^ *8 .*/;
+
+    # moving normal keys up
+    mv dave.pub dave
+    $pgm;                       ok
+    cat dave >> $ak;            ok
+    grep -n dave $ak;           ok;     /8:ssh-rsa/
+    mv dave dave.pub
+    $pgm;                       ok;     /carol.pub duplicates.*non-gitolite key/
+                                         /dave.pub duplicates.*non-gitolite key/
+    grep -n dave $ak;           ok;     /3:ssh-rsa/
+
+    # a bad key
+    ls -al > bad.pub
+    $pgm;                       !ok;    /fingerprinting failed for keydir/bad.pub/
+    wc < $ak;                   ok;     /^ *9 .*/;
+    # a good key doesn't get added
+    ssh-keygen -N '' -q -f good
+    $pgm;                       !ok;    /fingerprinting failed for keydir/bad.pub/
+    wc < $ak;                   ok;     /^ *9 .*/;
+    # till the bad key is removed
+    rm bad.pub
+    $pgm;                       ok;
+    wc < $ak;                   ok;     /^ *10 .*/;
+
+    # duplicate gl key
+    cp bob.pub robert.pub
+    $pgm;                       ok;     /robert.pub duplicates.*bob.pub/
+";

commit cbd4d4368783a2990718658cf3f1b19d1abb3f20
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 12 10:43:33 2012 +0530

    (minor) usage() sub can handle multiple usage sections in the same script

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 3ac2301..eb4b6f1 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -75,10 +75,12 @@ sub _die {
 }
 
 sub usage {
-    _warn(shift) if @_;
+    my ($warn, $section) = @_;
+    _warn($warn) if $warn;
+    $section ||= 'usage';
     my $scriptname = ( caller() )[1];
     my $script     = slurp($scriptname);
-    $script =~ /^=for usage(.*?)^=cut/sm;
+    $script =~ /^=for $section(.*?)^=cut/sm;
     say2( $1 ? $1 : "...no usage message in $scriptname" );
     exit 1;
 }

commit 84422ccf3077930ce2294ccf9447b0a1b20b8e80
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Mon Mar 12 08:38:50 2012 +0530

    (rc) prefix GL_BINDIR to PATH
    
    Needed when the user didn't actually "install" but is just running it by
    using the full path to "gitolite".  Without this, every time my code
    runs "gitolite <some sub-command>" I have to prefix "gitolite" with
    $ENV{GL_BINDIR}, which is kinda painful...

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index d716b6d..f526fde 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -52,6 +52,9 @@ _die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
+# fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
+$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
+
 # ----------------------------------------------------------------------
 
 use strict;

commit 428485086f676bb9a6948b799bb4b369e1ddec8d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 21:45:07 2012 +0530

    query-rc learned '-n' to avoid the need to chomp() the result

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 7ed5cef..d716b6d 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -101,7 +101,10 @@ sub glrc {
 =for usage
 
 Usage:  gitolite query-rc -a
-        gitolite query-rc <list of rc variables>
+        gitolite query-rc [-n] <list of rc variables>
+
+    -a          print all variables and values
+    -n          do not append a newline
 
 Example:
 
@@ -115,6 +118,7 @@ Example:
 # ----------------------------------------------------------------------
 
 my $all = 0;
+my $nonl = 0;
 
 sub query_rc {
     trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
@@ -123,14 +127,14 @@ sub query_rc {
 
     no strict 'refs';
 
-    if ( $vars[0] eq '-a' ) {
+    if ( $all ) {
         for my $e (sort keys %rc) {
             print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n";
         }
         return;
     }
 
-    print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars;
+    print join( "\t", map { $rc{$_} || '' } @vars ) . ($nonl ? '' : "\n") if @vars;
 }
 
 # ----------------------------------------------------------------------
@@ -140,11 +144,11 @@ sub args {
 
     GetOptions(
         'all|a'  => \$all,
+        'nonl|n' => \$nonl,
         'help|h' => \$help,
     ) or usage();
 
     usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
-    return '-a' if $all;
     usage() if not $all and not @ARGV or $help;
     return @ARGV;
 }

commit ef476f0d32d85529d0bf6a15af5de7a7c0db1f06
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 20:46:31 2012 +0530

    common: slurp() learns to look at wantarray

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index d2f2cea..3ac2301 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -127,7 +127,8 @@ sub _print {
 }
 
 sub slurp {
-    local $/ = undef;
+    return unless defined wantarray;
+    local $/ = undef unless wantarray;
     my $fh = _open( "<", $_[0] );
     return <$fh>;
 }

commit c19f75e119df6577473be97d70a71732969f71a9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 18:30:06 2012 +0530

    (subconf) add the warning message
    
    (not as prominent as in g2 though...)

diff --git a/src/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
index 6c7dca6..6fcc0cf 100644
--- a/src/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -73,6 +73,7 @@ sub parse {
             _warn "?? $line";
         }
     }
+    parse_done();
 }
 
 1;
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 23d918f..c513669 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -15,6 +15,7 @@ package Gitolite::Conf::Store;
   new_repo
   hook_repos
   store
+  parse_done
 );
 
 use Exporter 'import';
@@ -205,6 +206,13 @@ sub store {
     store_common();
 }
 
+sub parse_done {
+    for my $ig (sort keys %ignored)
+    {
+        _warn "$ig.conf attempting to set access for " . join (", ", sort keys %{ $ignored{$ig} });
+    }
+}
+
 # ----------------------------------------------------------------------
 
 sub check_subconf_repo_disallowed {

commit d64663d12e0421e6a806baf7502076697205fa72
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 16:38:46 2012 +0530

    COUNT VREF and tests

diff --git a/src/VREF/COUNT b/src/VREF/COUNT
new file mode 100755
index 0000000..f61ab57
--- /dev/null
+++ b/src/VREF/COUNT
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# gitolite VREF to count number of changed/new files in a push
+
+# see gitolite docs for what the first 7 arguments mean
+
+# inputs:
+#   arg-8 is a number
+#   arg-9 is optional, and can be "NEWFILES"
+# outputs (STDOUT)
+#   arg-7 if the number of changed (or new, if arg-9 supplied) files is > arg-8
+#   otherwise nothing
+# exit status:
+#   always 0
+
+die() { echo "$@" >&2; exit 1; }
+[ -z "$8" ] && die "not meant to be run manually"
+
+newsha=$3
+oldtree=$4
+newtree=$5
+refex=$7
+
+max=$8
+
+nf=
+[ "$9" = "NEWFILES" ] && nf='--diff-filter=A'
+# NO_SIGNOFF implies NEWFILES
+[ "$9" = "NO_SIGNOFF" ] && nf='--diff-filter=A'
+
+# count files against all the other commits in the system not just $oldsha
+# (why?  consider what is $oldtree when you create a new branch, or what is
+# $oldsha when you update an old feature branch from master and then push it
+count=`git log --name-only $nf --format=%n $newtree --not --all | grep . | sort -u | wc -l`
+
+[[ $count -gt $max ]] && {
+    # count has been exceeded.  If $9 was NO_SIGNOFF there's still a chance
+    # for redemption -- if the top commit has a proper signed-off by line
+    [ "$9" = "NO_SIGNOFF" ] && {
+        author_email=$(git log --format=%ae -1 $newsha)
+        git cat-file -p $newsha |
+            egrep -i >/dev/null "^ *$count +new +files +signed-off by: *$author_email *$" && exit 0
+        echo $refex top commit message should include the text \'$count new files signed-off by: $author_email\'
+        exit 0
+    }
+    echo -n $refex "(too many "
+    [ -n "$nf" ] && echo -n "new " || echo -n "changed "
+    echo "files in this push)"
+}
+
+exit 0
diff --git a/src/VREF/FILETYPE b/src/VREF/FILETYPE
new file mode 100755
index 0000000..e61acc6
--- /dev/null
+++ b/src/VREF/FILETYPE
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# gitolite VREF to find autogenerated files
+
+# *completely* site specific; use it as an illustration of what can be done
+# with gitolite VREFs if you wish
+
+# see gitolite docs for what the first 7 arguments mean
+
+# inputs:
+#   arg-8 is currently only one possible value: AUTOGENERATED
+# outputs (STDOUT)
+#   arg-7 if any files changed in the push look like they were autogenerated
+#   otherwise nothing
+# exit status:
+#   always 0
+
+die() { echo "$@" >&2; exit 1; }
+[ -z "$8" ] && die "not meant to be run manually"
+
+newsha=$3
+oldtree=$4
+newtree=$5
+refex=$7
+
+option=$8
+
+[ "$option" = "AUTOGENERATED" ] && {
+    # currently we only look for ".java" programs with the string "Generated
+    # by the protocol buffer compiler.  DO NOT EDIT" in them.
+
+    git log --name-only $nf --format=%n $newtree --not --all |
+        grep . |
+        sort -u |
+        grep '\.java$' |
+    while read fn
+    do
+        git show "$newtree:$fn" | egrep >/dev/null \
+            'Generated by the protocol buffer compiler. +DO NOT EDIT' ||
+            continue
+
+        echo $refex
+        exit 0
+    done
+}
diff --git a/t/vrefs-1.t b/t/vrefs-1.t
new file mode 100755
index 0000000..4512ab8
--- /dev/null
+++ b/t/vrefs-1.t
@@ -0,0 +1,135 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try "plan 90";
+
+put "conf/gitolite.conf", "
+    repo gitolite-admin
+        RW+     =   admin
+
+    \@gfoo = foo
+    \@lead = u1
+    \@dev2 = u2
+    \@dev4 = u4
+    \@devs = \@dev2 \@dev4 u6
+    repo  \@gfoo
+          RW+                   =   \@lead \@devs
+          # intentional mis-spelling
+          -     VREF/MISCOUNT/2    =   \@dev2
+          -     VREF/MISCOUNT/4    =   \@dev4
+          -     VREF/MISCOUNT/3/NEWFILES   =   u6
+          -     VREF/MISCOUNT/6            =   u6
+";
+
+try "
+    ADMIN_PUSH vr1a
+    cd ..
+    ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
+    CLONE u1 file://foo;        ok;     /Cloning into/
+                                        /You appear to have cloned an empty/
+    cd foo;                     ok
+    ls -Al;                     ok;     /\.git/
+
+    # VREF not called for u1
+    tc a1 a2 a3 a4 a5;          ok;     /aaf9e8e/
+    PUSH u1 origin master;      ok;     /new branch.*master -. master/
+                                        !/helper program missing/
+                                        !/hook declined/
+                                        !/remote rejected/
+    # VREF is called for u2
+    tc b1;                      ok;     /1f440d3/
+    PUSH u2 origin;             !ok;    /helper program missing/
+                                        /hook declined/
+                                        /remote rejected/
+";
+
+put "../gitolite-admin/conf/gitolite.conf", "
+    repo gitolite-admin
+        RW+     =   admin
+
+    \@gfoo = foo
+    \@lead = u1
+    \@dev2 = u2
+    \@dev4 = u4
+    \@devs = \@dev2 \@dev4 u6
+    repo  \@gfoo
+          RW+                   =   \@lead \@devs
+          -     VREF/COUNT/2    =   \@dev2
+          -     VREF/COUNT/4    =   \@dev4
+          -     VREF/COUNT/3/NEWFILES   =   u6
+          -     VREF/COUNT/6            =   u6
+";
+
+try "
+    ADMIN_PUSH vr1b
+    cd ../foo;                  ok
+
+    # u2 1 file
+    PUSH u2 origin;             ok;     /aaf9e8e..1f440d3.*master -. master/
+
+    # u2 2 files
+    tc b2 b3;                   ok;     /c3397f7/
+    PUSH u2 origin;             ok;     /1f440d3..c3397f7.*master -. master/
+
+    # u2 3 files
+    tc c1 c2 c3;                ok;     /be242d7/
+    PUSH u2 origin;             !ok;    /W VREF/COUNT/2 foo u2 DENIED by VREF/COUNT/2/
+                                        /too many changed files in this push/
+                                        /hook declined/
+                                        /remote rejected/
+
+    # u4 3 files
+    PUSH u4 origin;             ok;     /c3397f7..be242d7.*master -. master/
+
+    # u4 4 files
+    tc d1 d2 d3 d4;             ok;     /88d80e2/
+    PUSH u4 origin;             ok;     /be242d7..88d80e2.*master -. master/
+
+    # u4 5 files
+    tc d5 d6 d7 d8 d9;          ok;     /e9c60b0/
+    PUSH u4 origin;             !ok;    /W VREF/COUNT/4 foo u4 DENIED by VREF/COUNT/4/
+                                        /too many changed files in this push/
+                                        /hook declined/
+                                        /remote rejected/
+
+    # u1 all files
+    PUSH u1 origin;             ok;     /88d80e2..e9c60b0.*master -. master/
+
+    # u6 6 old files
+    test-tick
+    tc d1 d2 d3 d4 d5 d6
+                                ok;     /2773f0a/
+    PUSH u6 origin;             ok;     /e9c60b0..2773f0a.*master -. master/
+    tag six
+
+    # u6 updates 7 old files
+    test-tick; test-tick
+    tc d1 d2 d3 d4 d5 d6 d7
+                                ok;     /d3fb574/
+    PUSH u6 origin;             !ok;    /W VREF/COUNT/6 foo u6 DENIED by VREF/COUNT/6/
+                                        /too many changed files in this push/
+                                        /hook declined/
+                                        /remote rejected/
+    reset-h six;                ok;     /HEAD is now at 2773f0a/
+
+    # u6 4 new 2 old files
+    test-tick; test-tick
+    tc d1 d2 n1 n2 n3 n4
+                                ok;     /9e90848/
+    PUSH u6 origin;             !ok;    /W VREF/COUNT/3/NEWFILES foo u6 DENIED by VREF/COUNT/3/NEWFILES/
+                                        /too many new files in this push/
+                                        /hook declined/
+                                        /remote rejected/
+    reset-h six;                ok;     /HEAD is now at 2773f0a/
+
+    # u6 3 new 3 old files
+    test-tick; test-tick
+    tc d1 d2 d3 n1 n2 n3
+                                ok;     /e47ff5d/
+    PUSH u6 origin;             ok;     /2773f0a..e47ff5d.*master -. master/
+";
diff --git a/t/vrefs-2.t b/t/vrefs-2.t
new file mode 100755
index 0000000..3ff1037
--- /dev/null
+++ b/t/vrefs-2.t
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+
+try "plan 74";
+
+put "../gitolite-admin/conf/gitolite.conf", "
+    \@gfoo = foo
+    \@lead = u1
+    \@senior_devs = u2 u3
+    \@junior_devs = u4 u5 u6
+    repo  \@gfoo
+
+        RW+                                 =   \@all
+
+        RW+ VREF/COUNT/2/NO_SIGNOFF         =   \@lead
+        -   VREF/COUNT/2/NO_SIGNOFF         =   \@all
+
+        -   VREF/COUNT/10/NEWFILES          =   \@junior_devs
+
+        -   VREF/FILETYPE/AUTOGENERATED     =   \@all
+";
+
+try "
+    ADMIN_PUSH vr2a
+    cd ..
+    # setup
+    ls -al foo;                 !ok;    /cannot access foo: No such file or directory/
+    CLONE u1 file://foo;        ok;     /Cloning into/
+                                        /You appear to have cloned an empty/
+    cd foo;                     ok
+    ls -Al;                     ok;     /\.git/
+
+    # u1 push 15 new files
+    tc a b c d e f g h i j k l m n o
+                                ok;     /d8c0392/
+    PUSH u1 origin master;      ok;     /new branch.*master -. master/
+
+    # u2 push 2 new 10 old without signoff
+    tc a b c d e f g h i j u2a u2b
+                                ok;     /6787ac9/
+    PUSH u2 origin;             ok;     /d8c0392..6787ac9.*master -. master/
+
+    # u2 fail to push 3 new files without signoff
+    tc u2c u2d u2e;             ok;     /a74562b/
+    PUSH u2 origin;             !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u2 DENIED by VREF/COUNT/2/NO_SIGNOFF/
+                                        /top commit message should include the text .3 new files signed-off by: tester.example.com./
+                                        /hook declined/
+                                        /remote rejected/
+    # u2 push 15 new files with signoff
+    tc u2f u2g u2h u2i u2j u2k u2l u2m u2n u2o u2p u2q
+                                ok;     /8dd31aa/
+    git commit --allow-empty -m '15 new files signed-off by: tester\@example.com'
+                                ok;     /.master 6126489. 15 new files signed-off by: tester.example.com/
+    PUSH u2 origin;             ok;     /6787ac9..6126489.*master -. master/
+
+    # u4 push 2 new 10 old files without signoff
+    tc u4a u4b a b c d e f g h i j
+                                ok;     /76c5593/
+    PUSH u4 origin;             ok;     /6126489..76c5593.*master -. master/
+
+    # u4 fail push 3 new files withoug signoff
+    tc u4c u4d u4e;             ok;     /2a84398/
+    PUSH u4 origin;             !ok;    /W VREF/COUNT/2/NO_SIGNOFF foo u4 DENIED by VREF/COUNT/2/NO_SIGNOFF/
+                                        /top commit message should include the text .3 new files signed-off by: tester.example.com./
+                                        /hook declined/
+                                        /remote rejected/
+
+    # u4 push 10 new 5 old with signoff
+    tc u4f u4g u4h u4i u4j u4k u4l a b c d e
+                                ok;     /09b646a/
+    git commit --allow-empty -m '10 new files signed-off by: tester\@example.com'
+                                ok;     /.master 47f84b0. 10 new files signed-off by: tester.example.com/
+    PUSH u4 origin;             ok;     /76c5593..47f84b0.*master -. master/
+
+    # u4 fail push 11 new files even with signoff
+    tc u4ab u4ac u4ad u4ae u4af u4ag u4ah u4ai u4aj u4ak u4al
+                                ok;     /90e7344/
+    git commit --allow-empty -m '11 new files signed-off by: tester\@example.com'
+                                ok;     /.master 1f36537. 11 new files signed-off by: tester.example.com/
+    PUSH u4 origin;             !ok;    /W VREF/COUNT/10/NEWFILES foo u4 DENIED by VREF/COUNT/10/NEWFILES/
+                                        /too many new files in this push/
+                                        /hook declined/
+                                        /remote rejected/
+
+    # test AUTOGENERATED vref
+    glt fetch u1 origin;        ok;
+    reset-h origin/master;      ok;
+    tc not-really.java;         ok;     /0f88b2e/
+    PUSH u4 origin;             ok;     /47f84b0..0f88b2e.*master -. master/
+";
+
+put "|cat >> not-really.java", "
+    Generated by the protocol buffer compiler.  DO NOT EDIT
+";
+
+try "
+    commit -am pbc;             ok;     /b2df6ef/
+    PUSH u4 origin;             !ok;    /W VREF/FILETYPE/AUTOGENERATED foo u4 DENIED by VREF/FILETYPE/AUTOGENERATED/
+                                        /hook declined/
+                                        /remote rejected/
+";

commit 16d17def2a9c4fc24fd8f5db1bf5c55e61759033
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 07:36:42 2012 +0530

    VREF code

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 171927e..625d7eb 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -6,6 +6,7 @@ package Gitolite::Conf::Load;
 @EXPORT = qw(
   load
   access
+  vrefs
 
   list_groups
   list_users
@@ -139,26 +140,47 @@ sub load_1 {
     }
 }
 
-sub rules {
-    my ( $repo, $user ) = @_;
-    trace( 4, "repo=$repo, user=$user" );
-    my @rules = ();
+{
+    my $lastrepo = '';
+    my $lastuser = '';
+    my @cached = ();
+
+    sub rules {
+        my ( $repo, $user ) = @_;
+        trace( 4, "repo=$repo, user=$user" );
+
+        return @cached if ($lastrepo eq $repo and $lastuser eq $user and @cached);
+
+        my @rules = ();
 
-    my @repos = memberships($repo);
-    my @users = memberships($user);
-    trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
+        my @repos = memberships($repo);
+        my @users = memberships($user);
+        trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
 
-    for my $r (@repos) {
-        for my $u (@users) {
-            push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u};
+        for my $r (@repos) {
+            for my $u (@users) {
+                push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u};
+            }
         }
+
+        @rules = sort { $a->[0] <=> $b->[0] } @rules;
+
+        $lastrepo = $repo;
+        $lastuser = $user;
+        @cached = @rules;
+
+        return @rules;
     }
 
-    # dbg("before sorting rules:", \@rules);
-    @rules = sort { $a->[0] <=> $b->[0] } @rules;
-    # dbg("after sorting rules:", \@rules);
+    sub vrefs {
+        my ( $repo, $user ) = @_;
+        # fill the cache if needed
+        rules($repo, $user) unless ($lastrepo eq $repo and $lastuser eq $user and @cached);
 
-    return @rules;
+        my %seen;
+        my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
+        return @vrefs;
+    }
 }
 
 sub memberships {
diff --git a/src/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
index 37824c7..23d918f 100644
--- a/src/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -95,9 +95,9 @@ sub parse_refs {
     # if no ref is given, this PERM applies to all refs
     @refs = qw(refs/.*) unless @refs;
 
-    # fully qualify refs that dont start with "refs/" or "NAME/" or "VREF/";
+    # fully qualify refs that dont start with "refs/" or "VREF/";
     # prefix them with "refs/heads/"
-    @refs = map { m(^(refs|NAME|VREF)/) or s(^)(refs/heads/); $_ } @refs;
+    @refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs;
     # XXX what do we do? @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs;
 
     return @refs;
diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index dbf0926..30dcfc0 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -65,7 +65,7 @@ sub sugar {
 
     $lines = option($lines);
     $lines = owner_desc($lines);
-    # $lines = name_vref($lines);
+    $lines = name_vref($lines);
 
     return $lines;
 }
@@ -132,5 +132,21 @@ sub owner_desc {
     return \@ret;
 }
 
+sub name_vref {
+    my $lines = shift;
+    my @ret;
+
+    # <perm> NAME/foo = <user>
+    #   ->  <perm> VREF/NAME/foo = <user>
+
+    for my $line (@$lines) {
+        if ( $line =~ /^(-|R\S+) \S.* = \S.*/ ) {
+            $line =~ s( NAME/)( VREF/NAME/)g;
+        }
+        push @ret, $line;
+    }
+    return \@ret;
+}
+
 1;
 
diff --git a/src/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
index 488a3ec..dc94e97 100644
--- a/src/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -28,9 +28,50 @@ sub update {
     trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" );
     _die $ret if $ret =~ /DENIED/;
 
+    check_vrefs($ref, $oldsha, $newsha, $oldtree, $newtree, $aa);
+
     exit 0;
 }
 
+sub check_vrefs {
+    my($ref, $oldsha, $newsha, $oldtree, $newtree, $aa) = @_;
+    my $name_seen = 0;
+    for my $vref ( vrefs($ENV{GL_REPO}, $ENV{GL_USER}) ) {
+        trace(1, "vref=$vref");
+        if ($vref =~ m(^VREF/NAME/)) {
+            # this one is special; we process it right here, and only once
+            next if $name_seen++;
+
+            for my $ref ( map { chomp; s(^)(VREF/NAME/); $_; } `git diff --name-only $oldtree $newtree` ) {
+                check_vref($aa, $ref);
+            }
+        } else {
+            my($dummy, $pgm, @args) = split '/', $vref;
+            $pgm = "$ENV{GL_BINDIR}/VREF/$pgm";
+            -x $pgm or die "$vref: helper program missing or unexecutable\n";
+
+            open( my $fh, "-|", $pgm, @_, $vref, @args ) or die "$vref: can't spawn helper program: $!\n";
+            while (<$fh>) {
+                my ( $ref, $deny_message ) = split( ' ', $_, 2 );
+                check_vref($aa, $ref, $deny_message);
+            }
+            close($fh) or die $!
+              ? "Error closing sort pipe: $!"
+              : "$vref: helper program exit status $?";
+        }
+    }
+}
+
+sub check_vref {
+    my ($aa, $ref, $deny_message) = @_;
+
+    my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
+    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref)", "-> $ret" );
+    _die "$ret" . ( $deny_message ? "\n$deny_message" : '' )
+        if $ret =~ /DENIED/ and $ret !~ /by fallthru/;
+    trace( 1, "remember, fallthru is success here!") if $ret =~ /by fallthru/;
+}
+
 {
     my $text = '';
 

commit ef021ee2934b8b0d1233a93c521934115f0653eb
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 16:24:00 2012 +0530

    (test) forgot to set user.email/name to the standard value
    
    caused old test scripts to fail (wherever I was checking the actual SHA
    anyway)

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 0e15181..f950fb3 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -37,7 +37,7 @@ try "
     DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
 
     DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
-    DEF CHECK_SETUP = CS_1; git log; ok; /65a1b2acd78dd9a7a401fe81c25380c1ca90067c/
+    DEF CHECK_SETUP = CS_1; git log; ok; /6b18ec2ab0f765122ec133959b36c57f77d4565c/
 
     DEF CLONE = glt clone
     DEF PUSH  = glt push
@@ -46,6 +46,8 @@ try "
     mkdir -p $ENV{HOME}/bin
     ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin
     cd; rm -vrf .gito* gito* repositories
+    git config --global user.name \"gitolite tester\"
+    git config --global user.email \"tester\@example.com\"
 
     # setup
     gitolite setup -a admin
diff --git a/t/basic.t b/t/basic.t
index 8b1aa7c..fddc342 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -81,7 +81,7 @@ try "
     cd t1;                      ok;
 
     # push
-    test-commit tc1 tc2 tc2;    ok;     /f7153e3/
+    test-commit tc1 tc2 tc2;    ok;     /a530e66/
     PUSH u2 origin;             !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
@@ -89,15 +89,15 @@ try "
                                         /master -> master/
 
     # rewind
-    reset-h HEAD^;              ok;     /HEAD is now at 537f964 tc2/
-    test-tick; test-commit tc3; ok;     /a691552/
+    reset-h HEAD^;              ok;     /HEAD is now at aa2b5c5 tc2/
+    test-tick; test-commit tc3; ok;     /3ffced1/
     PUSH u3 origin;             !ok;    gsh
                                         /rejected.*master -> master.*non-fast-forward./
     PUSH u3 -f origin;          !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
     PUSH u4 origin +master;     ok;     gsh
-                                        / \\+ f7153e3...a691552 master -> master.*forced update./
+                                        / \\+ a530e66...3ffced1 master -> master.*forced update./
 ";
 
 put "../gitolite-admin/conf/gitolite.conf", "
@@ -146,7 +146,7 @@ try "
     test-commit r1;             ok
     PUSH u1 origin +master;     ok;     gsh
                                         /To file://aa/
-                                        /\\+ cecf671...70469f5 master -> master .forced update./
+                                        /\\+ 27ed463...05adfb0 master -> master .forced update./
 
     # u2 rewind master !ok
     reset-h HEAD^;              ok
@@ -160,13 +160,13 @@ try "
     test-commit r3;             ok
     PUSH u3 origin +master;     ok;     gsh
                                         /To file://aa/
-                                        /\\+ 70469f5...f1e6821 master -> master .forced update./
+                                        /\\+ 05adfb0...6a532fe master -> master .forced update./
 
     # u4 push master ok
     test-commit u4;             ok
     PUSH u4 origin master;      ok;     gsh
                                         /To file://aa/
-                                        /f1e6821..d308cfb +master -> master/
+                                        /6a532fe..f929773 +master -> master/
 
     # u4 rewind master !ok
     reset-h HEAD^;              ok
@@ -191,7 +191,7 @@ try "
     # clean up for next set
     PUSH u1 -f origin master dev foo
                                 ok;     gsh
-                                        /d308cfb...f1e6821 master -> master .forced update./
+                                        /f929773...6a532fe master -> master .forced update./
                                         /new branch.*dev -> dev/
                                         /new branch.*foo -> foo/
 
@@ -204,7 +204,7 @@ try "
     # u5 rewind dev ok
     PUSH u5 origin +dev^:dev
                                 ok;     gsh
-                                        /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./
+                                        /\\+ 27ed463...1ad477a dev\\^ -> dev .forced update./
 
 
     # u5 rewind foo !ok
@@ -219,7 +219,7 @@ try "
 
     test-commit u5
     PUSH u5 origin foo;         ok;     gsh
-                                        /cecf671..b27cf19 *foo -> foo/
+                                        /27ed463..83da62c *foo -> foo/
 
     # u1 delete dev ok
     PUSH u1 origin :dev;        ok;     gsh

commit fb69f6e3284253086bb4e00c4dff9aa841051ca6
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 13:10:32 2012 +0530

    (test setup) make Test.pm do a bit more

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index 8251c41..0e15181 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -36,12 +36,24 @@ try "
     DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/
     DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
 
+    DEF CS_1 = pwd; //tmp/tsh_tempdir.*gitolite-admin/; git remote -v; ok; /file://gitolite-admin/
+    DEF CHECK_SETUP = CS_1; git log; ok; /65a1b2acd78dd9a7a401fe81c25380c1ca90067c/
+
+    DEF CLONE = glt clone
+    DEF PUSH  = glt push
+
+    # clean install
     mkdir -p $ENV{HOME}/bin
     ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin
     cd; rm -vrf .gito* gito* repositories
 
-    cd tsh_tempdir;
+    # setup
     gitolite setup -a admin
+
+    # clone admin repo
+    cd tsh_tempdir
+    glt clone admin --progress file://gitolite-admin
+    cd gitolite-admin
 " or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
 
 sub dump {
diff --git a/t/basic.t b/t/basic.t
index 96c4012..8b1aa7c 100755
--- a/t/basic.t
+++ b/t/basic.t
@@ -10,14 +10,16 @@ use Gitolite::Test;
 # ----------------------------------------------------------------------
 
 try "
-    plan 213
+    plan 218
+    CHECK_SETUP
 
     ## subtest 1
-    glt clone dev2 file://gitolite-admin
+    cd ..
+    CLONE dev2 file://gitolite-admin ga2
                                 !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    glt clone admin --progress file://gitolite-admin
+    CLONE admin --progress file://gitolite-admin ga2
                                 ok;     gsh
                                         /Counting/; /Compressing/; /Total/
     cd gitolite-admin;          ok
@@ -39,12 +41,12 @@ try "
     git add conf;               ok
     git status -s;              ok;     /M  conf/gitolite.conf/
     git commit -m t01a;         ok;     /master.*t01a/
-    glt push dev2 origin;       !ok;    gsh
+    PUSH dev2 origin;           !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    glt push admin origin;      ok;     /master -> master/
+    PUSH admin origin;          ok;     /master -> master/
     tsh empty;                  ok;
-    glt push admin origin master:mm
+    PUSH admin origin master:mm
                                 !ok;    gsh
                                         /DENIED by refs/heads/mm/
                                         reject
@@ -70,31 +72,31 @@ try "
 
     # clone
     cd ..;                      ok;
-    glt clone u1 file://t1;     !ok;    gsh
+    CLONE u1 file://t1;         !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    glt clone u2 file://t1;     ok;     gsh
+    CLONE u2 file://t1;         ok;     gsh
                                         /warning: You appear to have cloned an empty repository./
     ls -al t1;                  ok;     /$ENV{USER}.*$ENV{USER}.*\.git/
     cd t1;                      ok;
 
     # push
     test-commit tc1 tc2 tc2;    ok;     /f7153e3/
-    glt push u2 origin;         !ok;    gsh
+    PUSH u2 origin;             !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    glt push u3 origin master;  ok;     gsh
+    PUSH u3 origin master;      ok;     gsh
                                         /master -> master/
 
     # rewind
     reset-h HEAD^;              ok;     /HEAD is now at 537f964 tc2/
     test-tick; test-commit tc3; ok;     /a691552/
-    glt push u3 origin;         !ok;    gsh
+    PUSH u3 origin;             !ok;    gsh
                                         /rejected.*master -> master.*non-fast-forward./
-    glt push u3 -f origin;      !ok;    gsh
+    PUSH u3 -f origin;          !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    glt push u4 origin +master; ok;     gsh
+    PUSH u4 origin +master;     ok;     gsh
                                         / \\+ f7153e3...a691552 master -> master.*forced update./
 ";
 
@@ -129,11 +131,11 @@ try "
 ";
 
 try "
-    glt clone u1 file://aa;     ok;     gsh
+    CLONE u1 file://aa;         ok;     gsh
     cd aa;                      ok
     test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9
                                 ok
-    glt push u1 origin HEAD;    ok;     gsh
+    PUSH u1 origin HEAD;        ok;     gsh
                                         /To file://aa/
                                         /\\* \\[new branch\\]      HEAD -> master/
     branch dev;                 ok
@@ -142,52 +144,52 @@ try "
     # u1 rewind master ok
     reset-h HEAD^;              ok
     test-commit r1;             ok
-    glt push u1 origin +master; ok;     gsh
+    PUSH u1 origin +master;     ok;     gsh
                                         /To file://aa/
                                         /\\+ cecf671...70469f5 master -> master .forced update./
 
     # u2 rewind master !ok
     reset-h HEAD^;              ok
     test-commit r2;             ok
-    glt push u2 origin +master; !ok;    gsh
+    PUSH u2 origin +master;     !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # u3 rewind master ok
     reset-h HEAD^;              ok
     test-commit r3;             ok
-    glt push u3 origin +master; ok;     gsh
+    PUSH u3 origin +master;     ok;     gsh
                                         /To file://aa/
                                         /\\+ 70469f5...f1e6821 master -> master .forced update./
 
     # u4 push master ok
     test-commit u4;             ok
-    glt push u4 origin master;  ok;     gsh
+    PUSH u4 origin master;      ok;     gsh
                                         /To file://aa/
                                         /f1e6821..d308cfb +master -> master/
 
     # u4 rewind master !ok
     reset-h HEAD^;              ok
-    glt push u4 origin +master; !ok;    gsh
+    PUSH u4 origin +master;     !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # u3,u4 push other branches !ok
-    glt push u3 origin dev;     !ok;    gsh
+    PUSH u3 origin dev;         !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    glt push u4 origin dev;     !ok;    gsh
+    PUSH u4 origin dev;         !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    glt push u3 origin foo;     !ok;    gsh
+    PUSH u3 origin foo;         !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
-    glt push u4 origin foo;     !ok;    gsh
+    PUSH u4 origin foo;         !ok;    gsh
                                         reject
                                         /DENIED by fallthru/
 
     # clean up for next set
-    glt push u1 -f origin master dev foo
+    PUSH u1 -f origin master dev foo
                                 ok;     gsh
                                         /d308cfb...f1e6821 master -> master .forced update./
                                         /new branch.*dev -> dev/
@@ -195,18 +197,18 @@ try "
 
     # u5 push master !ok
     test-commit u5
-    glt push u5 origin master;  !ok;    gsh
+    PUSH u5 origin master;      !ok;    gsh
                                         reject
                                         /DENIED by refs/heads/master/
 
     # u5 rewind dev ok
-    glt push u5 origin +dev^:dev
+    PUSH u5 origin +dev^:dev
                                 ok;     gsh
                                         /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./
 
 
     # u5 rewind foo !ok
-    glt push u5 origin +foo^:foo
+    PUSH u5 origin +foo^:foo
                                 !ok;    gsh
                                         reject
                                         /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/
@@ -216,15 +218,15 @@ try "
     /Switched to branch 'foo'/
 
     test-commit u5
-    glt push u5 origin foo;     ok;     gsh
+    PUSH u5 origin foo;         ok;     gsh
                                         /cecf671..b27cf19 *foo -> foo/
 
     # u1 delete dev ok
-    glt push u1 origin :dev;    ok;     gsh
+    PUSH u1 origin :dev;        ok;     gsh
                                         / - \\[deleted\\] *dev/
 
     # push it back
-    glt push u1 origin dev;     ok;     gsh
+    PUSH u1 origin dev;         ok;     gsh
                                         /\\* \\[new branch\\] *dev -> dev/
 
 ";
@@ -242,15 +244,15 @@ try "
 
     cd ..;                      ok
 
-    glt clone tester file://r1; ok;     gsh
+    CLONE tester file://r1;     ok;     gsh
                                         /Cloning into 'r1'.../
     cd r1;                      ok
     test-commit r1a r1b r1c r1d r1e r1f
                                 ok
-    glt push tester origin HEAD;ok;     gsh
+    PUSH tester origin HEAD;    ok;     gsh
                                         /\\* \\[new branch\\] *HEAD -> master/
     git branch v1
-    glt push tester origin v1;  ok;     gsh
+    PUSH tester origin v1;      ok;     gsh
                                         /\\* \\[new branch\\] *v1 -> v1/
 
 ";
@@ -269,14 +271,14 @@ try "
 
     cd ..;                      ok
 
-    glt clone tester file://r2; ok;     gsh
+    CLONE tester file://r2;     ok;     gsh
                                         /Cloning into 'r2'.../
     cd r2;                      ok
     test-commit r2a r2b r2c r2d r2e r2f
                                 ok
-    glt push tester origin HEAD;ok;     gsh
+    PUSH tester origin HEAD;    ok;     gsh
                                         /\\* \\[new branch\\] *HEAD -> master/
     git branch v1
-    glt push tester origin v1;  !ok;    gsh
+    PUSH tester origin v1;      !ok;    gsh
                                         /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
 "

commit 17476318b9e0f4e2c9b515b1f63c74a0f37f1596
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sun Mar 11 09:26:12 2012 +0530

    (trace) formatting changed when more than one arg passed

diff --git a/src/Gitolite/Common.pm b/src/Gitolite/Common.pm
index 6c62403..d2f2cea 100644
--- a/src/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -39,10 +39,11 @@ sub say2 {
 sub trace {
     return unless defined( $ENV{D} );
 
-    my $level = shift;
+    my $level = shift; return if $ENV{D} < $level;
     my $args  = ''; $args = join( ", ", @_ ) if @_;
     my $sub   = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) );
-    say2 "TRACE $level $sub", $args if $ENV{D} >= $level;
+    say2 "TRACE $level $sub", (@_ ? shift : ());
+    say2("TRACE $level " . (" " x 32), $_)for @_;
 }
 
 sub dbg {

commit 56cda99edd13bb8ae13fe517b0e800f93665626e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 22:57:01 2012 +0530

    fixup CWD in access(); see below
    
    Calling access() changes the CWD to $GL_REPO_BASE!
    
    This causes a problem in the update script -- you're suddenly in the
    wrong directory after calling access()!
    
    This is actually happening inside load_1(), so fix that.

diff --git a/src/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
index 1b0d199..171927e 100644
--- a/src/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -117,7 +117,7 @@ sub load_1 {
     my $repo = shift;
     trace( 4, $repo );
 
-    _chdir( $rc{GL_REPO_BASE} );
+    _chdir( "$rc{GL_REPO_BASE}/$repo.git" );
 
     if ( $repo eq $last_repo ) {
         $repos{$repo} = $one_repo{$repo};
@@ -125,10 +125,10 @@ sub load_1 {
         return;
     }
 
-    if ( -f "$repo.git/gl-conf" ) {
+    if ( -f "gl-conf" ) {
         _die "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
 
-        my $cc = "$repo.git/gl-conf";
+        my $cc = "gl-conf";
         _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
 
         $last_repo = $repo;

commit 877f6eb31bc75bc1b5bffb38a1b603874bfae451
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 20:30:14 2012 +0530

    catch older gitolite.rc and die gracefully

diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 0b81bf8..7ed5cef 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -48,6 +48,7 @@ my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
 do $rc if -r $rc;
+_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 

commit a9d5adcd106329ab1802e0197ffea44d479e0649
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 19:43:42 2012 +0530

    example sugar script 'continuation-lines' added

diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 881cbcf..dbf0926 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -1,3 +1,16 @@
+# and now for something completely different...
+
+package SugarBox;
+
+sub run_sugar_script {
+    my ($ss, $lref) = @_;
+    do $ss if -x $ss;
+    $lref = sugar_script($lref);
+    return $lref;
+}
+
+# ----------------------------------------------------------------------
+
 package Gitolite::Conf::Sugar;
 
 # syntactic sugar for the conf file, including site-local macros
@@ -35,9 +48,14 @@ sub sugar {
             _warn "bad syntax for specifying sugar scripts; see docs";
         } else {
             for my $s (@{ $rc{SYNTACTIC_SUGAR} }) {
-                _warn "ignoring unreadable sugar script $s" if not -r $s;
-                do $s if -r $s;
-                $lines = sugar_script($lines);
+
+                # perl-ism; apart from keeping the full path separate from the
+                # simple name, this also protects %rc from change by implicit
+                # aliasing, which would happen if you touched $s itself
+                my $sfp = "$ENV{GL_BINDIR}/syntactic-sugar/$s";
+
+                _warn("skipped sugar script '$s'"), next if not -x $sfp;
+                $lines = SugarBox::run_sugar_script($sfp, $lines);
                 $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
             }
         }
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index 18cc6c5..0b81bf8 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -158,11 +158,18 @@ __DATA__
 # PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
 
 # this file is in perl syntax.  However, you do NOT need to know perl to edit
-# it; it should be fairly self-explanatory and easy to maintain
+# it; it should be fairly self-explanatory and easy to maintain.  Just mind
+# the commas, make sure the brackets and braces stay matched up!
 
 %RC = (
     UMASK                       =>  0077,
     GL_GITCONFIG_KEYS           =>  "",
+
+    # uncomment as needed
+    SYNTACTIC_SUGAR             =>
+        [
+            # 'continuation-lines',
+        ]
 );
 
 # ------------------------------------------------------------------------------
diff --git a/src/syntactic-sugar/continuation-lines b/src/syntactic-sugar/continuation-lines
new file mode 100755
index 0000000..1d25379
--- /dev/null
+++ b/src/syntactic-sugar/continuation-lines
@@ -0,0 +1,34 @@
+# vim: syn=perl:
+
+# "sugar script" (syntactic sugar helper) for gitolite3
+
+# Enabling this script in the rc file allows you to use back-slash escaped
+# continuation lines, like in C or shell etc.
+
+# This script also serves as an example "sugar script" if you want to write
+# your own (and maybe send them to me).  A "sugar script" in gitolite will be
+# executed via a perl 'do' and is expected to contain one function called
+# 'sugar_script'.  This function should take a listref and return a listref.
+# Each item in the list is one line.  There are NO newlines; g3 kills them off
+# fairly early in the process.
+
+# If you're not familiar with perl please do not try this.  Ask me to write
+# you a sugar script instead.
+
+sub sugar_script {
+    my $lines = shift;
+
+    my @out = ();
+    my $keep = '';
+    for my $l (@$lines) {
+        if ($l =~ s/\\$//) {
+            $keep .= $l;
+        } else {
+            $l = $keep . $l if $keep;
+            $keep = '';
+            push @out, $l;
+        }
+    }
+
+    return \@out;
+}

commit 0fdd80f4871102f7f6312ffa84cc2e490b2df09d
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 18:57:04 2012 +0530

    (tests) added a module test for explode
    
    plus a helper function to Test.pm (helps while developing tests)

diff --git a/src/Gitolite/Test.pm b/src/Gitolite/Test.pm
index dcaf3fe..8251c41 100644
--- a/src/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -8,6 +8,7 @@ package Gitolite::Test;
   try
   put
   text
+  dump
 );
 #>>>
 use Exporter 'import';
@@ -43,4 +44,11 @@ try "
     gitolite setup -a admin
 " or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
 
+sub dump {
+    use Data::Dumper;
+    for my $i (@_) {
+        print STDERR "DBG: " . Dumper($i);
+    }
+}
+
 1;
diff --git a/t/m-explode.t b/t/m-explode.t
new file mode 100755
index 0000000..aef337f
--- /dev/null
+++ b/t/m-explode.t
@@ -0,0 +1,102 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use 5.10.0;
+
+use Test;
+BEGIN { plan tests =>
+    2
+}
+
+use lib "$ENV{PWD}/src";
+use Gitolite::Test;
+use Gitolite::Conf::Explode;
+
+my @out;
+my @out2;
+
+warn "
+        <<< expect a couple of warnings about already included files >>>
+";
+
+# test 1 -- space normalisation
+
+    put "foo", "
+        foo line 1          
+                                foo=line 2
+
+
+        foo      3
+    ";
+    @out = ();
+    explode("foo", 'master', \@out);
+    @out2 = (
+        'foo line 1',
+        'foo = line 2',
+        'foo 3',
+    );
+
+    ok(@out ~~ @out2);
+
+# test 2 -- include/subconf processing
+
+    put "foo", "
+        foo line 1
+        \@fog=line 2
+            include                         \"bar.conf\"
+
+        foo line=5
+            subconf \"subs/baz.conf\"
+            include                         \"bar.conf\"
+        foo line=7
+            include \"bazup.conf\"
+    ";
+
+    put "bar.conf", "
+        \@brg=line 1
+
+        bar line 3
+    ";
+
+    mkdir("subs");
+
+    put "subs/baz.conf", "
+        \@bzg         =           line 1
+
+            include \"subs/baz2.conf\"
+
+        baz=line 3
+    ";
+
+    put "subs/baz2.conf", "
+        baz2 line 1
+        baz2 line 2
+            include \"bazup.conf\"
+        baz2 line 4
+    ";
+
+    put "bazup.conf", "
+        whatever...
+    ";
+
+    @out = ();
+    explode("foo", 'master', \@out);
+
+    @out2 = (
+        'foo line 1',
+        '@fog = line 2',
+        '@brg = line 1',
+        'bar line 3',
+        'foo line = 5',
+        'subconf baz',
+        '@baz.bzg = line 1',
+        'baz2 line 1',
+        'baz2 line 2',
+        'whatever...',
+        'baz2 line 4',
+        'baz = line 3',
+        'subconf master',
+        'foo line = 7'
+    );
+
+    ok(@out ~~ @out2);

commit a0305ec029c93474f7c4cb09d27b25e1ba2dd428
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 13:56:02 2012 +0530

    sugar 'option'; see below
    
        option foo = bar
          ->  config gitolite-options.foo = bar

diff --git a/src/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
index 932928d..881cbcf 100644
--- a/src/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -45,12 +45,30 @@ sub sugar {
 
     # then our stuff:
 
+    $lines = option($lines);
     $lines = owner_desc($lines);
     # $lines = name_vref($lines);
 
     return $lines;
 }
 
+sub option {
+    my $lines = shift;
+    my @ret;
+
+    # option foo = bar
+    #   ->  config gitolite-options.foo = bar
+
+    for my $line (@$lines) {
+        if ( $line =~ /^option (\S+) = (\S.*)/ ) {
+            push @ret, "config gitolite-options.$1 = $2";
+        } else {
+            push @ret, $line;
+        }
+    }
+    return \@ret;
+}
+
 sub owner_desc {
     my $lines = shift;
     my @ret;

commit 379b0c9549faa2699d49da22fe49d3048e12f0a1
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Sat Mar 10 18:56:29 2012 +0530

    install/test made easy (WARNING: read below)
    
    (1) testing is very easy, just run this from a clone
    
            t/g3-clean-install-setup-test
    
        BUT BE WARNED THIS IS DESTRUCTIVE; details in t/WARNING
    
    (2) install is equally simple; see 'INSTALL' in the main directory

diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm
deleted file mode 100644
index 47f0cdb..0000000
--- a/Gitolite/Rc.pm
+++ /dev/null
@@ -1,177 +0,0 @@
-package Gitolite::Rc;
-
-# everything to do with 'rc'.  Also defines some 'constants'
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  %rc
-  glrc
-  query_rc
-
-  $ADC_CMD_ARGS_PATT
-  $REF_OR_FILENAME_PATT
-  $REPONAME_PATT
-  $REPOPATT_PATT
-  $USERNAME_PATT
-);
-
-use Exporter 'import';
-use Getopt::Long;
-
-use lib $ENV{GL_BINDIR};
-use Gitolite::Common;
-
-# ----------------------------------------------------------------------
-
-our %rc;
-
-# ----------------------------------------------------------------------
-
-# variables that are/could be/should be in the rc file
-# ----------------------------------------------------------------------
-
-$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
-$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
-
-# variables that should probably never be changed
-# ----------------------------------------------------------------------
-
-$ADC_CMD_ARGS_PATT    = qr(^[0-9a-zA-Z._\@/+:-]*$);
-$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
-$REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
-$REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
-$USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
-
-# ----------------------------------------------------------------------
-
-my $current_data_version = "3.0";
-
-my $rc = glrc('filename');
-do $rc if -r $rc;
-# let values specified in rc file override our internal ones
- at rc{ keys %RC } = values %RC;
-
-# ----------------------------------------------------------------------
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my $glrc_default_text = '';
-{
-    local $/ = undef;
-    $glrc_default_text = <DATA>;
-}
-
-sub glrc {
-    my $cmd = shift;
-    if ( $cmd eq 'default-filename' ) {
-        trace( 1, "..should happen only on first run" );
-        return "$ENV{HOME}/.gitolite.rc";
-    } elsif ( $cmd eq 'default-text' ) {
-        trace( 1, "..should happen only on first run" );
-        return $glrc_default_text if $glrc_default_text;
-        _die "rc file default text not set; this should not happen!";
-    } elsif ( $cmd eq 'filename' ) {
-        # where is the rc file?
-        trace(4);
-
-        # search $HOME first
-        return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
-        trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
-
-        # XXX for fedora, we can add the following line, but I would really prefer
-        # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
-        # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
-
-        return '';
-    } elsif ( $cmd eq 'current-data-version' ) {
-        return $current_data_version;
-    } else {
-        _die "unknown argument to glrc: $cmd";
-    }
-}
-
-# ----------------------------------------------------------------------
-# implements 'gitolite query-rc'
-# ----------------------------------------------------------------------
-
-=for usage
-
-Usage:  gitolite query-rc -a
-        gitolite query-rc <list of rc variables>
-
-Example:
-
-    gitolite query-rc GL_ADMIN_BASE GL_UMASK
-    # prints "/home/git/.gitolite<tab>0077" or similar
-
-    gitolite query-rc -a
-    # prints all known variables and values, one per line
-=cut
-
-# ----------------------------------------------------------------------
-
-my $all = 0;
-
-sub query_rc {
-    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
-
-    my @vars = args();
-
-    no strict 'refs';
-
-    if ( $vars[0] eq '-a' ) {
-        for my $e (sort keys %rc) {
-            print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n";
-        }
-        return;
-    }
-
-    our $GL_BINDIR = $ENV{GL_BINDIR};
-
-    print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars;
-}
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my $help = 0;
-
-    GetOptions(
-        'all|a'  => \$all,
-        'help|h' => \$help,
-    ) or usage();
-
-    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
-    return '-a' if $all;
-    usage() if not @ARGV or $help;
-    return @ARGV;
-}
-
-1;
-
-# ----------------------------------------------------------------------
-
-__DATA__
-# configuration variables for gitolite
-
-# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
-
-# this file is in perl syntax.  However, you do NOT need to know perl to edit
-# it; it should be fairly self-explanatory and easy to maintain
-
-%RC = (
-    GL_UMASK                    =>  0077,
-    GL_GITCONFIG_KEYS           =>  "",
-);
-
-# ------------------------------------------------------------------------------
-# per perl rules, this should be the last line in such a file:
-1;
-
-# Local variables:
-# mode: perl
-# End:
-# vim: set syn=perl:
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..b1c634d
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,17 @@
+1.  Clone the repo and copy src somewhere (or leave it where it is, if you're
+    sure no one will 'git pull' on a running system!)
+
+        cp -a src /some/full/path
+
+2.  (Optional) Make a symlink for the single executable 'gitolite' to
+    somewhere in `$PATH`
+
+        ln -sf /full/path/to/some/damn/place/gitolite $HOME/bin
+
+3.  Run setup.  That is, either run:
+
+        gitolite setup -a YourName -pk /tmp/YourName.pub
+
+    or, if you did not do step 2, run:
+
+        /some/full/path/src/gitolite -a YourName -pk /tmp/YourName.pub
diff --git a/g3-info b/g3-info
deleted file mode 100755
index 28fa9ad..0000000
--- a/g3-info
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/perl
-
-# gitolite shell, invoked from ~/.ssh/authorized_keys
-# ----------------------------------------------------------------------
-
-BEGIN {
-    # find and set bin dir
-    $ENV{GL_BINDIR} = "$ENV{HOME}/bin";
-}
-
-use lib $ENV{GL_BINDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-use Gitolite::Conf::Load;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my $user = shift or die;
-my $aa;
-my $ref = 'any';
-
-my $ret;
-while (<>) {
-    chomp;
-
-    my $perm = '';
-    for $aa (qw(R W C)) {
-        $ret = access($_, $user, $aa, $ref);
-        $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
-    }
-    print "$perm\t$_\n" if $perm =~ /\S/;
-}
diff --git a/g3-install b/g3-install
deleted file mode 100755
index ef40012..0000000
--- a/g3-install
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# this is specific to my test env; you may want to change it
-
-set -e
-
-cd /home/g3
-
-if [ "$1" = "-c" ]
-then
-    rm -rf .gito* gito* repositories proj* bin
-    mkdir bin
-    cp ~/.ssh/id_rsa.pub ~/.ssh/admin.pub
-
-    cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin
-    gitolite setup -a ${2:-admin} -pk ~/.ssh/admin.pub
-else
-    cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin
-    gitolite setup
-fi
diff --git a/gitolite b/gitolite
deleted file mode 100755
index 646d036..0000000
--- a/gitolite
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/perl
-
-# all gitolite CLI tools run as sub-commands of this command
-# ----------------------------------------------------------------------
-
-=for usage
-Usage:  gitolite [sub-command] [options]
-
-The following subcommands are available; they should all respond to '-h':
-
-    setup                       1st run: initial setup; all runs: hook fixups
-    compile                     compile gitolite.conf
-    query-rc                    get values of rc variables
-    list-groups                 list all group names in conf
-    list-users                  list all users/user groups in conf
-    list-repos                  list all repos/repo groups in conf
-    list-phy-repos              list all repos actually on disk
-    list-memberships            list all groups a name is a member of
-    list-members                list all members of a group
-
-Warnings:
-  - list-users is disk bound and could take a while on sites with 1000s of repos
-  - list-memberships does not check if the name is known; unknown names come
-    back with 2 answers: the name itself and '@all'
-=cut
-
-# ----------------------------------------------------------------------
-
-use FindBin;
-
-BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; }
-use lib $ENV{GL_BINDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-args();
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my ( $command, @args ) = @ARGV;
-    usage() if not $command or $command eq '-h';
-
-    if ( $command eq 'setup' ) {
-        shift @ARGV;
-        require Gitolite::Setup;
-        Gitolite::Setup->import;
-        setup();
-    } elsif ( $command eq 'compile' ) {
-        shift @ARGV;
-        _die "'gitolite compile' does not take any arguments" if @ARGV;
-        require Gitolite::Conf;
-        Gitolite::Conf->import;
-        compile();
-    } elsif ( $command eq 'query-rc' ) {
-        shift @ARGV;
-        query_rc();
-    } elsif ( $command eq 'list-groups' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_groups() } );
-    } elsif ( $command eq 'list-users' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_users() } );
-    } elsif ( $command eq 'list-repos' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_repos() } );
-    } elsif ( $command eq 'list-phy-repos' ) {
-        shift @ARGV;
-        _chdir( $rc{GL_REPO_BASE} );
-        print "$_\n" for ( @{ list_phy_repos() } );
-    } elsif ( $command eq 'list-memberships' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_memberships() } );
-    } elsif ( $command eq 'list-members' ) {
-        shift @ARGV;
-        require Gitolite::Conf::Load;
-        Gitolite::Conf::Load->import;
-        print "$_\n" for ( @{ list_members() } );
-    } else {
-        _die "unknown gitolite sub-command";
-    }
-}
diff --git a/Gitolite/Common.pm b/src/Gitolite/Common.pm
similarity index 92%
rename from Gitolite/Common.pm
rename to src/Gitolite/Common.pm
index 0435793..6c62403 100644
--- a/Gitolite/Common.pm
+++ b/src/Gitolite/Common.pm
@@ -7,7 +7,7 @@ package Gitolite::Common;
 @EXPORT = qw(
   print2  dbg     _mkdir  _open   ln_sf     tsh_rc      sort_u
   say     _warn   _chdir  _print            tsh_text    list_phy_repos
-  say2    _die            slurp             tsh_lines
+  say2    _die    _system slurp             tsh_lines
           trace           cleanup_conf_line tsh_try
           usage                             tsh_run
 );
@@ -97,6 +97,19 @@ sub _chdir {
     chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n";
 }
 
+sub _system {
+    if ( system(@_) != 0 ) {
+        say2 "system @_ failed";
+        if ( $? == -1 ) {
+            die "failed to execute: $!\n";
+        } elsif ( $? & 127 ) {
+            die "child died with signal " . ( $? & 127 ) . "\n";
+        } else {
+            die "child exited with value " . ( $? >> 8 ) . "\n";
+        }
+    }
+}
+
 sub _open {
     open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n";
     return $fh;
diff --git a/Gitolite/Conf.pm b/src/Gitolite/Conf.pm
similarity index 98%
rename from Gitolite/Conf.pm
rename to src/Gitolite/Conf.pm
index 2846f1f..6c7dca6 100644
--- a/Gitolite/Conf.pm
+++ b/src/Gitolite/Conf.pm
@@ -12,7 +12,6 @@ package Gitolite::Conf;
 use Exporter 'import';
 use Getopt::Long;
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 use Gitolite::Rc;
 use Gitolite::Conf::Sugar;
diff --git a/Gitolite/Conf/Explode.pm b/src/Gitolite/Conf/Explode.pm
similarity index 99%
rename from Gitolite/Conf/Explode.pm
rename to src/Gitolite/Conf/Explode.pm
index 43a5778..a821dc9 100644
--- a/Gitolite/Conf/Explode.pm
+++ b/src/Gitolite/Conf/Explode.pm
@@ -9,7 +9,6 @@ package Gitolite::Conf::Explode;
 
 use Exporter 'import';
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 
 use strict;
diff --git a/Gitolite/Conf/Load.pm b/src/Gitolite/Conf/Load.pm
similarity index 99%
rename from Gitolite/Conf/Load.pm
rename to src/Gitolite/Conf/Load.pm
index fd117fc..1b0d199 100644
--- a/Gitolite/Conf/Load.pm
+++ b/src/Gitolite/Conf/Load.pm
@@ -16,7 +16,6 @@ package Gitolite::Conf::Load;
 
 use Exporter 'import';
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 use Gitolite::Rc;
 
@@ -108,7 +107,7 @@ sub load_common {
     _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
 
     if ( data_version_mismatch() ) {
-        system("gitolite setup");
+        _system("gitolite setup");
         _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
         _die "data version update failed; this is serious" if data_version_mismatch();
     }
diff --git a/Gitolite/Conf/Store.pm b/src/Gitolite/Conf/Store.pm
similarity index 99%
rename from Gitolite/Conf/Store.pm
rename to src/Gitolite/Conf/Store.pm
index b99ac0b..37824c7 100644
--- a/Gitolite/Conf/Store.pm
+++ b/src/Gitolite/Conf/Store.pm
@@ -22,7 +22,6 @@ use Data::Dumper;
 $Data::Dumper::Indent   = 1;
 $Data::Dumper::Sortkeys = 1;
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 use Gitolite::Rc;
 use Gitolite::Hooks::Update;
@@ -169,7 +168,7 @@ sub new_repo {
 
     _mkdir("$repo.git");
     _chdir("$repo.git");
-    system("git init --bare >&2");
+    _system("git init --bare >&2");
     _chdir( $rc{GL_REPO_BASE} );
     hook_1($repo);
 
diff --git a/Gitolite/Conf/Sugar.pm b/src/Gitolite/Conf/Sugar.pm
similarity index 99%
rename from Gitolite/Conf/Sugar.pm
rename to src/Gitolite/Conf/Sugar.pm
index bfa7c5f..932928d 100644
--- a/Gitolite/Conf/Sugar.pm
+++ b/src/Gitolite/Conf/Sugar.pm
@@ -9,7 +9,6 @@ package Gitolite::Conf::Sugar;
 
 use Exporter 'import';
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Explode;
diff --git a/Gitolite/Hooks/PostUpdate.pm b/src/Gitolite/Hooks/PostUpdate.pm
similarity index 94%
rename from Gitolite/Hooks/PostUpdate.pm
rename to src/Gitolite/Hooks/PostUpdate.pm
index 1ce07b2..ef8e522 100644
--- a/Gitolite/Hooks/PostUpdate.pm
+++ b/src/Gitolite/Hooks/PostUpdate.pm
@@ -10,7 +10,6 @@ package Gitolite::Hooks::PostUpdate;
 
 use Exporter 'import';
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 
@@ -30,7 +29,7 @@ sub post_update {
         local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
         tsh_try("git checkout -f --quiet master");
     }
-    system("$ENV{GL_BINDIR}/gitolite compile");
+    _system("$ENV{GL_BINDIR}/gitolite compile");
 
     exit 0;
 }
diff --git a/Gitolite/Hooks/Update.pm b/src/Gitolite/Hooks/Update.pm
similarity index 99%
rename from Gitolite/Hooks/Update.pm
rename to src/Gitolite/Hooks/Update.pm
index 2c60914..488a3ec 100644
--- a/Gitolite/Hooks/Update.pm
+++ b/src/Gitolite/Hooks/Update.pm
@@ -10,7 +10,6 @@ package Gitolite::Hooks::Update;
 
 use Exporter 'import';
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 use Gitolite::Conf::Load;
 
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
index e84ee0c..18cc6c5 100644
--- a/src/Gitolite/Rc.pm
+++ b/src/Gitolite/Rc.pm
@@ -7,9 +7,8 @@ package Gitolite::Rc;
   %rc
   glrc
   query_rc
-  version
 
-  $REMOTE_COMMAND_PATT
+  $ADC_CMD_ARGS_PATT
   $REF_OR_FILENAME_PATT
   $REPONAME_PATT
   $REPOPATT_PATT
@@ -37,7 +36,7 @@ $rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
 # variables that should probably never be changed
 # ----------------------------------------------------------------------
 
-$REMOTE_COMMAND_PATT  = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$);
+$ADC_CMD_ARGS_PATT    = qr(^[0-9a-zA-Z._\@/+:-]*$);
 $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
 $REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
 $REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
@@ -49,17 +48,9 @@ my $current_data_version = "3.0";
 
 my $rc = glrc('filename');
 do $rc if -r $rc;
-_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
 # let values specified in rc file override our internal ones
 @rc{ keys %RC } = values %RC;
 
-# testing sometimes requires all of it to be overridden silently; use an
-# env var that is highly unlikely to appear in real life :)
-do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
-
-# fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
-$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
-
 # ----------------------------------------------------------------------
 
 use strict;
@@ -76,15 +67,19 @@ my $glrc_default_text = '';
 sub glrc {
     my $cmd = shift;
     if ( $cmd eq 'default-filename' ) {
+        trace( 1, "..should happen only on first run" );
         return "$ENV{HOME}/.gitolite.rc";
     } elsif ( $cmd eq 'default-text' ) {
+        trace( 1, "..should happen only on first run" );
         return $glrc_default_text if $glrc_default_text;
         _die "rc file default text not set; this should not happen!";
     } elsif ( $cmd eq 'filename' ) {
         # where is the rc file?
+        trace(4);
 
         # search $HOME first
         return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
+        trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
 
         # XXX for fedora, we can add the following line, but I would really prefer
         # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
@@ -99,68 +94,56 @@ sub glrc {
 }
 
 # ----------------------------------------------------------------------
-# implements 'gitolite query-rc' and 'version'
+# implements 'gitolite query-rc'
 # ----------------------------------------------------------------------
 
+=for usage
+
+Usage:  gitolite query-rc -a
+        gitolite query-rc <list of rc variables>
+
+Example:
+
+    gitolite query-rc GL_ADMIN_BASE GL_UMASK
+    # prints "/home/git/.gitolite<tab>0077" or similar
+
+    gitolite query-rc -a
+    # prints all known variables and values, one per line
+=cut
+
 # ----------------------------------------------------------------------
 
-my $all  = 0;
-my $nonl = 0;
+my $all = 0;
 
 sub query_rc {
+    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
 
     my @vars = args();
 
     no strict 'refs';
 
-    if ($all) {
-        for my $e ( sort keys %rc ) {
-            print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
+    if ( $vars[0] eq '-a' ) {
+        for my $e (sort keys %rc) {
+            print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n";
         }
         return;
     }
 
-    print join( "\t", map { $rc{$_} || '' } @vars ) . ( $nonl ? '' : "\n" ) if @vars;
-}
-
-sub version {
-    my $version = '';
-    $version = '(unknown)';
-    for ("$rc{GL_ADMIN_BASE}/VERSION") {
-        $version = slurp($_) if -r $_;
-    }
-    chomp($version);
-    return $version;
+    print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars;
 }
 
 # ----------------------------------------------------------------------
 
-=for args
-Usage:  gitolite query-rc -a
-        gitolite query-rc [-n] <list of rc variables>
-
-    -a          print all variables and values
-    -n          do not append a newline
-
-Example:
-
-    gitolite query-rc GL_ADMIN_BASE UMASK
-    # prints "/home/git/.gitolite<tab>0077" or similar
-
-    gitolite query-rc -a
-    # prints all known variables and values, one per line
-=cut
-
 sub args {
     my $help = 0;
 
     GetOptions(
         'all|a'  => \$all,
-        'nonl|n' => \$nonl,
         'help|h' => \$help,
     ) or usage();
 
     usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
+    return '-a' if $all;
     usage() if not $all and not @ARGV or $help;
     return @ARGV;
 }
@@ -172,36 +155,14 @@ sub args {
 __DATA__
 # configuration variables for gitolite
 
-# This file is in perl syntax.  But you do NOT need to know perl to edit it --
-# just mind the commas and make sure the brackets and braces stay matched up!
+# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
 
-# (Tip: perl allows a comma after the last item in a list also!)
+# this file is in perl syntax.  However, you do NOT need to know perl to edit
+# it; it should be fairly self-explanatory and easy to maintain
 
 %RC = (
     UMASK                       =>  0077,
     GL_GITCONFIG_KEYS           =>  "",
-
-    # comment out or uncomment as needed
-    # these will run in sequence during the conf file parse
-    SYNTACTIC_SUGAR             =>
-        [
-            # 'continuation-lines',
-        ],
-
-    # comment out or uncomment as needed
-    # these will run in sequence after post-update
-    POST_COMPILE                =>
-        [
-            'post-compile/ssh-authkeys',
-        ],
-
-    # comment out or uncomment as needed
-    # these are available to remote users
-    COMMANDS                    =>
-        {
-            'help'              =>  1,
-            'info'              =>  1,
-        },
 );
 
 # ------------------------------------------------------------------------------
diff --git a/Gitolite/Setup.pm b/src/Gitolite/Setup.pm
similarity index 96%
rename from Gitolite/Setup.pm
rename to src/Gitolite/Setup.pm
index 28d1d74..b42e0fc 100644
--- a/Gitolite/Setup.pm
+++ b/src/Gitolite/Setup.pm
@@ -28,7 +28,6 @@ Later runs:
 use Exporter 'import';
 use Getopt::Long;
 
-use lib $ENV{GL_BINDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
 use Gitolite::Conf::Store;
@@ -47,7 +46,7 @@ sub setup {
         setup_gladmin( $admin, $pubkey, $argv );
     }
 
-    system("$ENV{GL_BINDIR}/gitolite compile");
+    _system("$ENV{GL_BINDIR}/gitolite compile");
 
     hook_repos();    # all of them, just to be sure
 }
@@ -141,8 +140,8 @@ sub setup_gladmin {
 
     $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
     _chdir("$rc{GL_REPO_BASE}/gitolite-admin.git");
-    system("git add conf/gitolite.conf");
-    system("git add keydir") if $pubkey;
+    _system("git add conf/gitolite.conf");
+    _system("git add keydir") if $pubkey;
     tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
     tsh_try("git config --get user.name")  or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
     tsh_try("git diff --cached --quiet")
diff --git a/Gitolite/Test.pm b/src/Gitolite/Test.pm
similarity index 70%
rename from Gitolite/Test.pm
rename to src/Gitolite/Test.pm
index bb4d0a9..dcaf3fe 100644
--- a/Gitolite/Test.pm
+++ b/src/Gitolite/Test.pm
@@ -7,6 +7,7 @@ package Gitolite::Test;
 @EXPORT = qw(
   try
   put
+  text
 );
 #>>>
 use Exporter 'import';
@@ -17,6 +18,7 @@ BEGIN {
     require Gitolite::Test::Tsh;
     *{'try'} = \&Tsh::try;
     *{'put'} = \&Tsh::put;
+    *{'text'} = \&Tsh::text;
 }
 
 use strict;
@@ -30,11 +32,15 @@ try "
     DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/
 
     DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone;
-    DEF AP_2 = AP_1; git add conf keydir; ok; git commit -m %1; ok; /master.* %1/
+    DEF AP_2 = AP_1; git add conf ; ok; git commit -m %1; ok; /master.* %1/
     DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
 
-    ./g3-install -c admin
+    mkdir -p $ENV{HOME}/bin
+    ln -sf $ENV{PWD}/src/gitolite $ENV{PWD}/t/glt ~/bin
+    cd; rm -vrf .gito* gito* repositories
+
     cd tsh_tempdir;
-";
+    gitolite setup -a admin
+" or die "could not setup the test environment; errors:\n\n" . text() . "\n\n";
 
 1;
diff --git a/Gitolite/Test/Tsh.pm b/src/Gitolite/Test/Tsh.pm
similarity index 100%
rename from Gitolite/Test/Tsh.pm
rename to src/Gitolite/Test/Tsh.pm
diff --git a/src/gitolite b/src/gitolite
index c6a1f54..0457cc2 100755
--- a/src/gitolite
+++ b/src/gitolite
@@ -3,17 +3,14 @@
 # all gitolite CLI tools run as sub-commands of this command
 # ----------------------------------------------------------------------
 
-=for args
-Usage:  gitolite [<sub-command>] [<options>]
+=for usage
+Usage:  gitolite [sub-command] [options]
 
-The following built-in subcommands are available; they should all respond to
-'-h' if you want further details on each:
+The following subcommands are available; they should all respond to '-h':
 
     setup                       1st run: initial setup; all runs: hook fixups
     compile                     compile gitolite.conf
-
     query-rc                    get values of rc variables
-
     list-groups                 list all group names in conf
     list-users                  list all users/user groups in conf
     list-repos                  list all repos/repo groups in conf
@@ -25,10 +22,6 @@ Warnings:
   - list-users is disk bound and could take a while on sites with 1000s of repos
   - list-memberships does not check if the name is known; unknown names come
     back with 2 answers: the name itself and '@all'
-
-In addition, running 'gitolite help' should give you a list of custom commands
-available.  They may or may not respond to '-h', depending on how they were
-written.
 =cut
 
 # ----------------------------------------------------------------------
@@ -45,62 +38,58 @@ use warnings;
 
 # ----------------------------------------------------------------------
 
-my ( $command, @args ) = @ARGV;
-gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE};
 args();
 
-# the first two commands need options via @ARGV, as they have their own
-# GetOptions calls and older perls don't have 'GetOptionsFromArray'
-
-if ( $command eq 'setup' ) {
-    shift @ARGV;
-    require Gitolite::Setup;
-    Gitolite::Setup->import;
-    setup();
-
-} elsif ( $command eq 'query-rc' ) {
-    shift @ARGV;
-    query_rc();
-
-# the rest don't need @ARGV per se
-
-} elsif ( $command eq 'compile' ) {
-    require Gitolite::Conf;
-    Gitolite::Conf->import;
-    compile(@args);
-
-} elsif ( $command eq 'trigger' ) {
-    trigger(@args);
-
-} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
-    trace( 2, "attempting gitolite command $command" );
-    run_command( $command, @args );
-
-} elsif ( $command eq 'list-phy-repos' ) {
-    _chdir( $rc{GL_REPO_BASE} );
-    print "$_\n" for ( @{ list_phy_repos(@args) } );
-
-} elsif ( $command =~ /^list-/ ) {
-    trace( 2, "attempting lister command $command" );
-    require Gitolite::Conf::Load;
-    Gitolite::Conf::Load->import;
-    my $fn = lister_dispatch($command);
-    print "$_\n" for ( @{ $fn->(@args) } );
-
-} else {
-    _die "unknown gitolite sub-command";
-}
+# ----------------------------------------------------------------------
 
 sub args {
+    my ( $command, @args ) = @ARGV;
     usage() if not $command or $command eq '-h';
-}
-
-# ----------------------------------------------------------------------
 
-sub run_command {
-    my $pgm      = shift;
-    my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
-    _die "$pgm not found or not executable" if not -x $fullpath;
-    _system( $fullpath, @_ );
-    exit 0;
+    if ( $command eq 'setup' ) {
+        shift @ARGV;
+        require Gitolite::Setup;
+        Gitolite::Setup->import;
+        setup();
+    } elsif ( $command eq 'compile' ) {
+        shift @ARGV;
+        _die "'gitolite compile' does not take any arguments" if @ARGV;
+        require Gitolite::Conf;
+        Gitolite::Conf->import;
+        compile();
+    } elsif ( $command eq 'query-rc' ) {
+        shift @ARGV;
+        query_rc();
+    } elsif ( $command eq 'list-groups' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_groups() } );
+    } elsif ( $command eq 'list-users' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_users() } );
+    } elsif ( $command eq 'list-repos' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_repos() } );
+    } elsif ( $command eq 'list-phy-repos' ) {
+        shift @ARGV;
+        _chdir( $rc{GL_REPO_BASE} );
+        print "$_\n" for ( @{ list_phy_repos() } );
+    } elsif ( $command eq 'list-memberships' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_memberships() } );
+    } elsif ( $command eq 'list-members' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_members() } );
+    } else {
+        _die "unknown gitolite sub-command";
+    }
 }
diff --git a/gitolite-shell b/src/gitolite-shell
similarity index 93%
rename from gitolite-shell
rename to src/gitolite-shell
index 46ce08b..d7f6a19 100755
--- a/gitolite-shell
+++ b/src/gitolite-shell
@@ -3,11 +3,9 @@
 # gitolite shell, invoked from ~/.ssh/authorized_keys
 # ----------------------------------------------------------------------
 
-BEGIN {
-    # find and set bin dir
-    $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ( $1 || "$ENV{PWD}/" ) . $2;
-}
+use FindBin;
 
+BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
 use lib $ENV{GL_BINDIR};
 use Gitolite::Rc;
 use Gitolite::Common;
diff --git a/t/README b/t/README
new file mode 100644
index 0000000..c863b41
--- /dev/null
+++ b/t/README
@@ -0,0 +1,8 @@
+WARNING: THE TEST SUITE DELETES STUFF FIRST!
+
+Testing gitolite3 is now one command after the clone:
+
+    prove
+
+But because it starts by cleaning the slate, it's best to do it on a spare
+userid that you are ok to lose data on.
diff --git a/t/t01-basic b/t/basic.t
similarity index 99%
rename from t/t01-basic
rename to t/basic.t
index 83508a9..96c4012 100755
--- a/t/t01-basic
+++ b/t/basic.t
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 
 # this is hardcoded; change it if needed
-use lib "$ENV{HOME}/bin";
+use lib "src";
 use Gitolite::Test;
 
 # basic tests
diff --git a/t/gitolite-receive-pack b/t/gitolite-receive-pack
index 48c7428..a4cc5be 100755
--- a/t/gitolite-receive-pack
+++ b/t/gitolite-receive-pack
@@ -9,4 +9,4 @@ $repo =~ s/\.git$//;
 my $user = $ENV{G3T_USER} || 'no-such-user';
 
 $ENV{SSH_ORIGINAL_COMMAND} = "git-receive-pack '$repo'";
-exec( "$ENV{HOME}/bin/gitolite-shell", $user );
+exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
diff --git a/t/gitolite-upload-pack b/t/gitolite-upload-pack
index 8888abb..5981f17 100755
--- a/t/gitolite-upload-pack
+++ b/t/gitolite-upload-pack
@@ -9,4 +9,4 @@ $repo =~ s/\.git$//;
 my $user = $ENV{G3T_USER} || 'no-such-user';
 
 $ENV{SSH_ORIGINAL_COMMAND} = "git-upload-pack '$repo'";
-exec( "$ENV{HOME}/bin/gitolite-shell", $user );
+exec( "$ENV{GL_BINDIR}/../src/gitolite-shell", $user );
diff --git a/t/glt b/t/glt
index b5704f5..45e7b19 100755
--- a/t/glt
+++ b/t/glt
@@ -2,6 +2,9 @@
 use strict;
 use warnings;
 
+use FindBin;
+BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
+
 print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
 
 my $cmd  = shift or die "need command";
@@ -10,9 +13,9 @@ my $rc;
 
 $ENV{G3T_USER} = $user;
 if ( $cmd eq 'push' ) {
-    $rc = system( "git", $cmd, "--receive-pack=$ENV{HOME}/bin/gitolite-receive-pack", @ARGV );
+    $rc = system( "git", $cmd, "--receive-pack=$ENV{GL_BINDIR}/gitolite-receive-pack", @ARGV );
 } else {
-    $rc = system( "git", $cmd, "--upload-pack=$ENV{HOME}/bin/gitolite-upload-pack", @ARGV );
+    $rc = system( "git", $cmd, "--upload-pack=$ENV{GL_BINDIR}/gitolite-upload-pack", @ARGV );
 }
 
 if ( $? == -1 ) {
diff --git a/t/reset b/t/reset
new file mode 100755
index 0000000..8c5dcbf
--- /dev/null
+++ b/t/reset
@@ -0,0 +1,8 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "src";
+use Gitolite::Test;
+try 'put';

commit acb2f8fe8e3bb6f0f593bf3ff0913a74264fd8f5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 9 21:23:16 2012 +0530

    sugar high!
    
    make it easy to handle syntactic sugar.  In summary, compile now calls
    parse(sugar('gitolite.conf')).
    
    Details:
    
      - cleanup_conf_line went from subar.pm to common.pm
      - explode() and minions went from conf.pm to the new explode.pm
      - the callback went away; everyone just passes whole arrays around now
      - the new sugar() takes a filename and returns a listref
      - all sugar scripts take and return a listref
    
      - the first "built-in" sugar is written (setting gitweb.owner and
        gitweb.description)
    
    the new RC file format (of being a hash called %rc) is getting a nice
    workout :-)

diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm
index 16d25ba..0435793 100644
--- a/Gitolite/Common.pm
+++ b/Gitolite/Common.pm
@@ -8,7 +8,7 @@ package Gitolite::Common;
   print2  dbg     _mkdir  _open   ln_sf     tsh_rc      sort_u
   say     _warn   _chdir  _print            tsh_text    list_phy_repos
   say2    _die            slurp             tsh_lines
-          trace                             tsh_try
+          trace           cleanup_conf_line tsh_try
           usage                             tsh_run
 );
 #>>>
@@ -143,6 +143,19 @@ sub sort_u {
     return \@sort_u;
 }
 
+sub cleanup_conf_line {
+    my $line = shift;
+
+    # kill comments, but take care of "#" inside *simple* strings
+    $line =~ s/^((".*?"|[^#"])*)#.*/$1/;
+    # normalise whitespace; keeps later regexes very simple
+    $line =~ s/=/ = /;
+    $line =~ s/\s+/ /g;
+    $line =~ s/^ //;
+    $line =~ s/ $//;
+    return $line;
+}
+
 {
     my @phy_repos = ();
 
diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm
index cffee63..2846f1f 100644
--- a/Gitolite/Conf.pm
+++ b/Gitolite/Conf.pm
@@ -23,13 +23,6 @@ use warnings;
 
 # ----------------------------------------------------------------------
 
-# 'seen' for include/subconf files
-my %included = ();
-# 'seen' for group names on LHS
-my %prefixed_groupname = ();
-
-# ----------------------------------------------------------------------
-
 sub compile {
     trace(3);
     # XXX assume we're in admin-base/conf
@@ -37,7 +30,7 @@ sub compile {
     _chdir( $rc{GL_ADMIN_BASE} );
     _chdir("conf");
 
-    explode( 'gitolite.conf', 'master', \&parse );
+    parse(sugar('gitolite.conf'));
 
     # the order matters; new repos should be created first, to give store a
     # place to put the individual gl-conf files
@@ -45,139 +38,42 @@ sub compile {
     store();
 }
 
-sub explode {
-    trace( 4, @_ );
-    my ( $file, $subconf, $parser ) = @_;
-
-    # $parser is a ref to a callback; if not supplied we just print
-    $parser ||= sub { print shift, "\n"; };
-
-    # seed the 'seen' list if it's empty
-    $included{ device_inode("conf/gitolite.conf") }++ unless %included;
-
-    my $fh    = _open( "<", $file );
-    my @fh    = <$fh>;
-    my @lines = macro_expand( "# BEGIN $file\n", @fh, "# END $file\n" );
-    my $line;
-    while (@lines) {
-        $line = shift @lines;
-
-        $line = cleanup_conf_line($line);
-        next unless $line =~ /\S/;
-
-        $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
-
-        if ( $line =~ /^(include|subconf) "(.+)"$/ or $line =~ /^(include|subconf) '(.+)'$/ ) {
-            incsub( $1, $2, $subconf, $parser );
-        } else {
-            # normal line, send it to the callback function
-            $parser->($line);
-        }
-    }
-}
-
 sub parse {
-    trace( 4, @_ );
-    my $line = shift;
-
-    # user or repo groups
-    if ( $line =~ /^(@\S+) = (.*)/ ) {
-        add_to_group( $1, split( ' ', $2 ) );
-    } elsif ( $line =~ /^repo (.*)/ ) {
-        set_repolist( split( ' ', $1 ) );
-    } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
-        my $perm  = $1;
-        my @refs  = parse_refs( $2 || '' );
-        my @users = parse_users($3);
-
-        # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users;
-
-        for my $ref (@refs) {
-            for my $user (@users) {
-                add_rule( $perm, $ref, $user );
+    my $lines = shift;
+    trace(4, scalar(@$lines) . " lines incoming");
+
+    for my $line (@$lines) {
+        # user or repo groups
+        if ( $line =~ /^(@\S+) = (.*)/ ) {
+            add_to_group( $1, split( ' ', $2 ) );
+        } elsif ( $line =~ /^repo (.*)/ ) {
+            set_repolist( split( ' ', $1 ) );
+        } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
+            my $perm  = $1;
+            my @refs  = parse_refs( $2 || '' );
+            my @users = parse_users($3);
+
+            # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users;
+
+            for my $ref (@refs) {
+                for my $user (@users) {
+                    add_rule( $perm, $ref, $user );
+                }
             }
-        }
-    } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
-        my ( $key, $value ) = ( $1, $2 );
-        my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) );
-        push @validkeys, "gitolite-options\\..*";
-        my @matched = grep { $key =~ /^$_$/ } @validkeys;
-        # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1);
-        # XXX both $key and $value must satisfy a liberal but secure pattern
-        add_config( 1, $key, $value );
-    } elsif ( $line =~ /^subconf (\S+)$/ ) {
-        set_subconf($1);
-    } else {
-        _warn "?? $line";
-    }
-}
-
-# ----------------------------------------------------------------------
-
-sub incsub {
-    my $is_subconf = ( +shift eq 'subconf' );
-    my ( $include_glob, $subconf, $parser ) = @_;
-
-    _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master';
-
-    # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
-    # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME;
-
-    # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly*
-    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) {
-
-    trace( 3, $is_subconf, $include_glob );
-
-    for my $file ( glob($include_glob) ) {
-        _warn("included file not found: '$file'"), next unless -f $file;
-        _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$);
-        my $basename = $1;
-
-        next if already_included($file);
-
-        if ($is_subconf) {
-            $parser->("subconf $basename");
-            explode( $file, $basename, $parser );
-            $parser->("subconf $subconf");
-            # XXX g2 delegaton compat: deal with this: $subconf_seen++;
+        } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
+            my ( $key, $value ) = ( $1, $2 );
+            my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) );
+            push @validkeys, "gitolite-options\\..*";
+            my @matched = grep { $key =~ /^$_$/ } @validkeys;
+            # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1);
+            # XXX both $key and $value must satisfy a liberal but secure pattern
+            add_config( 1, $key, $value );
+        } elsif ( $line =~ /^subconf (\S+)$/ ) {
+            set_subconf($1);
         } else {
-            explode( $file, $subconf, $parser );
+            _warn "?? $line";
         }
     }
 }
 
-sub prefix_groupnames {
-    my ( $line, $subconf ) = @_;
-
-    my $lhs = '';
-    # save 'foo' if it's an '@foo = list' line
-    $lhs = $1 if $line =~ /^@(\S+) = /;
-    # prefix all @groups in the line
-    $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge;
-    # now prefix the LHS and store it if needed
-    if ($lhs) {
-        $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
-        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
-    }
-
-    return $line;
-}
-
-sub already_included {
-    my $file = shift;
-
-    my $file_id = device_inode($file);
-    return 0 unless $included{$file_id}++;
-
-    _warn("$file already included");
-    trace( 3, "$file already included" );
-    return 1;
-}
-
-sub device_inode {
-    my $file = shift;
-    trace( 3, $file, ( stat $file )[ 0, 1 ] );
-    return join( "/", ( stat $file )[ 0, 1 ] );
-}
-
 1;
diff --git a/Gitolite/Conf/Explode.pm b/Gitolite/Conf/Explode.pm
new file mode 100644
index 0000000..43a5778
--- /dev/null
+++ b/Gitolite/Conf/Explode.pm
@@ -0,0 +1,121 @@
+package Gitolite::Conf::Explode;
+
+# include/subconf processor
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  explode
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+# 'seen' for include/subconf files
+my %included = ();
+# 'seen' for group names on LHS
+my %prefixed_groupname = ();
+
+sub explode {
+    trace( 4, @_ );
+    my ( $file, $subconf, $out ) = @_;
+
+    # seed the 'seen' list if it's empty
+    $included{ device_inode("conf/gitolite.conf") }++ unless %included;
+
+    my $fh    = _open( "<", $file );
+    while (<$fh>) {
+        my $line = cleanup_conf_line($_);
+        next unless $line =~ /\S/;
+
+        $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
+
+        if ( $line =~ /^(include|subconf) (\S.+)$/ ) {
+            incsub( $1, $2, $subconf, $out );
+        } else {
+            # normal line, send it to the callback function
+            push @{$out}, $line;
+        }
+    }
+}
+
+sub incsub {
+    my $is_subconf = ( +shift eq 'subconf' );
+    my ( $include_glob, $subconf, $out ) = @_;
+
+    _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master';
+
+    _die "invalid include/subconf file/glob '$include_glob'"
+        unless $include_glob =~ /^"(.+)"$/
+            or $include_glob =~ /^'(.+)'$/;
+    $include_glob = $1;
+
+    # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
+    # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME;
+
+    # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly*
+    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) {
+
+    trace( 3, $is_subconf, $include_glob );
+
+    for my $file ( glob($include_glob) ) {
+        _warn("included file not found: '$file'"), next unless -f $file;
+        _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$);
+        my $basename = $1;
+
+        next if already_included($file);
+
+        if ($is_subconf) {
+            push @{$out}, "subconf $basename";
+            explode( $file, $basename, $out );
+            push @{$out}, "subconf $subconf";
+            # XXX g2 delegaton compat: deal with this: $subconf_seen++;
+        } else {
+            explode( $file, $subconf, $out );
+        }
+    }
+}
+
+sub prefix_groupnames {
+    my ( $line, $subconf ) = @_;
+
+    my $lhs = '';
+    # save 'foo' if it's an '@foo = list' line
+    $lhs = $1 if $line =~ /^@(\S+) = /;
+    # prefix all @groups in the line
+    $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge;
+    # now prefix the LHS and store it if needed
+    if ($lhs) {
+        $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
+        $prefixed_groupname{$subconf}{"\@$lhs"} = "\@$subconf.$lhs";
+        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
+    }
+
+    return $line;
+}
+
+sub already_included {
+    my $file = shift;
+
+    my $file_id = device_inode($file);
+    return 0 unless $included{$file_id}++;
+
+    _warn("$file already included");
+    trace( 3, "$file already included" );
+    return 1;
+}
+
+sub device_inode {
+    my $file = shift;
+    trace( 3, $file, ( stat $file )[ 0, 1 ] );
+    return join( "/", ( stat $file )[ 0, 1 ] );
+}
+
+1;
+
diff --git a/Gitolite/Conf/Sugar.pm b/Gitolite/Conf/Sugar.pm
index 5db96f2..bfa7c5f 100644
--- a/Gitolite/Conf/Sugar.pm
+++ b/Gitolite/Conf/Sugar.pm
@@ -4,78 +4,97 @@ package Gitolite::Conf::Sugar;
 # ----------------------------------------------------------------------
 
 @EXPORT = qw(
-  macro_expand
-  cleanup_conf_line
+  sugar
 );
 
 use Exporter 'import';
 
 use lib $ENV{GL_BINDIR};
-use Gitolite::Common;
 use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Explode;
 
 use strict;
 use warnings;
 
 # ----------------------------------------------------------------------
 
-sub macro_expand {
-    # site-local macros, if any, then gitolite internal macros, to munge the
-    # input conf line if needed
+sub sugar {
+    # gets a filename, returns a listref
 
-    my @lines = @_;
+    my @lines = ();
+    explode(shift, 'master', \@lines);
 
-    # TODO: user macros, how to allow the user to specify them?
+    my $lines;
+    $lines = \@lines;
 
-    # cheat, to keep *our* regexes simple :)
-    # XXX but this also kills the special '# BEGIN filename' and '# END
-    # filename' lines that explode() surrounds the actual data with when it
-    # called macro_expand().  Right now we don't need it, but...
-    @lines = grep /\S/, map { cleanup_conf_line($_) } @lines;
+    # run through the sugar stack one by one
+
+    # first, user supplied sugar:
+    if (exists $rc{SYNTACTIC_SUGAR}) {
+        if (ref($rc{SYNTACTIC_SUGAR}) ne 'ARRAY') {
+            _warn "bad syntax for specifying sugar scripts; see docs";
+        } else {
+            for my $s (@{ $rc{SYNTACTIC_SUGAR} }) {
+                _warn "ignoring unreadable sugar script $s" if not -r $s;
+                do $s if -r $s;
+                $lines = sugar_script($lines);
+                $lines = [ grep /\S/, map { cleanup_conf_line($_) } @$lines ];
+            }
+        }
+    }
 
-    @lines = owner_desc(@lines);
+    # then our stuff:
 
-    return @lines;
-}
+    $lines = owner_desc($lines);
+    # $lines = name_vref($lines);
 
-sub cleanup_conf_line {
-    my $line = shift;
-
-    # kill comments, but take care of "#" inside *simple* strings
-    $line =~ s/^((".*?"|[^#"])*)#.*/$1/;
-    # normalise whitespace; keeps later regexes very simple
-    $line =~ s/=/ = /;
-    $line =~ s/\s+/ /g;
-    $line =~ s/^ //;
-    $line =~ s/ $//;
-    return $line;
+    return $lines;
 }
 
 sub owner_desc {
-    my @lines = @_;
+    my $lines = shift;
     my @ret;
 
-    for my $line (@lines) {
-        #       reponame = "some description string"
-        #       reponame "owner name" = "some description string"
+    # XXX compat breakage: (1) adding repo/owner does not automatically add an
+    # entry to projects.list -- we need a post-procesor for that, and (2)
+    # removing the 'repo' line no longer suffices to remove the config entry
+    # from projects.list.  Maybe the post-procesor should do that as well?
+
+    # owner = "owner name"
+    #   ->  config gitweb.owner = owner name
+    # description = "some long description"
+    #   ->  config gitweb.description = some long description
+    # category = "whatever..."
+    #   ->  config gitweb.category = whatever...
+
+    # older formats:
+    # repo = "some long description"
+    # repo = "owner name" = "some long description"
+    #   ->  config gitweb.owner = owner name
+    #   ->  config gitweb.description = some long description
+
+    for my $line (@$lines) {
         if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
             my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
             # XXX these two checks should go into add_config
             # _die "bad repo name '$repo'" unless $repo =~ $REPONAME_PATT;
             # _die "$fragment attempting to set description for $repo"
             #   if check_fragment_repo_disallowed( $fragment, $repo );
-            push @ret, "config gitolite-options.repo-desc = $desc";
-            push @ret, "config gitolite-options.repo-owner = $owner" if $owner;
+            push @ret, "repo $repo";
+            push @ret, "config gitweb.description = $desc";
+            push @ret, "config gitweb.owner = $owner" if $owner;
         } elsif ( $line =~ /^desc = (\S.*)/ ) {
-            push @ret, "config gitolite-options.repo-desc = $1";
+            push @ret, "config gitweb.description = $1";
         } elsif ( $line =~ /^owner = (\S.*)/ ) {
-            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
-            push @ret, "config gitolite-options.repo-owner = $1";
+            push @ret, "config gitweb.owner = $1";
+        } elsif ( $line =~ /^category = (\S.*)/ ) {
+            push @ret, "config gitweb.category = $1";
         } else {
             push @ret, $line;
         }
     }
-    return @ret;
+    return \@ret;
 }
 
 1;

commit 4ab8db49252331785373e18ab2a3e241737cdab9
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 9 17:19:29 2012 +0530

    gitolite/commands/setup -> gitolite/setup

diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Setup.pm
similarity index 99%
rename from Gitolite/Commands/Setup.pm
rename to Gitolite/Setup.pm
index 939de8e..28d1d74 100644
--- a/Gitolite/Commands/Setup.pm
+++ b/Gitolite/Setup.pm
@@ -1,4 +1,4 @@
-package Gitolite::Commands::Setup;
+package Gitolite::Setup;
 
 # implements 'gitolite setup'
 # ----------------------------------------------------------------------
diff --git a/gitolite b/gitolite
index f6271b8..646d036 100755
--- a/gitolite
+++ b/gitolite
@@ -48,8 +48,8 @@ sub args {
 
     if ( $command eq 'setup' ) {
         shift @ARGV;
-        require Gitolite::Commands::Setup;
-        Gitolite::Commands::Setup->import;
+        require Gitolite::Setup;
+        Gitolite::Setup->import;
         setup();
     } elsif ( $command eq 'compile' ) {
         shift @ARGV;

commit dfccf1b0de0e1076125891134d6bf86797ed5f0e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 9 08:29:41 2012 +0530

    t01-basic completed, some change to test.pm as well

diff --git a/Gitolite/Test.pm b/Gitolite/Test.pm
index 6b4a0ae..bb4d0a9 100644
--- a/Gitolite/Test.pm
+++ b/Gitolite/Test.pm
@@ -27,6 +27,12 @@ use warnings;
 # required preamble for all tests
 try "
     DEF gsh = /TRACE: gsh.SOC=/
+    DEF reject = /hook declined to update/; /remote rejected.*hook declined/; /error: failed to push some refs to/
+
+    DEF AP_1 = cd ../gitolite-admin; ok or die cant find admin repo clone;
+    DEF AP_2 = AP_1; git add conf keydir; ok; git commit -m %1; ok; /master.* %1/
+    DEF ADMIN_PUSH = AP_2 %1; glt push admin origin; ok; gsh; /master -> master/
+
     ./g3-install -c admin
     cd tsh_tempdir;
 ";
diff --git a/t/t01-basic b/t/t01-basic
index 96d9fc9..83508a9 100755
--- a/t/t01-basic
+++ b/t/t01-basic
@@ -10,9 +10,9 @@ use Gitolite::Test;
 # ----------------------------------------------------------------------
 
 try "
-    plan 74
+    plan 213
 
-    ## clone
+    ## subtest 1
     glt clone dev2 file://gitolite-admin
                                 !ok;    gsh
                                         /DENIED by fallthru/
@@ -35,23 +35,19 @@ put "conf/gitolite.conf", "
 ";
 
 try "
-    ## push
+    # push
     git add conf;               ok
     git status -s;              ok;     /M  conf/gitolite.conf/
     git commit -m t01a;         ok;     /master.*t01a/
     glt push dev2 origin;       !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
-    glt push admin origin;      ok;     /master -. master/
+    glt push admin origin;      ok;     /master -> master/
     tsh empty;                  ok;
     glt push admin origin master:mm
                                 !ok;    gsh
                                         /DENIED by refs/heads/mm/
-                                        /remote: error: hook declined to update refs/heads/mm/
-                                        /To file://gitolite-admin/
-                                        /remote rejected. master -. mm .hook declined./
-                                        /error: failed to push some refs to 'file://gitolite-admin'/
-
+                                        reject
     ";
 
 put "conf/gitolite.conf", "
@@ -69,14 +65,10 @@ put "conf/gitolite.conf", "
 ";
 
 try "
-    ## push 2
-    git add conf;               ok
-    git status -s;              ok;     /M  conf/gitolite.conf/
-    git commit -m t01b;         ok;     /master.*t01b/
-    glt push admin origin;      ok;     gsh
-                                        /master -. master/
+    ## subtest 2
+    ADMIN_PUSH t01b
 
-    ## clone
+    # clone
     cd ..;                      ok;
     glt clone u1 file://t1;     !ok;    gsh
                                         /DENIED by fallthru/
@@ -86,25 +78,205 @@ try "
     ls -al t1;                  ok;     /$ENV{USER}.*$ENV{USER}.*\.git/
     cd t1;                      ok;
 
-    ## push
+    # push
     test-commit tc1 tc2 tc2;    ok;     /f7153e3/
     glt push u2 origin;         !ok;    gsh
                                         /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     glt push u3 origin master;  ok;     gsh
-                                        /master -. master/
+                                        /master -> master/
 
-    ## rewind
+    # rewind
     reset-h HEAD^;              ok;     /HEAD is now at 537f964 tc2/
     test-tick; test-commit tc3; ok;     /a691552/
     glt push u3 origin;         !ok;    gsh
-                                        /rejected.*master -. master.*non-fast-forward./
+                                        /rejected.*master -> master.*non-fast-forward./
     glt push u3 -f origin;      !ok;    gsh
+                                        reject
                                         /DENIED by fallthru/
-                                        /remote: error: hook declined to update refs/heads/master/
-                                        /To file://t1/
-                                        /remote rejected. master -. master .hook declined./
-                                        /error: failed to push some refs to 'file://t1'/
     glt push u4 origin +master; ok;     gsh
-                                        / \\+ f7153e3...a691552 master -. master.*forced update./
+                                        / \\+ f7153e3...a691552 master -> master.*forced update./
+";
+
+put "../gitolite-admin/conf/gitolite.conf", "
+    \@admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    include 'i1.conf'
+";
+
+put "../gitolite-admin/conf/i1.conf", "
+    \@g1 = u1
+    \@g2 = u2
+    \@g3 = u3
+    \@gaa = aa
+    repo \@gaa
+        RW+                 =   \@g1
+        RW                  =   \@g2
+        RW+     master      =   \@g3
+        RW      master      =   u4
+        -       master      =   u5
+        RW+     dev         =   u5
+        RW                  =   u5
+";
+
+try "
+    ## subtest 3
+    ADMIN_PUSH t01c
+
+    cd ..;                      ok
+";
+
+try "
+    glt clone u1 file://aa;     ok;     gsh
+    cd aa;                      ok
+    test-commit set3 t1 t2 t3 t4 t5 t6 t7 t8 t9
+                                ok
+    glt push u1 origin HEAD;    ok;     gsh
+                                        /To file://aa/
+                                        /\\* \\[new branch\\]      HEAD -> master/
+    branch dev;                 ok
+    branch foo;                 ok
+
+    # u1 rewind master ok
+    reset-h HEAD^;              ok
+    test-commit r1;             ok
+    glt push u1 origin +master; ok;     gsh
+                                        /To file://aa/
+                                        /\\+ cecf671...70469f5 master -> master .forced update./
+
+    # u2 rewind master !ok
+    reset-h HEAD^;              ok
+    test-commit r2;             ok
+    glt push u2 origin +master; !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+
+    # u3 rewind master ok
+    reset-h HEAD^;              ok
+    test-commit r3;             ok
+    glt push u3 origin +master; ok;     gsh
+                                        /To file://aa/
+                                        /\\+ 70469f5...f1e6821 master -> master .forced update./
+
+    # u4 push master ok
+    test-commit u4;             ok
+    glt push u4 origin master;  ok;     gsh
+                                        /To file://aa/
+                                        /f1e6821..d308cfb +master -> master/
+
+    # u4 rewind master !ok
+    reset-h HEAD^;              ok
+    glt push u4 origin +master; !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+
+    # u3,u4 push other branches !ok
+    glt push u3 origin dev;     !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+    glt push u4 origin dev;     !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+    glt push u3 origin foo;     !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+    glt push u4 origin foo;     !ok;    gsh
+                                        reject
+                                        /DENIED by fallthru/
+
+    # clean up for next set
+    glt push u1 -f origin master dev foo
+                                ok;     gsh
+                                        /d308cfb...f1e6821 master -> master .forced update./
+                                        /new branch.*dev -> dev/
+                                        /new branch.*foo -> foo/
+
+    # u5 push master !ok
+    test-commit u5
+    glt push u5 origin master;  !ok;    gsh
+                                        reject
+                                        /DENIED by refs/heads/master/
+
+    # u5 rewind dev ok
+    glt push u5 origin +dev^:dev
+                                ok;     gsh
+                                        /\\+ cecf671...5c8a89d dev\\^ -> dev .forced update./
+
+
+    # u5 rewind foo !ok
+    glt push u5 origin +foo^:foo
+                                !ok;    gsh
+                                        reject
+                                        /remote: FATAL: \\+ refs/heads/foo aa u5 DENIED by fallthru/
+
+    # u5 push foo ok
+    git checkout foo
+    /Switched to branch 'foo'/
+
+    test-commit u5
+    glt push u5 origin foo;     ok;     gsh
+                                        /cecf671..b27cf19 *foo -> foo/
+
+    # u1 delete dev ok
+    glt push u1 origin :dev;    ok;     gsh
+                                        / - \\[deleted\\] *dev/
+
+    # push it back
+    glt push u1 origin dev;     ok;     gsh
+                                        /\\* \\[new branch\\] *dev -> dev/
+
+";
+
+put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
+    \@gr1 = r1
+    repo \@gr1
+        RW  refs/heads/v[0-9]   = u1
+        RW  refs/heads          = tester
+";
+
+try "
+    ## subtest 4
+    ADMIN_PUSH t01d
+
+    cd ..;                      ok
+
+    glt clone tester file://r1; ok;     gsh
+                                        /Cloning into 'r1'.../
+    cd r1;                      ok
+    test-commit r1a r1b r1c r1d r1e r1f
+                                ok
+    glt push tester origin HEAD;ok;     gsh
+                                        /\\* \\[new branch\\] *HEAD -> master/
+    git branch v1
+    glt push tester origin v1;  ok;     gsh
+                                        /\\* \\[new branch\\] *v1 -> v1/
+
+";
+
+put "| cat >> ../gitolite-admin/conf/gitolite.conf", "
+    \@gr2 = r2
+    repo \@gr2
+        RW  refs/heads/v[0-9]   = u1
+        -   refs/heads/v[0-9]   = tester
+        RW  refs/heads          = tester
+";
+
+try "
+    ## subtest 5
+    ADMIN_PUSH t01e
+
+    cd ..;                      ok
+
+    glt clone tester file://r2; ok;     gsh
+                                        /Cloning into 'r2'.../
+    cd r2;                      ok
+    test-commit r2a r2b r2c r2d r2e r2f
+                                ok
+    glt push tester origin HEAD;ok;     gsh
+                                        /\\* \\[new branch\\] *HEAD -> master/
+    git branch v1
+    glt push tester origin v1;  !ok;    gsh
+                                        /W refs/heads/v1 r2 tester DENIED by refs/heads/v\\[0-9\\]/
 "

commit 56be906e5d739037f640d9aa2d9cb9c46910a6de
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 9 13:33:32 2012 +0530

    deny message change; t01 also changed accordingly

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index 3237748..fd117fc 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -69,13 +69,13 @@ sub access {
         trace( 4, "perm=$perm, refex=$refex" );
 
         # skip 'deny' rules if the ref is not (yet) known
-        next if $perm eq '-' and $ref eq 'unknown';
+        next if $perm eq '-' and $ref eq 'any';
 
-        # rule matches if ref matches or ref is unknown (see gitolite-shell)
-        next unless $ref =~ /^$refex/ or $ref eq 'unknown';
+        # rule matches if ref matches or ref is any (see gitolite-shell)
+        next unless $ref =~ /^$refex/ or $ref eq 'any';
 
         trace( 3, "DENIED by $refex" ) if $perm eq '-';
-        return "DENIED: $aa access to $repo by $user (rule: $refex)" if $perm eq '-';
+        return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-';
 
         # $perm can be RW\+?(C|D|CD|DC)?M?.  $aa can be W, +, C or D, or
         # any of these followed by "M".
@@ -85,7 +85,7 @@ sub access {
         return $refex if ( $perm =~ /$aaq/ );
     }
     trace( 3, "DENIED by fallthru" );
-    return "DENIED: $aa access to $repo by $user (fallthru)";
+    return "$aa $ref $repo $user DENIED by fallthru";
 }
 
 # ----------------------------------------------------------------------
diff --git a/g3-info b/g3-info
index d4db40e..28fa9ad 100755
--- a/g3-info
+++ b/g3-info
@@ -20,7 +20,7 @@ use warnings;
 
 my $user = shift or die;
 my $aa;
-my $ref = 'unknown';
+my $ref = 'any';
 
 my $ret;
 while (<>) {
diff --git a/gitolite-shell b/gitolite-shell
index 59b2984..46ce08b 100755
--- a/gitolite-shell
+++ b/gitolite-shell
@@ -31,11 +31,11 @@ sanity($repo);
 $ENV{GL_REPO} = $repo;
 my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
 
-# a ref of 'unknown' signifies that this is a pre-git check, where we don't
+# a ref of 'any' signifies that this is a pre-git check, where we don't
 # yet know the ref that will be eventually pushed (and even that won't apply
 # if it's a read operation).  See the matching code in access() for more.
-my $ret = access( $repo, $user, $aa, 'unknown' );
-trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" );
+my $ret = access( $repo, $user, $aa, 'any' );
+trace( 1, "access($repo, $user, $aa, 'any') -> $ret" );
 _die $ret if $ret =~ /DENIED/;
 
 $repo = "'$rc{GL_REPO_BASE}/$repo.git'";
diff --git a/t/t01-basic b/t/t01-basic
index 3970308..96d9fc9 100755
--- a/t/t01-basic
+++ b/t/t01-basic
@@ -15,7 +15,7 @@ try "
     ## clone
     glt clone dev2 file://gitolite-admin
                                 !ok;    gsh
-                                        /FATAL: DENIED: R access to gitolite-admin by dev2 .fallthru./
+                                        /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     glt clone admin --progress file://gitolite-admin
                                 ok;     gsh
@@ -40,13 +40,13 @@ try "
     git status -s;              ok;     /M  conf/gitolite.conf/
     git commit -m t01a;         ok;     /master.*t01a/
     glt push dev2 origin;       !ok;    gsh
-                                        /FATAL: DENIED: W access to gitolite-admin by dev2 .fallthru./
+                                        /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     glt push admin origin;      ok;     /master -. master/
     tsh empty;                  ok;
     glt push admin origin master:mm
                                 !ok;    gsh
-                                        /FATAL: DENIED: W access to gitolite-admin by admin .rule: refs/heads/mm./
+                                        /DENIED by refs/heads/mm/
                                         /remote: error: hook declined to update refs/heads/mm/
                                         /To file://gitolite-admin/
                                         /remote rejected. master -. mm .hook declined./
@@ -79,7 +79,7 @@ try "
     ## clone
     cd ..;                      ok;
     glt clone u1 file://t1;     !ok;    gsh
-                                        /FATAL: DENIED: R access to t1 by u1 .fallthru./
+                                        /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     glt clone u2 file://t1;     ok;     gsh
                                         /warning: You appear to have cloned an empty repository./
@@ -89,7 +89,7 @@ try "
     ## push
     test-commit tc1 tc2 tc2;    ok;     /f7153e3/
     glt push u2 origin;         !ok;    gsh
-                                        /FATAL: DENIED: W access to t1 by u2 .fallthru./
+                                        /DENIED by fallthru/
                                         /fatal: The remote end hung up unexpectedly/
     glt push u3 origin master;  ok;     gsh
                                         /master -. master/
@@ -100,7 +100,7 @@ try "
     glt push u3 origin;         !ok;    gsh
                                         /rejected.*master -. master.*non-fast-forward./
     glt push u3 -f origin;      !ok;    gsh
-                                        /FATAL: DENIED: \\+ access to t1 by u3 .fallthru./
+                                        /DENIED by fallthru/
                                         /remote: error: hook declined to update refs/heads/master/
                                         /To file://t1/
                                         /remote rejected. master -. master .hook declined./

commit b89ac4dd1ef405506f6a5fc4f72e70162eab57e8
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 9 06:22:01 2012 +0530

    queryrc.pm rolled into rc.pm, removed

diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm
deleted file mode 100644
index 4d241b4..0000000
--- a/Gitolite/Commands/QueryRc.pm
+++ /dev/null
@@ -1,81 +0,0 @@
-package Gitolite::Commands::QueryRc;
-
-# implements 'gitolite query-rc'
-# ----------------------------------------------------------------------
-
-=for usage
-
-Usage:  gitolite query-rc -a
-        gitolite query-rc <list of rc variables>
-
-Example:
-
-    gitolite query-rc GL_ADMIN_BASE GL_UMASK
-    # prints "/home/git/.gitolite<tab>0077" or similar
-
-    gitolite query-rc -a
-    # prints all known variables and values, one per line
-=cut
-
-# ----------------------------------------------------------------------
-
- at EXPORT = qw(
-  query_rc
-);
-
-use Exporter 'import';
-use Getopt::Long;
-
-use lib $ENV{GL_BINDIR};
-use Gitolite::Rc;
-use Gitolite::Common;
-
-use strict;
-use warnings;
-
-# ----------------------------------------------------------------------
-
-my $all = 0;
-
-# ----------------------------------------------------------------------
-
-sub query_rc {
-    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
-
-    my @vars = args();
-
-    no strict 'refs';
-
-    if ( $vars[0] eq '-a' ) {
-        for my $e (@Gitolite::Rc::EXPORT) {
-            # perl-ism warning: if you don't do this the implicit aliasing
-            # screws up Rc's EXPORT list
-            my $v = $e;
-            # we stop on the first non-$ var
-            last unless $v =~ s/^\$//;
-            print "$v=" . ( defined($$v) ? $$v : 'undef' ) . "\n";
-        }
-    }
-
-    our $GL_BINDIR = $ENV{GL_BINDIR};
-
-    print join( "\t", map { $$_ } grep { $$_ } @vars ) . "\n" if @vars;
-}
-
-# ----------------------------------------------------------------------
-
-sub args {
-    my $help = 0;
-
-    GetOptions(
-        'all|a'  => \$all,
-        'help|h' => \$help,
-    ) or usage();
-
-    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
-    return '-a' if $all;
-    usage() if not @ARGV or $help;
-    return @ARGV;
-}
-
-1;
diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm
index 6fe0ff9..47f0cdb 100644
--- a/Gitolite/Rc.pm
+++ b/Gitolite/Rc.pm
@@ -6,6 +6,7 @@ package Gitolite::Rc;
 @EXPORT = qw(
   %rc
   glrc
+  query_rc
 
   $ADC_CMD_ARGS_PATT
   $REF_OR_FILENAME_PATT
@@ -15,10 +16,17 @@ package Gitolite::Rc;
 );
 
 use Exporter 'import';
+use Getopt::Long;
 
 use lib $ENV{GL_BINDIR};
 use Gitolite::Common;
 
+# ----------------------------------------------------------------------
+
+our %rc;
+
+# ----------------------------------------------------------------------
+
 # variables that are/could be/should be in the rc file
 # ----------------------------------------------------------------------
 
@@ -86,6 +94,61 @@ sub glrc {
 }
 
 # ----------------------------------------------------------------------
+# implements 'gitolite query-rc'
+# ----------------------------------------------------------------------
+
+=for usage
+
+Usage:  gitolite query-rc -a
+        gitolite query-rc <list of rc variables>
+
+Example:
+
+    gitolite query-rc GL_ADMIN_BASE GL_UMASK
+    # prints "/home/git/.gitolite<tab>0077" or similar
+
+    gitolite query-rc -a
+    # prints all known variables and values, one per line
+=cut
+
+# ----------------------------------------------------------------------
+
+my $all = 0;
+
+sub query_rc {
+    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
+
+    my @vars = args();
+
+    no strict 'refs';
+
+    if ( $vars[0] eq '-a' ) {
+        for my $e (sort keys %rc) {
+            print "$e=" . ( defined($rc{$e}) ? $rc{$e} : 'undef' ) . "\n";
+        }
+        return;
+    }
+
+    our $GL_BINDIR = $ENV{GL_BINDIR};
+
+    print join( "\t", map { $rc{$_} } @vars ) . "\n" if @vars;
+}
+
+# ----------------------------------------------------------------------
+
+sub args {
+    my $help = 0;
+
+    GetOptions(
+        'all|a'  => \$all,
+        'help|h' => \$help,
+    ) or usage();
+
+    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
+    return '-a' if $all;
+    usage() if not @ARGV or $help;
+    return @ARGV;
+}
 
 1;
 
diff --git a/gitolite b/gitolite
index d8d1c3e..f6271b8 100755
--- a/gitolite
+++ b/gitolite
@@ -59,8 +59,6 @@ sub args {
         compile();
     } elsif ( $command eq 'query-rc' ) {
         shift @ARGV;
-        require Gitolite::Commands::QueryRc;
-        Gitolite::Commands::QueryRc->import;
         query_rc();
     } elsif ( $command eq 'list-groups' ) {
         shift @ARGV;

commit 8ffc5307d673bfeadc59547231185c320ad4fd7e
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Fri Mar 16 09:59:45 2012 +0530

    (lotsa files affected) rc file format changed; see below
    
    The rc file used to be a bunch of variables, each one requiring to be
    declared before being used.  While this was nice and all, it was a
    little cumbersome to add a new flag or option.
    
    If you disregard the "catch typos" aspect of having to predeclare
    variables, it's a lot more useful to have all of rc be in a hash and use
    any hash keys you want.
    
    There could be other uses; for instance it could hold arbitrary data
    that you would currently put in %ENV, without having to pollute %ENV if
    you don't need child tasks to inherit it.
    
    ----
    
    NOTE: I also ran perltidy, which I don't always remember to :)

diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm
index a36e4bd..4d241b4 100644
--- a/Gitolite/Commands/QueryRc.pm
+++ b/Gitolite/Commands/QueryRc.pm
@@ -40,7 +40,7 @@ my $all = 0;
 # ----------------------------------------------------------------------
 
 sub query_rc {
-    trace( 1, "rc file not found; default should be " . glrc_default_filename() ) if not glrc_filename();
+    trace( 1, "rc file not found; default should be " . glrc('default-filename') ) if not glrc('filename');
 
     my @vars = args();
 
diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Commands/Setup.pm
index aa312ad..939de8e 100644
--- a/Gitolite/Commands/Setup.pm
+++ b/Gitolite/Commands/Setup.pm
@@ -56,7 +56,7 @@ sub setup {
 
 sub first_run {
     # if the rc file could not be found, it's *definitely* a first run!
-    return not glrc_filename();
+    return not glrc('filename');
 }
 
 sub args {
@@ -91,7 +91,7 @@ sub args {
 
 sub setup_glrc {
     trace(1);
-    _print( glrc_default_filename(), glrc_default_text() );
+    _print( glrc('default-filename'), glrc('default-text') );
 }
 
 sub setup_gladmin {
@@ -99,7 +99,7 @@ sub setup_gladmin {
     trace( 1, $admin );
 
     # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
-    # $GL_REPO_BASE/gitolite-admin.git
+    # $rc{GL_REPO_BASE}/gitolite-admin.git
 
     # grab the pubkey content before we chdir() away
 
@@ -111,8 +111,8 @@ sub setup_gladmin {
 
     # set up the admin files in admin-base
 
-    _mkdir($GL_ADMIN_BASE);
-    _chdir($GL_ADMIN_BASE);
+    _mkdir( $rc{GL_ADMIN_BASE} );
+    _chdir( $rc{GL_ADMIN_BASE} );
 
     _mkdir("conf");
     my $conf;
@@ -132,15 +132,15 @@ sub setup_gladmin {
     # set up the admin repo in repo-base
 
     _chdir();
-    _mkdir($GL_REPO_BASE);
-    _chdir($GL_REPO_BASE);
+    _mkdir( $rc{GL_REPO_BASE} );
+    _chdir( $rc{GL_REPO_BASE} );
 
     new_repo("gitolite-admin");
 
     # commit the admin files to the admin repo
 
-    $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE;
-    _chdir("$GL_REPO_BASE/gitolite-admin.git");
+    $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
+    _chdir("$rc{GL_REPO_BASE}/gitolite-admin.git");
     system("git add conf/gitolite.conf");
     system("git add keydir") if $pubkey;
     tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm
index 8f7e111..cffee63 100644
--- a/Gitolite/Conf.pm
+++ b/Gitolite/Conf.pm
@@ -34,7 +34,7 @@ sub compile {
     trace(3);
     # XXX assume we're in admin-base/conf
 
-    _chdir($GL_ADMIN_BASE);
+    _chdir( $rc{GL_ADMIN_BASE} );
     _chdir("conf");
 
     explode( 'gitolite.conf', 'master', \&parse );
@@ -99,7 +99,7 @@ sub parse {
         }
     } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
         my ( $key, $value ) = ( $1, $2 );
-        my @validkeys = split( ' ', ( $GL_GITCONFIG_KEYS || '' ) );
+        my @validkeys = split( ' ', ( $rc{GL_GITCONFIG_KEYS} || '' ) );
         push @validkeys, "gitolite-options\\..*";
         my @matched = grep { $key =~ /^$_$/ } @validkeys;
         # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1);
@@ -123,8 +123,8 @@ sub incsub {
     # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
     # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME;
 
-    # XXX g2 diff: include glob is *implicitly* from $GL_ADMIN_BASE/conf, not *explicitly*
-    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$GL_ADMIN_BASE/conf/$include_glob")) {
+    # XXX g2 diff: include glob is *implicitly* from $rc{GL_ADMIN_BASE}/conf, not *explicitly*
+    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$rc{GL_ADMIN_BASE}/conf/$include_glob")) {
 
     trace( 3, $is_subconf, $include_glob );
 
diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index 9367296..3237748 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -92,7 +92,7 @@ sub access {
 
 sub load_common {
 
-    _chdir("$GL_ADMIN_BASE");
+    _chdir( $rc{GL_ADMIN_BASE} );
 
     # we take an unusual approach to caching this function!
     # (requires that first call to load_common is before first call to load_1)
@@ -118,7 +118,7 @@ sub load_1 {
     my $repo = shift;
     trace( 4, $repo );
 
-    _chdir("$GL_REPO_BASE");
+    _chdir( $rc{GL_REPO_BASE} );
 
     if ( $repo eq $last_repo ) {
         $repos{$repo} = $one_repo{$repo};
@@ -172,7 +172,7 @@ sub memberships {
 }
 
 sub data_version_mismatch {
-    return $data_version ne $current_data_version;
+    return $data_version ne glrc('current-data-version');
 }
 
 # ----------------------------------------------------------------------
@@ -192,10 +192,10 @@ Usage:  gitolite list-groups
     load_common();
 
     my @g = ();
-    while (my ($k, $v) = each ( %groups )) {
-        push @g, @{ $v };
+    while ( my ( $k, $v ) = each(%groups) ) {
+        push @g, @{$v};
     }
-    return (sort_u(\@g));
+    return ( sort_u( \@g ) );
 }
 
 sub list_users {
@@ -213,19 +213,18 @@ Usage:  gitolite list-users
 
     load_common();
 
-    my @u = map { keys %{ $_ } } values %repos;
-    $total = scalar(keys %split_conf);
+    my @u = map { keys %{$_} } values %repos;
+    $total = scalar( keys %split_conf );
     warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
     for my $one ( keys %split_conf ) {
         load_1($one);
-        $count++; print STDERR "$count / $total\r" if not ( $count % 100 ) and timer(5);
-        push @u, map { keys %{ $_ } } values %one_repo;
+        $count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
+        push @u, map { keys %{$_} } values %one_repo;
     }
     print STDERR "\n";
-    return (sort_u(\@u));
+    return ( sort_u( \@u ) );
 }
 
-
 sub list_repos {
 
     die "
@@ -241,7 +240,7 @@ Usage:  gitolite list-repos
     my @r = keys %repos;
     push @r, keys %split_conf;
 
-    return (sort_u(\@r));
+    return ( sort_u( \@r ) );
 }
 
 sub list_memberships {
@@ -258,7 +257,7 @@ Usage:  gitolite list-memberships <name>
 
     load_common();
     my @m = memberships($name);
-    return (sort_u(\@m));
+    return ( sort_u( \@m ) );
 }
 
 sub list_members {
@@ -276,13 +275,13 @@ Usage:  gitolite list-members <group name>
     load_common();
 
     my @m = ();
-    while (my ($k, $v) = each ( %groups )) {
-        for my $g ( @{ $v } ) {
+    while ( my ( $k, $v ) = each(%groups) ) {
+        for my $g ( @{$v} ) {
             push @m, $k if $g eq $name;
         }
     }
 
-    return (sort_u(\@m));
+    return ( sort_u( \@m ) );
 }
 
 # ----------------------------------------------------------------------
diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm
index 518a750..b99ac0b 100644
--- a/Gitolite/Conf/Store.pm
+++ b/Gitolite/Conf/Store.pm
@@ -145,14 +145,14 @@ sub set_subconf {
 
 sub new_repos {
     trace(3);
-    _chdir($GL_REPO_BASE);
+    _chdir( $rc{GL_REPO_BASE} );
 
     # normal repos
     my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
     # add in members of repo groups
     map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
 
-    for my $repo ( @{ sort_u(\@repos) } ) {
+    for my $repo ( @{ sort_u( \@repos ) } ) {
         next unless $repo =~ $REPONAME_PATT;    # skip repo patterns
         next if $repo =~ m(^\@|EXTCMD/);        # skip groups and fake repos
 
@@ -170,7 +170,7 @@ sub new_repo {
     _mkdir("$repo.git");
     _chdir("$repo.git");
     system("git init --bare >&2");
-    _chdir($GL_REPO_BASE);
+    _chdir( $rc{GL_REPO_BASE} );
     hook_1($repo);
 
     # XXX ignoring creator for now
@@ -180,7 +180,7 @@ sub new_repo {
 sub hook_repos {
     trace(3);
     # all repos, all hooks
-    _chdir($GL_REPO_BASE);
+    _chdir( $rc{GL_REPO_BASE} );
 
     # XXX g2 diff: we now don't care if it's a symlink -- it's upto the admin
     # on the server to make sure things are kosher
@@ -195,14 +195,14 @@ sub store {
     trace(3);
 
     # first write out the ones for the physical repos
-    _chdir($GL_REPO_BASE);
+    _chdir( $rc{GL_REPO_BASE} );
     my $phy_repos = list_phy_repos(1);
 
-    for my $repo (@{ $phy_repos }) {
+    for my $repo ( @{$phy_repos} ) {
         store_1($repo);
     }
 
-    _chdir($GL_ADMIN_BASE);
+    _chdir( $rc{GL_ADMIN_BASE} );
     store_common();
 }
 
@@ -261,7 +261,7 @@ sub store_common {
     my $cc = "conf/gitolite.conf-compiled.pm";
     my $compiled_fh = _open( ">", "$cc.new" );
 
-    my $data_version = $current_data_version;
+    my $data_version = glrc('current-data-version');
     trace( 1, "data_version = $data_version" );
     print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
 
@@ -296,20 +296,20 @@ sub store_common {
         # reset the gitolite supplied hooks, in case someone fiddled with
         # them, but only once per run
         if ( not $hook_reset ) {
-            _mkdir("$GL_ADMIN_BASE/hooks/common");
-            _mkdir("$GL_ADMIN_BASE/hooks/gitolite-admin");
-            _print( "$GL_ADMIN_BASE/hooks/common/update",              update_hook() );
-            _print( "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update", post_update_hook() );
-            chmod 0755, "$GL_ADMIN_BASE/hooks/common/update";
-            chmod 0755, "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update";
+            _mkdir("$rc{GL_ADMIN_BASE}/hooks/common");
+            _mkdir("$rc{GL_ADMIN_BASE}/hooks/gitolite-admin");
+            _print( "$rc{GL_ADMIN_BASE}/hooks/common/update",              update_hook() );
+            _print( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update", post_update_hook() );
+            chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/common/update";
+            chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update";
             $hook_reset++;
         }
 
         # propagate user hooks
-        ln_sf( "$GL_ADMIN_BASE/hooks/common", "*", "$repo.git/hooks" );
+        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
 
         # propagate admin hook
-        ln_sf( "$GL_ADMIN_BASE/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
+        ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
 
         # g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks
         # and user hooks) anymore.  I don't think anyone used them anyway...
diff --git a/Gitolite/Hooks/PostUpdate.pm b/Gitolite/Hooks/PostUpdate.pm
index 813733f..1ce07b2 100644
--- a/Gitolite/Hooks/PostUpdate.pm
+++ b/Gitolite/Hooks/PostUpdate.pm
@@ -27,7 +27,7 @@ sub post_update {
     _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/;
 
     {
-        local $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE;
+        local $ENV{GIT_WORK_TREE} = $rc{GL_ADMIN_BASE};
         tsh_try("git checkout -f --quiet master");
     }
     system("$ENV{GL_BINDIR}/gitolite compile");
diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm
index 94f6613..6fe0ff9 100644
--- a/Gitolite/Rc.pm
+++ b/Gitolite/Rc.pm
@@ -4,24 +4,14 @@ package Gitolite::Rc;
 # ----------------------------------------------------------------------
 
 @EXPORT = qw(
-  $GL_ADMIN_BASE
-  $GL_REPO_BASE
-
-  $GL_UMASK
-
-  $GL_GITCONFIG_KEYS
-
-  glrc_default_text
-  glrc_default_filename
-  glrc_filename
+  %rc
+  glrc
 
   $ADC_CMD_ARGS_PATT
   $REF_OR_FILENAME_PATT
   $REPONAME_PATT
   $REPOPATT_PATT
   $USERNAME_PATT
-
-  $current_data_version
 );
 
 use Exporter 'import';
@@ -32,14 +22,12 @@ use Gitolite::Common;
 # variables that are/could be/should be in the rc file
 # ----------------------------------------------------------------------
 
-$GL_ADMIN_BASE = "$ENV{HOME}/.gitolite";
-$GL_REPO_BASE  = "$ENV{HOME}/repositories";
+$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
+$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
 
 # variables that should probably never be changed
 # ----------------------------------------------------------------------
 
-$current_data_version = "3.0";
-
 $ADC_CMD_ARGS_PATT    = qr(^[0-9a-zA-Z._\@/+:-]*$);
 $REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
 $REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
@@ -48,44 +36,56 @@ $USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
 
 # ----------------------------------------------------------------------
 
+my $current_data_version = "3.0";
+
+my $rc = glrc('filename');
+do $rc if -r $rc;
+# let values specified in rc file override our internal ones
+ at rc{ keys %RC } = values %RC;
+
+# ----------------------------------------------------------------------
+
 use strict;
 use warnings;
 
 # ----------------------------------------------------------------------
 
-my $rc = glrc_filename();
-do $rc if -r $rc;
-
+my $glrc_default_text = '';
 {
-    my $glrc_default_text = '';
+    local $/ = undef;
+    $glrc_default_text = <DATA>;
+}
 
-    sub glrc_default_text {
+sub glrc {
+    my $cmd = shift;
+    if ( $cmd eq 'default-filename' ) {
+        trace( 1, "..should happen only on first run" );
+        return "$ENV{HOME}/.gitolite.rc";
+    } elsif ( $cmd eq 'default-text' ) {
         trace( 1, "..should happen only on first run" );
         return $glrc_default_text if $glrc_default_text;
-        local $/ = undef;
-        $glrc_default_text = <DATA>;
+        _die "rc file default text not set; this should not happen!";
+    } elsif ( $cmd eq 'filename' ) {
+        # where is the rc file?
+        trace(4);
+
+        # search $HOME first
+        return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
+        trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
+
+        # XXX for fedora, we can add the following line, but I would really prefer
+        # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
+        # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
+
+        return '';
+    } elsif ( $cmd eq 'current-data-version' ) {
+        return $current_data_version;
+    } else {
+        _die "unknown argument to glrc: $cmd";
     }
 }
 
-sub glrc_default_filename {
-    trace( 1, "..should happen only on first run" );
-    return "$ENV{HOME}/.gitolite.rc";
-}
-
-# where is the rc file?
-sub glrc_filename {
-    trace(4);
-
-    # search $HOME first
-    return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
-    trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
-
-    # XXX for fedora, we can add the following line, but I would really prefer
-    # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
-    # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
-
-    return '';
-}
+# ----------------------------------------------------------------------
 
 1;
 
@@ -99,8 +99,10 @@ __DATA__
 # this file is in perl syntax.  However, you do NOT need to know perl to edit
 # it; it should be fairly self-explanatory and easy to maintain
 
-$GL_UMASK = 0077;
-$GL_GITCONFIG_KEYS = "";
+%RC = (
+    GL_UMASK                    =>  0077,
+    GL_GITCONFIG_KEYS           =>  "",
+);
 
 # ------------------------------------------------------------------------------
 # per perl rules, this should be the last line in such a file:
diff --git a/gitolite b/gitolite
index 8cd39d2..d8d1c3e 100755
--- a/gitolite
+++ b/gitolite
@@ -79,7 +79,7 @@ sub args {
         print "$_\n" for ( @{ list_repos() } );
     } elsif ( $command eq 'list-phy-repos' ) {
         shift @ARGV;
-        _chdir($GL_REPO_BASE);
+        _chdir( $rc{GL_REPO_BASE} );
         print "$_\n" for ( @{ list_phy_repos() } );
     } elsif ( $command eq 'list-memberships' ) {
         shift @ARGV;
diff --git a/gitolite-shell b/gitolite-shell
index 479773c..59b2984 100755
--- a/gitolite-shell
+++ b/gitolite-shell
@@ -38,7 +38,7 @@ my $ret = access( $repo, $user, $aa, 'unknown' );
 trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" );
 _die $ret if $ret =~ /DENIED/;
 
-$repo = "'$GL_REPO_BASE/$repo.git'";
+$repo = "'$rc{GL_REPO_BASE}/$repo.git'";
 exec( "git", "shell", "-c", "$verb $repo" );
 
 # ----------------------------------------------------------------------
diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm
new file mode 100644
index 0000000..e84ee0c
--- /dev/null
+++ b/src/Gitolite/Rc.pm
@@ -0,0 +1,214 @@
+package Gitolite::Rc;
+
+# everything to do with 'rc'.  Also defines some 'constants'
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  %rc
+  glrc
+  query_rc
+  version
+
+  $REMOTE_COMMAND_PATT
+  $REF_OR_FILENAME_PATT
+  $REPONAME_PATT
+  $REPOPATT_PATT
+  $USERNAME_PATT
+);
+
+use Exporter 'import';
+use Getopt::Long;
+
+use Gitolite::Common;
+
+# ----------------------------------------------------------------------
+
+our %rc;
+
+# ----------------------------------------------------------------------
+
+# variables that are/could be/should be in the rc file
+# ----------------------------------------------------------------------
+
+$rc{GL_BINDIR}     = $ENV{GL_BINDIR};
+$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
+$rc{GL_REPO_BASE}  = "$ENV{HOME}/repositories";
+
+# variables that should probably never be changed
+# ----------------------------------------------------------------------
+
+$REMOTE_COMMAND_PATT  = qr(^[- 0-9a-zA-Z\@\%_=+:,./]*$);
+$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
+$REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
+$REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
+$USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
+
+# ----------------------------------------------------------------------
+
+my $current_data_version = "3.0";
+
+my $rc = glrc('filename');
+do $rc if -r $rc;
+_die "$rc seems to be for older gitolite" if defined($GL_ADMINDIR);
+# let values specified in rc file override our internal ones
+ at rc{ keys %RC } = values %RC;
+
+# testing sometimes requires all of it to be overridden silently; use an
+# env var that is highly unlikely to appear in real life :)
+do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
+
+# fix PATH (TODO: do it only if 'gitolite' isn't in PATH)
+$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}";
+
+# ----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $glrc_default_text = '';
+{
+    local $/ = undef;
+    $glrc_default_text = <DATA>;
+}
+
+sub glrc {
+    my $cmd = shift;
+    if ( $cmd eq 'default-filename' ) {
+        return "$ENV{HOME}/.gitolite.rc";
+    } elsif ( $cmd eq 'default-text' ) {
+        return $glrc_default_text if $glrc_default_text;
+        _die "rc file default text not set; this should not happen!";
+    } elsif ( $cmd eq 'filename' ) {
+        # where is the rc file?
+
+        # search $HOME first
+        return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
+
+        # XXX for fedora, we can add the following line, but I would really prefer
+        # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
+        # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
+
+        return '';
+    } elsif ( $cmd eq 'current-data-version' ) {
+        return $current_data_version;
+    } else {
+        _die "unknown argument to glrc: $cmd";
+    }
+}
+
+# ----------------------------------------------------------------------
+# implements 'gitolite query-rc' and 'version'
+# ----------------------------------------------------------------------
+
+# ----------------------------------------------------------------------
+
+my $all  = 0;
+my $nonl = 0;
+
+sub query_rc {
+
+    my @vars = args();
+
+    no strict 'refs';
+
+    if ($all) {
+        for my $e ( sort keys %rc ) {
+            print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
+        }
+        return;
+    }
+
+    print join( "\t", map { $rc{$_} || '' } @vars ) . ( $nonl ? '' : "\n" ) if @vars;
+}
+
+sub version {
+    my $version = '';
+    $version = '(unknown)';
+    for ("$rc{GL_ADMIN_BASE}/VERSION") {
+        $version = slurp($_) if -r $_;
+    }
+    chomp($version);
+    return $version;
+}
+
+# ----------------------------------------------------------------------
+
+=for args
+Usage:  gitolite query-rc -a
+        gitolite query-rc [-n] <list of rc variables>
+
+    -a          print all variables and values
+    -n          do not append a newline
+
+Example:
+
+    gitolite query-rc GL_ADMIN_BASE UMASK
+    # prints "/home/git/.gitolite<tab>0077" or similar
+
+    gitolite query-rc -a
+    # prints all known variables and values, one per line
+=cut
+
+sub args {
+    my $help = 0;
+
+    GetOptions(
+        'all|a'  => \$all,
+        'nonl|n' => \$nonl,
+        'help|h' => \$help,
+    ) or usage();
+
+    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
+    usage() if not $all and not @ARGV or $help;
+    return @ARGV;
+}
+
+1;
+
+# ----------------------------------------------------------------------
+
+__DATA__
+# configuration variables for gitolite
+
+# This file is in perl syntax.  But you do NOT need to know perl to edit it --
+# just mind the commas and make sure the brackets and braces stay matched up!
+
+# (Tip: perl allows a comma after the last item in a list also!)
+
+%RC = (
+    UMASK                       =>  0077,
+    GL_GITCONFIG_KEYS           =>  "",
+
+    # comment out or uncomment as needed
+    # these will run in sequence during the conf file parse
+    SYNTACTIC_SUGAR             =>
+        [
+            # 'continuation-lines',
+        ],
+
+    # comment out or uncomment as needed
+    # these will run in sequence after post-update
+    POST_COMPILE                =>
+        [
+            'post-compile/ssh-authkeys',
+        ],
+
+    # comment out or uncomment as needed
+    # these are available to remote users
+    COMMANDS                    =>
+        {
+            'help'              =>  1,
+            'info'              =>  1,
+        },
+);
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:

commit 1a1be8b222b6b4c7542af49c892eb84a498d1337
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 19:50:34 2012 +0530

    'gitolite list-phy-repos' added

diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm
index 35a5c0f..16d25ba 100644
--- a/Gitolite/Common.pm
+++ b/Gitolite/Common.pm
@@ -147,6 +147,7 @@ sub sort_u {
     my @phy_repos = ();
 
     sub list_phy_repos {
+        _die "'gitolite list_phy_repos' takes no arguments" if @ARGV;
         trace(3);
 
         # use cached value only if it exists *and* no arg was received (i.e.,
diff --git a/gitolite b/gitolite
index dc7fb0e..8cd39d2 100755
--- a/gitolite
+++ b/gitolite
@@ -14,6 +14,7 @@ The following subcommands are available; they should all respond to '-h':
     list-groups                 list all group names in conf
     list-users                  list all users/user groups in conf
     list-repos                  list all repos/repo groups in conf
+    list-phy-repos              list all repos actually on disk
     list-memberships            list all groups a name is a member of
     list-members                list all members of a group
 
@@ -29,6 +30,7 @@ use FindBin;
 
 BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; }
 use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
 use Gitolite::Common;
 
 use strict;
@@ -75,6 +77,10 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_repos() } );
+    } elsif ( $command eq 'list-phy-repos' ) {
+        shift @ARGV;
+        _chdir($GL_REPO_BASE);
+        print "$_\n" for ( @{ list_phy_repos() } );
     } elsif ( $command eq 'list-memberships' ) {
         shift @ARGV;
         require Gitolite::Conf::Load;
@@ -85,5 +91,7 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_members() } );
+    } else {
+        _die "unknown gitolite sub-command";
     }
 }

commit 95c6952e11eaaef1285342e1667ba99a07ca873c
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 19:20:00 2012 +0530

    list_phy_repos() moved from store.pm to common.pm
    
    but you need to chdir() to the right place before calling it

diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm
index e5d492a..35a5c0f 100644
--- a/Gitolite/Common.pm
+++ b/Gitolite/Common.pm
@@ -6,7 +6,7 @@ package Gitolite::Common;
 #<<<
 @EXPORT = qw(
   print2  dbg     _mkdir  _open   ln_sf     tsh_rc      sort_u
-  say     _warn   _chdir  _print            tsh_text
+  say     _warn   _chdir  _print            tsh_text    list_phy_repos
   say2    _die            slurp             tsh_lines
           trace                             tsh_try
           usage                             tsh_run
@@ -142,6 +142,26 @@ sub sort_u {
     my @sort_u = sort keys %uniq;
     return \@sort_u;
 }
+
+{
+    my @phy_repos = ();
+
+    sub list_phy_repos {
+        trace(3);
+
+        # use cached value only if it exists *and* no arg was received (i.e.,
+        # receiving *any* arg invalidates cache)
+        return \@phy_repos if ( @phy_repos and not @_ );
+
+        for my $repo (`find . -name "*.git" -prune`) {
+            chomp($repo);
+            $repo =~ s(\./(.*)\.git$)($1);
+            push @phy_repos, $repo;
+        }
+        return sort_u(\@phy_repos);
+    }
+}
+
 # ----------------------------------------------------------------------
 
 # bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh)
diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm
index 69056a0..518a750 100644
--- a/Gitolite/Conf/Store.pm
+++ b/Gitolite/Conf/Store.pm
@@ -195,10 +195,10 @@ sub store {
     trace(3);
 
     # first write out the ones for the physical repos
-    my @phy_repos = list_physical_repos(1);
-
     _chdir($GL_REPO_BASE);
-    for my $repo (@phy_repos) {
+    my $phy_repos = list_phy_repos(1);
+
+    for my $repo (@{ $phy_repos }) {
         store_1($repo);
     }
 
@@ -228,26 +228,6 @@ sub check_subconf_repo_disallowed {
     return 1;
 }
 
-{
-    my @phy_repos = ();
-
-    sub list_physical_repos {
-        trace(3);
-        _chdir($GL_REPO_BASE);
-
-        # use cached value only if it exists *and* no arg was received (i.e.,
-        # receiving *any* arg invalidates cache)
-        return @phy_repos if ( @phy_repos and not @_ );
-
-        for my $repo (`find . -name "*.git" -prune`) {
-            chomp($repo);
-            $repo =~ s(\./(.*)\.git$)($1);
-            push @phy_repos, $repo;
-        }
-        return @phy_repos;
-    }
-}
-
 sub store_1 {
     # warning: writes and *deletes* it from %repos and %configs
     my ($repo) = shift;

commit 00934c83045262a1c38214ca94b8bb55af3a1426
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 17:59:44 2012 +0530

    'gitolite list-members' added

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index cc88225..9367296 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -11,6 +11,7 @@ package Gitolite::Conf::Load;
   list_users
   list_repos
   list_memberships
+  list_members
 );
 
 use Exporter 'import';
@@ -260,6 +261,30 @@ Usage:  gitolite list-memberships <name>
     return (sort_u(\@m));
 }
 
+sub list_members {
+
+    die "
+Usage:  gitolite list-members <group name>
+
+  - list all members of a group
+  - takes one group name
+
+" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_;
+
+    my $name = ( @_ ? shift @_ : shift @ARGV );
+
+    load_common();
+
+    my @m = ();
+    while (my ($k, $v) = each ( %groups )) {
+        for my $g ( @{ $v } ) {
+            push @m, $k if $g eq $name;
+        }
+    }
+
+    return (sort_u(\@m));
+}
+
 # ----------------------------------------------------------------------
 
 {
diff --git a/gitolite b/gitolite
index 5d5f3dd..dc7fb0e 100755
--- a/gitolite
+++ b/gitolite
@@ -15,6 +15,7 @@ The following subcommands are available; they should all respond to '-h':
     list-users                  list all users/user groups in conf
     list-repos                  list all repos/repo groups in conf
     list-memberships            list all groups a name is a member of
+    list-members                list all members of a group
 
 Warnings:
   - list-users is disk bound and could take a while on sites with 1000s of repos
@@ -79,5 +80,10 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_memberships() } );
+    } elsif ( $command eq 'list-members' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_members() } );
     }
 }

commit c9826eee077a0c43977398efeed6fc2855e31d69
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 17:00:27 2012 +0530

    'gitolite list-memberships' added

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index 130850a..cc88225 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -10,6 +10,7 @@ package Gitolite::Conf::Load;
   list_groups
   list_users
   list_repos
+  list_memberships
 );
 
 use Exporter 'import';
@@ -242,6 +243,23 @@ Usage:  gitolite list-repos
     return (sort_u(\@r));
 }
 
+sub list_memberships {
+
+    die "
+Usage:  gitolite list-memberships <name>
+
+  - list all groups a name is a member of
+  - takes one user/repo name
+
+" if @ARGV and $ARGV[0] eq '-h' or not @ARGV and not @_;
+
+    my $name = ( @_ ? shift @_ : shift @ARGV );
+
+    load_common();
+    my @m = memberships($name);
+    return (sort_u(\@m));
+}
+
 # ----------------------------------------------------------------------
 
 {
diff --git a/gitolite b/gitolite
index 6adc347..5d5f3dd 100755
--- a/gitolite
+++ b/gitolite
@@ -14,9 +14,12 @@ The following subcommands are available; they should all respond to '-h':
     list-groups                 list all group names in conf
     list-users                  list all users/user groups in conf
     list-repos                  list all repos/repo groups in conf
+    list-memberships            list all groups a name is a member of
 
 Warnings:
-  - list-users is disk bound and could take a while on sites with thousands of repos
+  - list-users is disk bound and could take a while on sites with 1000s of repos
+  - list-memberships does not check if the name is known; unknown names come
+    back with 2 answers: the name itself and '@all'
 =cut
 
 # ----------------------------------------------------------------------
@@ -71,5 +74,10 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_repos() } );
+    } elsif ( $command eq 'list-memberships' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_memberships() } );
     }
 }

commit 4e81d3cfeddb3c30a8c306377d514b2b3375eee0
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 16:14:13 2012 +0530

    'gitolite list-repos' added, plus some usage message changes

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index c9b09b5..130850a 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -9,6 +9,7 @@ package Gitolite::Conf::Load;
 
   list_groups
   list_users
+  list_repos
 );
 
 use Exporter 'import';
@@ -178,7 +179,13 @@ sub data_version_mismatch {
 
 # list all groups
 sub list_groups {
-    die "\nUsage:  gitolite list-groups\n\n(no options, no flags)\n\n" if @ARGV;
+    die "
+Usage:  gitolite list-groups
+
+  - lists all group names in conf
+  - no options, no flags
+
+" if @ARGV;
 
     load_common();
 
@@ -193,7 +200,14 @@ sub list_users {
     my $count = 0;
     my $total = 0;
 
-    die "\nUsage:  gitolite list-users\n\n  - no options, no flags\n  - may be slow if you have thousands of repos\n\n" if @ARGV;
+    die "
+Usage:  gitolite list-users
+
+  - lists all users/user groups in conf
+  - no options, no flags
+  - WARNING: may be slow if you have thousands of repos
+
+" if @ARGV;
 
     load_common();
 
@@ -209,6 +223,25 @@ sub list_users {
     return (sort_u(\@u));
 }
 
+
+sub list_repos {
+
+    die "
+Usage:  gitolite list-repos
+
+  - lists all repos/repo groups in conf
+  - no options, no flags
+
+" if @ARGV;
+
+    load_common();
+
+    my @r = keys %repos;
+    push @r, keys %split_conf;
+
+    return (sort_u(\@r));
+}
+
 # ----------------------------------------------------------------------
 
 {
diff --git a/gitolite b/gitolite
index fb835e2..6adc347 100755
--- a/gitolite
+++ b/gitolite
@@ -12,7 +12,8 @@ The following subcommands are available; they should all respond to '-h':
     compile                     compile gitolite.conf
     query-rc                    get values of rc variables
     list-groups                 list all group names in conf
-    list-users                  list all user names in conf
+    list-users                  list all users/user groups in conf
+    list-repos                  list all repos/repo groups in conf
 
 Warnings:
   - list-users is disk bound and could take a while on sites with thousands of repos
@@ -65,5 +66,10 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_users() } );
+    } elsif ( $command eq 'list-repos' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_repos() } );
     }
 }

commit d88cdbefd644185a332f5854f027780cb8ceca30
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 15:06:05 2012 +0530

    'gitolite list-users' added (but see warnings)
    
    this is pretty slow if you have thousands of repos, since it has to read
    and parse a 'gl-conf' file for every repo.  (For example, on a Lenovo
    X201 thinkpad with 11170 repos and a cold cache, it took 288 seconds).
    
    (With a hot cache -- like if you run the command again -- it took 2.1
    seconds!  So if you have a fast disk this may not be an issue for you
    even if you have 10,000+ repos).

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index 34b6a1e..c9b09b5 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -8,6 +8,7 @@ package Gitolite::Conf::Load;
   access
 
   list_groups
+  list_users
 );
 
 use Exporter 'import';
@@ -188,5 +189,42 @@ sub list_groups {
     return (sort_u(\@g));
 }
 
+sub list_users {
+    my $count = 0;
+    my $total = 0;
+
+    die "\nUsage:  gitolite list-users\n\n  - no options, no flags\n  - may be slow if you have thousands of repos\n\n" if @ARGV;
+
+    load_common();
+
+    my @u = map { keys %{ $_ } } values %repos;
+    $total = scalar(keys %split_conf);
+    warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
+    for my $one ( keys %split_conf ) {
+        load_1($one);
+        $count++; print STDERR "$count / $total\r" if not ( $count % 100 ) and timer(5);
+        push @u, map { keys %{ $_ } } values %one_repo;
+    }
+    print STDERR "\n";
+    return (sort_u(\@u));
+}
+
+# ----------------------------------------------------------------------
+
+{
+    my $start_time = 0;
+
+    sub timer {
+        unless ($start_time) {
+            $start_time = time();
+            return 0;
+        }
+        my $elapsed = shift;
+        return 0 if time() - $start_time < $elapsed;
+        $start_time = time();
+        return 1;
+    }
+}
+
 1;
 
diff --git a/gitolite b/gitolite
index 43868a5..fb835e2 100755
--- a/gitolite
+++ b/gitolite
@@ -12,6 +12,10 @@ The following subcommands are available; they should all respond to '-h':
     compile                     compile gitolite.conf
     query-rc                    get values of rc variables
     list-groups                 list all group names in conf
+    list-users                  list all user names in conf
+
+Warnings:
+  - list-users is disk bound and could take a while on sites with thousands of repos
 =cut
 
 # ----------------------------------------------------------------------
@@ -56,5 +60,10 @@ sub args {
         require Gitolite::Conf::Load;
         Gitolite::Conf::Load->import;
         print "$_\n" for ( @{ list_groups() } );
+    } elsif ( $command eq 'list-users' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_users() } );
     }
 }

commit 1be66dc10e0fe2130fbc8cfb6ddd19c1c20b84e5
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 12:13:50 2012 +0530

    'gitolite list-groups' added

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index af62812..34b6a1e 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -6,6 +6,8 @@ package Gitolite::Conf::Load;
 @EXPORT = qw(
   load
   access
+
+  list_groups
 );
 
 use Exporter 'import';
@@ -169,5 +171,22 @@ sub data_version_mismatch {
     return $data_version ne $current_data_version;
 }
 
+# ----------------------------------------------------------------------
+# api functions
+# ----------------------------------------------------------------------
+
+# list all groups
+sub list_groups {
+    die "\nUsage:  gitolite list-groups\n\n(no options, no flags)\n\n" if @ARGV;
+
+    load_common();
+
+    my @g = ();
+    while (my ($k, $v) = each ( %groups )) {
+        push @g, @{ $v };
+    }
+    return (sort_u(\@g));
+}
+
 1;
 
diff --git a/gitolite b/gitolite
index f89f12f..43868a5 100755
--- a/gitolite
+++ b/gitolite
@@ -11,6 +11,7 @@ The following subcommands are available; they should all respond to '-h':
     setup                       1st run: initial setup; all runs: hook fixups
     compile                     compile gitolite.conf
     query-rc                    get values of rc variables
+    list-groups                 list all group names in conf
 =cut
 
 # ----------------------------------------------------------------------
@@ -50,5 +51,10 @@ sub args {
         require Gitolite::Commands::QueryRc;
         Gitolite::Commands::QueryRc->import;
         query_rc();
+    } elsif ( $command eq 'list-groups' ) {
+        shift @ARGV;
+        require Gitolite::Conf::Load;
+        Gitolite::Conf::Load->import;
+        print "$_\n" for ( @{ list_groups() } );
     }
 }

commit b0ceac2594c17b06c1175845757b7a9bba5d1d9a
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 12:31:01 2012 +0530

    chdirs moved from load() to load_common() and load_1()

diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
index db11679..af62812 100644
--- a/Gitolite/Conf/Load.pm
+++ b/Gitolite/Conf/Load.pm
@@ -43,8 +43,8 @@ my $last_repo = '';
         trace( 4, "$repo" );
         if ( $repo ne $loaded_repo ) {
             trace( 3, "loading $repo..." );
-            _chdir("$GL_ADMIN_BASE"); load_common();
-            _chdir("$GL_REPO_BASE");  load_1($repo);
+            load_common();
+            load_1($repo);
             $loaded_repo = $repo;
         }
     }
@@ -86,6 +86,8 @@ sub access {
 
 sub load_common {
 
+    _chdir("$GL_ADMIN_BASE");
+
     # we take an unusual approach to caching this function!
     # (requires that first call to load_common is before first call to load_1)
     if ( $last_repo and $split_conf{$last_repo} ) {
@@ -110,6 +112,8 @@ sub load_1 {
     my $repo = shift;
     trace( 4, $repo );
 
+    _chdir("$GL_REPO_BASE");
+
     if ( $repo eq $last_repo ) {
         $repos{$repo} = $one_repo{$repo};
         $configs{$repo} = $one_config{$repo} if $one_config{$repo};

commit 60e190215e5e6defe593df8b3eb2e7d3bd409f46
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Thu Mar 8 13:30:13 2012 +0530

    very basic, usable, first cut done
    
      - sausage making hidden
      - lots of important features missing

diff --git a/Gitolite/Commands/QueryRc.pm b/Gitolite/Commands/QueryRc.pm
new file mode 100644
index 0000000..a36e4bd
--- /dev/null
+++ b/Gitolite/Commands/QueryRc.pm
@@ -0,0 +1,81 @@
+package Gitolite::Commands::QueryRc;
+
+# implements 'gitolite query-rc'
+# ----------------------------------------------------------------------
+
+=for usage
+
+Usage:  gitolite query-rc -a
+        gitolite query-rc <list of rc variables>
+
+Example:
+
+    gitolite query-rc GL_ADMIN_BASE GL_UMASK
+    # prints "/home/git/.gitolite<tab>0077" or similar
+
+    gitolite query-rc -a
+    # prints all known variables and values, one per line
+=cut
+
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  query_rc
+);
+
+use Exporter 'import';
+use Getopt::Long;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $all = 0;
+
+# ----------------------------------------------------------------------
+
+sub query_rc {
+    trace( 1, "rc file not found; default should be " . glrc_default_filename() ) if not glrc_filename();
+
+    my @vars = args();
+
+    no strict 'refs';
+
+    if ( $vars[0] eq '-a' ) {
+        for my $e (@Gitolite::Rc::EXPORT) {
+            # perl-ism warning: if you don't do this the implicit aliasing
+            # screws up Rc's EXPORT list
+            my $v = $e;
+            # we stop on the first non-$ var
+            last unless $v =~ s/^\$//;
+            print "$v=" . ( defined($$v) ? $$v : 'undef' ) . "\n";
+        }
+    }
+
+    our $GL_BINDIR = $ENV{GL_BINDIR};
+
+    print join( "\t", map { $$_ } grep { $$_ } @vars ) . "\n" if @vars;
+}
+
+# ----------------------------------------------------------------------
+
+sub args {
+    my $help = 0;
+
+    GetOptions(
+        'all|a'  => \$all,
+        'help|h' => \$help,
+    ) or usage();
+
+    usage("'-a' cannot be combined with other arguments") if $all and @ARGV;
+    return '-a' if $all;
+    usage() if not @ARGV or $help;
+    return @ARGV;
+}
+
+1;
diff --git a/Gitolite/Commands/Setup.pm b/Gitolite/Commands/Setup.pm
new file mode 100644
index 0000000..aa312ad
--- /dev/null
+++ b/Gitolite/Commands/Setup.pm
@@ -0,0 +1,161 @@
+package Gitolite::Commands::Setup;
+
+# implements 'gitolite setup'
+# ----------------------------------------------------------------------
+
+=for usage
+Usage:  gitolite setup [<at least one option>]
+
+
+    -a, --admin <name>          admin user name
+    -pk --pubkey <file>         pubkey file name
+    -f, --fixup-hooks           fixup hooks
+
+First run:
+    -a      required
+    -pk     required for ssh mode install
+
+Later runs:
+    no options required; but '-f' can be specified for clarity
+=cut
+
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  setup
+);
+
+use Exporter 'import';
+use Getopt::Long;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Store;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub setup {
+    my ( $admin, $pubkey, $argv ) = args();
+    # first time
+    if ( first_run() ) {
+        trace( 1, "..should happen only on first run" );
+        setup_glrc();
+        setup_gladmin( $admin, $pubkey, $argv );
+    }
+
+    system("$ENV{GL_BINDIR}/gitolite compile");
+
+    hook_repos();    # all of them, just to be sure
+}
+
+# ----------------------------------------------------------------------
+
+sub first_run {
+    # if the rc file could not be found, it's *definitely* a first run!
+    return not glrc_filename();
+}
+
+sub args {
+    my $admin  = '';
+    my $pubkey = '';
+    my $fixup  = 0;
+    my $help   = 0;
+    my $argv   = join( " ", @ARGV );
+
+    GetOptions(
+        'admin|a=s'     => \$admin,
+        'pubkey|pk=s'   => \$pubkey,
+        'fixup-hooks|f' => \$fixup,
+        'help|h'        => \$help,
+    ) or usage();
+
+    usage() if $help;
+    usage("first run requires '-a'")     if first_run() and not($admin);
+    _warn("not setting up ssh...")       if first_run() and $admin and not $pubkey;
+    _warn("first run, ignoring '-f'...") if first_run() and $fixup;
+    _warn("not first run, ignoring '-a' / '-pk'...") if not first_run() and ( $admin or $pubkey );
+
+    if ($pubkey) {
+        $pubkey =~ /\.pub$/ or _die "$pubkey name does not end in .pub";
+        tsh_try("cat $pubkey")              or _die "$pubkey not a readable file";
+        tsh_lines() == 1                    or _die "$pubkey must have exactly one line";
+        tsh_try("ssh-keygen -l -f $pubkey") or _die "$pubkey does not seem to be a valid ssh pubkey file";
+    }
+
+    return ( $admin || '', $pubkey || '', $argv );
+}
+
+sub setup_glrc {
+    trace(1);
+    _print( glrc_default_filename(), glrc_default_text() );
+}
+
+sub setup_gladmin {
+    my ( $admin, $pubkey, $argv ) = @_;
+    trace( 1, $admin );
+
+    # reminder: 'admin files' are in ~/.gitolite, 'admin repo' is
+    # $GL_REPO_BASE/gitolite-admin.git
+
+    # grab the pubkey content before we chdir() away
+
+    my $pubkey_content = '';
+    if ($pubkey) {
+        $pubkey_content = slurp($pubkey);
+        $pubkey =~ s(.*/)();    # basename
+    }
+
+    # set up the admin files in admin-base
+
+    _mkdir($GL_ADMIN_BASE);
+    _chdir($GL_ADMIN_BASE);
+
+    _mkdir("conf");
+    my $conf;
+    {
+        local $/ = undef;
+        $conf = <DATA>;
+    }
+    $conf =~ s/%ADMIN/$admin/g;
+
+    _print( "conf/gitolite.conf", $conf );
+
+    if ($pubkey) {
+        _mkdir("keydir");
+        _print( "keydir/$pubkey", $pubkey_content );
+    }
+
+    # set up the admin repo in repo-base
+
+    _chdir();
+    _mkdir($GL_REPO_BASE);
+    _chdir($GL_REPO_BASE);
+
+    new_repo("gitolite-admin");
+
+    # commit the admin files to the admin repo
+
+    $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE;
+    _chdir("$GL_REPO_BASE/gitolite-admin.git");
+    system("git add conf/gitolite.conf");
+    system("git add keydir") if $pubkey;
+    tsh_try("git config --get user.email") or tsh_run( "git config user.email $ENV{USER}\@" . `hostname` );
+    tsh_try("git config --get user.name")  or tsh_run( "git config user.name '$ENV{USER} on '" . `hostname` );
+    tsh_try("git diff --cached --quiet")
+      or tsh_try("git commit -am 'gl-setup $argv'")
+      or die "setup failed to commit to the admin repo";
+    delete $ENV{GIT_WORK_TREE};
+}
+
+1;
+
+__DATA__
+repo gitolite-admin
+    RW+     =   %ADMIN
+
+repo testing
+    RW+     =   @all
diff --git a/Gitolite/Common.pm b/Gitolite/Common.pm
new file mode 100644
index 0000000..e5d492a
--- /dev/null
+++ b/Gitolite/Common.pm
@@ -0,0 +1,175 @@
+package Gitolite::Common;
+
+# common (non-gitolite-specific) functions
+# ----------------------------------------------------------------------
+
+#<<<
+ at EXPORT = qw(
+  print2  dbg     _mkdir  _open   ln_sf     tsh_rc      sort_u
+  say     _warn   _chdir  _print            tsh_text
+  say2    _die            slurp             tsh_lines
+          trace                             tsh_try
+          usage                             tsh_run
+);
+#>>>
+use Exporter 'import';
+use File::Path qw(mkpath);
+use Carp qw(carp cluck croak confess);
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub print2 {
+    local $/ = "\n";
+    print STDERR @_;
+}
+
+sub say {
+    local $/ = "\n";
+    print @_, "\n";
+}
+
+sub say2 {
+    local $/ = "\n";
+    print STDERR @_, "\n";
+}
+
+sub trace {
+    return unless defined( $ENV{D} );
+
+    my $level = shift;
+    my $args  = ''; $args = join( ", ", @_ ) if @_;
+    my $sub   = ( caller 1 )[3] || ''; $sub =~ s/.*://; $sub .= ' ' x ( 32 - length($sub) );
+    say2 "TRACE $level $sub", $args if $ENV{D} >= $level;
+}
+
+sub dbg {
+    use Data::Dumper;
+    return unless defined( $ENV{D} );
+    for my $i (@_) {
+        print STDERR "DBG: " . Dumper($i);
+    }
+}
+
+sub _warn {
+    if ( $ENV{D} and $ENV{D} >= 3 ) {
+        cluck "WARNING: ", @_, "\n";
+    } elsif ( defined( $ENV{D} ) ) {
+        carp "WARNING: ", @_, "\n";
+    } else {
+        warn "WARNING: ", @_, "\n";
+    }
+}
+
+sub _die {
+    if ( $ENV{D} and $ENV{D} >= 3 ) {
+        confess "FATAL: " . join( ",", @_ ) . "\n" if defined( $ENV{D} );
+    } elsif ( defined( $ENV{D} ) ) {
+        croak "FATAL: " . join( ",", @_ ) . "\n";
+    } else {
+        die "FATAL: " . join( ",", @_ ) . "\n";
+    }
+}
+
+sub usage {
+    _warn(shift) if @_;
+    my $scriptname = ( caller() )[1];
+    my $script     = slurp($scriptname);
+    $script =~ /^=for usage(.*?)^=cut/sm;
+    say2( $1 ? $1 : "...no usage message in $scriptname" );
+    exit 1;
+}
+
+sub _mkdir {
+    # it's not an error if the directory exists, but it is an error if it
+    # doesn't exist and we can't create it
+    my $dir  = shift;
+    my $perm = shift;    # optional
+    return if -d $dir;
+    mkpath($dir);
+    chmod $perm, $dir if $perm;
+    return 1;
+}
+
+sub _chdir {
+    chdir( $_[0] || $ENV{HOME} ) or _die "chdir $_[0] failed: $!\n";
+}
+
+sub _open {
+    open( my $fh, $_[0], $_[1] ) or _die "open $_[1] failed: $!\n";
+    return $fh;
+}
+
+sub _print {
+    my ( $file, @text ) = @_;
+    my $fh = _open( ">", "$file.$$" );
+    print $fh @text;
+    close($fh) or _die "close $file failed: $! at ", (caller)[1], " line ", (caller)[2], "\n";
+    my $oldmode = ( ( stat $file )[2] );
+    rename "$file.$$", $file;
+    chmod $oldmode, $file if $oldmode;
+}
+
+sub slurp {
+    local $/ = undef;
+    my $fh = _open( "<", $_[0] );
+    return <$fh>;
+}
+
+sub dos2unix {
+    # WARNING: when calling this, make sure you supply a list context
+    s/\r\n/\n/g for @_;
+    return @_;
+}
+
+sub ln_sf {
+    trace( 4, @_ );
+    my ( $srcdir, $glob, $dstdir ) = @_;
+    for my $hook ( glob("$srcdir/$glob") ) {
+        $hook =~ s/$srcdir\///;
+        unlink "$dstdir/$hook";
+        symlink "$srcdir/$hook", "$dstdir/$hook" or croak "could not symlink $srcdir/$hook to $dstdir\n";
+    }
+}
+
+sub sort_u {
+    my %uniq;
+    my $listref = shift;
+    return [] unless @{ $listref };
+    undef @uniq{ @{ $listref } }; # expect a listref
+    my @sort_u = sort keys %uniq;
+    return \@sort_u;
+}
+# ----------------------------------------------------------------------
+
+# bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh)
+{
+    my ( $rc, $text );
+    sub tsh_rc   { return $rc   || 0; }
+    sub tsh_text { return $text || ''; }
+    sub tsh_lines { return split /\n/, $text; }
+
+    sub tsh_try {
+        my $cmd = shift; die "try: expects only one argument" if @_;
+        $text = `( $cmd ) 2>&1; echo -n RC=\$?`;
+        if ( $text =~ s/RC=(\d+)$// ) {
+            $rc = $1;
+            trace( 4, $text );
+            return ( not $rc );
+        }
+        die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
+    }
+
+    sub tsh_run {
+        open( my $fh, "-|", @_ ) or die "popen failed: $!";
+        local $/ = undef; $text = <$fh>;
+        close $fh; warn "pclose failed: $!" if $!;
+        $rc = ( $? >> 8 );
+        trace( 4, $text );
+        return $text;
+    }
+}
+
+1;
diff --git a/Gitolite/Conf.pm b/Gitolite/Conf.pm
new file mode 100644
index 0000000..8f7e111
--- /dev/null
+++ b/Gitolite/Conf.pm
@@ -0,0 +1,183 @@
+package Gitolite::Conf;
+
+# explode/parse a conf file
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  compile
+  explode
+  parse
+);
+
+use Exporter 'import';
+use Getopt::Long;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+use Gitolite::Rc;
+use Gitolite::Conf::Sugar;
+use Gitolite::Conf::Store;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+# 'seen' for include/subconf files
+my %included = ();
+# 'seen' for group names on LHS
+my %prefixed_groupname = ();
+
+# ----------------------------------------------------------------------
+
+sub compile {
+    trace(3);
+    # XXX assume we're in admin-base/conf
+
+    _chdir($GL_ADMIN_BASE);
+    _chdir("conf");
+
+    explode( 'gitolite.conf', 'master', \&parse );
+
+    # the order matters; new repos should be created first, to give store a
+    # place to put the individual gl-conf files
+    new_repos();
+    store();
+}
+
+sub explode {
+    trace( 4, @_ );
+    my ( $file, $subconf, $parser ) = @_;
+
+    # $parser is a ref to a callback; if not supplied we just print
+    $parser ||= sub { print shift, "\n"; };
+
+    # seed the 'seen' list if it's empty
+    $included{ device_inode("conf/gitolite.conf") }++ unless %included;
+
+    my $fh    = _open( "<", $file );
+    my @fh    = <$fh>;
+    my @lines = macro_expand( "# BEGIN $file\n", @fh, "# END $file\n" );
+    my $line;
+    while (@lines) {
+        $line = shift @lines;
+
+        $line = cleanup_conf_line($line);
+        next unless $line =~ /\S/;
+
+        $line = prefix_groupnames( $line, $subconf ) if $subconf ne 'master';
+
+        if ( $line =~ /^(include|subconf) "(.+)"$/ or $line =~ /^(include|subconf) '(.+)'$/ ) {
+            incsub( $1, $2, $subconf, $parser );
+        } else {
+            # normal line, send it to the callback function
+            $parser->($line);
+        }
+    }
+}
+
+sub parse {
+    trace( 4, @_ );
+    my $line = shift;
+
+    # user or repo groups
+    if ( $line =~ /^(@\S+) = (.*)/ ) {
+        add_to_group( $1, split( ' ', $2 ) );
+    } elsif ( $line =~ /^repo (.*)/ ) {
+        set_repolist( split( ' ', $1 ) );
+    } elsif ( $line =~ /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ ) {
+        my $perm  = $1;
+        my @refs  = parse_refs( $2 || '' );
+        my @users = parse_users($3);
+
+        # XXX what do we do? s/\bCREAT[EO]R\b/~\$creator/g for @users;
+
+        for my $ref (@refs) {
+            for my $user (@users) {
+                add_rule( $perm, $ref, $user );
+            }
+        }
+    } elsif ( $line =~ /^config (.+) = ?(.*)/ ) {
+        my ( $key, $value ) = ( $1, $2 );
+        my @validkeys = split( ' ', ( $GL_GITCONFIG_KEYS || '' ) );
+        push @validkeys, "gitolite-options\\..*";
+        my @matched = grep { $key =~ /^$_$/ } @validkeys;
+        # XXX move this also to add_config: _die "git config $key not allowed\ncheck GL_GITCONFIG_KEYS in the rc file for how to allow it" if (@matched < 1);
+        # XXX both $key and $value must satisfy a liberal but secure pattern
+        add_config( 1, $key, $value );
+    } elsif ( $line =~ /^subconf (\S+)$/ ) {
+        set_subconf($1);
+    } else {
+        _warn "?? $line";
+    }
+}
+
+# ----------------------------------------------------------------------
+
+sub incsub {
+    my $is_subconf = ( +shift eq 'subconf' );
+    my ( $include_glob, $subconf, $parser ) = @_;
+
+    _die "subconf $subconf attempting to run 'subconf'\n" if $is_subconf and $subconf ne 'master';
+
+    # XXX move this to Macros... substitute HOSTNAME word if GL_HOSTNAME defined, otherwise leave as is
+    # $include_glob =~ s/\bHOSTNAME\b/$GL_HOSTNAME/ if $GL_HOSTNAME;
+
+    # XXX g2 diff: include glob is *implicitly* from $GL_ADMIN_BASE/conf, not *explicitly*
+    # for my $file (glob($include_glob =~ m(^/) ? $include_glob : "$GL_ADMIN_BASE/conf/$include_glob")) {
+
+    trace( 3, $is_subconf, $include_glob );
+
+    for my $file ( glob($include_glob) ) {
+        _warn("included file not found: '$file'"), next unless -f $file;
+        _die "invalid include/subconf filename $file" unless $file =~ m(([^/]+).conf$);
+        my $basename = $1;
+
+        next if already_included($file);
+
+        if ($is_subconf) {
+            $parser->("subconf $basename");
+            explode( $file, $basename, $parser );
+            $parser->("subconf $subconf");
+            # XXX g2 delegaton compat: deal with this: $subconf_seen++;
+        } else {
+            explode( $file, $subconf, $parser );
+        }
+    }
+}
+
+sub prefix_groupnames {
+    my ( $line, $subconf ) = @_;
+
+    my $lhs = '';
+    # save 'foo' if it's an '@foo = list' line
+    $lhs = $1 if $line =~ /^@(\S+) = /;
+    # prefix all @groups in the line
+    $line =~ s/(^| )(@\S+)(?= |$)/ $1 . ($prefixed_groupname{$subconf}{$2} || $2) /ge;
+    # now prefix the LHS and store it if needed
+    if ($lhs) {
+        $line =~ s/^@\S+ = /"\@$subconf.$lhs = "/e;
+        trace( 3, "prefixed_groupname.$subconf.\@$lhs = \@$subconf.$lhs" );
+    }
+
+    return $line;
+}
+
+sub already_included {
+    my $file = shift;
+
+    my $file_id = device_inode($file);
+    return 0 unless $included{$file_id}++;
+
+    _warn("$file already included");
+    trace( 3, "$file already included" );
+    return 1;
+}
+
+sub device_inode {
+    my $file = shift;
+    trace( 3, $file, ( stat $file )[ 0, 1 ] );
+    return join( "/", ( stat $file )[ 0, 1 ] );
+}
+
+1;
diff --git a/Gitolite/Conf/Load.pm b/Gitolite/Conf/Load.pm
new file mode 100644
index 0000000..db11679
--- /dev/null
+++ b/Gitolite/Conf/Load.pm
@@ -0,0 +1,169 @@
+package Gitolite::Conf::Load;
+
+# load conf data from stored files
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  load
+  access
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+use Gitolite::Rc;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $subconf = 'master';
+
+# our variables, because they get loaded by a 'do'
+our $data_version = '';
+our %repos;
+our %one_repo;
+our %groups;
+our %configs;
+our %one_config;
+our %split_conf;
+
+# helps maintain the "cache" in both "load_common" and "load_1"
+my $last_repo = '';
+
+# ----------------------------------------------------------------------
+
+{
+    my $loaded_repo = '';
+
+    sub load {
+        my $repo = shift or _die "load() needs a reponame";
+        trace( 4, "$repo" );
+        if ( $repo ne $loaded_repo ) {
+            trace( 3, "loading $repo..." );
+            _chdir("$GL_ADMIN_BASE"); load_common();
+            _chdir("$GL_REPO_BASE");  load_1($repo);
+            $loaded_repo = $repo;
+        }
+    }
+}
+
+sub access {
+    my ( $repo, $user, $aa, $ref ) = @_;
+    trace( 3, "repo=$repo, user=$user, aa=$aa, ref=$ref" );
+    load($repo);
+
+    my @rules = rules( $repo, $user );
+    trace( 3, scalar(@rules) . " rules found" );
+    for my $r (@rules) {
+        my $perm  = $r->[1];
+        my $refex = $r->[2];
+        trace( 4, "perm=$perm, refex=$refex" );
+
+        # skip 'deny' rules if the ref is not (yet) known
+        next if $perm eq '-' and $ref eq 'unknown';
+
+        # rule matches if ref matches or ref is unknown (see gitolite-shell)
+        next unless $ref =~ /^$refex/ or $ref eq 'unknown';
+
+        trace( 3, "DENIED by $refex" ) if $perm eq '-';
+        return "DENIED: $aa access to $repo by $user (rule: $refex)" if $perm eq '-';
+
+        # $perm can be RW\+?(C|D|CD|DC)?M?.  $aa can be W, +, C or D, or
+        # any of these followed by "M".
+        ( my $aaq = $aa ) =~ s/\+/\\+/;
+        $aaq =~ s/M/.*M/;
+        # as far as *this* ref is concerned we're ok
+        return $refex if ( $perm =~ /$aaq/ );
+    }
+    trace( 3, "DENIED by fallthru" );
+    return "DENIED: $aa access to $repo by $user (fallthru)";
+}
+
+# ----------------------------------------------------------------------
+
+sub load_common {
+
+    # we take an unusual approach to caching this function!
+    # (requires that first call to load_common is before first call to load_1)
+    if ( $last_repo and $split_conf{$last_repo} ) {
+        delete $repos{$last_repo};
+        delete $configs{$last_repo};
+        return;
+    }
+
+    trace(4);
+    my $cc = "conf/gitolite.conf-compiled.pm";
+
+    _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+
+    if ( data_version_mismatch() ) {
+        system("gitolite setup");
+        _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+        _die "data version update failed; this is serious" if data_version_mismatch();
+    }
+}
+
+sub load_1 {
+    my $repo = shift;
+    trace( 4, $repo );
+
+    if ( $repo eq $last_repo ) {
+        $repos{$repo} = $one_repo{$repo};
+        $configs{$repo} = $one_config{$repo} if $one_config{$repo};
+        return;
+    }
+
+    if ( -f "$repo.git/gl-conf" ) {
+        _die "split conf not set, gl-conf present for $repo" if not $split_conf{$repo};
+
+        my $cc = "$repo.git/gl-conf";
+        _die "parse $cc failed: " . ( $! or $@ ) unless do $cc;
+
+        $last_repo = $repo;
+        $repos{$repo} = $one_repo{$repo};
+        $configs{$repo} = $one_config{$repo} if $one_config{$repo};
+    } else {
+        _die "split conf set, gl-conf not present for $repo" if $split_conf{$repo};
+    }
+}
+
+sub rules {
+    my ( $repo, $user ) = @_;
+    trace( 4, "repo=$repo, user=$user" );
+    my @rules = ();
+
+    my @repos = memberships($repo);
+    my @users = memberships($user);
+    trace( 4, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
+
+    for my $r (@repos) {
+        for my $u (@users) {
+            push @rules, @{ $repos{$r}{$u} } if exists $repos{$r}{$u};
+        }
+    }
+
+    # dbg("before sorting rules:", \@rules);
+    @rules = sort { $a->[0] <=> $b->[0] } @rules;
+    # dbg("after sorting rules:", \@rules);
+
+    return @rules;
+}
+
+sub memberships {
+    my $item = shift;
+
+    my @ret = ( $item, '@all' );
+    push @ret, @{ $groups{$item} } if $groups{$item};
+
+    return @ret;
+}
+
+sub data_version_mismatch {
+    return $data_version ne $current_data_version;
+}
+
+1;
+
diff --git a/Gitolite/Conf/Store.pm b/Gitolite/Conf/Store.pm
new file mode 100644
index 0000000..69056a0
--- /dev/null
+++ b/Gitolite/Conf/Store.pm
@@ -0,0 +1,356 @@
+package Gitolite::Conf::Store;
+
+# receive parsed conf data and store it
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  add_to_group
+  expand_list
+  set_repolist
+  parse_refs
+  parse_users
+  add_rule
+  set_subconf
+  new_repos
+  new_repo
+  hook_repos
+  store
+);
+
+use Exporter 'import';
+use Data::Dumper;
+$Data::Dumper::Indent   = 1;
+$Data::Dumper::Sortkeys = 1;
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+use Gitolite::Rc;
+use Gitolite::Hooks::Update;
+use Gitolite::Hooks::PostUpdate;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my %repos;
+my %groups;
+my %configs;
+my %split_conf;
+
+my @repolist;    # current repo list; reset on each 'repo ...' line
+my $subconf = 'master';
+my $ruleseq = 0;
+my %ignored;
+# XXX you still have to "warn" if this has any entries
+
+# ----------------------------------------------------------------------
+
+sub add_to_group {
+    my ( $lhs, @rhs ) = @_;
+    _die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT;
+
+    # store the group association, but overload it to keep track of when
+    # the group was *first* created by using $subconf as the *value*
+    do { $groups{$lhs}{$_} ||= $subconf }
+      for ( expand_list(@rhs) );
+
+    # create the group hash even if empty
+    $groups{$lhs} = {} unless $groups{$lhs};
+}
+
+sub expand_list {
+    my @list     = @_;
+    my @new_list = ();
+
+    for my $item (@list) {
+        if ( $item =~ /^@/ and $item ne '@all' )    # nested group
+        {
+            _die "undefined group $item" unless $groups{$item};
+            # add those names to the list
+            push @new_list, sort keys %{ $groups{$item} };
+        } else {
+            push @new_list, $item;
+        }
+    }
+
+    return @new_list;
+}
+
+sub set_repolist {
+    @repolist = @_;
+
+    # ...sanity checks
+    for (@repolist) {
+        _warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
+        _die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
+    }
+    # XXX -- how do we deal with this? s/\bCREAT[EO]R\b/\$creator/g for @{ $repos_p };
+}
+
+sub parse_refs {
+    my $refs = shift;
+    my @refs; @refs = split( ' ', $refs ) if $refs;
+    @refs = expand_list(@refs);
+
+    # if no ref is given, this PERM applies to all refs
+    @refs = qw(refs/.*) unless @refs;
+
+    # fully qualify refs that dont start with "refs/" or "NAME/" or "VREF/";
+    # prefix them with "refs/heads/"
+    @refs = map { m(^(refs|NAME|VREF)/) or s(^)(refs/heads/); $_ } @refs;
+    # XXX what do we do? @refs = map { s(/USER/)(/\$gl_user/); $_ } @refs;
+
+    return @refs;
+}
+
+sub parse_users {
+    my $users = shift;
+    my @users = split ' ', $users;
+    do { _die "bad username '$_'" unless $_ =~ $USERNAME_PATT }
+      for @users;
+
+    return @users;
+}
+
+sub add_rule {
+    my ( $perm, $ref, $user ) = @_;
+
+    $ruleseq++;
+    for my $repo (@repolist) {
+        if ( check_subconf_repo_disallowed( $subconf, $repo ) ) {
+            my $repo = $repo;
+            $repo =~ s/^\@$subconf\./locally modified \@/;
+            $ignored{$subconf}{$repo} = 1;
+            next;
+        }
+
+        push @{ $repos{$repo}{$user} }, [ $ruleseq, $perm, $ref ];
+
+        # XXX g2 diff: we're not doing a lint check for usernames versus pubkeys;
+        # maybe we can add that later
+
+        # XXX to do: C/R/W, then CREATE_IS_C, etc
+        # XXX to do: also NAME_LIMITS
+        # XXX and hacks like $creator -> "$creatror - wild"
+
+        # XXX consider if you want to use rurp_seen; initially no
+    }
+}
+
+sub set_subconf {
+    $subconf = shift;
+    trace( 1, $subconf );
+}
+
+sub new_repos {
+    trace(3);
+    _chdir($GL_REPO_BASE);
+
+    # normal repos
+    my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
+    # add in members of repo groups
+    map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
+
+    for my $repo ( @{ sort_u(\@repos) } ) {
+        next unless $repo =~ $REPONAME_PATT;    # skip repo patterns
+        next if $repo =~ m(^\@|EXTCMD/);        # skip groups and fake repos
+
+        # XXX how do we deal with GL_NO_CREATE_REPOS?
+        new_repo($repo) if not -d "$repo.git";
+    }
+}
+
+sub new_repo {
+    my $repo = shift;
+    trace( 4, $repo );
+
+    # XXX ignoring UMASK for now
+
+    _mkdir("$repo.git");
+    _chdir("$repo.git");
+    system("git init --bare >&2");
+    _chdir($GL_REPO_BASE);
+    hook_1($repo);
+
+    # XXX ignoring creator for now
+    # XXX ignoring gl-post-init for now
+}
+
+sub hook_repos {
+    trace(3);
+    # all repos, all hooks
+    _chdir($GL_REPO_BASE);
+
+    # XXX g2 diff: we now don't care if it's a symlink -- it's upto the admin
+    # on the server to make sure things are kosher
+    for my $repo (`find . -name "*.git" -prune`) {
+        chomp($repo);
+        $repo =~ s/\.git$//;
+        hook_1($repo);
+    }
+}
+
+sub store {
+    trace(3);
+
+    # first write out the ones for the physical repos
+    my @phy_repos = list_physical_repos(1);
+
+    _chdir($GL_REPO_BASE);
+    for my $repo (@phy_repos) {
+        store_1($repo);
+    }
+
+    _chdir($GL_ADMIN_BASE);
+    store_common();
+}
+
+# ----------------------------------------------------------------------
+
+sub check_subconf_repo_disallowed {
+    # trying to set access for $repo (='foo')...
+    my ( $subconf, $repo ) = @_;
+
+    # processing the master config, not a subconf
+    return 0 if $subconf eq 'master';
+    # subconf is also called 'foo' (you're allowed to have a
+    # subconf that is only concerned with one repo)
+    return 0 if $subconf eq $repo;
+    # same thing in big-config-land; foo is just @foo now
+    return 0 if ( "\@$subconf" eq $repo );
+    my @matched = grep { $repo =~ /^$_$/ }
+      grep { $groups{"\@$subconf"}{$_} eq 'master' }
+      sort keys %{ $groups{"\@$subconf"} };
+    return 0 if @matched > 0;
+
+    trace( 3, "disallowed: $subconf for $repo" );
+    return 1;
+}
+
+{
+    my @phy_repos = ();
+
+    sub list_physical_repos {
+        trace(3);
+        _chdir($GL_REPO_BASE);
+
+        # use cached value only if it exists *and* no arg was received (i.e.,
+        # receiving *any* arg invalidates cache)
+        return @phy_repos if ( @phy_repos and not @_ );
+
+        for my $repo (`find . -name "*.git" -prune`) {
+            chomp($repo);
+            $repo =~ s(\./(.*)\.git$)($1);
+            push @phy_repos, $repo;
+        }
+        return @phy_repos;
+    }
+}
+
+sub store_1 {
+    # warning: writes and *deletes* it from %repos and %configs
+    my ($repo) = shift;
+    trace( 4, $repo );
+    return unless $repos{$repo} and -d "$repo.git";
+
+    my ( %one_repo, %one_config );
+
+    open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
+
+    $one_repo{$repo} = $repos{$repo};
+    delete $repos{$repo};
+    my $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
+
+    if ( $configs{$repo} ) {
+        $one_config{$repo} = $configs{$repo};
+        delete $configs{$repo};
+        $dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] );
+    }
+
+    # XXX deal with this better now
+    # $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
+    print $compiled_fh $dumped_data;
+    close $compiled_fh;
+
+    $split_conf{$repo} = 1;
+}
+
+sub store_common {
+    trace(4);
+    my $cc = "conf/gitolite.conf-compiled.pm";
+    my $compiled_fh = _open( ">", "$cc.new" );
+
+    my $data_version = $current_data_version;
+    trace( 1, "data_version = $data_version" );
+    print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
+
+    my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] );
+    $dumped_data .= Data::Dumper->Dump( [ \%configs ], [qw(*configs)] ) if %configs;
+
+    # XXX and again...
+    # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
+
+    print $compiled_fh $dumped_data;
+
+    if (%groups) {
+        my %groups = %{ inside_out( \%groups ) };
+        $dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
+        # XXX $dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g;
+        # XXX $dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
+        print $compiled_fh $dumped_data;
+    }
+    print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
+
+    close $compiled_fh or _die "close compiled-conf failed: $!\n";
+    rename "$cc.new", $cc;
+}
+
+{
+    my $hook_reset = 0;
+
+    sub hook_1 {
+        my $repo = shift;
+        trace( 4, $repo );
+
+        # reset the gitolite supplied hooks, in case someone fiddled with
+        # them, but only once per run
+        if ( not $hook_reset ) {
+            _mkdir("$GL_ADMIN_BASE/hooks/common");
+            _mkdir("$GL_ADMIN_BASE/hooks/gitolite-admin");
+            _print( "$GL_ADMIN_BASE/hooks/common/update",              update_hook() );
+            _print( "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update", post_update_hook() );
+            chmod 0755, "$GL_ADMIN_BASE/hooks/common/update";
+            chmod 0755, "$GL_ADMIN_BASE/hooks/gitolite-admin/post-update";
+            $hook_reset++;
+        }
+
+        # propagate user hooks
+        ln_sf( "$GL_ADMIN_BASE/hooks/common", "*", "$repo.git/hooks" );
+
+        # propagate admin hook
+        ln_sf( "$GL_ADMIN_BASE/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
+
+        # g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks
+        # and user hooks) anymore.  I don't think anyone used them anyway...
+    }
+}
+
+sub inside_out {
+    my $href = shift;
+    # input conf: @aa = bb cc <newline> @bb = @aa dd
+
+    my %ret = ();
+    while ( my ( $k, $v ) = each( %{$href} ) ) {
+        # $k is '@aa', $v is a href
+        for my $k2 ( keys %{$v} ) {
+            # $k2 is bb, then cc
+            push @{ $ret{$k2} }, $k;
+        }
+    }
+    return \%ret;
+    # %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]);
+}
+
+1;
+
diff --git a/Gitolite/Conf/Sugar.pm b/Gitolite/Conf/Sugar.pm
new file mode 100644
index 0000000..5db96f2
--- /dev/null
+++ b/Gitolite/Conf/Sugar.pm
@@ -0,0 +1,82 @@
+package Gitolite::Conf::Sugar;
+
+# syntactic sugar for the conf file, including site-local macros
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  macro_expand
+  cleanup_conf_line
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+use Gitolite::Rc;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub macro_expand {
+    # site-local macros, if any, then gitolite internal macros, to munge the
+    # input conf line if needed
+
+    my @lines = @_;
+
+    # TODO: user macros, how to allow the user to specify them?
+
+    # cheat, to keep *our* regexes simple :)
+    # XXX but this also kills the special '# BEGIN filename' and '# END
+    # filename' lines that explode() surrounds the actual data with when it
+    # called macro_expand().  Right now we don't need it, but...
+    @lines = grep /\S/, map { cleanup_conf_line($_) } @lines;
+
+    @lines = owner_desc(@lines);
+
+    return @lines;
+}
+
+sub cleanup_conf_line {
+    my $line = shift;
+
+    # kill comments, but take care of "#" inside *simple* strings
+    $line =~ s/^((".*?"|[^#"])*)#.*/$1/;
+    # normalise whitespace; keeps later regexes very simple
+    $line =~ s/=/ = /;
+    $line =~ s/\s+/ /g;
+    $line =~ s/^ //;
+    $line =~ s/ $//;
+    return $line;
+}
+
+sub owner_desc {
+    my @lines = @_;
+    my @ret;
+
+    for my $line (@lines) {
+        #       reponame = "some description string"
+        #       reponame "owner name" = "some description string"
+        if ( $line =~ /^(\S+)(?: "(.*?)")? = "(.*)"$/ ) {
+            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
+            # XXX these two checks should go into add_config
+            # _die "bad repo name '$repo'" unless $repo =~ $REPONAME_PATT;
+            # _die "$fragment attempting to set description for $repo"
+            #   if check_fragment_repo_disallowed( $fragment, $repo );
+            push @ret, "config gitolite-options.repo-desc = $desc";
+            push @ret, "config gitolite-options.repo-owner = $owner" if $owner;
+        } elsif ( $line =~ /^desc = (\S.*)/ ) {
+            push @ret, "config gitolite-options.repo-desc = $1";
+        } elsif ( $line =~ /^owner = (\S.*)/ ) {
+            my ( $repo, $owner, $desc ) = ( $1, $2, $3 );
+            push @ret, "config gitolite-options.repo-owner = $1";
+        } else {
+            push @ret, $line;
+        }
+    }
+    return @ret;
+}
+
+1;
+
diff --git a/Gitolite/Hooks/PostUpdate.pm b/Gitolite/Hooks/PostUpdate.pm
new file mode 100644
index 0000000..813733f
--- /dev/null
+++ b/Gitolite/Hooks/PostUpdate.pm
@@ -0,0 +1,69 @@
+package Gitolite::Hooks::PostUpdate;
+
+# everything to do with the post-update hook
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  post_update
+  post_update_hook
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub post_update {
+    trace(3);
+    # this is the *real* post_update hook for gitolite
+
+    tsh_try("git ls-tree --name-only master");
+    _die "no files/dirs called 'hooks' or 'logs' are allowed" if tsh_text() =~ /^(hooks|logs)$/;
+
+    {
+        local $ENV{GIT_WORK_TREE} = $GL_ADMIN_BASE;
+        tsh_try("git checkout -f --quiet master");
+    }
+    system("$ENV{GL_BINDIR}/gitolite compile");
+
+    exit 0;
+}
+
+{
+    my $text = '';
+
+    sub post_update_hook {
+        trace(1);
+        if ( not $text ) {
+            local $/ = undef;
+            $text = <DATA>;
+        }
+        return $text;
+    }
+}
+
+1;
+
+__DATA__
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+BEGIN {
+    die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
+}
+use lib $ENV{GL_BINDIR};
+use Gitolite::Hooks::PostUpdate;
+
+# gitolite post-update hook (only for the admin repo)
+# ----------------------------------------------------------------------
+
+post_update(@ARGV);     # is not expected to return
+exit 1;                 # so if it does, something is wrong
diff --git a/Gitolite/Hooks/Update.pm b/Gitolite/Hooks/Update.pm
new file mode 100644
index 0000000..2c60914
--- /dev/null
+++ b/Gitolite/Hooks/Update.pm
@@ -0,0 +1,114 @@
+package Gitolite::Hooks::Update;
+
+# everything to do with the update hook
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  update
+  update_hook
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+sub update {
+    trace( 3, @_ );
+    # this is the *real* update hook for gitolite
+
+    my ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa ) = args(@ARGV);
+
+    my $ret = access( $ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref );
+    trace( 1, "access($ENV{GL_REPO}, $ENV{GL_USER}, $aa, $ref) -> $ret" );
+    _die $ret if $ret =~ /DENIED/;
+
+    exit 0;
+}
+
+{
+    my $text = '';
+
+    sub update_hook {
+        trace(1);
+        if ( not $text ) {
+            local $/ = undef;
+            $text = <DATA>;
+        }
+        return $text;
+    }
+}
+
+# ----------------------------------------------------------------------
+
+sub args {
+    my ( $ref, $oldsha, $newsha ) = @_;
+    my ( $oldtree, $newtree, $aa );
+
+    # this is special to git -- the hash of an empty tree
+    my $empty = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+    $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
+    $newtree = $newsha eq '0' x 40 ? $empty : $newsha;
+
+    my $merge_base = '0' x 40;
+    # for branch create or delete, merge_base stays at '0'x40
+    chomp( $merge_base = `git merge-base $oldsha $newsha` )
+      unless $oldsha eq '0' x 40
+          or $newsha eq '0' x 40;
+
+    $aa = 'W';
+    # tag rewrite
+    $aa = '+' if $ref =~ m(refs/tags/) and $oldsha ne ( '0' x 40 );
+    # non-ff push to ref (including ref delete)
+    $aa = '+' if $oldsha ne $merge_base;
+
+    # XXX $aa = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
+    # XXX $aa = 'C' if ( $repos{$ENV{GL_REPO}}{CREATE_IS_C} or $repos{'@all'}{CREATE_IS_C} ) and $oldsha eq '0' x 40;
+
+    # and now "M" commits.  This presents a bit of a problem.  All the other
+    # accesses (W, +, C, D) were mutually exclusive in some sense.  Sure a W could
+    # be a C or a + could be a D but that's by design.  A merge commit, however,
+    # could still be any of the others (except a "D").
+
+    # so we have to *append* 'M' to $aa (if the repo has MERGE_CHECK in
+    # effect and this push contains a merge inside)
+
+=for XXX
+    if ( $repos{ $ENV{GL_REPO} }{MERGE_CHECK} or $repos{'@all'}{MERGE_CHECK} ) {
+        if ( $oldsha eq '0' x 40 or $newsha eq '0' x 40 ) {
+            warn "ref create/delete ignored for purposes of merge-check\n";
+        } else {
+            $aa .= 'M' if `git rev-list -n 1 --merges $oldsha..$newsha` =~ /./;
+        }
+    }
+=cut
+
+    return ( $ref, $oldsha, $newsha, $oldtree, $newtree, $aa );
+}
+
+1;
+
+__DATA__
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+BEGIN {
+    exit 0 if $ENV{GL_BYPASS_UPDATE_HOOK};
+    die "GL_BINDIR not set; aborting\n" unless $ENV{GL_BINDIR};
+}
+use lib $ENV{GL_BINDIR};
+use Gitolite::Hooks::Update;
+
+# gitolite update hook
+# ----------------------------------------------------------------------
+
+update(@ARGV);          # is not expected to return
+exit 1;                 # so if it does, something is wrong
diff --git a/Gitolite/Rc.pm b/Gitolite/Rc.pm
new file mode 100644
index 0000000..94f6613
--- /dev/null
+++ b/Gitolite/Rc.pm
@@ -0,0 +1,112 @@
+package Gitolite::Rc;
+
+# everything to do with 'rc'.  Also defines some 'constants'
+# ----------------------------------------------------------------------
+
+ at EXPORT = qw(
+  $GL_ADMIN_BASE
+  $GL_REPO_BASE
+
+  $GL_UMASK
+
+  $GL_GITCONFIG_KEYS
+
+  glrc_default_text
+  glrc_default_filename
+  glrc_filename
+
+  $ADC_CMD_ARGS_PATT
+  $REF_OR_FILENAME_PATT
+  $REPONAME_PATT
+  $REPOPATT_PATT
+  $USERNAME_PATT
+
+  $current_data_version
+);
+
+use Exporter 'import';
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+
+# variables that are/could be/should be in the rc file
+# ----------------------------------------------------------------------
+
+$GL_ADMIN_BASE = "$ENV{HOME}/.gitolite";
+$GL_REPO_BASE  = "$ENV{HOME}/repositories";
+
+# variables that should probably never be changed
+# ----------------------------------------------------------------------
+
+$current_data_version = "3.0";
+
+$ADC_CMD_ARGS_PATT    = qr(^[0-9a-zA-Z._\@/+:-]*$);
+$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][0-9a-zA-Z._\@/+ :,-]*$);
+$REPONAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
+$REPOPATT_PATT        = qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/,-]*$);
+$USERNAME_PATT        = qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
+
+# ----------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $rc = glrc_filename();
+do $rc if -r $rc;
+
+{
+    my $glrc_default_text = '';
+
+    sub glrc_default_text {
+        trace( 1, "..should happen only on first run" );
+        return $glrc_default_text if $glrc_default_text;
+        local $/ = undef;
+        $glrc_default_text = <DATA>;
+    }
+}
+
+sub glrc_default_filename {
+    trace( 1, "..should happen only on first run" );
+    return "$ENV{HOME}/.gitolite.rc";
+}
+
+# where is the rc file?
+sub glrc_filename {
+    trace(4);
+
+    # search $HOME first
+    return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
+    trace( 2, "$ENV{HOME}/.gitolite.rc not found" );
+
+    # XXX for fedora, we can add the following line, but I would really prefer
+    # if ~/.gitolite.rc on each $HOME was just a symlink to /etc/gitolite.rc
+    # XXX return "/etc/gitolite.rc" if -f "/etc/gitolite.rc";
+
+    return '';
+}
+
+1;
+
+# ----------------------------------------------------------------------
+
+__DATA__
+# configuration variables for gitolite
+
+# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
+
+# this file is in perl syntax.  However, you do NOT need to know perl to edit
+# it; it should be fairly self-explanatory and easy to maintain
+
+$GL_UMASK = 0077;
+$GL_GITCONFIG_KEYS = "";
+
+# ------------------------------------------------------------------------------
+# per perl rules, this should be the last line in such a file:
+1;
+
+# Local variables:
+# mode: perl
+# End:
+# vim: set syn=perl:
diff --git a/Gitolite/Test.pm b/Gitolite/Test.pm
new file mode 100644
index 0000000..6b4a0ae
--- /dev/null
+++ b/Gitolite/Test.pm
@@ -0,0 +1,34 @@
+package Gitolite::Test;
+
+# functions for the test code to use
+# ----------------------------------------------------------------------
+
+#<<<
+ at EXPORT = qw(
+  try
+  put
+);
+#>>>
+use Exporter 'import';
+use File::Path qw(mkpath);
+use Carp qw(carp cluck croak confess);
+
+BEGIN {
+    require Gitolite::Test::Tsh;
+    *{'try'} = \&Tsh::try;
+    *{'put'} = \&Tsh::put;
+}
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+# required preamble for all tests
+try "
+    DEF gsh = /TRACE: gsh.SOC=/
+    ./g3-install -c admin
+    cd tsh_tempdir;
+";
+
+1;
diff --git a/Gitolite/Test/Tsh.pm b/Gitolite/Test/Tsh.pm
new file mode 100644
index 0000000..b4b3b41
--- /dev/null
+++ b/Gitolite/Test/Tsh.pm
@@ -0,0 +1,624 @@
+#!/usr/bin/perl
+use 5.10.0;
+
+# Tsh -- non interactive Testing SHell in perl
+
+# TODO items:
+# - allow an RC file to be used to add basic and extended commands
+# - convert internal defaults to additions to the RC file
+# - implement shell commands as you go
+# - solve the "pass/fail" inconsistency between shell and perl
+# - solve the pipes problem (use 'overload'?)
+
+# ----------------------------------------------------------------------
+# modules
+
+package Tsh;
+
+use Exporter 'import';
+ at EXPORT = qw(
+  try run AUTOLOAD
+  rc error_count text lines error_list put
+  cd tsh_tempdir
+
+  $HOME $PWD $USER
+);
+ at EXPORT_OK = qw();
+
+use Env qw(@PATH HOME PWD USER TSH_VERBOSE);
+# other candidates:
+# GL_ADMINDIR GL_BINDIR GL_RC GL_REPO_BASE_ABS GL_REPO GL_USER
+
+use strict;
+use warnings;
+
+use Text::Tabs;    # only used for formatting the usage() message
+use Text::ParseWords;
+
+use File::Temp qw(tempdir);
+END { chdir( $ENV{HOME} ); }
+# we need this END handler *after* the 'use File::Temp' above.  Without
+# this, if $PWD at exit was $tempdir, you get errors like "cannot remove
+# path when cwd is [...] at /usr/share/perl5/File/Temp.pm line 902".
+
+use Data::Dumper;
+
+# ----------------------------------------------------------------------
+# globals
+
+my $rc;      # return code from backticked (external) programs
+my $text;    # STDOUT+STDERR of backticked (external) programs
+my $lec;     # the last external command (the rc and text are from this)
+my $cmd;     # the current command
+
+my $testnum;     # current test number, for info in TAP output
+my $testname;    # current test name, for error info to user
+my $line;        # current line number
+
+my $err_count;   # count of test failures
+my @errors_in;   # list of testnames that errored
+
+my $tick;        # timestamp for git commits
+
+my %autoloaded;
+my $tempdir = '';
+
+# ----------------------------------------------------------------------
+# setup
+
+# unbuffer STDOUT and STDERR
+select(STDERR); $|++;
+select(STDOUT); $|++;
+
+# set the timestamp (needed only under harness)
+test_tick() if $ENV{HARNESS_ACTIVE};
+
+# ----------------------------------------------------------------------
+# this is for one-liner access from outside, using @ARGV, as in:
+#   perl -MTsh -e 'tsh()' 'tsh command list'
+# or via STDIN
+#   perl -MTsh -e 'tsh()' < file-containing-tsh-commands
+# NOTE: it **exits**!
+
+sub tsh {
+    my @lines;
+
+    if (@ARGV) {
+        # simple, single argument which is a readable filename
+        if ( @ARGV == 1 and $ARGV[0] !~ /\s/ and -r $ARGV[0] ) {
+            # take the contents of the file
+            @lines = <>;
+        } else {
+            # more than one argument *or* not readable filename
+            # just take the arguments themselves as the command list
+            @lines = @ARGV;
+            @ARGV  = ();
+        }
+    } else {
+        # no arguments given, take STDIN
+        usage() if -t;
+        @lines = <>;
+    }
+
+    # and process them
+    try(@lines);
+
+    # print error summary by default
+    if ( not defined $TSH_VERBOSE ) {
+        say STDERR "$err_count error(s)" if $err_count;
+    }
+
+    exit $err_count;
+}
+
+# these two get called with series of tsh commands, while the autoload,
+# (later) handles single commands
+
+sub try {
+    $rc = $err_count = 0;
+    @errors_in = ();
+
+    # break up multiline arguments into separate lines
+    my @lines = map { split /\n/ } @_;
+
+    # and process them
+    rc_lines(@lines);
+
+    # bump err_count if the last command had a non-0 rc (that was apparently not checked).
+    $err_count++ if $rc;
+
+    # finish up...
+    dbg( 1, "$err_count error(s)" ) if $err_count;
+    return ( not $err_count );
+}
+
+# run() differs from try() in that
+#   -   uses open(), not backticks
+#   -   takes only one command, not tsh-things like ok, /patt/ etc
+#   -   -   if you pass it an array it uses the list form!
+
+sub run {
+    open( my $fh, "-|", @_ ) or die "tell sitaram $!";
+    local $/ = undef; $text = <$fh>;
+    close $fh; warn "tell sitaram $!" if $!;
+    $rc = ( $? >> 8 );
+    return $text;
+}
+
+sub put {
+    my ( $file, $data ) = @_;
+    die "probable quoting error in arguments to put: $file\n" if $file =~ /^\s*['"]/;
+    my $mode = ">";
+    $mode = "|-" if $file =~ s/^\s*\|\s*//;
+
+    $rc = 0;
+    my $fh;
+    open( $fh, $mode, $file )
+      and print $fh $data
+      and close $fh
+      and return 1;
+
+    $rc = 1;
+    dbg( 1, "put $file: $!" );
+    return '';
+}
+
+# ----------------------------------------------------------------------
+# TODO: AUTOLOAD and exportable convenience subs for common shell commands
+
+sub cd {
+    my $dir = shift || '';
+    _cd($dir);
+    dbg( 1, "cd $dir: $!" ) if $rc;
+    return ( not $rc );
+}
+
+# this is classic AUTOLOAD, almost from the perlsub manpage.  Although, if
+# instead of `ls('bin');` you want to be able to say `ls 'bin';` you will need
+# to predeclare ls, with `sub ls;`.
+sub AUTOLOAD {
+    my $program = $Tsh::AUTOLOAD;
+    dbg( 4, "program = $program, arg=$_[0]" );
+    $program =~ s/.*:://;
+    $autoloaded{$program}++;
+
+    die "tsh's autoload support expects only one arg\n" if @_ > 1;
+    _sh("$program $_[0]");
+    return ( not $rc );    # perl truth
+}
+
+# ----------------------------------------------------------------------
+# exportable service subs
+
+sub rc {
+    return $rc || 0;
+}
+
+sub text {
+    return $text || '';
+}
+
+sub lines {
+    return split /\n/, $text;
+}
+
+sub error_count {
+    return $err_count;
+}
+
+sub error_list {
+    return (
+        wantarray
+        ? @errors_in
+        : join( "\n", @errors_in )
+    );
+}
+
+sub tsh_tempdir {
+    # create tempdir if not already done
+    $tempdir = tempdir( "tsh_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 ) unless $tempdir;
+    # XXX TODO that 'UNLINK' doesn't work for Ctrl_C
+
+    return $tempdir;
+}
+
+# ----------------------------------------------------------------------
+# internal (non-exportable) service subs
+
+sub print_plan {
+    return unless $ENV{HARNESS_ACTIVE};
+    my $_ = shift;
+    say "1..$_";
+}
+
+sub rc_lines {
+    my @lines = @_;
+
+    while (@lines) {
+        my $_ = shift @lines;
+        chomp; $_ = trim_ws($_);
+
+        # this also sets $testname
+        next if is_comment_or_empty($_);
+
+        dbg( 2, "L: $_" );
+        $line = $_;    # save line for printing with 'FAIL:'
+
+        # a DEF has to be on a line by itself
+        if (/^DEF\s+([-.\w]+)\s*=\s*(\S.*)$/) {
+            def( $1, $2 );
+            next;
+        }
+
+        my @cmds = cmds($_);
+
+        # process each command
+        # (note: some of the commands may put stuff back into @lines)
+        while (@cmds) {
+            # this needs to be the 'global' one, since fail() prints it
+            $cmd = shift @cmds;
+
+            # is the current command a "testing" command?
+            my $testing_cmd =
+              ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) or $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) or $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) );
+
+            # warn if the previous command failed but rc is not being checked
+            if ( $rc and not $testing_cmd ) {
+                dbg( 1, "rc: $rc from cmd prior to '$cmd'\n" );
+                # count this as a failure, for exit status purposes
+                $err_count++;
+                # and reset the rc, otherwise for example 'ls foo; tt; tt; tt'
+                # will tell you there are 3 errors!
+                $rc = 0;
+                push @errors_in, $testname if $testname;
+            }
+
+            # prepare to run the command
+            dbg( 3, "C: $cmd" );
+            if ( def($cmd) ) {
+                # expand macro and replace head of @cmds (unshift)
+                dbg( 2, "DEF: $cmd" );
+                unshift @cmds, cmds( def($cmd) );
+            } else {
+                parse($cmd);
+            }
+            # reset rc if checking is done
+            $rc = 0 if $testing_cmd;
+            # assumes you will (a) never have *both* 'ok' and '!ok' after
+            # an action command, and (b) one of them will come immediately
+            # after the action command, with /patt/ only after it.
+        }
+    }
+}
+
+sub def {
+    my ( $cmd, $list ) = @_;
+    state %def;
+    %def = read_rc_file() unless %def;
+
+    if ($list) {
+        # set mode
+        die "attempt to redefine macro $cmd\n" if $def{$cmd};
+        $def{$cmd} = $list;
+        return;
+    }
+
+    # get mode: split the $cmd at spaces, see if there is a definition
+    # available, substitute any %1, %2, etc., in it and send it back
+    my ( $c, @d ) = shellwords($cmd);
+    my $e;    # the expanded value
+    if ( $e = $def{$c} ) {    # starting value
+        for my $i ( 1 .. 9 ) {
+            last unless $e =~ /%$i/;    # no more %N's (we assume sanity)
+            die "$def{$c} requires more arguments\n" unless @d;
+            my $f = shift @d;           # get the next datum
+            $e =~ s/%$i/$f/g;           # and substitute %N all over
+        }
+        return join( " ", $e, @d );     # join up any remaining data
+    }
+    return '';
+}
+
+sub _cd {
+    my $dir = shift || $HOME;
+    # a directory name of 'tsh_tempdir' is special
+    $dir = tsh_tempdir() if $dir eq 'tsh_tempdir';
+    $rc = 0;
+    chdir($dir) or $rc = 1;
+}
+
+sub _sh {
+    my $cmd = shift;
+    # TODO: switch to IPC::Open3 or something...?
+
+    dbg( 4, "  running: ( $cmd ) 2>&1" );
+    $text = `( $cmd ) 2>&1; echo -n RC=\$?`;
+    $lec  = $cmd;
+    dbg( 4, "  results:\n$text" );
+
+    if ( $text =~ /RC=(\d+)$/ ) {
+        $rc = $1;
+        $text =~ s/RC=\d+$//;
+    } else {
+        die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n";
+    }
+}
+
+sub _perl {
+    my $perl = shift;
+    local $_;
+    $_ = $text;
+
+    dbg( 4, "  eval: $perl" );
+    my $evrc = eval $perl;
+
+    if ($@) {
+        $rc = 1;    # shell truth
+        dbg( 1, $@ );
+        # leave $text unchanged
+    } else {
+        $rc = not $evrc;
+        # $rc is always shell truth, so we need to cover the case where
+        # there was no error but it still returned a perl false
+        $text = $_;
+    }
+    dbg( 4, "  eval-rc=$evrc, results:\n$text" );
+}
+
+sub parse {
+    my $cmd = shift;
+
+    if ( $cmd =~ /^sh (.*)/ ) {
+
+        _sh($1);
+
+    } elsif ( $cmd =~ /^perl (.*)/ ) {
+
+        _perl($1);
+
+    } elsif ( $cmd eq 'tt' or $cmd eq 'test-tick' ) {
+
+        test_tick();
+
+    } elsif ( $cmd =~ /^plan ?(\d+)$/ ) {
+
+        print_plan($1);
+
+    } elsif ( $cmd =~ /^cd ?(\S*)$/ ) {
+
+        _cd($1);
+
+    } elsif ( $cmd =~ /^ENV (\w+)=['"]?(.+?)['"]?$/ ) {
+
+        $ENV{$1} = $2;
+
+    } elsif ( $cmd =~ /^(?:tc|test-commit)\s+(\S.*)$/ ) {
+
+        # this is the only "git special" really; the default expansions are
+        # just that -- defaults.  But this one is hardwired!
+        dummy_commits($1);
+
+    } elsif ( $cmd =~ '^put(?:\s+(\S.*))?$' ) {
+
+        if ($1) {
+            put( $1, $text );
+        } else {
+            print $text if defined $text;
+        }
+
+    } elsif ( $cmd =~ m(^ok(?:\s+or\s+(.*))?$) ) {
+
+        $rc ? fail( "ok, rc=$rc from $lec", $1 || '' ) : ok();
+
+    } elsif ( $cmd =~ m(^!ok(?:\s+or\s+(.*))?$) ) {
+
+        $rc ? ok() : fail( "!ok, rc=0 from $lec", $1 || '' );
+
+    } elsif ( $cmd =~ m(^/(.*?)/(?:\s+or\s+(.*))?$) ) {
+
+        expect( $1, $2 );
+
+    } elsif ( $cmd =~ m(^!/(.*?)/(?:\s+or\s+(.*))?$) ) {
+
+        not_expect( $1, $2 );
+
+    } else {
+
+        _sh($cmd);
+
+    }
+}
+
+# currently unused
+sub executable {
+    my $cmd = shift;
+    # path supplied
+    $cmd =~ m(/) and -x $cmd and return 1;
+    # barename; look up in $PATH
+    for my $p (@PATH) {
+        -x "$p/$cmd" and return 1;
+    }
+    return 0;
+}
+
+sub ok {
+    $testnum++;
+    say "ok ($testnum)" if $ENV{HARNESS_ACTIVE};
+}
+
+sub fail {
+    $testnum++;
+    say "not ok ($testnum)" if $ENV{HARNESS_ACTIVE};
+
+    my $die = 0;
+    my ( $msg1, $msg2 ) = @_;
+    if ($msg2) {
+        # if arg2 is non-empty, print it regardless of debug level
+        $die = 1 if $msg2 =~ s/^die //;
+        say STDERR "# $msg2";
+    }
+
+    local $TSH_VERBOSE = 1 if $ENV{TSH_ERREXIT};
+    dbg( 1, "FAIL: $msg1", $testname || '', "test number $testnum", "L: $line", "results:\n$text" );
+
+    # count the error and add the testname to the list if it is set
+    $err_count++;
+    push @errors_in, $testname if $testname;
+
+    return unless $die or $ENV{TSH_ERREXIT};
+    dbg( 1, "exiting at cmd $cmd\n" );
+
+    exit( $rc || 74 );
+}
+
+sub expect {
+    my ( $patt, $msg ) = @_;
+    $msg =~ s/^\s+// if $msg;
+    my $sm;
+    if ( $sm = sm($patt) ) {
+        dbg( 4, "  M: $sm" );
+        ok();
+    } else {
+        fail( "/$patt/", $msg || '' );
+    }
+}
+
+sub not_expect {
+    my ( $patt, $msg ) = @_;
+    $msg =~ s/^\s+// if $msg;
+    my $sm;
+    if ( $sm = sm($patt) ) {
+        dbg( 4, "  M: $sm" );
+        fail( "!/$patt/", $msg || '' );
+    } else {
+        ok();
+    }
+}
+
+sub sm {
+    # smart match?  for now we just do regex match
+    my $patt = shift;
+
+    return ( $text =~ qr($patt) ? $& : "" );
+}
+
+sub trim_ws {
+    my $_ = shift;
+    s/^\s+//; s/\s+$//;
+    return $_;
+}
+
+sub is_comment_or_empty {
+    my $_ = shift;
+    chomp; $_ = trim_ws($_);
+    if (/^##\s(.*)/) {
+        $testname = $1;
+        say "# $1";
+    }
+    return ( /^#/ or /^$/ );
+}
+
+sub cmds {
+    my $_ = shift;
+    chomp; $_ = trim_ws($_);
+
+    # split on unescaped ';'s, then unescape the ';' in the results
+    my @cmds = map { s/\\;/;/g; $_ } split /(?<!\\);/;
+    @cmds = grep { $_ = trim_ws($_); /\S/; } @cmds;
+    return @cmds;
+}
+
+sub dbg {
+    return unless $TSH_VERBOSE;
+    my $level = shift;
+    return unless $TSH_VERBOSE >= $level;
+    my $all = join( "\n", grep( /./, @_ ) );
+    chomp($all);
+    $all =~ s/\n/\n\t/g;
+    say STDERR "# $all";
+}
+
+sub ddump {
+    for my $i (@_) {
+        print STDERR "DBG: " . Dumper($i);
+    }
+}
+
+sub usage {
+    # TODO
+    print "Please see documentation at:
+
+        https://github.com/sitaramc/tsh/blob/master/README.mkd
+
+Meanwhile, here are your local 'macro' definitions:
+
+";
+    my %m = read_rc_file();
+    my @m = map { "$_\t$m{$_}\n" } sort keys %m;
+    $tabstop = 16;
+    print join( "", expand(@m) );
+    exit 1;
+}
+
+# ----------------------------------------------------------------------
+# git-specific internal service subs
+
+sub dummy_commits {
+    for my $f ( split ' ', shift ) {
+        if ( $f eq 'tt' or $f eq 'test-tick' ) {
+            test_tick();
+            next;
+        }
+        my $ts = ( $tick ? localtime($tick) : localtime() );
+        _sh("echo $f at $ts >> $f && git add $f && git commit -m '$f at $ts'");
+    }
+}
+
+sub test_tick {
+    unless ( $ENV{HARNESS_ACTIVE} ) {
+        sleep 1;
+        return;
+    }
+    $tick += 60 if $tick;
+    $tick ||= 1310000000;
+    $ENV{GIT_COMMITTER_DATE} = "$tick +0530";
+    $ENV{GIT_AUTHOR_DATE}    = "$tick +0530";
+}
+
+# ----------------------------------------------------------------------
+# the internal macros, for easy reference and reading
+
+sub read_rc_file {
+    my $rcfile = "$HOME/.tshrc";
+    my $rctext;
+    if ( -r $rcfile ) {
+        local $/ = undef;
+        open( my $rcfh, "<", $rcfile ) or die "this should not happen: $!\n";
+        $rctext = <$rcfh>;
+    } else {
+        # this is the default "rc" content
+        $rctext = "
+            add         =   git add
+            branch      =   git branch
+            clone       =   git clone
+            checkout    =   git checkout
+            commit      =   git commit
+            fetch       =   git fetch
+            init        =   git init
+            push        =   git push
+            reset       =   git reset
+            tag         =   git tag
+
+            empty       =   git commit --allow-empty -m empty
+            push-om     =   git push origin master
+            reset-h     =   git reset --hard
+            reset-hu    =   git reset --hard \@{u}
+        "
+    }
+
+    # ignore everything except lines of the form "aa = bb cc dd"
+    my %commands = ( $rctext =~ /^\s*([-.\w]+)\s*=\s*(\S.*)$/gm );
+    return %commands;
+}
+
+1;
diff --git a/g3-info b/g3-info
new file mode 100755
index 0000000..d4db40e
--- /dev/null
+++ b/g3-info
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+# gitolite shell, invoked from ~/.ssh/authorized_keys
+# ----------------------------------------------------------------------
+
+BEGIN {
+    # find and set bin dir
+    $ENV{GL_BINDIR} = "$ENV{HOME}/bin";
+}
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my $user = shift or die;
+my $aa;
+my $ref = 'unknown';
+
+my $ret;
+while (<>) {
+    chomp;
+
+    my $perm = '';
+    for $aa (qw(R W C)) {
+        $ret = access($_, $user, $aa, $ref);
+        $perm .= ( $ret =~ /DENIED/ ? "  " : " $aa" );
+    }
+    print "$perm\t$_\n" if $perm =~ /\S/;
+}
diff --git a/g3-install b/g3-install
new file mode 100755
index 0000000..ef40012
--- /dev/null
+++ b/g3-install
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# this is specific to my test env; you may want to change it
+
+set -e
+
+cd /home/g3
+
+if [ "$1" = "-c" ]
+then
+    rm -rf .gito* gito* repositories proj* bin
+    mkdir bin
+    cp ~/.ssh/id_rsa.pub ~/.ssh/admin.pub
+
+    cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin
+    gitolite setup -a ${2:-admin} -pk ~/.ssh/admin.pub
+else
+    cd g3; cp -a gito* Gito* t/glt t/gito* ~/bin
+    gitolite setup
+fi
diff --git a/gitolite b/gitolite
new file mode 100755
index 0000000..f89f12f
--- /dev/null
+++ b/gitolite
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+# all gitolite CLI tools run as sub-commands of this command
+# ----------------------------------------------------------------------
+
+=for usage
+Usage:  gitolite [sub-command] [options]
+
+The following subcommands are available; they should all respond to '-h':
+
+    setup                       1st run: initial setup; all runs: hook fixups
+    compile                     compile gitolite.conf
+    query-rc                    get values of rc variables
+=cut
+
+# ----------------------------------------------------------------------
+
+use FindBin;
+
+BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; }
+use lib $ENV{GL_BINDIR};
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+args();
+
+# ----------------------------------------------------------------------
+
+sub args {
+    my ( $command, @args ) = @ARGV;
+    usage() if not $command or $command eq '-h';
+
+    if ( $command eq 'setup' ) {
+        shift @ARGV;
+        require Gitolite::Commands::Setup;
+        Gitolite::Commands::Setup->import;
+        setup();
+    } elsif ( $command eq 'compile' ) {
+        shift @ARGV;
+        _die "'gitolite compile' does not take any arguments" if @ARGV;
+        require Gitolite::Conf;
+        Gitolite::Conf->import;
+        compile();
+    } elsif ( $command eq 'query-rc' ) {
+        shift @ARGV;
+        require Gitolite::Commands::QueryRc;
+        Gitolite::Commands::QueryRc->import;
+        query_rc();
+    }
+}
diff --git a/gitolite-shell b/gitolite-shell
new file mode 100755
index 0000000..479773c
--- /dev/null
+++ b/gitolite-shell
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+# gitolite shell, invoked from ~/.ssh/authorized_keys
+# ----------------------------------------------------------------------
+
+BEGIN {
+    # find and set bin dir
+    $0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ( $1 || "$ENV{PWD}/" ) . $2;
+}
+
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+print STDERR "TRACE: gsh(", join( ")(", @ARGV ), ")\n";
+print STDERR "TRACE: gsh(SOC=$ENV{SSH_ORIGINAL_COMMAND})\n";
+
+# ----------------------------------------------------------------------
+
+# XXX lots of stuff from gl-auth-command is missing for now...
+
+# set up the user
+my $user = $ENV{GL_USER} = shift;
+
+# set up the repo and the attempted access
+my ( $verb, $repo ) = split_soc();
+sanity($repo);
+$ENV{GL_REPO} = $repo;
+my $aa = ( $verb =~ 'upload' ? 'R' : 'W' );
+
+# a ref of 'unknown' signifies that this is a pre-git check, where we don't
+# yet know the ref that will be eventually pushed (and even that won't apply
+# if it's a read operation).  See the matching code in access() for more.
+my $ret = access( $repo, $user, $aa, 'unknown' );
+trace( 1, "access($repo, $user, $aa, 'unknown') -> $ret" );
+_die $ret if $ret =~ /DENIED/;
+
+$repo = "'$GL_REPO_BASE/$repo.git'";
+exec( "git", "shell", "-c", "$verb $repo" );
+
+# ----------------------------------------------------------------------
+
+sub split_soc {
+    my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+    return ( $1, $2 ) if $soc =~ m(^(git-(?:upload|receive)-pack) '/?(.*?)(?:\.git)?'$);
+    _die "unknown command: $soc";
+}
+
+sub sanity {
+    my $repo = shift;
+    _die "'$repo' contains bad characters" if $repo !~ $REPONAME_PATT;
+    _die "'$repo' ends with a '/'"         if $repo =~ m(/$);
+    _die "'$repo' contains '..'"           if $repo =~ m(\.\.$);
+}
diff --git a/src/gitolite b/src/gitolite
new file mode 100755
index 0000000..c6a1f54
--- /dev/null
+++ b/src/gitolite
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+
+# all gitolite CLI tools run as sub-commands of this command
+# ----------------------------------------------------------------------
+
+=for args
+Usage:  gitolite [<sub-command>] [<options>]
+
+The following built-in subcommands are available; they should all respond to
+'-h' if you want further details on each:
+
+    setup                       1st run: initial setup; all runs: hook fixups
+    compile                     compile gitolite.conf
+
+    query-rc                    get values of rc variables
+
+    list-groups                 list all group names in conf
+    list-users                  list all users/user groups in conf
+    list-repos                  list all repos/repo groups in conf
+    list-phy-repos              list all repos actually on disk
+    list-memberships            list all groups a name is a member of
+    list-members                list all members of a group
+
+Warnings:
+  - list-users is disk bound and could take a while on sites with 1000s of repos
+  - list-memberships does not check if the name is known; unknown names come
+    back with 2 answers: the name itself and '@all'
+
+In addition, running 'gitolite help' should give you a list of custom commands
+available.  They may or may not respond to '-h', depending on how they were
+written.
+=cut
+
+# ----------------------------------------------------------------------
+
+use FindBin;
+
+BEGIN { $ENV{GL_BINDIR} = $FindBin::RealBin; }
+use lib $ENV{GL_BINDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# ----------------------------------------------------------------------
+
+my ( $command, @args ) = @ARGV;
+gl_log( 'gitolite', @ARGV ) if -d $rc{GL_ADMIN_BASE};
+args();
+
+# the first two commands need options via @ARGV, as they have their own
+# GetOptions calls and older perls don't have 'GetOptionsFromArray'
+
+if ( $command eq 'setup' ) {
+    shift @ARGV;
+    require Gitolite::Setup;
+    Gitolite::Setup->import;
+    setup();
+
+} elsif ( $command eq 'query-rc' ) {
+    shift @ARGV;
+    query_rc();
+
+# the rest don't need @ARGV per se
+
+} elsif ( $command eq 'compile' ) {
+    require Gitolite::Conf;
+    Gitolite::Conf->import;
+    compile(@args);
+
+} elsif ( $command eq 'trigger' ) {
+    trigger(@args);
+
+} elsif ( -x "$rc{GL_BINDIR}/commands/$command" ) {
+    trace( 2, "attempting gitolite command $command" );
+    run_command( $command, @args );
+
+} elsif ( $command eq 'list-phy-repos' ) {
+    _chdir( $rc{GL_REPO_BASE} );
+    print "$_\n" for ( @{ list_phy_repos(@args) } );
+
+} elsif ( $command =~ /^list-/ ) {
+    trace( 2, "attempting lister command $command" );
+    require Gitolite::Conf::Load;
+    Gitolite::Conf::Load->import;
+    my $fn = lister_dispatch($command);
+    print "$_\n" for ( @{ $fn->(@args) } );
+
+} else {
+    _die "unknown gitolite sub-command";
+}
+
+sub args {
+    usage() if not $command or $command eq '-h';
+}
+
+# ----------------------------------------------------------------------
+
+sub run_command {
+    my $pgm      = shift;
+    my $fullpath = "$ENV{GL_BINDIR}/commands/$pgm";
+    _die "$pgm not found or not executable" if not -x $fullpath;
+    _system( $fullpath, @_ );
+    exit 0;
+}
diff --git a/t/gitolite-receive-pack b/t/gitolite-receive-pack
new file mode 100755
index 0000000..48c7428
--- /dev/null
+++ b/t/gitolite-receive-pack
@@ -0,0 +1,12 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+print STDERR "TRACE: grp(", join( ")(", @ARGV ), ")\n";
+
+my $repo = shift;
+$repo =~ s/\.git$//;
+my $user = $ENV{G3T_USER} || 'no-such-user';
+
+$ENV{SSH_ORIGINAL_COMMAND} = "git-receive-pack '$repo'";
+exec( "$ENV{HOME}/bin/gitolite-shell", $user );
diff --git a/t/gitolite-upload-pack b/t/gitolite-upload-pack
new file mode 100755
index 0000000..8888abb
--- /dev/null
+++ b/t/gitolite-upload-pack
@@ -0,0 +1,12 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+print STDERR "TRACE: gup(", join( ")(", @ARGV ), ")\n";
+
+my $repo = shift;
+$repo =~ s/\.git$//;
+my $user = $ENV{G3T_USER} || 'no-such-user';
+
+$ENV{SSH_ORIGINAL_COMMAND} = "git-upload-pack '$repo'";
+exec( "$ENV{HOME}/bin/gitolite-shell", $user );
diff --git a/t/glt b/t/glt
new file mode 100755
index 0000000..b5704f5
--- /dev/null
+++ b/t/glt
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+print STDERR "TRACE: glt(", join( ")(", @ARGV ), ")\n";
+
+my $cmd  = shift or die "need command";
+my $user = shift or die "need user";
+my $rc;
+
+$ENV{G3T_USER} = $user;
+if ( $cmd eq 'push' ) {
+    $rc = system( "git", $cmd, "--receive-pack=$ENV{HOME}/bin/gitolite-receive-pack", @ARGV );
+} else {
+    $rc = system( "git", $cmd, "--upload-pack=$ENV{HOME}/bin/gitolite-upload-pack", @ARGV );
+}
+
+if ( $? == -1 ) {
+    die "F: failed to execute: $!\n";
+} elsif ( $? & 127 ) {
+    printf STDERR "E: child died with signal %d\n", ( $? & 127 );
+    exit 1;
+} else {
+    printf STDERR "W: child exited with value %d\n", $? >> 8 if $? >> 8;
+    exit( $? >> 8 );
+}
+
+exit 0;
diff --git a/t/t01-basic b/t/t01-basic
new file mode 100755
index 0000000..3970308
--- /dev/null
+++ b/t/t01-basic
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# this is hardcoded; change it if needed
+use lib "$ENV{HOME}/bin";
+use Gitolite::Test;
+
+# basic tests
+# ----------------------------------------------------------------------
+
+try "
+    plan 74
+
+    ## clone
+    glt clone dev2 file://gitolite-admin
+                                !ok;    gsh
+                                        /FATAL: DENIED: R access to gitolite-admin by dev2 .fallthru./
+                                        /fatal: The remote end hung up unexpectedly/
+    glt clone admin --progress file://gitolite-admin
+                                ok;     gsh
+                                        /Counting/; /Compressing/; /Total/
+    cd gitolite-admin;          ok
+    ";
+
+put "conf/gitolite.conf", "
+    \@admins     =   admin dev1
+    repo gitolite-admin
+        -   mm  =   \@admins
+        RW      =   \@admins
+        RW+     =   admin
+
+    repo testing
+        RW+     =   \@all
+";
+
+try "
+    ## push
+    git add conf;               ok
+    git status -s;              ok;     /M  conf/gitolite.conf/
+    git commit -m t01a;         ok;     /master.*t01a/
+    glt push dev2 origin;       !ok;    gsh
+                                        /FATAL: DENIED: W access to gitolite-admin by dev2 .fallthru./
+                                        /fatal: The remote end hung up unexpectedly/
+    glt push admin origin;      ok;     /master -. master/
+    tsh empty;                  ok;
+    glt push admin origin master:mm
+                                !ok;    gsh
+                                        /FATAL: DENIED: W access to gitolite-admin by admin .rule: refs/heads/mm./
+                                        /remote: error: hook declined to update refs/heads/mm/
+                                        /To file://gitolite-admin/
+                                        /remote rejected. master -. mm .hook declined./
+                                        /error: failed to push some refs to 'file://gitolite-admin'/
+
+    ";
+
+put "conf/gitolite.conf", "
+    \@admins     =   admin dev1
+    repo gitolite-admin
+        RW+     =   admin
+
+    repo testing
+        RW+     =   \@all
+
+    repo t1
+        R       =   u2
+        RW      =   u3
+        RW+     =   u4
+";
+
+try "
+    ## push 2
+    git add conf;               ok
+    git status -s;              ok;     /M  conf/gitolite.conf/
+    git commit -m t01b;         ok;     /master.*t01b/
+    glt push admin origin;      ok;     gsh
+                                        /master -. master/
+
+    ## clone
+    cd ..;                      ok;
+    glt clone u1 file://t1;     !ok;    gsh
+                                        /FATAL: DENIED: R access to t1 by u1 .fallthru./
+                                        /fatal: The remote end hung up unexpectedly/
+    glt clone u2 file://t1;     ok;     gsh
+                                        /warning: You appear to have cloned an empty repository./
+    ls -al t1;                  ok;     /$ENV{USER}.*$ENV{USER}.*\.git/
+    cd t1;                      ok;
+
+    ## push
+    test-commit tc1 tc2 tc2;    ok;     /f7153e3/
+    glt push u2 origin;         !ok;    gsh
+                                        /FATAL: DENIED: W access to t1 by u2 .fallthru./
+                                        /fatal: The remote end hung up unexpectedly/
+    glt push u3 origin master;  ok;     gsh
+                                        /master -. master/
+
+    ## rewind
+    reset-h HEAD^;              ok;     /HEAD is now at 537f964 tc2/
+    test-tick; test-commit tc3; ok;     /a691552/
+    glt push u3 origin;         !ok;    gsh
+                                        /rejected.*master -. master.*non-fast-forward./
+    glt push u3 -f origin;      !ok;    gsh
+                                        /FATAL: DENIED: \\+ access to t1 by u3 .fallthru./
+                                        /remote: error: hook declined to update refs/heads/master/
+                                        /To file://t1/
+                                        /remote rejected. master -. master .hook declined./
+                                        /error: failed to push some refs to 'file://t1'/
+    glt push u4 origin +master; ok;     gsh
+                                        / \\+ f7153e3...a691552 master -. master.*forced update./
+"

commit 5fd06036edf408f81dbc33dff869f732c0b057e2
Author: Sitaram Chamarty <sitaram at atc.tcs.com>
Date:   Tue Feb 28 22:15:45 2012 +0530

    empty

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list