Enforce code quality and increase productivity
Git, one of the most popular version-control systems (VCS) for modern software development, is used to coordinate changes for source code files and facilitate teamwork among multiple developers. Using a “hooks system,” Git allows developers and administrators to perform custom actions at every stage of the version-control process (e.g. before a commit, after a commit, before a push, etc.)
I came across custom hooks during my search for a way to automatically run Pa11y-ci, a web accessibility testing tool, against every commit. I had already set up the Pa11y-ci script in my package.json file, but wanted to implement an automated gatekeeper that would reject any commits that do not meet accessibility standards. I will later dedicate an entire post on adopting the Pa11y library, but for today, let’s take a look at Git hooks.
Understanding Git Hooks
As an Atlassian tutorial so elegantly explained:
Git hooks are scripts that run automatically every time a particular event occurs in a Git repository. They let you customize Git’s internal behavior and trigger customizable actions at key points in the development life cycle.
Major benefits of using Git hooks include encouraging a commit policy, automating development workflow, and implementing continuous integration.
There are two groups of Git hooks:
- client-side / local hooks, which are prompted by events on the local repository, such as when a developer commits or merges code.
- server-side / remote hooks, which are run on the network hosting the repository, and they are prompted by events such as receiving pushes.
Of the client-side hooks, the most commonly used ones are:
Some scripts take in one to three arguments, while others take none. Common use cases include checking the commit message for spelling errors, notifying (via email or text) team members of new commits, and implementing continuous integration workflows. As noted in the official Git hooks guide:
These hook scripts are only limited by a developer’s imagination.
Below, I walk through how to create a pre-commit hook in Bash to run a web accessibility testing tool and ensure compliance of all commits. As its name suggests, the pre-commit script is triggered every time the developer issues git commit.
Implementing Git Hooks
Git hooks are a built-in feature that come with every Git repository. Upon initializing a new project, Git populates the hooks folder with template files.
1. Navigate to the hooks directory
$ cd /my-git-repo/.git/hooks
Notice the files inside, namely:
applypatch-msg.sample
pre-applypatch.sample
pre-commit.sample
prepare-commit-msg.sample
commit-msg.sample
post-update.sample
pre-push.sample
pre-rebase.sample
update.sample
2. Install your hook
To enable the hook scripts, simply remove the .sample extension from the file name. Git will automatically execute the scripts based on the naming. For my purposes, I renamed the “pre-commit.sample” file to “pre-commit.”
3. Select a language to write your hook scripts in
The default files are written as shell scripts, but you can use any scripting language you are familiar with as long as it can be run as an executable. This includes Bash, Python, Ruby, Perl, Rust, Swift, and Go.
Open up the file in your code editor and define your language of choice in the first line, using the shebang (#!) sign, so Git knows how to interpret the subsequent scripts. Note that you need to include the path of your interpreter. For Mac users who wish to write the scripts in Python, for instance, the Apple-provided build of Python is located in /usr/bin. So, the first line would look like:
#!/usr/bin python
If you want to use Bash, on the other hand, the first line would be:
#!/bin/bash
And for shell:
#!/bin/sh
4. Write your script
From here on, you could write any script and Git will execute it before any commits to this project. For reference, I wrote my script in Bash, and here is what I ended up with:
- Lines 3–6 define text decoration variables, which are used to enhance my console messages.
- Line 8 prints out in bold “Starting a11y testing…”
- Line 10 calls the pa11y-ci script I had set up in package.json.
- Lines 12–17 is a conditional that evaluates the return value ($?) of the previously-run program, which, in this case, is pa11y-ci. The pa11y test exits with a 0 status for success and 1 status for failure, so I used that to print out messages to the console accordingly.
- Notice that if the conditional above evaluates to a 1 status, line 16 extends that by exiting with a 1 status to indicate failure. This prevents the changes from being commit.
After writing your hook scripts, just sit back and watch Git do all the work:
Sharing Git Hooks
As mentioned above, client-side hooks are local to any given Git repository. This means they are not cloned with the rest of the project and are not version-controlled. As such, maintaining hooks for a team can be tricky.
One workaround is to store the hooks in the project directory, above the .git folder, which would allow the files to be included in the version-control flow. Then, each developer would just copy and paste the files into their local .git/hooks directory. Alternatively, your could create server-side hooks to reject commits that do not conform to certain standards.
Summary
Adopting pre/post-event hooks is a simple way to maintain code quality and boost productivity. These hooks can act as checkpoints along the software development lifecycle, automating repetitive testing and development workflow. While the initial set up of custom hooks can be a frustrating process, the time spent is usually worth the investment, as it will likely save the team manual work later on.
Reference
- Customizing Git (Official Git Documentation)
- Git Hooks Tutorial (Atlassian)
- How To Use Git Hooks To Automate Development and Deployment Tasks (Digital Ocean)
- Introduction to BASH (The Linux Documentation Project)