I use Git for source-control management on almost all of my projects. Git allows you to write hooks to perform arbitrary actions when you create commits, merge branches, fetch updates, and so on. Today I want to show you how I use the pre-push hook to run test suites automatically.
I am going to use my Chance library for the example use-case. The project uses Busted for its test suite, and Tup for its build system. Running
tup within the project directory will, among other things, execute the test suite; worth noting for this article is that
tup also runs Luacheck on the Chance source code, which will catch any errors in the Lua code along with warning me about a variety of stylistic issues.
Here’s what we want to do:
Check to see if we are pushing updates to the
masterbranch, i.e. are we running
git push origin master.
If so, we want to run
tupin order to run the test suite and Luacheck on the source code.
If either of those fail, then we want to prevent the push.
The ultimate result is that
git push origin master will fail if there is anything wrong with the code.
All Git hooks go into the
.git/hooks/ directory of a project, as executable scripts with specific names. In this case we need to write the
.git/hooks/pre-push hook. That script must perform the actions described above.
Here is the entire hook script:
#!/bin/sh remote="$1" url="$2" while read local_ref local_sha remote_ref remote_sha do if [ "$local_ref" == "refs/heads/master" ] then cd "$(git rev-parse --show-toplevel)" && tup upd > /dev/null exit $? fi exit 0 done
The script receives two arguments: the name of the remote destination and its URL. If wanted we could use these to only run the test suite when pushing to
origin, which would be the value of
$remote whenever we run
git push origin master. But here it’s not necessary.
The script receives more information from standard input, which will have a single line representing everything we’re pushing. By using
read we can bind this information to shell variables. Since we want to run the test suite when pushing the master branch the variable we’re most interested in is
$local_ref. As you can see from the script, its value will be
refs/heads/master when pushing that branch. So that’s what we search for to determine whether to run the test suite.
As previously mention, running
tup upd will invoke the build system, which executes the test suite and more. But we want to run it from the top-level directory of the project. We could do something like
cd ../../, but a more elegant solution is to use
git rev-parse --show-toplevel. This command is useful for obtaining the top-level directory of a Git project no matter where we are in the directory tree. For example, on my computer the Chance library is in the
/home/eric/Projects/chance.lua directory, which is exactly the value that
git rev-parse --show-toplevel returns regardless of where we run that command within the project. This leads us to this code:
cd "$(git rev-parse --show-toplevel)" && tup upd > /dev/null exit $?
It changes to the top-level directory, runs
tup upd, and exits with the status code from Tup, i.e.
$?. If Tup runs successfully, meaning the test suite passed, then
$? will be equal to zero; if our
pre-push script exits with zero then Git will proceed with the push. But if anything goes wrong during the build process then
pre-push will exit with a non-zero value, preventing
git push from doing anything.
Note that if
$local_ref is not equal to
refs/heads/master then we immediately exit with a value of zero, allowing us to push any branch except master without requiring a successful build.
Hopefully the example and explanation is simple and straight-forward. In general what you want to do with
Decide if you want to run your tests based on the remote to which you are pushing.
Decide which local branches, if any, should act as explicit triggers for the tests you want to run.
And then run any appropriate tests, making sure to exit with the value zero if everything is successful, and taking advantage of
git rev-parse --show-toplevelto make it easier to navigate the project’s directory structure to run any such tests.
Here’s another example from a different project, which is more simple than the example in this article since it runs tests regardless of the remote and local branch being pushed.