Git Hooks for Fun and Profit

When certain commands are run, Git searches the .git/hooks directory for suitable hook scripts which are then executed if found. You’ll find a small set of example scripts there (you can activate them by renaming them to remove the .sample prefix and setting their executable bit), and a complete list of hooks can be found in the githooks(5) man page.

This article suggests a handful of hooks which can streamline development and help improve your efficiency.

Lint Checks

There’s really no excuse, yet I’ll admit even I’ve done this once or twice before: commit syntactically broken code. But what if a lint check could be performed automatically as part of the commit process to safeguard from this? And if you run a lint check manually before each commit, automating it can protect against the occasional moment of forgetfulness.

The following shell code can be saved as .git/hooks/pre-commit (or appended if pre-commit hook code already exists) to trigger an automatic check whenever a commit is made:

#!/bin/bash

git diff --cached --name-status --diff-filter=ACMR | while read STATUS FILE; do
if [[ "$FILE" =~ ^.+(php|inc)$ ]]; then
    php -l "$FILE" 1> /dev/null
    if [ $? -ne 0 ]; then
        echo "Aborting commit due to files with syntax errors" >&2
        exit 1
    fi
fi
done

git diff reports on what’s changed between commits, and the options above return only the files that have been added (A), copied (C), modified (M), or renamed (R) in the staged commit. The files with an extension of .php or .inc are targeted for a lint check, and a failed check will exit the script with a non-zero return code and thus abort the commit itself. Your code may be buggy, but at least you know it’s got all of its semicolons.

Spell-Check Commit Messages

Developers are supposed to be meticulous and have an eye for detail (at least that’s what we all put on our CV when looking for a job), so a commit message with bad spelling can be embarrassing.

Commit messages give the reader a broad idea of what changes were made in a commit and thus have the potential to be read by a large audience, from other developers to compliance auditors. Even if you code only for yourself, writing professional messages is a good habit to develop, and automating a “second set of eyes” can help reduce speling erors in your comit messeges.

The following code can be saved as .git/hooks/post-commit (or appended); it calls Aspell and outputs a list of suspect words. If there are errors, you can immediately fix the commit message by running git commit --amend.

#!/bin/bash

ASPELL=$(which aspell)
if [ $? -ne 0 ]; then
    echo "Aspell not installed - unable to check spelling" >&2
    exit
fi

AWK=$(which awk)
if [ $? -ne 0 ]; then
    echo "Awk not installed - unable to filter spelling errors" >&2
    WORDS=$($ASPELL list < "$1")
else
    WORDS=$($ASPELL list < "$1" | $AWK '!_[$0]++')
fi

if [ -n "$WORDS" ]; then
    echo "Possible spelling errors found in commit message: " $WORDS >&2
fi

You can also compile a supplemental dictionary using identifiers extracted from your project’s source code (perhaps triggered with a post-checkout hook) and pass it to Aspell with --extra-dicts to reduce the number of false positives. Parsing code and managing dictionary files is beyond the scope of this article though, so I’ll leave it as an exercise for you to explore later.

Checking Standards

I don’t know of any scientific research that definitively proves formatting standards reduce cognitive friction when reading code. Most of the standards irritatingly target only low-hanging fruit anyway: capitalize something this way, place your braces here and not there, etc. Standards that enforce good architectural design and address specific interoperability concerns have more merit in my opinion.

But if you’re working with people who think 81 characters on a line is the eighth deadly sin, it might help smooth political friction by making sure your code conforms to an adopted formatting standard. The following code can be used as a post-commit hook (.git/hooks/post-commit) to automatically check for formatting violations.

#!/bin/bash

DIR=$(git rev-parse --show-toplevel)
EMAIL=$(git config user.email)
TMPFILE=$(mktemp)

git diff --cached --name-status --diff-filter=ACMR | while read STATUS FILE; do
    if [[ "$FILE" =~ ^.+(php|inc)$ ]]; then
        phpcs --standard=zend "$FILE" >> $TMPFILE
    fi
done

if [ -s $TMPFILE ]; then
    mailx -s "PHPCS: Coding Standards Violations" $EMAIL < $TMPFILE
fi
rm $TMPFILE

The script uses PHP CodeSniffer to scan each PHP file in the commit for violations and then sends an email listing the violations that were found. If you’re using one of those buzzword productivity approaches like Getting Things Done to manage your email, you can set up an email filter that routes the received emails directly to your TODO folder to make sure you fix the violations in a timely manner.

Automatically Run Composer

So far we’ve seen hooks that focus on commits and the development process, but there are also some useful hooks that can be executed at other points in a Git workflow, even deployment. Suppose you have a remote repository set up on a production server, and pushing to it is your approach to deployment.

Further suppose that you use Composer to manage your application’s dependencies and you only keep your own code in your repository. This means you still have additional work to do after a push, either copying the dependencies over manually or ssh-ing into the server to run composer.phar update.

To make your life easier, putting this in the remote repository’s .git/hooks/post-receive file for a post-receive hook will run Composer automatically.

#!/bin/bash

DIR=$(git rev-parse --show-toplevel)

if [ -e "$DIR/composer.json" ]; then
    if [ -d "$DIR/vendor" ]; then
        composer.phar install
    else
        composer.phar update
    fi
fi

Conclusion

In this article I shared a handful of hooks that can hopefully streamline how you develop your applications and make you more efficient. Feel free to share whether you are taking advantage of this powerful feature or not, and maybe even some of your own hooks in the comments below.

Here are a few additional resources for hints, tips, and inspiration:

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • DarkSide

    Definitely +100 for this article. Very interesting and useful.

  • Steve

    Should’ve run this article through a spell checker…..

    • Anonymous

      The errors were intentional.

  • Anonymous

    great…thanks!

  • andy

    It;s good for me .

  • dV

    Regarding running composer on post-receive hook. This is only in case if your GIT repository located on the same server where your production is, which is a bit weird I have to say. Besides it will be triggered on every push. What if you not the only developer on the project and you have to push your code to the remote repository which is accessible by all the team members? Even if you create a dev brunch (what would be the right solution) your production will be updated on every push to this branch according to the hook code you provided.

    So the better solution would be NOT to depend on the remote GIT repository location. Add this code to the post-merge hook and each time when you want to update your production, log in on the server via SSH and pull the code manually. Which also eliminates you from the unexpected updates.