In general, you can't reliably prevent anyone from making changes in their local repositories. Unless you trust your developers to never make any mistakes (in which case you wouldn't need this), you have to run your checks in hooks on your server.
That means you can't count on preventing people from branching, and I'm not sure why you'd really want to anyway. What you can do is keep people from pushing work that hasn't been somehow "approved" into the server's deployment branch (which I'm going to call deploy
).
Now the first question is how to express that approval.
If you want a workflow where an authorized person has to review work before it's deployed, then I'd have the reviewer do these steps, if the work is currently living on a branch named review
:
- Ensure
deploy
is fully merged into review
, so that later deploy
can be fast-forwarded to review
.
- Do any appropriate verification on
review
.
- Create and push a signed tag (
git tag -s
), using an authorized key, for the contents of review
.
- Fast-forward
deploy
to review
(git checkout deploy
, git merge --ff-only review
) and push it.
If instead you want anyone to be able to deploy, but you want them to have to think about it first, you could do the same thing but relax the signature requirements. Or you could bless a particular branch (tested
, say) and require that all work be merged and pushed to tested
before being pushed to deploy
.
How can the server verify that this approval has been given for the protected deploy
branch? The basic plan is to use the update
hook to accept or reject ref-updates for refs/heads/deploy
according to the acceptance criteria you've decided on.
If you want a tag that satisfies particular criteria, like being signed by an approved key, then you can find all tags that point to the new object proposed for deployment using git for-each-ref refs/tags
. Remember to accept the update if any tag meets the criteria, or else sooner or later somebody's going to push a bad tag that blocks deployment. Verifying that the right key was used is left as an exercise for the reader, but gpg --no-default-keyring --keyring=approved.gpg
may help.
If you'll take any commit as long as somebody's tagged it, you can use git describe --exact-match <object>
. If you want to limit to tags with a particular name pattern, add --match <pattern>
. If you'll accept unannotated tags, add --tags
.
If you want work merged to a blessed branch before being merged to deployment, you can check that the output of git rev-parse tested
equals the proposed new object.
In all cases, you probably want to double-check that the push is a fast-forwarding push. You can check that git merge-base <old> <new>
equals <old>
.