Using Git hooks to check your commit code
Secure Commitment
The pre-commit framework lets you automatically manage and maintain your Git hook scripts to deliver better Git commits.
When developing software in a public Git [1] repository, it's recommended to check for common issues in your code prior to committing your changes. Neglecting to do so could lead to your Git repository being cluttered with commits that just fix some minor syntax or style issue. To err is human. Consequently, relying solely on manual checks isn't enough to deliver quality code.
To address this issue, the Git version control system offers a way to start custom scripts when specific actions occur, such as committing changes or merging branches: Git hooks [2]. These hooks are executable (often shell) scripts, stored in the .git/hooks
directory of a Git repository. When you create a new repository with the git init
command, this directory is populated with several example scripts (Figure 1). Removing the .sample
extension from a file name is all that's necessary to enable this hook.
You can use these Git hooks to check for code style on a snapshot that's about to be committed, to edit the default commit message before the commit author sees it, to validate a commit message before allowing a commit to go through, or even to send a notification after the commit process is completed. It's also possible to run scripts before rebasing anything, after a successful git checkout
or git merge
command, before pushing your commits, and more.
So, if you want to automatically do something before or after one of these Git operations, just create an appropriately named script (without any extension), such as pre-commit
if you want Git to run it before committing changes. Put this script in the .git/hooks
directory of your Git repository and make it executable. Git will automatically find and run it. It doesn't matter what type of script this is, as long as it's executable. Git hooks can be shell scripts, written in Python, JavaScript, Go, or anything you want.
Most prevalent among users, Git's pre-commit
hook allows you to run code linters such as Stylelint [3], Ruff [4], Vale [5], and more, and correct any errors they discover prior to committing your code. But what if you have a complex project where you need Stylelint (written in JavaScript) to check CSS files, Ruff (written in Rust) to inspect Python code, and Vale (written in Go) to validate your documentation? Then, you need to be sure that you can easily install those linters and their language environments.
A Package Manager for Git Hooks
This challenge of managing and maintaining pre-commit
hooks for Git repositories has spurred the creation of a dedicated framework, conveniently named pre-commit [6]. The pre-commit framework identifies itself as a "multi-language package manager for pre-commit hooks." All you need to do is list the hooks you wish to use in a YAML [7] file located in your repository. Then pre-commit manages the installation of any hook written in any supported programming language. It automatically installs the necessary programming language environment in an isolated environment (for example, a Python virtual environment) without the need for root access.
You will find pre-commit in the package manager of most distributions, but it is often an outdated version. You can install the most recent release using Python's package manager, pip:
$ pip install pre-commit
Then check whether it's installed correctly:
$ pre-commit --version pre-commit 3.7.0
A Basic Configuration
To manage your Git hooks with the pre-commit package manager in your Git repository, you need to create a configuration file in the repository's root directory, named .pre-commit-config.yaml
. If you don't know where to begin, pre-commit can generate a configuration file with hooks for some basic checks. Just run this command in your Git repository's root directory:
pre-commit sample-config > .pre-commit-config.yaml
The generated configuration file, which looks like Listing 1, is a YAML file with only one mandatory top-level key, repos
. The repos
key's value is a list of repositories where pre-commit can get the code for the Git hooks.
Listing 1
Default pre-commit Config File
01 # See https://pre-commit.com for more information 02 # See https://pre-commit.com/hooks.html for more hooks 03 repos: 04 - repo: https://github.com/pre-commit/pre-commit-hooks 05 rev: v3.2.0 06 hooks: 07 - id: trailing-whitespace 08 - id: end-of-file-fixer 09 - id: check-yaml 10 - id: check-added-large-files
Listing 1 refers to a single repository, pre-commit's own pre-commit-hooks
[8]. The repo
key refers to the repository's URL; thus, pre-commit knows which repository to git clone
. The rev
key holds the version (or Git tag) to install, and the hooks
key constitutes a list of mappings describing which hooks to use from the repository.
The trailing-whitespace
hook trims all white space from the ends of lines. The end-of-file-fixer
hook makes sure files end in a newline and only a newline. The check-yaml
hook attempts to load all YAML files to verify their syntax. And the check-added-large-files
hook prevents large files (by default files larger than 500KB) from being committed.
Running pre-commit
Before modifying Listing 1 to meet specific needs, I'll test how pre-commit works on a repository. First run the following command in your Git repository's root directory to install pre-commit's Git hook scripts:
$ pre-commit install pre-commit installed at .git/hooks/pre-commit
As indicated by the command's output, it sets up a Git hook script in .git/hooks/pre-commit
. This is a shell script that runs the pre-commit
command with certain arguments (you can take a peek at the file if you're interested). Thus when you now add files to the index with git add
and then run git commit
, pre-commit will automatically run the hooks specified in the configuration file (Figure 2).
In the command's output, you see that pre-commit installs the hooks from the repository in its own environment and runs the hook scripts on the added files. While this runs slow the first time due to the installation, pre-commit runs the hooks directly on subsequent commits, which is much faster.
For each of the four hooks that pre-commit runs in this configuration, you get an indication of whether the files pass or fail their checks. The commit is only allowed to go through if all hooks pass their checks.
Some hooks are able to automatically fix a file when it fails a check. For instance, if the trailing-whitespace
hook finds white space at the end of a line, it removes it from the file, which is indicated in the output with the message 'Fixing README.md'. Other hook scripts don't automatically fix failures, requiring you to manually make the necessary corrections. This is often the case with code flagged by a code linter.
Regardless if a failure is fixed automatically or manually, you need to re-add the file with git add
and commit again. If all hooks now display success, the commit goes through. This approach ensures that all code entering the (local) repository abides by the specific guidelines verified by your pre-commit
hooks.
From time to time, it's wise to update all your Git hooks to the latest available version. You can do this with the following command:
$ pre-commit autoupdate [https://github.com/pre-commit/pre-commit-hooks] updating v3.2.0 -> v4.6.0
This command checks for the latest tag on the default branch of each repository defined in pre-commit's configuration file and updates the rev
key to this tag. The next time you run pre-commit, it checks out the new version from the repository and installs it before running the hooks.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Thousands of Linux Servers Infected with Stealth Malware Since 2021
Perfctl is capable of remaining undetected, which makes it dangerous and hard to mitigate.
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.