diff --git a/contrib/adc/hub.mkd b/contrib/adc/hub.mkd
new file mode 100644
index 0000000..a45e8af
--- /dev/null
+++ b/contrib/adc/hub.mkd
@@ -0,0 +1,182 @@
+# the 'hub' ADC
+
+In this document:
+
+ * a home grown 'hub' for git repos
+ * general syntax
+ * Bob's commands
+ * Alice's "just looking" commands
+ * Alice's "action" commands
+ * what next?
+ * note to the admin: configuration variables
+
+
+
+### a home grown 'hub' for git repos
+
+This ADC (admin-defined command) helps collaboration among repos. The name is
+in honor of github, which is the primary host for gitolite itself.
+
+[Note that github is a web-based service, and does a lot more, like comments,
+code reviews, etc., none of which are possible here. We're only talking about
+some basic stuff to make the most common operations easier. In particular,
+this system is not a replacement for normal project communications like
+email!]
+
+**Conventions used**: all through the following description, we will assume
+that **Alice** has a repo **parent**, **Bob** has a fork of parent called
+**child**, and he is asking Alice to pull a branch he made called **b1** from
+child to parent.
+
+In plain git (without using github or similar), the pull request process
+starts with an email from Bob to Alice, followed by Alice running a `git fetch
+ b1` (optionally preceded by a `git remote add` for convenience of
+long term use). Until this happens she can't see much detail about the
+commits to be pulled.
+
+**What this ADC does** is (a) collect all the pull requests she needs to look
+at in one place, and (b) allow her to examine the changes with any combination
+of `git log` and `git diff` options *without having to fetch the content
+first*, and (c) act as a trusted intermediary to allow Alice to fetch *just
+one branch* from Bob even if she does not have any access to Bob's repository!
+
+In a situation where there may be lots of requests, being able to take a quick
+look at them (and possibly reject some), without having to pull down anything
+at all, could be very useful.
+
+Once past that level, she could get the changes down to her workstation using
+`git fetch` as normal. With this ADC, however, she has another alternative:
+get them over to `parent` (her repo) on the same gitolite server, then later
+do a normal `git fetch [origin]` to get it to her workstation. This has the
+added advantage that other people, who may be watching her repo but not Bob's,
+now get to see what Bob sent her and send comments etc.
+
+
+
+### general syntax
+
+The general syntax is
+
+ ssh git@server hub
+
+
+
+#### Bob's commands
+
+The following commands do not cause a fetch, and should be quite fast:
+
+ * Bob sends a pull request for branch b1 to repo parent. Notice he does not
+ mention Alice by name. This command expects a message to be piped/typed
+ in via STDIN [this message is meant to be transient and is not stored long
+ term; use email for more "permanent" communications].
+
+ echo "hi Alice, please pull" | request-pull child b1 [parent]
+
+ If `child` was created by a recent version of the 'fork' ADC (or the KDE
+ 'clone' ADC), which records the name of the parent repo on a fork, and it
+ is *that* repo to which Bob wishes to send the pull request, the third
+ argument is optional.
+
+ * Bob lists the status (fetched/rejected/pending) of pull requests he has
+ made from his repo child to repo parent. (Note we don't say "accepted" but
+ "fetched"; see later for why):
+
+ request-status child [parent]
+
+ The second argument is optional the same way as the 3rd argument in the
+ previous command.
+
+
+
+#### Alice's "just looking" commands
+
+ * Alice lists requests waiting for her to check and possibly pull into
+ parent. For each waiting pull request, she will see a serial number, the
+ originating repo name (child, in our example), the requestor (Bob, here),
+ and the branch/tag-name (b1) being pulled:
+
+ list-requests parent
+
+ * Alice views request # 1 waiting to be pulled into parent. Shows the same
+ details as above for that request, followed by the message that Bob typed
+ in when he ran `request-pull`:
+
+ view-request parent 1
+
+ * Alice views the log of the branch she is being asked to pull. Note that
+ this does NOT involve a fetch, so it will be pretty fast. The log starts
+ from b1, and stops at a SHA that represents any of the branches in parent.
+ Alice can use any git-log options she wants to; for instance `--graph`,
+ `--decorate`, `--boundary`, etc., could be quite useful. However, she
+ can't use any GUI; it has to be 'git log':
+
+ view-log parent 1
+
+ Notice that the repo name Alice supplies is still her own, although the
+ log comes from the child repo that Bob wants Alice to pull from. It's
+ best if you think of this as "view the commits from pull request #1 in
+ 'parent'".
+
+ * Alice views the diff between arbitrary commits on child:
+
+ view-diff parent 1
+
+ Again, she mentions *her* reponame but the diff's come from `child`. Also
+ note that, due to restrictions on what characters are allowed in arguments
+ to ADCs, you probably can't do things like `pu^` or `master~3`, and have
+ to use SHAs instead.
+
+
+
+#### Alice's "action" commands
+
+ * Alice doesn't like what she sees and decides to reject it. This command
+ expects some text on STDIN as the rejection message:
+
+ echo "hi Bob, your patch needs work; see email" | reject parent 1
+
+The following commands will actually fetch from child into parent, and may take
+time if the changes are large. However all this is on the server so it does
+not involve network traffic:
+
+ * Alice likes what she sees so far and wants to fetch the branch Bob is
+ asking her to pull. Note that we are intentionally not using the word
+ "accept", because this command does not (and cannot, since it is running
+ on a bare repo on the server) do a pull. What it does is it fetches into
+ a branch whose name will be `requests/child/b1`.
+
+ Note that when multiple requests from the same repo (child) for the same
+ branch (b1) happen, each "fetch" overwrites the branch. This allows Bob
+ to continually refine the branch he is requesting for a pull based on
+ (presumably emailed) comments from Alice. In a way, this is a "remote
+ tracking branch", just like `refs/remotes/origin/b1`.
+
+ fetch parent 1
+
+
+
+### what next?
+
+At this point, you're done with the `hub` ADC. However, all this is on the
+bare `parent.git` on the server, and nothing has hit Alice's workstation yet!
+Alice will still have to run a fetch or a pull on her workstation if she wants
+to check the code in detail, use a tool like gitk, and especially to
+compile/test it. *Then* she decides whether to accept or reject the request,
+which will have to be communicated via email, of course; see the second para
+of this document ;-)
+
+Finally, note that Alice does not actually need to use the `fetch` subcommand.
+She can do the traditional thing and fetch Bob's repo/branch directly to her
+*workstation*.
+
+
+
+### note to the admin: configuration variables
+
+There are 2 configuration variables. `BASE_FETCH_URL` should be set to a
+simple "read" URL (so it doesn't even have to be ssh) that almost anyone using
+this server can use. It's only used in producing the `git fetch` command
+mentioned just above.
+
+`GL_FORKED_FROM` is set to `gl-forked-from` by default, but if your initials
+are `JM` you can set it to `kde-cloned-from` to save time and trouble ;-)
diff --git a/t/t67-hub b/t/t67-hub
new file mode 100644
index 0000000..75d6ffd
--- /dev/null
+++ b/t/t67-hub
@@ -0,0 +1,156 @@
+# vim: syn=sh:
+for bc in 0 1
+do
+ cd $TESTDIR
+ $TESTDIR/rollback || die "rollback failed"
+ editrc GL_BIG_CONFIG $bc
+ editrc GL_WILDREPOS 1
+
+ rm -rf /tmp/glt-adc
+ mkdir /tmp/glt-adc || die "mkdir /tmp/glt-adc failed"
+ cp ../contrib/adc/* /tmp/glt-adc
+ echo "\$GL_ADC_PATH = '/tmp/glt-adc';" | addrc
+ runremote rm -f .gitolite.down
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @alice = u1
+ @bob = u2
+ @parent = r1
+ @child = r2
+
+ repo @parent
+ RW+ = @alice
+ RW = tester
+ R = @bob
+
+ @children = child/CREATOR/..*
+ repo @children
+ C = @all
+ RW+ = CREATOR
+ " | ugc
+ expect_push_ok "master -> master"
+
+ name "setup: parent gets some branches"
+ cd ~/td
+ runlocal git clone u1:r1
+ cd r1
+ mdc base1; mdc base2; mdc base3
+ runlocal git branch p3
+ runlocal git branch p2
+ runlocal git branch p1
+ mdc p1a; mdc p1b; mdc p1c
+ runlocal git checkout p2
+ mdc p2a; mdc p2b; mdc p2c
+ runlocal git checkout p3
+ mdc p3a; mdc p3b; mdc p3c
+ runlocal git push origin --all
+ expect To u1:r1
+ expect "\* \[new branch\] master -> master"
+ expect "\* \[new branch\] p1 -> p1"
+ expect "\* \[new branch\] p2 -> p2"
+ expect "\* \[new branch\] p3 -> p3"
+
+ name "setup: child is cloned and adds b1 and b2"
+ cd ~/td
+ runlocal ssh u2 fork r1 child/u2/myr1
+ runremote ls -al repositories/child/u2/myr1.git/gl-forked-from
+ expect "gitolite-test gitolite-test 3 .* repositories/child/u2/myr1.git/gl-forked-from"
+ runremote cat repositories/child/u2/myr1.git/gl-forked-from
+ expect r1
+ runlocal git clone u2:child/u2/myr1
+ cd myr1
+ runlocal git checkout -b b1 origin/p1
+ mdc c1
+ runlocal git checkout -b b2 origin/p2
+ mdc d1; mdc d2
+ runlocal git checkout -b b3 origin/p3
+ mdc e1; mdc e2; mdc e3
+ runlocal git push origin b1 b2 b3
+ expect To u2:child/u2/myr1
+ expect "\* \[new branch\] b1 -> b1"
+ expect "\* \[new branch\] b2 -> b2"
+ expect "\* \[new branch\] b3 -> b3"
+
+ name "bob sends in a few pull requests"
+ printf "hello\nthere" | runlocal ssh u2 hub request-pull child/u2/myr1 b1
+ notexpect .
+ printf "hi\nthere" | runlocal ssh u2 hub request-pull child/u2/myr1 b2
+ notexpect .
+ printf "hello\nagain" | runlocal ssh u2 hub request-pull child/u2/myr1 b3
+ notexpect .
+
+ name "bob checks his pending requests"
+ runlocal ssh u2 hub request-status child/u2/myr1
+ expect "1 child/u2/myr1 (u2) b1 pending"
+ expect "2 child/u2/myr1 (u2) b2 pending"
+ expect "3 child/u2/myr1 (u2) b3 pending"
+
+ name "alice checks her pull requests"
+ runlocal ssh u1 hub list-requests r1
+ expect "1 child/u2/myr1 (u2) b1 pending"
+ expect "2 child/u2/myr1 (u2) b2 pending"
+ expect "3 child/u2/myr1 (u2) b3 pending"
+
+ name "alice views request 1"
+ runlocal ssh u1 hub view-request r1 1
+ expect "1 child/u2/myr1 (u2) b1 pending"
+ expect ^hello
+ expect ^there
+
+ name "alice views log and diffs"
+ runlocal ssh u1 hub view-log r1 1
+ expect "setup: child is cloned and adds b1 and b2"
+ expect commit
+ wc < ~/1 > ~/2; > ~/1
+ expect "5 22 178"
+
+ runlocal ssh u1 hub view-log r1 3 --oneline
+ expect "setup: child is cloned and adds b1 and b2"
+ notexpect commit
+ diffargs=`tac ~/1 | cut -f1 -d' ' | sed -n -e1p -e3p`
+ wc < ~/1 > ~/2; > ~/1
+ expect "3 30 150"
+
+ runlocal ssh u1 hub view-log r1 2 b1
+ expect "fatal: ambiguous argument 'b1': unknown revision or path not in the working tree."
+
+ runlocal ssh u1 hub view-diff r1 3 $diffargs
+ expect "diff.*e2"
+ expect "diff.*e3"
+ expect "new file mode"
+
+ name "alice tries to view a SHA she shouldnt"
+ echo > ~/1
+ echo > ~/2
+ runlocal ssh u1 hub view-diff r1 2 $diffargs
+ notexpect "diff.*e2"
+ notexpect "diff.*e3"
+ notexpect "new file mode"
+ expect "invalid SHA:"
+
+ name "alice rejects 2, accepts 3"
+ echo captain was sober today | runlocal ssh u1 hub reject r1 2
+ notexpect .
+ echo | runlocal ssh u1 hub fetch r1 3
+ expect "user u2 asked you to"
+ expect "git fetch git://gl.example.com/child/u2/myr1 b3"
+ expect "From /home/gitolite-test/repositories/child/u2/myr1"
+ expect "\* \[new branch\] b3 -> requests/child/b3"
+
+ name "bob checks his pending requests"
+ runlocal ssh u2 hub request-status child/u2/myr1
+ expect "1 child/u2/myr1 (u2) b1 pending"
+ expect "2 child/u2/myr1 (u2) b2 rejected"
+ expect "3 child/u2/myr1 (u2) b3 fetched"
+
+ name "alice checks her pull requests"
+ runlocal ssh u1 hub list-requests r1
+ expect "1 child/u2/myr1 (u2) b1 pending"
+ expect "2 child/u2/myr1 (u2) b2 rejected"
+ expect "3 child/u2/myr1 (u2) b3 fetched"
+
+ name "INTERNAL"
+done