Revised 2019-05-13. Code updated. Check git-hub for latest version.

Over and over again, I find myself committing focused tests, e.g. test files that has fdescribe instead of describe, fcontext instead of context and fit instead of it. The effect is that any testruns based on my committed code will not run the whole testsuite. Typically this is noticed by the buildserver (e.g. by watching the count of executed testcases and treating large drops in count as an error) so the risk that actually ends up in the product shouldn’t be very high.

Loosing time and breaking flow

But still, when this happens I break my flow and loose valuable time and it should not have to be this way. For some time, I have thought about fixing myself a git-hook, that will alert me that I am trying to commit a focused test. Today, was the day I turned thought into action. So, I took the time needed to learn the basics about git-hooks and created my first pre-commit git-hook.

The pre-commit source

Here’s the code I wrote in the first round:

#!/bin/sh

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=$(git hash-object -t tree /dev/null)
fi

for FILE in `git diff-index --name-status $against -- | cut -c3-` ; do
    # Check if the file contains 'fdescribe("'
    if grep -q -e '^ *fdescribe(\"' -e '^ *fcontext(\"' -e '^ *fit(\"' "$FILE"
    then
        echo $FILE  'Commit blocked to prevent commiting test focus. (do not commmit fdescribe, fcontext, or fit)'
        exit 1
    fi
done
exit<pre class="lang:bash" title="First iteration">
</pre>

It seemed to work fine in the first few simple tests but soon I realized it didn’t do what I really wanted, namely to check the changes that was actually staged for the commit, and not the files as they were on the filesystem. So, after some trial and error, the hook code turned out like:

#!/bin/sh
echo "Pre-commit hook to block focused tests"

if git rev-parse --verify HEAD >/dev/null 2>&1
then
	against=HEAD
else
	# Initial commit: diff against an empty tree object
	against=$(git hash-object -t tree /dev/null)
fi

SAVEIFS=$IFS
IFS=$'\n'
for FILE in `git diff-index --diff-filter=AM --name-only $against`
do
    # Check if the file contains patterns
    if git grep --cached -q -e '^ *fdescribe(\"' -e '^ *fcontext(\"' -e '^ *fit(\"' -- $FILE
    then
        echo $FILE  ': Commit blocked to prevent commiting test focus. (do not commit fdescribe, fcontext, or fit)'
        exit 1
    fi
done
IFS=$SAVEIFS
echo "Commit ok, no focused tests found in staged changes"
exit

So, first I found a git way of filtering the changes to only additions or modifications, ie. git diff-index –diff-filter=AM and then restricting the output to –name-only which took away the pain of some parsing. Then using git grep –cached I could search the staged data for the patterns and ignore changes to the same file that is not staged. I also added some printouts to let me know that the hook is active and when the commit passes the checks.

Setup git to use your hook for repo

Git allows you to setup hooks in multiple ways; local to the repo, system global, custom absolute path to the hooks, and custom path to the hooks relative to the repo. Here I will show you the most basic way (takes least config changes). To use this hook in this repo, copy the script-file into your repo’s ./git/hooks-folder and name it pre-commit. Like this, assuming your repo is in your home folder and named the-repo:

cp pre-commit-block-focused-test ~/the-repo/.git/hooks/pre-commit
chmod +x ~/the-repo/.git/hooks/pre-commit-block-focused-test

Github repo for comments and issues

You can find the latest version of the script and a README.md that shows some other ways to config git to use your hooks in my git-hooks repo on github.