Using pre-commit hooks makes software development life easier

Werner Dijkerman
ITNEXT
Published in
7 min readJan 13, 2024

--

Photo by Alvaro Reyes on Unsplash

Pull requests, you either love and see that they do provide benefits in the way of working. Or you dislike them and see no purpose in them at all. No matter which side you are on, but I do think you want to create quality code and be consistent with each change. And if you are like me, then you would expect this as well from your teammates/co-workers. But how can we make that happen?

Pre-commit hooks can help with that. A pre-commit is one of the several hooks that are available in git. We can use these hooks to execute scripts, while running an git command. A full list with hooks can be found on this page https://githooks.com/.

With a pre-commit hook, we can execute 1 or more scripts as part of the “git commit” command. When the exit code of the configured scripts results in zero (successful execution), the the commit will take place. If 1 or more of these scripts fail, the commit will be unsuccessful. Once that is happening we need to resolve the issue and try again.

When you have a git repository cloned on your device, you are already able to use pre-commit hooks. In the “.git/hook” directory you will find a bunch of “.sample” scripts. You can use these as an example and if you remove the “.sample” in the filename. Now the script is active and triggered when the stage is executed.But we won’t do that with this blog post. So don’t do anything yet.

This shows an overview of the .git/hooks directory on my device.

ls -l of the .git/hooks directory in the cloned git repository.

But we are focussing on to pre-commit hooks and there is an easier way to maintain the pre-commit hooks. Like you already have seen, we can only use 1 pre-commit file, and we want to use a lot more than just one. Sure you can call other scripts, but that makes the maintenance a bit more complicated. Especially when you have more than just a single git repository. Don’t start yet when you are part of a team that wants to use pre-commit hooks. There is a package that helps us to have a more maintainable solution. This package will be installed on our device and make use of a configuration file.

Installation

We have to install it first and we have to install it on our development workstation/laptop. Make sure you have Python and Pip installed before executing the following command:

$ pip install pre-commit

This will install the pre-commit application on our system.

“That is all nice, but I have no idea what kind of scripts we can execute as part of a pre-commit?”

- you?

No worries, lets use an example. This Github repository https://github.com/dj-wasabi/dj-wasabi-release/ contains some scripts that I use for maintaining my repositories. Discussing these scripts is outside of scope for this post. But there is 1 important file in this repository and it is named “.pre-commit-config.yaml”. You will find this file also on other repositories that are on my Github account. This file contains all the scripts that will is executed when we do a “git commit”. Lets take a look at the file.

It contains a “repos” key which is a list. This means you can have more than 1 entries configured in the file (As this is currently the case). Each of these entries needs the following 3 properties to work correctly:

  1. repo: The git location where the scripts are stored. This repository should contain an .pre-commit-hooks.yaml file;
  2. rev: The version of the repository, can be a tag, branch or git commit;
  3. 1 or more hooks, which has 1 or 2 keys: id (and in some cases an args). The id is the name of the hook that is configured in the file mentioned in the `repo` explanation.

In the code block above, we see that there is a Github repository https://github.com/dj-wasabi/pre-commit-hooks . The rev is on master we have several scripts available. The pre-commit hook will see that in this repository a file named “.pre-commit-hooks.yaml” exist. This file knows exactly what to do when the pre-commit hook is executed. The “id” is linked to a script also found in the same repository. In the “.pre-commit-hooks.yaml” file, there is a configuration for the “shellcheck” id.

You are not just there yet by configuring the pre-commit hook in the configuration file. 9 out of 10 times you will need to check the README in the mentioned repository. A script can be relying on 1 or more dependencies. Because I am working on a Mac, I do need to make sure that shellcheck is installed:

$ brew install shellcheck

I also need to make sure that other dependencies are installed. Once I have done that, we need to tell our “.git/hooks” directory that we want to make use of the pre-commit hooks. We don’t make a change ourself by editing these files. No, we will do that by executing the following command in the root of our git repository:

$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
$

