How do we verify commit messages for a push?

Using the update hook

You know about hooks – please, read the documentation about them! The hook you probably want is update, which is run once per ref. (The pre-receive hook is run once for the entire push) There are tons and tons of questions and answers about these hooks already on SO; depending on what you want to do, you can probably find guidance about how to write the hook if you need it.

To emphasize that this really is possible, a quote from the docs:

This hook can be used to prevent forced update on certain refs by making sure that the object name is a commit object that is a descendant of the commit object named by the old object name. That is, to enforce a “fast-forward only” policy.

It could also be used to log the old..new status.

And the specifics:

The hook executes once for each ref to be updated, and takes three parameters:

  • the name of the ref being updated,
  • the old object name stored in the ref,
  • and the new objectname to be stored in the ref.

So, for example, if you want to make sure that none of the commit subjects are longer than 80 characters, a very rudimentary implementation would be:

#!/bin/bash
long_subject=$(git log --pretty=%s $2..$3 | egrep -m 1 '.{81}')
if [ -n "$long_subject" ]; then
    echo "error: commit subject over 80 characters:"
    echo "    $long_subject"
    exit 1
fi

Of course, that’s a toy example; in the general case, you’d use a log output containing the full commit message, split it up per-commit, and call your verification code on each individual commit message.

Why you want the update hook

This has been discussed/clarified in the comments; here’s a summary.

The update hook runs once per ref. A ref is a pointer to an object; in this case, we’re talking about branches and tags, and generally just branches (people don’t push tags often, since they’re usually just for marking versions).

Now, if a user is pushing updates to two branches, master and experimental:

o - o - o (origin/master) - o - X - o - o (master)
 \
  o - o (origin/experimental) - o - o (experimental)

Suppose that X is the “bad” commit, i.e. the one which would fail the commit-msg hook. Clearly we don’t want to accept the push to master. So, the update hook rejects that. But there’s nothing wrong with the commits on experimental! The update hook accepts that one. Therefore, origin/master stays unchanged, but origin/experimental gets updated:

o - o - o (origin/master) - o - X - o - o (master)
 \
  o - o - o - o (origin/experimental, experimental)

The pre-receive hook runs only once, just before beginning to update refs (before the first time the update hook is run). If you used it, you’d have to cause the whole push to fail, thus saying that because there was a bad commit message on master, you somehow no longer trust that the commits on experimental are good even though their messages are fine!

Leave a Comment