A pre-receive or update hook is called after the new objects (commits, annotated tag objects, trees, and blobs) have been loaded into the repository, but before the references (branch names, tag names, etc) have been changed.
This is why the pre-receive hook gets a list of (old
, new
, ref
) triples: the existing repo has the objects and the existing ref (if any) points to the object (usually commit, sometimes tag) whose SHA-1 is old
. Git is proposing to change it to point to the object whose SHA-1 is new
(or create it or delete it if exactly one of those two is the all-zeros "null SHA-1").
the only way I've found to inspect file contents is diffing those references
That's one way, but you have the entire suite of git commands to extract everything. You can, for instance, make a new, empty directory somewhere (mkdir path
) and run git --work-tree=path checkout sha1
to get a complete tree into that path. (If the tree is large this could take some time. Of course any tests you run on it will take even more time.)
You must decide what, precisely, you want to check. This is as complicated as you want it to be, but for branch names (a ref
of the form refs/heads/name
, where name
is any branch name, i.e., may contain more slashes), consider that the ref update may do one or more of the following (some combinations are obviously impossible):
- Add a new branch name
- Add new commits to an existing branch
- Remove commits from an existing branch
- Remove an existing branch name
For instance, if I have a clone of a bare repo origin
and I do this:
git fetch origin # get up-to-date with origin
git checkout -b branch origin/branch # make tracking branch for origin/branch
git reset --hard HEAD~3 # back up 3 commits
echo more stuff >> existing_file # modify something
git commit -a -m 'add new text' # commit the change
git revert --no-edit HEAD # add another commit that undoes change
git push -f origin branch # and push
then the update will remove three commits and add two more. The tree you would get if you checked out the new
SHA-1 (again, using the old
, new
, ref
triple notation) would look exactly like the version I have asked to push. If there is some test that trees must pass, presumably the version that was HEAD~3
did in fact pass those tests, so this version would too. However, the thing I committed that added one line to existing_file
might not pass the tests, and you might not like the fact that I removed three commits.
So, again, it's up to you to decide what you want to check, and write code to achieve that. Check whether a force-push is removing commits; allow or forbid this. Check whether a new branch name is being created; allow or forbid. Check whether a branch name is being deleted; allow or forbid. If commits are being added, check every intermediate commit's tree, or check only the final tree; allow or forbid. Do commits being added involve merges? Are tags being added, removed, or changed? And so on.
Just for fun, a while ago I wrote a pre-receive shell script (in POSIX-style shell) that does many of these (it does not check the contents of any commit). I did some very light testing and it seems to work. It could be used as a starting-point for more thorough checking.
If you're doing serious checking, though, you might want to look into using gitolite.