Forgetting to run checks before committing codes or scripts to run can be troublesome. But what if you can create a script that would run automatically to prevent disaster even before it happens? Running a Git pre-commit hook will save you from that disaster.
In this tutorial, you’ll learn how to make sure you wouldn’t miss even a semi-colon and run an entire unit testing suite. So, let’s start rolling!
Prerequisites
This tutorial will be a hands-on demonstration. If you’d like to follow along, be sure you have the following:
- A macOS, Linux, or Windows device. Demonstrations on this tutorial are on Ubuntu 20.04 running on WSL.
- A Git local repository.
- The Git command-line tool.
- Python 3.8 with pip installed on your device. This tutorial uses Python, but you can write hook scripts for Golang, Javascript, or even HCL.
Learning How Git Commit and Git Hook Works
Before you jump to automating Git Actions with Git hooks, it’s worth doing a quick review of what git commit
is and how it works. The git commit
command captures a snapshot of the changes you staged for a project, along with a message describing the changes.
When you run git commit
, Git creates an object in memory with all of the information about the files added to the commit. That information includes the timestamp, author, and line-by-line file changes. A hash of these changes is generated to identify the change uniquely, then is saved to the object in memory.
After that object in memory is created, Git then reads the commit message or prompts the user to enter a commit message. The object and message are then saved to the filesystem under the ~/.git folder. You’ll know more about the ~/.git folder later.
Below is a diagram to help you better understand the step-by-step git commit
process flow.
For each step of the git commit
process, a Git hook is called. Git hooks are scripts that automatically run to change or alter steps in the git commit
process before anything gets saved to the filesystem.
Git hooks aren’t meant to replace continuous integration and deployment pipelines. But they help catch any changes that would break those pipelines before they leave your local machine.
What’s great about Git hook scripts is that you can write the script in any scripting language that doesn’t need compiling, like Python or Ruby.
When you run git commit
, there are three hooks that Git calls one after another:
- The pre-commit hook gets called first before any memory is used for the commit object.
- The prepare-commit-msg hook gets called when Git checks for the existence of a commit message.
- The commit-msg hook gets called after Git has received a commit message
Each hook has its own use cases, but for simplicity, the only one covered in this tutorial is the pre-commit hook. You can see in the diagram below where each hook is triggered in the commit process.
Correcting Badly Formatted Code via Black Code Formatter
Perhaps you’re looking for a way to correct a code’s format automatically. Ensuring that code looks a certain way may be important if you’re on a team. In this example, you’re going to invoke the black code formatted utility automatically when you initiate a git commit
.
The black code formatter is a Python module, which automatically corrects your code to a much readable format—how cool is that!
1. Launch your terminal and run the pip
package manager to install the black
code formatter, as shown below.
pip install black
2. Next, create a simple Python script like the one below and save it as ~/hello.py. You’ll notice that the "Hello world!"
string is on a different line than the print command.
The code below is valid and will print
the words Hello World!
, but the "Hello World!"
line should be on the same line as the parentheses.
#!/usr/bin/python
print(
"Hello World!"
)
3. Now, create a Git hook Shell script named pre-commit.sh by copying and pasting the code below to your script and save it under the ~/.git/hooks directory.
You do not save Git hook scripts to a remote Git repository. Git hook scripts are stored locally.
The script below executes the black
code formatter in the working directory (.
) to automatically reformat the script created in the previous step.
#!/bin/bash
python3 -m black .
If you prefer to change the code format manually, add the
--check
flag like this:python3 -m black . --check
. The--check
flag aborts the commit process when the code you’re committing is in bad format.
4. Stage and commit the ~/hello.py file by running the following commands.
# Stage the hello.py file
git add hello.py
# Commit the hello.py file to the Git repository with a commit message (-m)
git commit -m "This is badly formatted code"
Since you created the Git hook named pre-commit in the previous step, the black code formatter will automatically run and correct the code from the ~/hello.py script.
If any dependencies change, or you have tests that need to reach out to a database that your device doesn’t have access to, the hooks won’t work as expected.
5. To see the changes made by the black
formatter, run the bat
command below followed by the file’s name (hello.py
).
bat hello.py
The
bat
command comes from a Git repository, letting you view a file’s content on the terminal, but is not required. Running thecat
command on Linux and macOS or theGet-Content
cmdlet in PowerShell will do the same thing, but not as fancy.
Below, you can see that the code from the hello.py file is now in a much readable code format.
You’ve now seen how to execute a Git hook automatically before you perform a commit using the black
formatter. If you plan to follow along with the rest of this tutorial, restore (git restore
) the committed version of the hello.py
file with the following command.
You’ll need an example of a badly formatted script for the following sections.
git restore hello.py
Sharing Personal Pre-Commit Hooks with the Pre-Commit Framework
To manage hooks at scale, use a purpose-built tool like the pre-commit framework instead.
If you’re on a team and have now seen how beneficial a Git pre-commit hook can be, you might be wondering how to share Git hooks with other people. In that case, you may want to leverage the pre-commit framework. The pre-commit framework is a multi-language package manager, letting you save Git hooks to version control.
With the pre-commit framework, your team or anyone else who clones your Git repository gets to use your Git hooks! Let’s cover a quick demonstration and see how the pre-commit framework works. For this demonstration, you’re installing hooks from the black code formatter’s repository.
1. First, install the pre-commit framework on your local machine and check the installed version by running the following commands.
#!/bin/bash
# Downloads and runs the installation script (install-local.py)
# from the https://pre-commit.com website
curl https://pre-commit.com/install-local.py | python3 -
# Verify the pre-commit framework version installed
pre-commit --version
2. Next, add a configuration file to reach out to the black code formatter’s repository on GitHub. To do so, create a new file called .pre-commit-config.yaml in your text editor, then copy and paste the below configuration.
Below, the configuration tells the pre-commit framework to look in the psf/black
repository on GitHub for the pre-commit hook.
repos:
- repo: https://github.com/psf/black
rev: 21.6b0
hooks:
- id: black
3. Run the pre-commit
command below to set up (install
) the hooks defined in step two in your local ~/.git/hooks directory.
pre-commit install
4. Finally, stage the ~/hello.py file and run git commit
. The outcome should be the same as the script earlier, except you’ll see the hook id (black), as shown below.
# Stage the hello.py file
git add hello.py
# Commit the hello.py file to the Git repository with a commit message (-m)
git commit -m "Using the pre-commit framework"
Conclusion
Throughout this tutorial, you’ve learned how to create Git hooks locally and via the pre-commit framework to automate Git actions. You’ve also learned how Git hooks can increase code quality on your repository.
Now, go nuts and kick your automation skills up a notch by adding Git hooks to your projects or contributing to other projects!