#!/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