You can do now an “ls -l .git/hooks” and compare the output with before. When you execute a “git commit” from now on in this repository, it will execute the pre-commit hooks. But do remember, for each git repository you have cloned on your device, it has its own configuration file. That also means you need to enable the pre-commit hooks for all your repositories cloned on your device.

Why?

“But I still don’t know what kind of scripts I can execute as part of a pre-commit hook?”

- you?

One thing I will see a lot when someone reviews a PR, they focus on the things that don’t matter. Instead, they focus on things like identation, many empy lines or trailing spaces. Even though I can understand, they are annoying, but the focus should be on the quality of the change.

These can be solved with for example a linter or formatter. And if we can execute these first before we make a Pull Request, then we have solved that part. Good thing the pre-commit hooks can help us with that! Why else do we write about it ;-). When configured, when we do a git commit, the linter/formatters are executed to fix it. This helps the reviewers to focus on the actual code and change. And not on the things that don’t matter, these are personal anyways.

Example, I have a repository containing Python scripts. https://github.com/dj-wasabi/dj-wasabi-release/ See the following file: “.pre-commit-hooks.yaml”:

This allows me to make use of the flake8 pre commit hook.Now every time I will do a git commit, it runs the linter:

Look! The pre-commit “Failed” (Or succeeded, depends on how you look at it ;-)) and it prevented to do the actual commit in git. You can see that there where 4 lines in the “lib/djWasabi/git.py” file that needs to be fixed. But you will also see a 2nd script has failed, namely the “Trim Trailing Whitespace”.

A pre-commit script can also update files when needed (which isn’t a bad thing!) and not just fail on when certain criteria is met. The “Trim Trailing Whitespace” script has updated a single file “lib/djWasabi/git.py”. And all it does it removed the trailing space. When a script updates a file, the script should exit with an exit code of 1 and provide info on what file has been updated. If I would run the git commit again, it will succeed. You will see that the “Trim Trailing Whitespace” is “Passed” and that the “flake8” script only has 3 errors:

Updating files as part of the pre-commit hooks are very common and I do make use of it a lot. When you work with Terraform, you will know the “terraform fmt” command. I do make use of the “terraform fmt” command as part of the pre-commit hook. People that will review my Pull Requests, can not write any comment on formatting issues. :) I like it.

Before I end this blog post, I want to say one last things that really helps me with pre-commit hooks. I like writing documentation in Asciidoctor format. I personally thinks it is a bit better that any other “language” like Markdown, but I don’t want to start a flamewar. With Asciidoctor I can write on top of the document “:toc: left” and I have my Table of Content on the left side of the page. With Markdown, you’ll have to manually write one and keep the Table of Content up 2 date. Or we will make use of pre-commit hooks! You install a tool with this command:

$ pip install md-toc

Then you can make use of the following line in your markdown file:

<!--TOC-->

Now I have to make sure that an id with “markdown-toc” is added to the “.pre-commit-hooks.yaml” file. And of course, writing some documentation with the # headings, followed by a git commmit.

“I can now see the benefits of using pre-commit hooks and will use them as well!

- you?

I hope so! I hope I showed you how awesome pre-commit hooks are that they help you write better code. Not only that, it also helps the reviewers to focus on a PR that matters instead of looking for the nitpicky things. With this blog post I only mentioned linting and formatting scripts. But you basically can do anything with a pre-commit hook. One small tip with using pre-commit hooks, don’t go wild on it. My commits will take some seconds to complete. It doesn’t stop you from even running unit tests as well, but that will result in longer duration of doing a commit. And when you have to wait till your commit is finished, you can not do anything. So be careful with the hooks you add.

What are your thoughts about using pre-commit hooks? Do you use them as well? Share your thoughts in the comments below! And follow me for more knowledge sharing opportunities!

May the commits be with you!

--

--

Awesome Ansible | AWS | Dev(Sec)Ops | IaC | Kubernetes | Wazuh | Zabbix Engineer sharing howto's and book reviews. And if you have suggestions, let me know!