diff --git a/t/README.mkd b/t/README.mkd
new file mode 100644
index 0000000..c90d016
--- /dev/null
+++ b/t/README.mkd
@@ -0,0 +1,79 @@
+# notes on the testing setup
+
+In this document:
+
+ * terminology
+ * notes and background
+ * quick instructions for running the test suite
+ * instructions for adding new tests
+
+
+
+### terminology
+
+ #define PW "patches welcome!"
+ #define TODO PW
+
+
+
+### notes and background
+
+ * all testing is done on one machine, using 2 userids
+
+ * test driver exits on the *first* failed test; no fancy counting here.
+ (PW).
+
+ * installs are done using "gl-easy-install". As such, this test suite is
+ mainly meant for testing **the core (access control) functionality**, and
+ will not help you test the install/upgrade parts themselves. Those are a
+ lot more difficult to test in an automated fashion, but luckily they also
+ change infrequently and can easily be tested manually when they do.
+ Errors in those are much more visible too. (PW).
+
+ * the test driver has evolved as new scripts were added; you will see that
+ older scripts are a little less sophisticated.
+
+
+
+### quick instructions for running the test suite
+
+ * create two brand new user IDs: tester and gitolite-test
+ * these are hard-coded, sorry. (PW)
+ * give them some passwords
+ * allow ssh into gitolite-test in `/etc/ssh/sshd_config`; preferably use
+ a line like `AllowUsers gitolite-test@127.0.0.1` so that no one can
+ ssh in from outside
+
+ * prepare/push a bare clone of gitolite itself on /tmp so that the "tester"
+ userid can grab it painlessly
+
+ cd your-gitolite-working-repo
+ git clone --bare $PWD /tmp/gitolite # first time
+ git push -f --all /tmp/gitolite # subsequent times
+
+ * "su" to "tester" and clone/fetch the gitolite source:
+
+ cd $HOME; git clone /tmp/gitolite # first time
+ cd gitolite; git fetch origin # subsequent times
+
+ * checkout and "install" the branch you want to test (install from "tester"
+ userid to "gitolite-test" userid). Example, if you want to test "pu"
+ branch:
+
+ git checkout -t origin/pu # if needed
+ git checkout -f pu; git reset --hard origin/pu # subsequent times
+ cd t # THIS IS IMPORTANT (patches welcome, or will be fixed eventually!)
+ ./install-gitolite
+
+ * run all or some of the tests
+
+ ./test-driver.sh
+ # or
+ ./test-driver.sh t51
+
+
+
+### instructions for adding new tests
+
+(TODO)
+
diff --git a/t/basic.conf b/t/basic.conf
new file mode 100644
index 0000000..93bf22b
--- /dev/null
+++ b/t/basic.conf
@@ -0,0 +1,7 @@
+repo gitolite-admin
+ RW+ = tester
+
+repo testing
+ RW+ = @all
+
+
diff --git a/t/cleanout-gitolite b/t/cleanout-gitolite
new file mode 100755
index 0000000..4feaa06
--- /dev/null
+++ b/t/cleanout-gitolite
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+export TESTDIR=$PWD
+
+[[ -f /tmp/$1.tar ]] || { echo need tar file 2>&1; exit 1; }
+
+# blank out the client side
+rm -rf ~/gitolite-admin
+# make sure we have the u1-u6 keys and the config to refer to them
+cp $TESTDIR/keys/config ~/.ssh
+cp $TESTDIR/keys/u[1-6]* ~/.ssh
+chmod 755 ~/.ssh ~/.ssh/config ~/.ssh/*pub
+chmod 600 ~/.ssh/u?
+
+# blank out the server side
+echo the next command MAY ask for a password
+ssh gitolite-test@localhost rm -rf .ssh .gitolite .gitolite.rc repositories gitolite-install
+echo the next command SHOULD ask for a password
+ssh-copy-id -i ~/.ssh/id_rsa gitolite-test@localhost
+echo the next command should NOT ask for a password
+
+cd ~/gitolite
diff --git a/t/install-gitolite b/t/install-gitolite
new file mode 100755
index 0000000..fd02b85
--- /dev/null
+++ b/t/install-gitolite
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+export TESTDIR=$PWD
+
+# prepare local ssh
+mkdir -p $HOME/.ssh; chmod go-rx $HOME/.ssh
+[ -f "$HOME/.ssh/id_rsa" ] || ssh-keygen -q -N "" -t rsa -f $HOME/.ssh/id_rsa
+
+# blank out the client side
+rm -rf ~/gitolite-admin
+# make sure we have the u1-u6 keys and the config to refer to them
+cp $TESTDIR/keys/config ~/.ssh
+cp $TESTDIR/keys/u[1-6]* ~/.ssh
+chmod 755 ~/.ssh ~/.ssh/config ~/.ssh/*pub
+chmod 600 ~/.ssh/u?
+
+# blank out the server side
+echo the next command MAY ask for a password
+ssh gitolite-test@localhost rm -rf .ssh .gitolite .gitolite.rc repositories gitolite-install
+echo the next command SHOULD ask for a password
+ssh-copy-id -i ~/.ssh/id_rsa gitolite-test@localhost
+echo the next command should NOT ask for a password
+
+# install it
+../src/gl-easy-install -q gitolite-test localhost tester
+
+# add 6 keys
+cd ~/gitolite-admin
+cp $TESTDIR/keys/u*pub keydir
+git add keydir; git commit -m 'added 6 keys'
+git push
+
+# make the rollback.tar files on both sides
+cd
+tar cf rollback.tar gitolite-admin
+ssh gitolite-test@localhost tar cf rollback.tar .ssh .gitolite .gitolite.rc repositories gitolite-install
diff --git a/t/keys/config b/t/keys/config
new file mode 100644
index 0000000..7330f7b
--- /dev/null
+++ b/t/keys/config
@@ -0,0 +1,15 @@
+host u?
+ user gitolite-test
+ 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..43410d0
--- /dev/null
+++ b/t/keys/u1
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoAIBAAKCAQEAnqiW8xCSfg/JO7acBvarVghAyM2Xw2ZzwaQN5xOg/XOSt2wV
+5wsT32hmPg/v8NqXcjKXMyI76ZO4U9To9yDvs6xKC9NwFmV6prjf6IC2gryykg2L
+BvWKIrcr/tNBnKgDAs7SDtf1EEN5Byf0mTcy9c9eVMsxvwm3oXqXKCm1YkKeaWOE
+d0xuRMRAVHlfon4M67r/heDZho9QLwvyDhorefhoLGOzrUbsXbcTHAEkt+m56swz
+7d7yZVSXckqrQ+KyY6cVjm1kdBvO7B34TpjMYubv4a3WPC98QUAFSRir3+HW43JF
+U/ty4Z7pCbpDBPctvg1fdCzIx1sg54zE6MZGnQIBIwKCAQBoQubaPhcfo/lEf1CW
+3Jx6XTHjCsLQ3Oz7l7FdVgq1LrDe70GX1BRfnGBx7TdGgQRvnZaPUQLMsYfCD5HG
++GMBCtGSviUWCCwHKQgrEsEUFZnq8vT18c/Nq5HwmXRCX8d2366cCkH0vp/9Y2Y4
+zICS51s/CS9Rp0zJM01jiR9sc+LG9cz7pI8nk7jaCjhzV/229mQC5/1TxDqgdafH
+/27cdtA0sqbG7lvL6k5qCYM+C96pOjmqap7x6YSMlUbcCKL1HLWzLsqMBjbgtm2M
+jHB3Kv3aDk5Mf2Lj1G4Bauw/NCedoQ+bj4LCXfYIR5BKNSAmDigZitBMRHOInBEl
+O7drAoGBAM9BTk9KYtS3yXmz/GVoNpgTZ1IFF90hjAhkPN5iFqOKL/f43U9Gn5gK
+9wJp68FJbpB7V8qH2shI8S0jv3AJicH8MG3RHA2FSMjUTy4nKBP835OKxgl49J87
+OZNigS8BFbWKm5o0Wlm17H4GH6sEKgt4dFOelHmxO0H/4L1r0GGZAoGBAMP5UUAH
+BXHLMD4GxnfNieegg7EjUbKMf6CqHAk44wCgS0mgjMrIoMiiAnf6djeKf1wrJvBB
+S6eK3qu7ZUnVQKqDKZM9WAxyyZExKHx+FXFt51R6YrnJQVzMuA1Im7xSGJcmL30c
+pVIwcVjKe5sKT8TUCRGYdyhfSGa+vok5K7elAoGATPr/1E7UQGGFWRb3WN2QnuKi
+uBCFNOCTGQ9JzvE7m9Q9s+AXr7slVbruCDX4ev4Efs66NUhnNHLdPKbgtJXUFNoD
+W/02Tiow+iL41qDUXzIJ5lgdq782ScWKafFjLrdCk+MGmFyWllmDucfCnpsljzte
+aDOWO9QdUwdE2KRjV3MCgYAyZK5+LbDxYCJZF69gopEs7sLHJlbWJCDRitQCXxXM
+9gS7KUjGbhq3P5o8G9VBe2KUCxikLgwjxJ+ogKUEW2hmXD3kCHW6DuNf2XDPjiLF
+ZWde3aRbqMeqJgNxIUyeI70uLmP7mabanr4PhIYvLomKYmi7Ybg9lNgaa5AUqE0R
+9wKBgB8LYjinisX8pGaSfGpxEHQu/giH9SqUyXoDyk/SkqmhmayB341P+x3Zdlrx
+xBVYsxdQRbxbiO2CdihPDbiT4wXS+p9mReOJeb/QluzGpPuloSkx87uW1kcErZ/m
+vr8opZ6ITlSSQeigwabVpkthDzNgQbEeHblbX0F9io56vX8w
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u1.pub b/t/keys/u1.pub
new file mode 100644
index 0000000..3838e9a
--- /dev/null
+++ b/t/keys/u1.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAnqiW8xCSfg/JO7acBvarVghAyM2Xw2ZzwaQN5xOg/XOSt2wV5wsT32hmPg/v8NqXcjKXMyI76ZO4U9To9yDvs6xKC9NwFmV6prjf6IC2gryykg2LBvWKIrcr/tNBnKgDAs7SDtf1EEN5Byf0mTcy9c9eVMsxvwm3oXqXKCm1YkKeaWOEd0xuRMRAVHlfon4M67r/heDZho9QLwvyDhorefhoLGOzrUbsXbcTHAEkt+m56swz7d7yZVSXckqrQ+KyY6cVjm1kdBvO7B34TpjMYubv4a3WPC98QUAFSRir3+HW43JFU/ty4Z7pCbpDBPctvg1fdCzIx1sg54zE6MZGnQ== tester@sita-wd.atc.tcs.com
diff --git a/t/keys/u2 b/t/keys/u2
new file mode 100644
index 0000000..f259739
--- /dev/null
+++ b/t/keys/u2
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA9NNn9rpF3+1reKJxfqQcjmugWkvN4VZfi6Uln1q6RJlohh6G
+FhSTbA+puGjWy03VROa2K3TUxKKtPHMCFIOVpnNN3e4kAJbW1P/DbeYL5/kYo0tc
+uNexFch2/4FrcykBoxZvTGUH1hJNIANjcAs+u+rzbwyiH5iQKIukfe7de2LrJEyw
+/n05yAkMVfc2E97ge6X7rwGOpKaqMyy5S5Wjr6YxD54CAybdys9XWbwZYFx9s14j
+PFK0frtXE7qFmR+hch9rgEro3jvIqLkQpO2u/ZfcoHllRDTqXZVGRGfrfZ8UHYsn
+32+iApLFFdKcPkTtJokmq++b61NoQVQfk54yvQIBIwKCAQBM8fwaV8zRWT8IqBUK
+i1lultqgCTITns2SSdifzA6n2HFOuSLTvVLncqMOElIio27pxNoq6jQ9zLoaUAf3
+0ZVu08gD68IsEiZC8UwMmMHp0fHM68yvHzenwqkOeSHFG4Qr8RuqS6NgiWiySjUx
+16YAi6uX7gcRPpO0+LAKUmLdoiGLjF/bpMAyhfvB/EzAQESoscEhGywwboJfMJGK
+FlKdazqYlYy4txjw53bUbuN/lsQYToAbe68zIVTBkKVZlBrBeOp1FPPoNXtaOuWx
+tXsaiQIBXbJY9AaI9wCJsK9pRU92k23gyBENNHC/hPvyK0JWpkRhd9rsl79BG/xh
+dJ6LAoGBAP6RSyKtjbwb6tuZWMpGeY87WnM2JXmuNRwR2n3KnM5VOsgLgyTKx11Y
+9+B51b0yx3hVKJ6xbs4pjq/MLgAGXgfzh0VBJUjxiVKfHc0qhrUg1KAmGbhczYkE
+69/oQP7d0vsgC7gR3xr0QMI8v9tZ9UDsPrzqOKZ4162YlNAmOv19AoGBAPY0FF1Q
+QF/0FsnfB0kMmNPhuNwmy3EWhkxmzvgPa/0Ic1F7s2NJu9P+9vzHnM4jQPKFO75h
+/USD8SU8wEjqGOdFi8fh4vTyk+2Na3/n37f8sjYY77tAvAa8Ix7Ul6c5knk+ZIaN
+/K3kThB3sXCzOmTJfzMXOZn2DULZlM9Okw5BAoGBAKdJiSVqvDnJM+9kxVG5SJFS
+46rLz3vY4RJyId2iZws/UoN1R400zCdmWb9mAXxUkbV5yjxl+FuXpuiNfVB5NnpA
+8n33nCieuVONIjZdxjyDSeWVYVvlNpveqaHEgnuZFZ21+RKPZrnw91sR+muS4v7H
+IenbvtPLwO5rlP3P+uEXAoGBAOgieZnPUqOc/4sivbnnTkQd93B8WWqgNXs8XL3/
+6XmLn+29xmTtv7lA2j7K1axqYc64ImML7suSWGTwIwLrWU8Vr63U+o8t2+6bSBmC
+2j/CXuKMikLWoqdDrBXBIUXl0z8dkfs1apVTmg+Gy99J5pmKyGNfCmyXiNihQync
+XsRLAoGBAN50w7bf+lGJ0KDoMIAGRqLJpm0NLd980R7j+yR49g8N/RjKrXz8zFuo
+7NJztYXT1oBNv9HuO7Aq0z7gGQiNbjarGVgblqZi3RN7IE8/Cs4Ev50BLVWM1WTN
+x4yFgK1OC3FfJaY6rn8u9y0dygTFvKrQx68gO+RgAIb4t/ZUoySW
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u2.pub b/t/keys/u2.pub
new file mode 100644
index 0000000..11fa108
--- /dev/null
+++ b/t/keys/u2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA9NNn9rpF3+1reKJxfqQcjmugWkvN4VZfi6Uln1q6RJlohh6GFhSTbA+puGjWy03VROa2K3TUxKKtPHMCFIOVpnNN3e4kAJbW1P/DbeYL5/kYo0tcuNexFch2/4FrcykBoxZvTGUH1hJNIANjcAs+u+rzbwyiH5iQKIukfe7de2LrJEyw/n05yAkMVfc2E97ge6X7rwGOpKaqMyy5S5Wjr6YxD54CAybdys9XWbwZYFx9s14jPFK0frtXE7qFmR+hch9rgEro3jvIqLkQpO2u/ZfcoHllRDTqXZVGRGfrfZ8UHYsn32+iApLFFdKcPkTtJokmq++b61NoQVQfk54yvQ== tester@sita-wd.atc.tcs.com
diff --git a/t/keys/u3 b/t/keys/u3
new file mode 100644
index 0000000..561bbc8
--- /dev/null
+++ b/t/keys/u3
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAs7YceedkXYoPDinUWKISRv48DbcAdIgw+bm/EBwsHcBHxr8L
+bX6jNTRQifp7aiCNeoW7U+sqDsJ9r4CqDWagXg/o2uhYDbdizz3Gpiwslqi0tvu+
+e3/0J327wTB81v2pZ8j6DILR+kN+S8LXUHzUsFus8XHK3SvyWqlxqRBOpkP3tqB5
+Grfjuwn9IJJcvqcqkjXJcDuij9RE7EHmWGEv6c4MtS5mTpXGy9xPnehwcGzm5fEi
+sxzLts/F6YgC6p7D7ZxR0Hm6YQteIFjhFkIMqNjPu78jrEfWD84C+wmpypSjVFMW
+QaH+qDeeDWRE6WUufH+Uirzm7DLvHdnsgCeGoQIBIwKCAQBhjr8AWQqZLbBmxkAS
+26OFn/S2/PGeWI+dghdDQn5ZSx+mZ7W/GNxBdCu4nes5nKSS+CPdGUK3jib433JB
+yf9JAVKFdtC2/ScCycOcCVoIpLnfrTuE3w+DJv+GIaLbG/zgkaxP7z7Jr5xVA17x
+LdKLn3sj+/HIhZIxN2mWSq5aQT4GElmiyXTcOLr/WyFZwhlwCAWPFlSxnqUEq+fi
+qgPOxmI+w4a+GnrulqDYEpGvY8g0Z87yJHr8oROOhPRVWp/km3fodA4gBanwzK7P
+8bvYtcMxOdK6cD85zgGbuaI+IB0jP9NQijBr6a8I8Mxj2VUHk9VtGrv+HcUZ39g/
+V99PAoGBAOTWbNbEmcgfJutlqEBgbl36g4hkpYazSQZlcp1IkHP4V9kk1+Zyibsq
+9HtQ4JCT0CEJ7pdXNnfeRbG1jgnovWTqFGxHyPDqwYtHuQ6qMKLVQA/qqBxbp4AN
+sPQqy2SdMPcr5Hb9di3WX/ztlMiZlk8Nd73DSQXum0+2j3PEuxO3AoGBAMkK6WkB
+02zC2OGuoI4XMZBClgDpDqhEgfOZJtPMl4yCCMH0TKKYzcrb7U2xh1o7Nc5TDWx1
+bTtlfE5+SGzQcFNr4sVIRspjuKdFd53figJR3iJbdp4UImhGyf5gif9cocdTSeQb
+mQvVKSjL+PZUgeR8BPzIk3k29P5thbR8oihnAoGBAJzqzkogaXNIj7dbppKLRF2z
+GF2G1+dWXfXB6DFWVGzHjLIn5IDDkaTqQT6d29DaYtTTnEqE8iZPRbuvsdrovGJ0
+oEo/2j7M3HzDd5UG/MdtqEVttRrCgXxhKOHxdYbSMDR13n16mi4PV8NhFZDeWHC4
+xyMJkSijnbMA12VTs3s7AoGBAInbmL0IkPofNaIRWCbrVS+4oV+1wOhpfa5aY8Rv
+CNVgewinhQHH3ZJq692BDFshSXeJaEpfJlSdXI2YbC1burzcQ7p6tDMCjT+AF4Iy
+4kq7y4VGCDHh67U32veSp8UMUa1AbS6z1qkHMiqaftTMO8/gA2uCOT0s/8RoW672
+YJC7AoGAbpYmmhMp/hITKqLxFmzN+mK0lt8Ulgqo+xhoPycRXGJ0LcQntwL28Bvv
+gKU4L0YockExwedM4Xjgvos0bj8vtAmFsVqWTiVOMIr1hMwPj79cyMgOILO5fJ2T
+2s2dafFQiysUddn9RlDpcm18/Nn3CYOc5ZZvMtqMWe8nFwjklLE=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u3.pub b/t/keys/u3.pub
new file mode 100644
index 0000000..a0ecdf6
--- /dev/null
+++ b/t/keys/u3.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAs7YceedkXYoPDinUWKISRv48DbcAdIgw+bm/EBwsHcBHxr8LbX6jNTRQifp7aiCNeoW7U+sqDsJ9r4CqDWagXg/o2uhYDbdizz3Gpiwslqi0tvu+e3/0J327wTB81v2pZ8j6DILR+kN+S8LXUHzUsFus8XHK3SvyWqlxqRBOpkP3tqB5Grfjuwn9IJJcvqcqkjXJcDuij9RE7EHmWGEv6c4MtS5mTpXGy9xPnehwcGzm5fEisxzLts/F6YgC6p7D7ZxR0Hm6YQteIFjhFkIMqNjPu78jrEfWD84C+wmpypSjVFMWQaH+qDeeDWRE6WUufH+Uirzm7DLvHdnsgCeGoQ== tester@sita-wd.atc.tcs.com
diff --git a/t/keys/u4 b/t/keys/u4
new file mode 100644
index 0000000..be1b027
--- /dev/null
+++ b/t/keys/u4
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAyjyPl0ARSBYZTuZtkPJH+3wqruonGvchQpR+RgUjJKbPJqgv
+dbuqaiTUMHnEDzTPWM54IBP78OdGntcDxhUCgZKC9k2IZg0aT/EvwcavZswBrw/3
+GSJ22pr5VR/zBLoLBK1oQedJY1rMIo3LeqreyRo7gFnvSGX1s2VsmkrLhz687HM+
+64GII0RtoFj2wZ2ZL5ssT++Nt8mLypdwOHEiIZjtrqK5UtEvUrVvGVN0vICyqB/1
+mftzo9zjEj3BoBIQieOKLZOraHgsvUwdpYD52ASD3kAS5tiJbvpG9D81E5Zkfk4P
+xDaKeRF5IFivxbM2MCztcGtJi5hRw9eU0evXrQIBIwKCAQAXHN0122+wd43dIaYf
+MaHTmSlzE3Iu96wHm/Eskt9x5y2eBJe1rwwpY0tzQR24mFI2CPfJJtr29dwvaQe+
+3dRmlGa9ECzYdoaqDO+D3DFNk6hrx09wljl4EbYYW2w7DfKhcudX/Sz1aXZqWVkV
+VVtKLuI6je91/QYjIYjBLR6THOiXZRePbzY2/zMktm35v625b5k3CS1BKbd8jAfY
+zUf70ceOgFZlLT55ab1tAEQaMFI/UhJTl9oTKnnZF9ULc0HBqHo6/pbaxZ1rrlb7
+D0H/Wbqf/INtdpMI32OVRSdtyYo9GezBBLc1VkcSmA+o2eOUQe6jldt71welXSFX
+fsPbAoGBAP2Xql6WyGvKVzksZOHL1Huh0x01UPSIdSIjnA5zQe1euZbCKUZDN939
+xPM8Xtno4nSMtLF6wbFS8iWJI1Ecp121rzG5dNSIvFYNjaXma44PlU70smrfGPTt
+v2LKeXKiaZAaPo8b+qwhdfqgGyuCRqSsD6d5dEf3skGf6rRnoyRXAoGBAMwoFFIt
+rAH3tDTyt9Kse+13N5J5Dk01J2QlMh0cKv7nc8TNln8aTW6uk0JjMkbud25xXSPD
+yQ4pNxNdiehfOjN1sirv6y7lerVBiP6qT+pOHhPxrCoJ8g3zmDzPqKlwiVrNbKkP
+UptE6JCdivs9+goy3F3fl8EYTI08/fyss4GbAoGBAKalfpXuDq070usOi2/PFppj
+BxMxqjpLCyUQFhgf2t3QiJZFE88WOqfSpf7tKGNP5UVGhV6vwRzBeo2x5AIaJNcu
+P+153w9SimRvTnRVlyLXClh06kY61eLIHq6iT879A4qU50gZs1sr7nF/J8wiaO/e
+yHVecPwXzOlL1L+x0ZuJAoGARf8c6Pm3USG6IMg/BmcF6wRNkU4TiC97OEdEYcCD
+xRwnsTCLXscwfbgyfSlTEQFNhPOq57DXOA4hgvt/vWJ6WslEZn4kv58iwc1TioOJ
+HSIY8OUlFbpEXI4IT10j4lJ4PGOwOfaf+riKQDYDw9q8IMD7GN7yM5NNckDMHB30
+ZvMCgYEAstjuwtdD5qNQaGL4dCplVHmfBohXpZayhAQN3ziY/yScIziM9uBCGcKv
+E8toH0EgJBp06+dWZ/2mzgByEhlqKwfbGd1Z33ed3zzQNl7NAMzqKHOtN7KPeNLx
+5mzEOwryf9WqPGpjAnFyIYF/ZTN9JgHWUqTI1jVW4lefOw2Ji8w=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u4.pub b/t/keys/u4.pub
new file mode 100644
index 0000000..36ef4f1
--- /dev/null
+++ b/t/keys/u4.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAyjyPl0ARSBYZTuZtkPJH+3wqruonGvchQpR+RgUjJKbPJqgvdbuqaiTUMHnEDzTPWM54IBP78OdGntcDxhUCgZKC9k2IZg0aT/EvwcavZswBrw/3GSJ22pr5VR/zBLoLBK1oQedJY1rMIo3LeqreyRo7gFnvSGX1s2VsmkrLhz687HM+64GII0RtoFj2wZ2ZL5ssT++Nt8mLypdwOHEiIZjtrqK5UtEvUrVvGVN0vICyqB/1mftzo9zjEj3BoBIQieOKLZOraHgsvUwdpYD52ASD3kAS5tiJbvpG9D81E5Zkfk4PxDaKeRF5IFivxbM2MCztcGtJi5hRw9eU0evXrQ== tester@sita-wd.atc.tcs.com
diff --git a/t/keys/u5 b/t/keys/u5
new file mode 100644
index 0000000..f525303
--- /dev/null
+++ b/t/keys/u5
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoQIBAAKCAQEAonPym8U2ElyMBPVtk5rLqz44QG9pSTTpOj8AmqBRpy9W0B6C
+4yuYf4oVNJMD3GEq6Y65uhmSmN/Nqi99++TtwrNPCRYwXHHa2ZUwHT6ow6Izkrnn
+nzkkPBgpBOKmn/M8z7xlsQhmEBuKlUgYOrdnfRnkvOTTuF4Fq4jSpagOnLjpPlxr
+KZ6vcdvVjZ3kWtZEI+HowS1IKxZsI0UbP/FzSUFrlxLAedrPFBUiUfdl/VPOMs7t
+AjHFUMepq4vzjQlupuJEhaB24nCQFlV5gLQjrGgRs3gs6AJRCYMtE5edsllG1hJV
+ro2UGiH8pvOOso4LYKbyiHuUjuToRlUNjpqRqQIBIwKCAQBTjBZevTHATNpLsWuN
+vVLNGK83FLnN4LJ1ugBPhaZV+xa0LPLbOv34cuZVjW+zKqha26/5TvrviQqvS5/g
+oZ7ZKQQTTT1xXyAYINbqd/8Fhp4uM7jyzO4QRvCGK2uit5ueCR5bC6I0K2vXvq1g
+Boz+glhhJTmvRkwPEyqBFJnM8JBAxp57WEjC0tK6Cw8soKEgfwe6dUouRRdEA6q7
++Z86azz8YkenUM8o/j6IRshJGBBpOfF1c6NY1M9CLebSKIocg9jxBqdMZHnzpqSW
+qxjibSUGQWU/7o45TRAefdpQR6+HLR1mGxvxtU94/AH94MjmUv827Nd6OhMyynh5
+s0cjAoGBAMwm96LBouDUsmhThbK27a3B0CnlkS1FYkwuNbudWoS+kGPktu3S/rqe
+i8Czn6mQEKaEvY0ECcnPaSN9Nno7G26TrrUGjIAjWFTKWJ4HognuLj0eBf+OLfrg
+WgcXZJtK4vk8hY26DsleIO8DEg/Vkg0x9EozEI4Qbg3IRbvroiHzAoGBAMu14pQ/
+QevNAUVCHyJbWutNjIUp8O42ZqubegVt+Kq1iy2OuSrhhg/et3ZABv4YBiQ2y1/S
+qq+p+Z3YUY3Vu54xFr7/v+pVOGqc437+xWlbSSkJSFpBErNkaKMWPkuu6uoStQ7K
+ZoYtIIFVE+zEqneLSincZZxmIl/UMErvOEjzAoGAF1TpGeo+gBhO51oAprXSBTq4
+tFTAIm5UYHpPSKRE2/iFh8JeDI0kbRlvDrVxVTUJN5okSqFgNFI35sx7QStTl5vZ
+c8Y78WMgCbC5q6kZ1T/KxSgAr332oFQni8gowUpjFSt8+kEmQuYvpkl+aDWqSqaf
+k3OM3QkxJiWET/2sISMCgYBLqfUSfeVI9F+OwMm8TdFQFX1WCEOLrc5c/zvzY1xc
+qdSb5I3rWxSJjTzUJmj/SsBz2daCrVVXMIFJQbfkOXGSh0L2fD/388R/Xtgn2vjd
+/VWwKATuUq8ssEQfUWeYdCteQXZzNTwF84EaGEk6r3KELGwA63YyxtmRTtAb1TIp
+uQKBgQC60e8R2YqZ6ylmPygjOAMw3rsEyDYrEtMqIKZIgrjAvEnrhyif9LS2a1aF
+6Hg3IJmeTVt6ql5iDYK3nfnv/rSLs2vAdGwD082Lasx/bk8kDro0fSVylI50g1/U
+BRzkoHBZb6mKKJfFNzDr0Py790lmsp3DYIYPxoouvSgDvcKJZw==
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u5.pub b/t/keys/u5.pub
new file mode 100644
index 0000000..95b8d39
--- /dev/null
+++ b/t/keys/u5.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAonPym8U2ElyMBPVtk5rLqz44QG9pSTTpOj8AmqBRpy9W0B6C4yuYf4oVNJMD3GEq6Y65uhmSmN/Nqi99++TtwrNPCRYwXHHa2ZUwHT6ow6IzkrnnnzkkPBgpBOKmn/M8z7xlsQhmEBuKlUgYOrdnfRnkvOTTuF4Fq4jSpagOnLjpPlxrKZ6vcdvVjZ3kWtZEI+HowS1IKxZsI0UbP/FzSUFrlxLAedrPFBUiUfdl/VPOMs7tAjHFUMepq4vzjQlupuJEhaB24nCQFlV5gLQjrGgRs3gs6AJRCYMtE5edsllG1hJVro2UGiH8pvOOso4LYKbyiHuUjuToRlUNjpqRqQ== tester@sita-wd.atc.tcs.com
diff --git a/t/keys/u6 b/t/keys/u6
new file mode 100644
index 0000000..8896e8a
--- /dev/null
+++ b/t/keys/u6
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEApqmFYqqjj1pPRGICRIIYLTF9JoLz+oU5nf+jei7WVxlE4Z7T
+AtUn/8ldZiJb8+OKtX0h/P6cuxcs3ZclAeICC5DYZ+zfgb0blIrz3n7x4EC8Ti/w
+PhIL3OjmoUIl3PqvxE9QmwIMG5TfIIOkvGBZAGB2YiyusohLqqJ26m/SY29yzpf8
+zqu2aJLV+rYeDPGgkOFVFaTAYxTWDUZAqFEuBZm1Nhs413dOa2oBDT+DY/TnY7D0
+TDbhai3/nX1v0Mx3F96aGKxPfQsiQs8bniAZa+F5NO5F67avq0YtKF24JmP8zCm1
+WQ2I/CqjtxbBKJNPKZAE25VnJi7+BRStFAwXIwIBIwKCAQEAihduhPPJWYVXnw9g
+9u9zHiGw1sRGfx3t6VAoXexLMjmCN0kVPNyAOlZjVKAgTbyI34TwRqcUJf1CbnXr
+dpaxPMh4yyNaGwpnTzFNuFqN5bH7HDZZWABS+N47b6vWOsEVSuKh34VafUDWIkEw
+uVclLDKrO2bZ5GJNUt5ih7u880xgibWN6sm6UdO5SL/Y+0imWwipGDFERBBexBT9
+xZwFTilSb9Eo+WxfLJQhqRnbhPQvH107oId4v5oxw5FvQSM7JSF2hyTLbz7pXMIb
+sjolhxIikhok3S991xPRl62Q38rl6RitzsrN1/UVl24qN0CUmu3qGqO7YRix4m1h
+K3FS6wKBgQDSpqRGEy41eUlmwNhXz8K0pSJkYB3j3OgaxlorDebrE5GVT0o4SKaB
+qyx4z0XjARrrQxU91OoCtotsdIkjYtKArQzgqv4Wrc6MoulTdpmSCxGtwxgvSTSv
+YOEsywDomS6c/hwziTciRyPAD6eUxOle93qmX2lMsvMNcwiHoYn60QKBgQDKipQu
+w2+OaThAKK+dDejlWpEtdQTH7WhsNvYPdvSU0X36M7oVuhA2k0Qh0v26wmPU6RmI
+/C3IjpkHptrXz+ns1BNE3DsxoUAa4H/tWUBaFhIu9ni+AX2mNSnFnXmt8d9kzzZX
+xJsEWBzkE/ve9NeVHtLrq3gmpcXcFteVsNYHswKBgAwJhbrchkwy0P6OrUbYrApS
+lEBAAbU/0r+zkB+3psuat9ylcfSWb+onCdsEh6aSWU9FqXE/XdRE8gYyi35dax1L
+t5fPQbgnMF/Oy4EcuFF8+bIoZ8gwEaOfIs9cDq40o5PxQ3CowVJqdxJKCZN492SD
+K5R6ewuydEnpUPHOt21rAoGAEVxV2B9hVVl52Z0WXes/2SUFISdJjXq/zsLh5BGC
+rauOdIgXQ7Dc0XpWTArFSoWwyRtLVOJqUwToi6CWakUFbBItiY84RhUiwHmkkK/g
+8cdgpPCOAahbOiHQYWVE8acEhPvRdTy81HyUwxen3+kZyvQDc0k2LzLH0QlFrb6z
+Qn0CgYEAmQqc19g5dEpHfbdOPeSrgdf3urcyt6ot1jL9i6B2U6akfvp5GFhGoAs/
+C6q6mplU0by/bmMtYyRhY+7tux2Wz5yaMDwUU5/jnECtt8W7gDi5Y6vaRTIc/2n/
+j53jeI8PJgKNHeF+tbILdUUHDsTLvx/PWVkae1SzDRY8swbIxyI=
+-----END RSA PRIVATE KEY-----
diff --git a/t/keys/u6.pub b/t/keys/u6.pub
new file mode 100644
index 0000000..d0b5fbb
--- /dev/null
+++ b/t/keys/u6.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApqmFYqqjj1pPRGICRIIYLTF9JoLz+oU5nf+jei7WVxlE4Z7TAtUn/8ldZiJb8+OKtX0h/P6cuxcs3ZclAeICC5DYZ+zfgb0blIrz3n7x4EC8Ti/wPhIL3OjmoUIl3PqvxE9QmwIMG5TfIIOkvGBZAGB2YiyusohLqqJ26m/SY29yzpf8zqu2aJLV+rYeDPGgkOFVFaTAYxTWDUZAqFEuBZm1Nhs413dOa2oBDT+DY/TnY7D0TDbhai3/nX1v0Mx3F96aGKxPfQsiQs8bniAZa+F5NO5F67avq0YtKF24JmP8zCm1WQ2I/CqjtxbBKJNPKZAE25VnJi7+BRStFAwXIw== tester@sita-wd.atc.tcs.com
diff --git a/t/out/t01-repo-groups.1 b/t/out/t01-repo-groups.1
new file mode 100644
index 0000000..1cf2e03
--- /dev/null
+++ b/t/out/t01-repo-groups.1
@@ -0,0 +1,99 @@
+$data_version = '1.5';
+%repos = (
+ 'aa' => {
+ 'R' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'W' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'u1' => [
+ [
+ 2,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'u2' => [
+ [
+ 4,
+ 'refs/.*',
+ 'RW'
+ ]
+ ],
+ 'u3' => [
+ [
+ 5,
+ 'refs/.*',
+ 'RW'
+ ]
+ ]
+ },
+ 'bb' => {
+ 'R' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'W' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'u1' => [
+ [
+ 3,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'u2' => [
+ [
+ 6,
+ 'refs/.*',
+ 'RW'
+ ]
+ ],
+ 'u3' => [
+ [
+ 7,
+ 'refs/.*',
+ 'RW'
+ ]
+ ]
+ },
+ 'gitolite-admin' => {
+ 'R' => {
+ 'tester' => 1
+ },
+ 'W' => {
+ 'tester' => 1
+ },
+ 'tester' => [
+ [
+ 0,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ]
+ },
+ 'testing' => {
+ '@all' => [
+ [
+ 1,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'R' => {
+ '@all' => 1
+ },
+ 'W' => {
+ '@all' => 1
+ }
+ }
+);
diff --git a/t/out/t01-repo-groups.2 b/t/out/t01-repo-groups.2
new file mode 100644
index 0000000..9a321b0
--- /dev/null
+++ b/t/out/t01-repo-groups.2
@@ -0,0 +1,63 @@
+$data_version = '1.5';
+%repos = (
+ '@g1' => {
+ '@g1' => [
+ [
+ 2,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ '@g2' => [
+ [
+ 3,
+ 'refs/.*',
+ 'RW'
+ ]
+ ],
+ 'R' => {
+ '@g1' => 1,
+ '@g2' => 1
+ },
+ 'W' => {
+ '@g1' => 1,
+ '@g2' => 1
+ }
+ },
+ 'gitolite-admin' => {
+ 'R' => {
+ 'tester' => 1
+ },
+ 'W' => {
+ 'tester' => 1
+ },
+ 'tester' => [
+ [
+ 0,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ]
+ },
+ 'testing' => {
+ '@all' => [
+ [
+ 1,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'R' => {
+ '@all' => 1
+ },
+ 'W' => {
+ '@all' => 1
+ }
+ }
+);
+%groups = (
+ '@g1' => {
+ 'aa' => 'master',
+ 'bb' => 'master'
+ }
+);
diff --git a/t/out/t02-user-groups.1 b/t/out/t02-user-groups.1
new file mode 100644
index 0000000..f5067a8
--- /dev/null
+++ b/t/out/t02-user-groups.1
@@ -0,0 +1,66 @@
+$data_version = '1.5';
+%repos = (
+ 'aa' => {
+ 'R' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'W' => {
+ 'u1' => 1,
+ 'u2' => 1,
+ 'u3' => 1
+ },
+ 'u1' => [
+ [
+ 2,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'u2' => [
+ [
+ 3,
+ 'refs/.*',
+ 'RW'
+ ]
+ ],
+ 'u3' => [
+ [
+ 4,
+ 'refs/.*',
+ 'RW'
+ ]
+ ]
+ },
+ 'gitolite-admin' => {
+ 'R' => {
+ 'tester' => 1
+ },
+ 'W' => {
+ 'tester' => 1
+ },
+ 'tester' => [
+ [
+ 0,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ]
+ },
+ 'testing' => {
+ '@all' => [
+ [
+ 1,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'R' => {
+ '@all' => 1
+ },
+ 'W' => {
+ '@all' => 1
+ }
+ }
+);
diff --git a/t/out/t02-user-groups.2 b/t/out/t02-user-groups.2
new file mode 100644
index 0000000..63ab262
--- /dev/null
+++ b/t/out/t02-user-groups.2
@@ -0,0 +1,71 @@
+$data_version = '1.5';
+%repos = (
+ 'aa' => {
+ '@g1' => [
+ [
+ 2,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ '@g2' => [
+ [
+ 3,
+ 'refs/.*',
+ 'RW'
+ ]
+ ],
+ 'R' => {
+ '@g1' => 1,
+ '@g2' => 1
+ },
+ 'W' => {
+ '@g1' => 1,
+ '@g2' => 1
+ }
+ },
+ 'gitolite-admin' => {
+ 'R' => {
+ 'tester' => 1
+ },
+ 'W' => {
+ 'tester' => 1
+ },
+ 'tester' => [
+ [
+ 0,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ]
+ },
+ 'testing' => {
+ '@all' => [
+ [
+ 1,
+ 'refs/.*',
+ 'RW+'
+ ]
+ ],
+ 'R' => {
+ '@all' => 1
+ },
+ 'W' => {
+ '@all' => 1
+ }
+ }
+);
+%groups = (
+ '@g1' => {
+ 'u1' => 'master'
+ },
+ '@g2' => {
+ 'u2' => 'master',
+ 'u3' => 'master'
+ },
+ '@g3' => {
+ 'u4' => 'master',
+ 'u5' => 'master',
+ 'u6' => 'master'
+ }
+);
diff --git a/t/out/t04-wild1.1 b/t/out/t04-wild1.1
new file mode 100644
index 0000000..c82ca2b
--- /dev/null
+++ b/t/out/t04-wild1.1
@@ -0,0 +1,5 @@
+New perms are:
+
+R u5
+RW u6
+
diff --git a/t/out/t04-wild1.2 b/t/out/t04-wild1.2
new file mode 100644
index 0000000..3b769ae
--- /dev/null
+++ b/t/out/t04-wild1.2
@@ -0,0 +1,4 @@
+
+R u5
+RW u6
+
diff --git a/t/rollback b/t/rollback
new file mode 100755
index 0000000..4fdb260
--- /dev/null
+++ b/t/rollback
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export TESTDIR=$PWD
+
+cd
+rm -rf gitolite-admin td
+tar xf rollback.tar
+mkdir td
+
+scp $TESTDIR/rollback.server gitolite-test@localhost:rollback
+ssh gitolite-test@localhost ./rollback
diff --git a/t/rollback.server b/t/rollback.server
new file mode 100755
index 0000000..b9c7e45
--- /dev/null
+++ b/t/rollback.server
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+cd
+rm -rf .ssh .gitolite .gitolite.rc repositories gitolite-install
+tar xf rollback.tar
diff --git a/t/t-fedora-big-config b/t/t-fedora-big-config
new file mode 100644
index 0000000..6604bd0
--- /dev/null
+++ b/t/t-fedora-big-config
@@ -0,0 +1,24 @@
+# vim: syn=sh:
+cd $TESTDIR
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+editrc GL_NO_DAEMON_NO_GITWEB 1
+catrc
+expect "GL_BIG_CONFIG *= *1;"
+
+# ----------
+
+name "first round"
+date | hl
+cat data/fedora.conf | ugc
+date | hl
+
+time sync
+
+name "second round"
+date | hl
+echo "
+repo foo
+RW+ = u1 u2
+" | ugc
+date | hl
diff --git a/t/t00-initial b/t/t00-initial
new file mode 100644
index 0000000..7ff8157
--- /dev/null
+++ b/t/t00-initial
@@ -0,0 +1,70 @@
+# vim: syn=sh:
+$TESTDIR/rollback
+
+# ----------
+name "basic push admin repo"
+echo "
+ repo aa
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+expect "To gitolite:gitolite-admin"
+expect "master -> master"
+
+name "basic create repo"
+expect "remote: Initialized empty Git repository in /home/gitolite-test/repositories/aa.git/"
+
+# ----------
+name "basic clone"
+cd ~/td
+runlocal git clone u1:aa u1aa
+expect "Initialized empty Git repository in /home/tester/td/u1aa/.git/"
+expect "warning: You appear to have cloned an empty repository"
+runlocal ls -ald u1aa
+expect "drwxr-xr-x 3 $USER $USER 4096 201.-..-.. ..:.. u1aa"
+
+# ----------
+name "basic clone deny"
+cd ~/td
+runlocal git clone u4:aa u4aa
+expect "R access for aa DENIED to u4"
+runlocal ls -ald u4aa
+expect "ls: cannot access u4aa: No such file or directory"
+
+# ----------
+name "basic push"
+cd ~/td/u1aa
+mdc
+runlocal git push origin HEAD
+expect "To u1:aa"
+expect "\[new branch\] *HEAD -> master"
+
+# ----------
+name "basic rewind"
+cd ~/td/u1aa
+mdc
+mdc
+mdc
+runlocal git push origin HEAD
+runlocal git reset --hard HEAD^
+mdc
+runlocal git push -f origin HEAD
+expect "+ .* HEAD -> master (forced update)"
+name "basic rewind log"
+taillog
+expect "\+.*aa.refs/heads/master.u1.refs/.\*"
+
+# ----------
+name "basic rewind deny"
+cd ~/td
+runlocal git clone u2:aa u2aa
+cd ~/td/u2aa
+mdc
+mdc
+mdc
+runlocal git push origin HEAD
+runlocal git reset --hard HEAD^
+mdc
+runlocal git push -f origin HEAD
+expect "remote: + refs/heads/master aa u2 DENIED by fallthru"
diff --git a/t/t01-repo-groups b/t/t01-repo-groups
new file mode 100644
index 0000000..d879894
--- /dev/null
+++ b/t/t01-repo-groups
@@ -0,0 +1,58 @@
+# vim: syn=sh:
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 0
+
+name "base output with no groups"
+echo "
+ repo aa bb
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t01-repo-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 0
+
+name "output with eqvt repo groups"
+echo "
+ @g1 = aa bb
+ repo @g1
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t01-repo-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+
+name "base output with no groups but GL_BIG_CONFIG on"
+echo "
+ repo aa bb
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t01-repo-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+
+name "repo groups with GL_BIG_CONFIG on"
+echo "
+ @g1 = aa bb
+ repo @g1
+ RW+ = @g1
+ RW = @g2
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t01-repo-groups.2
diff --git a/t/t02-user-groups b/t/t02-user-groups
new file mode 100644
index 0000000..29d98ce
--- /dev/null
+++ b/t/t02-user-groups
@@ -0,0 +1,64 @@
+# vim: syn=sh:
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 0
+
+name "base output with no groups"
+echo "
+ repo aa
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t02-user-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 0
+
+name "output with eqvt user groups"
+echo "
+ @g1 = u1
+ @g2 = u2 u3
+ @g3 = u4 u5 u6
+
+ repo aa
+ RW+ = @g1
+ RW = @g2
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t02-user-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+
+name "base output with no groups but GL_BIG_CONFIG on"
+echo "
+ repo aa
+ RW+ = u1
+ RW = u2 u3
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t02-user-groups.1
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+
+name "user groups with GL_BIG_CONFIG on"
+echo "
+ @g1 = u1
+ @g2 = u2 u3
+ @g3 = u4 u5 u6
+
+ repo aa
+ RW+ = @g1
+ RW = @g2
+" | ugc
+
+catconf
+expect_filesame $TESTDIR/out/t02-user-groups.2
diff --git a/t/t03-branch-permissions b/t/t03-branch-permissions
new file mode 100644
index 0000000..c1766c1
--- /dev/null
+++ b/t/t03-branch-permissions
@@ -0,0 +1,20 @@
+# vim: syn=sh:
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 0
+
+echo ===== testing with GL_BIG_CONFIG set to 0 =====
+
+cd $TESTDIR
+. ./t03a-branch-permissions
+cd $TESTDIR
+
+# ----------
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG 1
+
+echo ===== testing with GL_BIG_CONFIG set to 1 =====
+
+cd $TESTDIR
+. ./t03a-branch-permissions
+cd $TESTDIR
diff --git a/t/t03a-branch-permissions b/t/t03a-branch-permissions
new file mode 100644
index 0000000..2d7c6e4
--- /dev/null
+++ b/t/t03a-branch-permissions
@@ -0,0 +1,177 @@
+# vim: syn=sh:
+# ----------
+name "setup"
+echo "
+ @g1 = u1
+ @g2 = u2
+ @g3 = u3
+ repo aa
+ RW+ = @g1
+ RW = @g2
+ RW+ master = @g3
+ RW master = u4
+ - master = u5
+ RW+ dev = u5
+ RW = u5
+" | ugc
+
+# reasonably complex setup; we'll do everything from one repo though
+cd ~/td
+runlocal git clone u1:aa
+cd ~/td/aa
+mdc; mdc; mdc
+mdc; mdc; mdc
+mdc; mdc; mdc
+name "INTERNAL"
+runlocal git push origin HEAD
+expect "To u1:aa"
+expect "\* \[new branch\] HEAD -> master"
+
+git branch dev
+git branch foo
+
+# u1 tries to rewind master; succeeds
+name "u1 rewind master succeed"
+runlocal git reset --hard HEAD^
+mdc
+runlocal git push origin +master
+expect "+ .* master -> master (forced update)"
+
+# u2 tries to rewind master; fails
+name "u2 rewind master fail"
+runlocal git reset --hard HEAD^
+mdc
+runlocal git push u2:aa +master
+expect "remote: + refs/heads/master aa u2 DENIED by fallthru"
+
+# u3 tries to rewind master; succeeds
+name "u3 rewind master succeed"
+runlocal git reset --hard HEAD^
+mdc
+runlocal git push u3:aa +master
+expect "+ .* master -> master (forced update)"
+
+# u4 tries to push master; succeeds
+name "u4 push master succeed"
+mdc
+runlocal git push u4:aa master
+expect "master -> master"
+
+# u4 then tries to rewind master; fails
+name "u4 rewind master fail"
+runlocal git reset --hard HEAD^
+runlocal git push u4:aa +master
+expect "remote: + refs/heads/master aa u4 DENIED by fallthru"
+
+# u3 and u4 try to push the other 2 branches; fail
+name "u3 and u4 / dev foo -- all 4 fail"
+runlocal git push u3:aa dev
+expect "remote: W refs/heads/dev aa u3 DENIED by fallthru"
+runlocal git push u4:aa dev
+expect "remote: W refs/heads/dev aa u4 DENIED by fallthru"
+runlocal git push u3:aa foo
+expect "remote: W refs/heads/foo aa u3 DENIED by fallthru"
+runlocal git push u4:aa foo
+expect "remote: W refs/heads/foo aa u4 DENIED by fallthru"
+
+# clean up for next set
+runlocal git push -f origin master dev foo
+
+# u5 tries to push master; fails
+name "u5 push master fail"
+mdc
+runlocal git push u5:aa master
+expect "remote: W refs/heads/master u5 DENIED by refs/heads/master"
+
+# u5 tries to rewind dev; succeeds
+name "u5 rewind dev succeed"
+runlocal git push u5:aa +dev^:dev
+expect "+ .* dev^ -> dev (forced update)"
+
+# u5 tries to rewind foo; fails
+name "u5 rewind foo fail"
+runlocal git push u5:aa +foo^:foo
+expect "remote: + refs/heads/foo aa u5 DENIED by fallthru"
+
+# u5 tries to push foo; succeeds
+name "INTERNAL"
+runlocal git checkout foo
+expect "Switched to branch 'foo'"
+
+name "u5 push foo succeed"
+mdc
+runlocal git push u5:aa foo
+expect "foo -> foo"
+
+# u1 tries to delete branch dev
+name "u1 delete branch dev succeed"
+runlocal git push origin :dev
+expect " - \[deleted\] *dev"
+
+# quietly push it back again
+name "INTERNAL"
+runlocal git push origin dev
+expect " * \[new branch\] dev -> dev"
+
+# u1 tries to delete dev on a new setup
+name "INTERNAL"
+echo "
+ repo aa
+ RWD dev = u4
+" | ugc
+expect "master -> master"
+
+# u1 tries to delete branch dev; fails
+name "u1 delete branch dev fail"
+runlocal git push origin :dev
+expect "remote: D refs/heads/dev aa u1 DENIED by fallthru"
+
+# u4 tries to delete branch dev; succeeds
+name "u4 delete branch dev succeed"
+runlocal git push u4:aa :dev
+expect " - \[deleted\] *dev"
+
+# ----------
+
+name "INTERNAL"
+echo "
+repo r1
+ RW refs/heads/v[0-9] = u1
+ RW refs/heads = tester
+" | ugc
+notexpect ABORT
+cd ~/td
+runlocal git clone gitolite:r1
+expect "Initialized empty Git repository in /home/tester/td/r1/.git/"
+cd ~/td/r1
+mdc; mdc; mdc
+mdc; mdc; mdc
+runlocal git push origin HEAD
+expect "\* \[new branch\] HEAD -> master"
+runlocal git branch v1
+runlocal git push origin v1
+name "no deny rules -- push v1 succeeds"
+expect "\* \[new branch\] v1 -> v1"
+
+name "INTERNAL"
+echo "
+repo r2
+ RW refs/heads/v[0-9] = u1
+ - refs/heads/v[0-9] = tester
+ RW refs/heads = tester
+" | ugc
+notexpect ABORT
+cd ~/td
+runlocal git clone gitolite:r2
+expect "Initialized empty Git repository in /home/tester/td/r2/.git/"
+cd ~/td/r2
+mdc; mdc; mdc
+mdc; mdc; mdc
+runlocal git push origin HEAD
+expect "\* \[new branch\] HEAD -> master"
+runlocal git branch v1
+runlocal git push origin v1
+name "deny rules -- push v1 fails"
+expect "remote: W refs/heads/v1 tester DENIED by refs/heads/v\[0-9\]"
+
+name "INTERNAL"
diff --git a/t/t04-wild b/t/t04-wild
new file mode 100644
index 0000000..e7b5464
--- /dev/null
+++ b/t/t04-wild
@@ -0,0 +1,18 @@
+# vim: syn=sh:
+# ----------
+
+for i in 0 1
+do
+ for j in students all
+ do
+ hl t04-wild with GL_WILDREPOS $i, C=@$j
+
+ cd $TESTDIR
+ $TESTDIR/rollback
+ editrc GL_BIG_CONFIG $i
+ editrc GL_WILDREPOS 1
+ . ./t04a-wild-$j
+
+ cd $TESTDIR
+ done
+done
diff --git a/t/t04a-wild-all b/t/t04a-wild-all
new file mode 100644
index 0000000..ae9010f
--- /dev/null
+++ b/t/t04a-wild-all
@@ -0,0 +1,144 @@
+# vim: syn=sh:
+# ----------
+
+name "INTERNAL"
+echo "
+ @prof = u1
+ @TAs = u2 u3
+ @students = u4 u5 u6
+
+ repo foo/CREATOR/a[0-9][0-9]
+ C = @all
+ RW+ = CREATOR
+ RW = WRITERS @TAs
+ R = READERS @prof
+
+" | ugc
+notexpect ABORT
+
+# reasonably complex setup; we'll do everything from one repo though
+cd ~/td
+
+name "u1 create success"
+runlocal git clone u1:foo/u1/a01
+expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u1/a01.git/"
+# expect "R access for foo/u1/a01 DENIED to u1"
+
+name "u2 create success"
+runlocal git clone u2:foo/u2/a02
+expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u2/a02.git/"
+# expect "R access for foo/u2/a02 DENIED to u2"
+
+name "u4 tries to create u2 repo"
+runlocal git clone u4:foo/u2/a12
+expect "R access for foo/u2/a12 DENIED to u4"
+
+name "line anchored regexes"
+runlocal git clone u4:foo/u4/a1234
+expect "R access for foo/u4/a1234 DENIED to u4"
+
+name "u4 tries to create his own repo"
+runlocal git clone u4:foo/u4/a12
+expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u4/a12.git/"
+expect "warning: You appear to have cloned an empty repository."
+
+name "u4 push success"
+cd ~/td/a12
+mdc;mdc;mdc;mdc
+runlocal git push origin master
+expect "To u4:foo/u4/a12"
+expect "\* \[new branch\] master -> master"
+
+name "u1 clone success"
+cd ~/td
+runlocal git clone u1:foo/u4/a12 u1a12
+expect "Initialized empty Git repository in /home/tester/td/u1a12/.git/"
+
+name "u1 push fail"
+cd ~/td/u1a12
+mdc; mdc
+runlocal git push
+expect "W access for foo/u4/a12 DENIED to u1"
+
+name "u2 clone success"
+cd ~/td
+runlocal git clone u2:foo/u4/a12 u2a12
+expect "Initialized empty Git repository in /home/tester/td/u2a12/.git/"
+
+name "u2 push success"
+cd ~/td/u2a12
+mdc; mdc
+runlocal git push
+expect "To u2:foo/u4/a12"
+expect "master -> master"
+
+name "u2 rewind fail"
+runlocal git push -f origin master^:master
+expect "remote: + refs/heads/master foo/u4/a12 u2 DENIED by fallthru"
+expect "remote: error: hook declined to update refs/heads/master"
+expect "To u2:foo/u4/a12"
+expect "\[remote rejected\] master^ -> master (hook declined)"
+expect "error: failed to push some refs to 'u2:foo/u4/a12'"
+
+name INTERNAL
+# u4 pull to sync up
+cd ~/td/a12
+runlocal git pull
+expect "Fast-forward"
+expect "From u4:foo/u4/a12"
+expect "master -> origin/master"
+
+name "u4 rewind success"
+runlocal git reset --hard HEAD^
+runlocal git push -f
+expect "To u4:foo/u4/a12"
+expect "+ .* master -> master (forced update)"
+
+name "u5 clone fail"
+cd ~/td
+runlocal git clone u5:foo/u4/a12 u5a12
+expect "R access for foo/u4/a12 DENIED to u5"
+
+name "setperm"
+echo "
+R u5
+RW u6
+" | runlocal ssh u4 setperms foo/u4/a12
+expect_filesame $TESTDIR/out/t04-wild1.1
+
+name "getperms"
+runlocal ssh u4 getperms foo/u4/a12
+expect_filesame $TESTDIR/out/t04-wild1.2
+
+name "u5 clone success"
+cd ~/td
+runlocal git clone u5:foo/u4/a12 u5a12
+expect "Initialized empty Git repository in /home/tester/td/u5a12/.git/"
+
+name "u5 push fail"
+cd ~/td/u5a12
+mdc; mdc
+runlocal git push
+expect "W access for foo/u4/a12 DENIED to u5"
+
+name "u6 clone success"
+cd ~/td
+runlocal git clone u6:foo/u4/a12 u6a12
+expect "Initialized empty Git repository in /home/tester/td/u6a12/.git/"
+
+name "u6 push success"
+cd ~/td/u6a12
+mdc; mdc
+runlocal git push u6:foo/u4/a12
+expect "To u6:foo/u4/a12"
+expect "master -> master"
+
+name "u6 rewind fail"
+runlocal git push -f u6:foo/u4/a12 master^:master
+expect "remote: + refs/heads/master foo/u4/a12 u6 DENIED by fallthru"
+expect "remote: error: hook declined to update refs/heads/master"
+expect "To u6:foo/u4/a12"
+expect "\[remote rejected\] master^ -> master (hook declined)"
+expect "error: failed to push some refs to 'u6:foo/u4/a12'"
+
+name "INTERNAL"
diff --git a/t/t04a-wild-students b/t/t04a-wild-students
new file mode 100644
index 0000000..a37a6ab
--- /dev/null
+++ b/t/t04a-wild-students
@@ -0,0 +1,142 @@
+# vim: syn=sh:
+# ----------
+
+name "INTERNAL"
+echo "
+ @prof = u1
+ @TAs = u2 u3
+ @students = u4 u5 u6
+
+ repo foo/CREATOR/a[0-9][0-9]
+ C = @students
+ RW+ = CREATOR
+ RW = WRITERS @TAs
+ R = READERS @prof
+
+" | ugc
+notexpect ABORT
+
+# reasonably complex setup; we'll do everything from one repo though
+cd ~/td
+
+name "u1 create fail"
+runlocal git clone u1:foo/u1/a01
+expect "R access for foo/u1/a01 DENIED to u1"
+
+name "u2 create fail"
+runlocal git clone u2:foo/u2/a02
+expect "R access for foo/u2/a02 DENIED to u2"
+
+name "u4 tries to create u2 repo"
+runlocal git clone u4:foo/u2/a12
+expect "R access for foo/u2/a12 DENIED to u4"
+
+name "line anchored regexes"
+runlocal git clone u4:foo/u4/a1234
+expect "R access for foo/u4/a1234 DENIED to u4"
+
+name "u4 tries to create his own repo"
+runlocal git clone u4:foo/u4/a12
+expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u4/a12.git/"
+expect "warning: You appear to have cloned an empty repository."
+
+name "u4 push success"
+cd ~/td/a12
+mdc;mdc;mdc;mdc
+runlocal git push origin master
+expect "To u4:foo/u4/a12"
+expect "\* \[new branch\] master -> master"
+
+name "u1 clone success"
+cd ~/td
+runlocal git clone u1:foo/u4/a12 u1a12
+expect "Initialized empty Git repository in /home/tester/td/u1a12/.git/"
+
+name "u1 push fail"
+cd ~/td/u1a12
+mdc; mdc
+runlocal git push
+expect "W access for foo/u4/a12 DENIED to u1"
+
+name "u2 clone success"
+cd ~/td
+runlocal git clone u2:foo/u4/a12 u2a12
+expect "Initialized empty Git repository in /home/tester/td/u2a12/.git/"
+
+name "u2 push success"
+cd ~/td/u2a12
+mdc; mdc
+runlocal git push
+expect "To u2:foo/u4/a12"
+expect "master -> master"
+
+name "u2 rewind fail"
+runlocal git push -f origin master^:master
+expect "remote: + refs/heads/master foo/u4/a12 u2 DENIED by fallthru"
+expect "remote: error: hook declined to update refs/heads/master"
+expect "To u2:foo/u4/a12"
+expect "\[remote rejected\] master^ -> master (hook declined)"
+expect "error: failed to push some refs to 'u2:foo/u4/a12'"
+
+name INTERNAL
+# u4 pull to sync up
+cd ~/td/a12
+runlocal git pull
+expect "Fast-forward"
+expect "From u4:foo/u4/a12"
+expect "master -> origin/master"
+
+name "u4 rewind success"
+runlocal git reset --hard HEAD^
+runlocal git push -f
+expect "To u4:foo/u4/a12"
+expect "+ .* master -> master (forced update)"
+
+name "u5 clone fail"
+cd ~/td
+runlocal git clone u5:foo/u4/a12 u5a12
+expect "R access for foo/u4/a12 DENIED to u5"
+
+name "setperm"
+echo "
+R u5
+RW u6
+" | runlocal ssh u4 setperms foo/u4/a12
+expect_filesame $TESTDIR/out/t04-wild1.1
+
+name "getperms"
+runlocal ssh u4 getperms foo/u4/a12
+expect_filesame $TESTDIR/out/t04-wild1.2
+
+name "u5 clone success"
+cd ~/td
+runlocal git clone u5:foo/u4/a12 u5a12
+expect "Initialized empty Git repository in /home/tester/td/u5a12/.git/"
+
+name "u5 push fail"
+cd ~/td/u5a12
+mdc; mdc
+runlocal git push
+expect "W access for foo/u4/a12 DENIED to u5"
+
+name "u6 clone success"
+cd ~/td
+runlocal git clone u6:foo/u4/a12 u6a12
+expect "Initialized empty Git repository in /home/tester/td/u6a12/.git/"
+
+name "u6 push success"
+cd ~/td/u6a12
+mdc; mdc
+runlocal git push u6:foo/u4/a12
+expect "To u6:foo/u4/a12"
+expect "master -> master"
+
+name "u6 rewind fail"
+runlocal git push -f u6:foo/u4/a12 master^:master
+expect "remote: + refs/heads/master foo/u4/a12 u6 DENIED by fallthru"
+expect "remote: error: hook declined to update refs/heads/master"
+expect "To u6:foo/u4/a12"
+expect "\[remote rejected\] master^ -> master (hook declined)"
+expect "error: failed to push some refs to 'u6:foo/u4/a12'"
+
+name "INTERNAL"
diff --git a/t/t05-delegation b/t/t05-delegation
new file mode 100644
index 0000000..ad2c36b
--- /dev/null
+++ b/t/t05-delegation
@@ -0,0 +1,11 @@
+# vim: syn=sh:
+# ----------
+
+for i in 0 1
+do
+ hl t05-delegation with GL_BIG_CONFIG $i
+
+ . ./t05a-delegation $i
+
+ cd $TESTDIR
+done
diff --git a/t/t05a-delegation b/t/t05a-delegation
new file mode 100644
index 0000000..40243d0
--- /dev/null
+++ b/t/t05a-delegation
@@ -0,0 +1,95 @@
+# vim: syn=sh:
+cd $TESTDIR
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG $1
+# ----------
+
+name "INTERNAL"
+echo "
+ # group your projects/repos however you want
+ @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/ = tester
+ RW NAME/conf/fragments/u1r = u1
+ RW NAME/conf/fragments/u2r = u2
+ RW NAME/conf/fragments/u3r = u3
+" | ugc
+notexpect ABORT
+
+name "tester push frag u1r"
+cd ~/gitolite-admin
+mkdir -p conf/fragments
+echo "
+ repo @u1r
+ RW+ = tester
+" > conf/fragments/u1r.conf
+ugc < /dev/null
+expect "create mode 100644 conf/fragments/u1r.conf"
+expect "remote: Initialized empty Git repository in /home/gitolite-test/repositories/r1a.git/"
+
+expect "To gitolite:gitolite-admin"
+expect "master -> master"
+
+name "u1 push frag u1r"
+cd ~/gitolite-admin
+echo "
+ repo @u1r
+ RW+ = u5
+" > conf/fragments/u1r.conf
+ugc u1 < /dev/null
+expect "To u1:gitolite-admin"
+expect "master -> master"
+
+name "u2 push main conf fail"
+cd ~/gitolite-admin
+echo "
+ repo @u1r
+ RW+ = u6
+" | ugc u2
+expect "W NAME/conf/gitolite.conf gitolite-admin u2 DENIED by fallthru"
+expect "To u2:gitolite-admin"
+expect "\[remote rejected\] master -> master (hook declined)"
+git reset --hard origin/master &>/dev/null
+
+name "u2 push frag u1r fail"
+cd ~/gitolite-admin
+echo "
+ repo @u1r
+ RW+ = u6
+" > conf/fragments/u1r.conf
+ugc u2 < /dev/null
+expect "remote: W NAME/conf/fragments/u1r.conf gitolite-admin u2 DENIED by fallthru"
+expect "To u2:gitolite-admin"
+expect "\[remote rejected\] master -> master (hook declined)"
+git reset --hard origin/master &>/dev/null
+
+name "u3 set perms for r2a fail"
+cd ~/gitolite-admin
+echo "
+ repo r2a
+ RW+ = u6
+" > conf/fragments/u3r.conf
+ugc u3 < /dev/null
+expect "u3r.conf attempting to set access for r2a"
+git reset --hard origin/master &>/dev/null
+
+name "u3 add r2b to u3r fail"
+cd ~/gitolite-admin
+echo "
+ @u3r = r2b
+ repo @u3r
+ RW+ = u6
+" > conf/fragments/u3r.conf
+ugc u3 < /dev/null
+[[ $1 == 0 ]] && expect "u3r.conf attempting to set access for r2b"
+[[ $1 == 1 ]] && expect "defining groups is not allowed inside fragments"
+[[ $1 == 1 ]] && notexpect "u3r.conf attempting to set access for r2b"
+[[ $1 == 0 ]] && notexpect "defining groups is not allowed inside fragments"
+git reset --hard origin/master &>/dev/null
+
+name INTERNAL
diff --git a/t/t09-oldtests b/t/t09-oldtests
new file mode 100644
index 0000000..388950b
--- /dev/null
+++ b/t/t09-oldtests
@@ -0,0 +1,11 @@
+# vim: syn=sh:
+# ----------
+
+for i in 0 1
+do
+ hl t09-oldtests with GL_BIG_CONFIG $i
+
+ . ./t09a-oldtests $i
+
+ cd $TESTDIR
+done
diff --git a/t/t09a-oldtests b/t/t09a-oldtests
new file mode 100644
index 0000000..1ab3b6e
--- /dev/null
+++ b/t/t09a-oldtests
@@ -0,0 +1,50 @@
+# vim: syn=sh:
+cd $TESTDIR
+$TESTDIR/rollback
+editrc GL_BIG_CONFIG $1
+
+name "bad repo name"
+echo "
+ repo abc*def
+ RW = tester
+" | ugc
+expect "To gitolite:gitolite-admin"
+expect "master -> master"
+[[ $1 == 0 ]] && expect "ABORTING"
+[[ $1 == 0 ]] && expect "bad reponame abc\*def or you forgot to set .GL_WILDREPOS"
+
+name "bad user name"
+echo "
+ repo abc
+ RW = sitaram*tester
+
+ repo abcdef
+ RW = sitaram
+" | ugc -r
+expect "ABORTING"
+expect "bad username sitaram\*tester"
+
+name "NAME deny"
+echo "
+ repo abc
+ RW = u1
+ - NAME/i = u1
+ RW NAME/j = u1
+ RW NAME/u = u1
+" | ugc -r
+notexpect "failed to push"
+
+cd ~/td
+runlocal git clone u1:abc
+expect "Initialized empty Git repository in /home/tester/td/abc/.git/"
+cd ~/td/abc
+mdc jfile; runlocal git push origin master
+expect "To u1:abc"
+expect "\[new branch\] master -> master"
+mdc ufile; runlocal git push origin master
+expect "To u1:abc"
+expect "master -> master"
+mdc ifile; runlocal git push origin master
+expect "remote: W NAME/ifile u1 DENIED by NAME/i"
+
+name INTERNAL
diff --git a/t/t50-sequence-test b/t/t50-sequence-test
new file mode 100644
index 0000000..6014e50
--- /dev/null
+++ b/t/t50-sequence-test
@@ -0,0 +1,93 @@
+# vim: syn=sh:
+for bc in 0 1
+do
+ cd $TESTDIR
+ $TESTDIR/rollback
+ editrc GL_WILDREPOS 1
+ editrc GL_BIG_CONFIG $bc
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @staff = u1 u2 u3
+ repo foo/CREATOR/.+
+ C = u1
+ RW+ = CREATOR
+ RW = WRITERS
+ - = @staff
+ " | ugc
+
+ cd ~/td
+ runlocal git clone u1:foo/u1/bar
+ expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u1/bar.git/"
+
+ cd bar
+ mdc u1file1
+ runlocal git push origin master
+ expect "To u1:foo/u1/bar"
+ expect "\[new branch\] master -> master"
+ echo RW u2 | runlocal ssh u1 setperms foo/u1/bar
+ runlocal ssh u1 getperms foo/u1/bar
+ expect "RW u2"
+ name "expand"
+ runlocal ssh u2 expand
+ expect "R W.(u1).foo/u1/bar"
+
+ name "push"
+ cd ~/td
+ runlocal git clone u2:foo/u1/bar u2bar
+ expect "Initialized empty Git repository in /home/tester/td/u2bar/.git/"
+ cd u2bar
+ mdc u2file1
+ runlocal git push
+ expect "master -> master"
+ notexpect "DENIED"
+ notexpect "failed to push"
+
+ name "INTERNAL"
+
+ cd $TESTDIR
+ $TESTDIR/rollback
+ editrc GL_WILDREPOS 1
+ editrc GL_BIG_CONFIG $bc
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @staff = u1 u2 u3
+ repo foo/CREATOR/.+
+ C = u1
+ RW+ = CREATOR
+ - = @staff
+ RW = WRITERS
+ " | ugc -r
+
+ cd ~/td
+ runlocal git clone u1:foo/u1/bar
+ expect "Initialized empty Git repository in /home/gitolite-test/repositories/foo/u1/bar.git/"
+
+ cd bar
+ mdc u1file1
+ runlocal git push origin master
+ expect "To u1:foo/u1/bar"
+ expect "\[new branch\] master -> master"
+ echo RW u2 | runlocal ssh u1 setperms foo/u1/bar
+ runlocal ssh u1 getperms foo/u1/bar
+ expect "RW u2"
+ name "expand"
+ runlocal ssh u2 expand
+ expect "R W.(u1).foo/u1/bar"
+
+ name "push"
+ cd ~/td
+ runlocal git clone u2:foo/u1/bar u2bar
+ expect "Initialized empty Git repository in /home/tester/td/u2bar/.git/"
+ cd u2bar
+ mdc u2file1
+ runlocal git push
+ expect "remote: W refs/heads/master u2 DENIED by refs/.\*"
+
+ name INTERNAL
+done
diff --git a/t/t51-personal-branches b/t/t51-personal-branches
new file mode 100644
index 0000000..dbbeb55
--- /dev/null
+++ b/t/t51-personal-branches
@@ -0,0 +1,88 @@
+# vim: syn=sh:
+for wr in 0 1
+do
+ for bc in 0 1
+ do
+ cd $TESTDIR
+ $TESTDIR/rollback
+ editrc GL_WILDREPOS $wr
+ editrc GL_BIG_CONFIG $bc
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @staff = u1 u2 u3 u4 u5 u6
+ repo foo
+ RW+ = u1 u2
+ RW+ personal/USER/ = u3 u4
+ RW temp = u5 u6
+ " | ugc
+
+ cd ~/td
+ runlocal git clone u1:foo
+
+ cd foo
+ mdc; mdc; mdc; mdc; mdc
+
+ name "u1 and u2 can push"
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+ runlocal git push u2:foo master:personal/u1/foo
+ expect_push_ok "master -> personal/u1/foo"
+ runlocal git push origin master:personal/u2/foo
+ expect_push_ok "master -> personal/u2/foo"
+ runlocal git push u1:foo master:personal/u3/foo
+ expect_push_ok "master -> personal/u3/foo"
+
+ mdc;
+ name "u3 cant push u1/u4 personal branches"
+ runlocal git push u3:foo master:personal/u1/foo
+ expect "remote: W refs/heads/personal/u1/foo foo u3 DENIED by fallthru"
+ expect "\[remote rejected\] master -> personal/u1/foo (hook declined)"
+ expect "failed to push"
+ runlocal git push u3:foo master:personal/u4/foo
+ expect "remote: W refs/heads/personal/u4/foo foo u3 DENIED by fallthru"
+ expect "\[remote rejected\] master -> personal/u4/foo (hook declined)"
+ expect "failed to push"
+ name "u4 can push u4 personal branch"
+ runlocal git push u4:foo master:personal/u4/foo
+ expect_push_ok "master -> personal/u4/foo"
+ name "u5 push temp"
+ runlocal git push u5:foo master:temp
+ expect_push_ok "master -> temp"
+
+ runlocal git reset --hard HEAD^^^
+
+ name "u1 and u2 can rewind"
+ runlocal git push -f origin master
+ expect "master -> master (forced update)"
+ runlocal git push -f u2:foo master:personal/u1/foo
+ expect "master -> personal/u1/foo (forced update)"
+ runlocal git push -f origin master:personal/u2/foo
+ expect "master -> personal/u2/foo (forced update)"
+ runlocal git push -f u1:foo master:personal/u3/foo
+ expect "master -> personal/u3/foo (forced update)"
+
+ name "u3 cant rewind u1/u4 personal branches"
+ runlocal git reset --hard HEAD^; mdc
+ runlocal git push -f u3:foo master:personal/u1/foo
+ expect "remote: + refs/heads/personal/u1/foo foo u3 DENIED by fallthru"
+ expect "\[remote rejected\] master -> personal/u1/foo (hook declined)"
+ expect "failed to push"
+ runlocal git push -f u3:foo master:personal/u4/foo
+ expect "remote: + refs/heads/personal/u4/foo foo u3 DENIED by fallthru"
+ expect "\[remote rejected\] master -> personal/u4/foo (hook declined)"
+ expect "failed to push"
+ name "u4 can rewind u4 personal branch"
+ runlocal git push -f u4:foo master:personal/u4/foo
+ expect "master -> personal/u4/foo (forced update)"
+ name "u5 cant rewind temp"
+ runlocal git push -f u5:foo master:temp
+ expect "remote: + refs/heads/temp foo u5 DENIED by fallthru"
+ expect "\[remote rejected\] master -> temp (hook declined)"
+ expect "failed to push"
+
+ name INTERNAL
+ done
+done
diff --git a/t/t52-deny-create-ref b/t/t52-deny-create-ref
new file mode 100644
index 0000000..ff04f8c
--- /dev/null
+++ b/t/t52-deny-create-ref
@@ -0,0 +1,51 @@
+# vim: syn=sh:
+for wr in 0 1
+do
+ for bc in 0 1
+ do
+ cd $TESTDIR
+ $TESTDIR/rollback
+ editrc GL_WILDREPOS $wr
+ editrc GL_BIG_CONFIG $bc
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @leads = u1 u2
+ @devs = u1 u2 u3 u4
+ repo foo
+ RW+ = @leads
+ - CREATE_REF = @devs
+ RW+ = @devs
+ " | ugc
+
+ cd ~/td
+ runlocal git clone u1:foo
+
+ cd foo
+ mdc; mdc; mdc; mdc; mdc
+
+ name "u1 can push master"
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "u2 can create newbr1"
+ runlocal git push u2:foo master:newbr1
+ expect_push_ok "master -> newbr1"
+
+ name "u3 can push newbr1"
+ mdc; mdc; mdc; mdc; mdc
+ runlocal git push u3:foo master:newbr1
+ expect_push_ok "master -> newbr1"
+
+ name "u4 canNOT push newbr3"
+ mdc; mdc; mdc; mdc; mdc
+ runlocal git push u3:foo master:newbr3
+ expect "remote: W refs/heads/CREATE_REF u3 DENIED by refs/heads/CREATE_REF"
+ expect "\[remote rejected\] master -> newbr3 (hook declined)"
+ expect "failed to push"
+
+ name INTERNAL
+ done
+done
diff --git a/t/test-driver.sh b/t/test-driver.sh
new file mode 100755
index 0000000..fbfbce0
--- /dev/null
+++ b/t/test-driver.sh
@@ -0,0 +1,160 @@
+#!/bin/bash
+
+# see some sample tests for how to use these functions; there is not
+# documentation
+
+testnum=0
+subtests=0
+
+# remote local command
+runlocal() { "$@" > ~/1 2> ~/2; }
+# remote run command
+runremote() { ssh gitolite-test@localhost "$@" > ~/1 2> ~/2; }
+# remote list repositories
+listrepos() { ssh gitolite-test@localhost find repositories -type d -name "*.git" | sort > ~/1 2> ~/2; }
+# remote cat compiled pm
+catconf() { ssh gitolite-test@localhost cat .gitolite/conf/gitolite.conf-compiled.pm > ~/1 2> ~/2; }
+# remote cat ~/.gitolite.rc
+catrc() { ssh gitolite-test@localhost cat .gitolite.rc > ~/1 2> ~/2; }
+# tail gitolite logfile
+taillog() { ssh gitolite-test@localhost tail $1 .gitolite/logs/gitolite-????-??.log > ~/1 2> ~/2; }
+hl() { # highlight function
+ normal=`tput sgr0`
+ red=`tput sgr0; tput setaf 1; tput bold`
+ if [[ -n $1 ]]
+ then
+ echo $red"$@"$normal
+ else
+ echo $red
+ cat
+ echo $normal
+ fi
+}
+pause() { echo pausing\; hit enter or ctrl-c...; read; }
+
+capture() { cf=$1; shift; "$@" >& $TESTDIR/$cf; }
+
+editrc() {
+ scp gitolite-test@localhost:.gitolite.rc ~/junk >/dev/null
+ perl -pi -e "print STDERR if /$1/ and s/=.*/= $2;/" ~/junk
+ scp ~/junk gitolite-test@localhost:.gitolite.rc >/dev/null
+}
+
+ugc ()
+{
+ (
+ cd ~/gitolite-admin;
+ [[ $1 == -r ]] && {
+ shift
+ cat $TESTDIR/basic.conf > conf/gitolite.conf
+ }
+ cat >> conf/gitolite.conf
+ git add conf keydir;
+ git commit --allow-empty -m "$TESTNAME";
+ git push ${1:-gitolite}:gitolite-admin master
+ git fetch origin >/dev/null 2>&1
+ ) >~/1 2>~/2
+}
+
+mdc()
+{
+ (
+ echo $RANDOM > ${1:-$RANDOM}
+ git add .
+ git commit -m "$TESTNAME"
+ ) >~/1 2>~/2
+}
+
+# flush result of last test when next one comes along
+testdone() {
+ [[ $subtests > 1 ]] && TESTNAME="($subtests) $TESTNAME"
+ echo -e $testnum\\t$TESTNAME
+}
+
+# set test name/desc
+name() {
+ if [[ -n $TESTNAME ]]
+ then
+ if [[ $TESTNAME != INTERNAL ]]
+ then
+ (( testnum++ ))
+ testdone
+ fi
+ subtests=0
+ fi
+ export TESTNAME="$*"
+}
+
+notok() {
+ echo ----------
+ head -999 ~/1 ~/2 | sed -e 's/^/ /'
+}
+
+expect_filesame() {
+ if cmp ~/1 "$1"
+ then
+ (( subtests++ ))
+ else
+ echo files ~/1 and "$1" are different
+ echo '*** ABORTING ***'
+ exit 1
+ fi
+}
+
+die() {
+ echo '***** AAAAARRRGGH! *****'
+ echo ${BASH_LINENO[1]} ${BASH_SOURCE[2]}
+ read
+ cd $TESTDIR
+ vim +${BASH_LINENO[1]} '+r !head ~/1 ~/2 /dev/null' ${BASH_SOURCE[2]}
+ exit 1
+}
+
+expect() {
+ if cat ~/1 ~/2 | grep "$1" >/dev/null
+ then
+ (( subtests++ ))
+ else
+ notok
+ echo ----------
+ echo " expecting: $1"
+ echo ----------
+ die $TESTNAME
+ exit 1
+ fi
+}
+
+notexpect() {
+ if cat ~/1 ~/2 | grep "$1" >/dev/null
+ then
+ notok
+ echo "NOT expecting: $1"
+ echo ----------
+ die $TESTNAME
+ exit 1
+ else
+ (( subtests++ ))
+ fi
+}
+
+print_summary() {
+ echo -e "==========\n$testnum tests succeeded"
+}
+
+expect_push_ok() {
+ expect "$1"
+ notexpect "DENIED"
+ notexpect "failed to push"
+}
+
+export TESTDIR=$PWD
+arg1=$1; shift
+for testfile in ${arg1:-t??-}*
+do
+ hl $testfile
+ . $testfile "$@"
+ cd $TESTDIR
+ hl $testfile DONE
+done
+
+print_summary
diff --git a/t/update-gitolite b/t/update-gitolite
new file mode 100755
index 0000000..55af1ef
--- /dev/null
+++ b/t/update-gitolite
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export TESTDIR=$PWD
+
+rm -rf ~/junk.ga
+mv ~/gitolite-admin ~/junk.ga
+
+# install it
+src/gl-easy-install -q gitolite-test localhost tester
+
+# make the rollback.tar files on both sides
+cd
+tar cf rollback.tar gitolite-admin
+ssh gitolite-test@localhost tar cf rollback.tar .ssh .gitolite .gitolite.rc repositories gitolite-install