How to Use Git Hooks
I recently got bit by a pitfall in my workflow process: we have a robust unit testing suite in place to validate that every contribution to our project is sane, but as a part of that testing suite we also do code style checking and if the code style checks fail, no matter how insignificant, the build at large fails since our CI pipelines are dependent on a successful run of the test suite upon git push.
The thing is, I pushed to master before realizing that a code style rule was failing. Literally something that could be avoided by running a test build before pushing to the master branch of our project.
The solution was simple: adhere to the styling rules and push the new changes, ensuring that the project builds. Simple. All is well.
But I'm human and fallible and my intentions are not enough, and as easily as this happened this time, it can happen any other time.
We should have guard rails in place, then, to avoid falling into this pitfall. As much as I can now, I can commit it some other time if I forget. Or anyone else in my team could, for that matter.
Tenet: if you want people to constantly do things the right way you have to make it **easy** to do them.
This involves putting mechanisms in place of intentions, and what better mechanism than automation?
I still love git though even though it can give you too much power it seems, and remember: Great Power => Great Responsibility. What to do about it, then? How do you curb your git enthusiasm?
Enter Git Hooks.
Whenever you have a repository under Git, git will create a set of sample scripts under {your project's directory}/.git/hooks:
applypatch-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-push.sample pre-receive.sample update.sample commit-msg.sample post-update.sample pre-commit.sample pre-rebase.sample prepare-commit-msg.sample
Each of these is nothing but a tiny bash script that will execute on different stages of the git workflow, such as:
Before push
After pull
Before commit
It is possible, likewise, to setup a variety of other hooks by yourself since the system is very flexible. And here's the kicker, if a hook runs but exits with a unix exit status code other than 0, git will suspend the activity. Genius, right?
So to fit my scenario I just had to hook my test/build system (such as gradle or maven) as a pre-push hook, which meant editing the pre-push.sample file. By the way, this is how it looks on my repository:
#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # <local ref><local sha1><remote ref><remote sha1> # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" z40=0000000000000000000000000000000000000000 while read local_ref local_sha remote_ref remote_sha do if [ "$local_sha" = $z40 ] then # Handle delete : else if [ "$remote_sha" = $z40 ] then # New branch, examine all commits range="$local_sha" else # Update to existing branch, examine new commits range="$remote_sha..$local_sha" fi # Check for WIP commit commit=`git rev-list -n 1 --grep '^WIP' "$range"` if [ -n "$commit" ] then echo >&2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0
Do not fret, though. You can notice that there's several scenarios already coded for you there, such as:
"this is a new branch that I'm pushing" or
"this is an update to an existing branch", or
"the last commit in the log has a message starting with the word WIP, so I should consider the state of the branch at the moment as not pushable"
...in case you want to go further in depth in terms of granularity in regards to what kind of operation you want to run depending on your push scenario. I don't need any of those, I just want to run my build system, so I will edit that file and rename pre-push.sample to pre-push and make it executable, chmod +x pre-push:
#!/bin/sh gradle build #or whatever build system you're running
And now whenever I try to git push but there's something wrong with the build, the system will prevent me from pushing:
alfredo@work $ git push ...... Total time: 21 seconds BUILD FAILED error: failed to push some refs to 'ssh://my.repo:222222'
You're probably now coming up with a lot of ideas about how to exploit these. Since you're running a simple shell script the sky's the limit, you could lint, pre-process or call external services on your code if you so wished.










