Git hooks

Carlos Espino Timón
The Startup
Published in
8 min readJul 8, 2019

The other day I was talking with a coworker and he was telling me about a project that he had worked in where he had to dealt with git hooks. He explained me that his code had to pass all the tests before pushing anything, and to be honest, it amazed me!

What are git hooks

I started to dig about it and the first thing I did was to look for what git hooks really are:

Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. Git hooks are a built-in feature — no need to download anything. Git hooks are run locally.

https://githooks.com/

Now that I know this concept, it’s pretty clear that in my coworker’s project there is a git hook with a script that runs the tests before the push, if they pass, the push is completed. Sweet, isn’t it?

With git hooks we can ensure that any code to be pushed won’t break what is already working. Imagine that we are in a project where when we push our code to develop, it triggers a pipeline that runs the tests, checks the code quality, builds the project and deploys it. If we have been pushing bad code to our branch, the moment we merge it with develop and push it, it will break the pipeline as it will not pass the tests. This is an awkward moment for us, as it tells our teammates that we haven’t been running the tests to check whether our code had broken something that was already working. If we’d had configured the git hooks, it would have saved us this uncomfortable moment.

Once I understood this useful use case of git hooks it rapidly came to my mind another handy hook that could be configured. What if we can restrict the days on which we can upload code to some branches?

As in the previous example, imagine that we have a pipeline that in the end, deploys our app to production. Presumably, the code we pushed is perfect, as it has passed all the tests, however, sometimes new problems not checked by the tests arise, and our app starts to fail and what’s more… it’s Saturday and there is no one in the office.

With git hooks we can control the “allowed days” to push to the specific branch that triggers the pipeline, for example, we could allow it from Monday to Thursday because we will always have someone in the office the day after the app is deployed to solve any problem.

With these two cases, I think we have enough to play with, so… let’s get our hands dirty! I have created a repository to work with the hooks, the code can be checked here.

How to configure the hooks

First of all, we need to know how to “install” a hook.

The hooks are all stored in the hooks subdirectory of the Git directory. In most projects, that’s .git/hooks. When you initialize a new repository with git init, Git populates the hooks directory with a bunch of example scripts, many of which are useful by themselves; but they also document the input values of each script. All the examples are written as shell scripts, with some Perl thrown in, but any properly named executable scripts will work fine — you can write them in Ruby or Python or what have you. If you want to use the bundled hook scripts, you’ll have to rename them; their file names all end with .sample.

https://git-scm.com/book/uz/v2/Customizing-Git-Git-Hooks

Every git repository has a few default hooks, they are placed in the .git/hooks directory. Let’s check it out in an empty repository that I have just init:

As we can see, when we init a repository it comes with some sample hooks. All of them come with the .sample extension, if we want to activate a hook we just have to remove the .sample extension. Easy, isn’t it?

There are lots of hooks that could be really useful but in this post I will just talk about two, pre-commit and pre-push.

Git pre-commit

Let’s start with the first example I talked about, the hook that controlled that our code passed the tests before pushing it, however, I will configure my hook to do it before any commit. With this approach I see two advantages, in one hand, it will ensure that all our code that has been tracked by git is good and passes the tests, in the other, it saves us having to be fixing everything in the last minute before we push, forcing us to commit chunks of “good code”.

After removing the .sample extension of the pre-commit.sample hook, and replace it with the code we want to be executed, this is the result.

I will not get into detail about my development environment as is irrelevant for the scope of this post. I am just going to say that I am becoming a container freak (I’m starting to use Docker for everything) and to facilitate the execution of some commands that have to be run inside the container I usually create a Makefile. In this case, I the tests must be run from within the local_environment directory with the make backend-tests command.

That’s it, this is the hook. We run the test and the result is used as output of the script, if the tests passed we can commit.

Let’s see it in action. I have changed a test to not pass, and I will try to commit my code.

As we can see, the commit run the tests and as I did not passed them, the commit was not made.

Let’s fix the test to see how it works.

Perfect there was a successful execution of the tests and the commit was made :)

Git pre-push

With this hook I am going to control two things: first, that I will never push something to the master branch, and second that the pushes to develop must be done from from Monday to Thursday. Here is the code.

Now let’s see it in action, I am going to try to push something to master.

As we can see, the hook blocked the push to master. Now to try the restriction by days I will modify the hook to control master instead develop (as I do not have develop branch in this project) and remove the first restriction that forbids to push to master.

This is the new hook.

Here is the result:

Today is Friday and the hook blocked the push :)

This is really cool, all the team could configure the same hooks and it would increase the quality of the code that is tracked by git and also prevent problems in production. However, sometimes we will be in a situation where we must to commit or push even if our code has a bug or if is Friday and this hook could be our worst friend. Do not worry, there is a way to avoid the hooks!

Skip hooks

You probably have noticed that in the first line of both hooks there is an echo line that explains how to avoid the hook.

Skip pre-push hooks with --no-verify (not recommended)

If we do our commit or push with the flag — no-verify, the hooks will be ignored.

Let’s see it in action. I’ll try to push something to master.

The first push was blocked by the hook, in the second I used the flag --no-verify and I could do the push.

How to share it with the team

Maintaining hooks for a team of developers can be a little tricky because the .git/hooks directory isn’t cloned with the rest of your project, nor is it under version control. A simple solution to both of these problems is to store your hooks in the actual project directory (above the .git directory). This lets you edit them like any other version-controlled file. To install the hook, you can either create a symlink to it in .git/hooks, or you can simply copy and paste it into the .git/hooks directory whenever the hook is updated.

https://www.atlassian.com/git/tutorials/git-hooks

As we can see, the hooks’ directory isn’t tracked by git, with the solution given by Atlassian, we could have a version control of the hooks. However, regarding the solution to install the hook, I prefer to configure the hookspath, an option that is available since Git 2.9.

In my repository I have created a directory .githooks/ where I have put both hooks, so now are tracked, then I configured the hookspath by running:

git config core.hooksPath .githooks

For now on, every developer that starts working in this project, it will have to run this command to configure the hookspath and use the hooks tracked by the repository.

Conclusion

Git hooks are a powerful tool that, configured in the right way, can help us to improve our code quality, protect branches or forbid some actions given specific situations. In this post I have talked about two hooks but there are much more such us pre-rebase, a hook that could stop a rebase if not desirable, or post-merge, a hook that could check whether after the merge there is new dependencies and reminds you to install them (or even install them). You can see a list of the hooks you can attach scripts to here.

Now we have to stop for a moment to think in what we could control to improve our workflow, how could we control it from a script and when to control it.

So, tell me, what do you already control or would like to control with git hooks?

--

--