Like most Version Control Systems (VCS), Git comes with the ability to run scripts and trigger actions at certain points. These scripts are called hooks and they can reside to either of the client or server side. Server-side hooks are usually triggered before or after a push is received while client-side hooks are typically triggered before committing or merging. There are numerous hook types that can be used to enforce specific actions to serve your workflow.
In this article we are going to explore pre-commit hooks, which are certain actions executed on client-side. We will discuss what purposes they serve and which specific actions you need to consider triggering in the workflow of your Python projects. Finally, we will explore a quick way to bypass pre-commit hooks when creating a commit.
What are pre-commit hooks?
As the name suggests, pre-commit hooks are certain actions that are triggered the moment before a commit is created. Pre-commit hooks are usually used to inspect your code and ensure it meets certain standards. For instance, you can enable a pre-commit hook in order to verify that the code style of the content of commit conform to PEP-8 style guide.
Now every pre-commit hook that is executed returns a status code and any non-zero code will abort the commit and the details about the hook(s) that have failed will be reported back to the user.
It is important for development teams to agree and share appropriate pre-commit hooks as they can help ensuring that the source code is consistent and high quality. Additionally, pre-commit hooks can increase the development speed as developers can use them to trigger certain actions that otherwise, would have been executed manually and repeatedly.
How to install and enable pre-commit hooks
Firstly, make sure that you have enabled your virtual environment. Once this is done, you can start by installing pre-commit
through pip
:
$ pip install pre-commit
We can now verify that the installation was successful
$ pre-commit --version
pre-commit 2.10.1
Now that we have successfully installed pre-commit
package, the next step is about enabling the desired pre-commit hooks. The command-line tool comes with a handy option that allows us to automatically generate a sample configuration for pre-commit hooks:
$ pre-commit sample-config
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
The sample configuration comes with 4 distinct pre-commit hooks;
trailing-whitespace
: This hook trims all whitespace from the end of each line. If for any reason you wish to trim additional characters, you can specify the following argument: args: [--chars,"<chars to trim>"]
. For example,
hooks:
- id: trailing-whitespace
args: [--chars,"<chars to trim>"]
- id: end-of-file-fixer
end-of-file-fixer
: This hooks ensures that all files end in a newline and only a newline.
check-yaml
: This hook will attempt to load all yaml files and verify their syntax
check-added-large-files
: Finally, this hook will prevent -extremely- large files from being committed. By default, any file that exceeds 500Kb will be considered as a large file. You can modify the size by providing the following argument:
hooks:
- id: check-added-large-files
args: [--maxkb=10000]
Now that we explored a few basic pre-commit hooks, it’s time to see them in action. What we need to do is to add these hooks into a file called .pre-commit-config.yaml
. For the sake of this example, we are going to use the sample configuration that we already explored earlier. To do so, simply run the following command:
$ pre-commit sample-config > .pre-commit-config.yaml
and now verify that the sample configuration has been successfully added to the yaml file:
$ cat .pre-commit-config.yaml
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
Finally, we need to install the hooks from the file. The following command will automatically parse the content of .pre-commit-config.yaml
and install all the hooks defined:
$ pre-commit install --install-hooks
Note that by default, pre-commit
will install the target hooks under the directory .git/hooks/pre-commit
. If everything goes to plan, you should see the output below
pre-commit installed at .git/hooks/pre-commit
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
Now let’s commit the changes we made and push the pre-commit hooks on our Git repository.
$ git add .pre-commit-config.yaml
$ git commit -m "Added pre-commit hooks in the repo"
Once the git commit
command is executed, pre-commit hooks will run all the checks specified and installed before creating the new commit.
Trim Trailing Whitespace............................................Passed
Fix End of Files......................................Passed
Check Yaml............................................Passed
Check for added large files...........................Passed
[main f3f6caa] Test pre-commit hooks
1 file changed, 10 insertions(+)
create mode 100644 .pre-commit-config.yaml
As we can see, the pre-commit
hooks have run all 4 defined hooks and all of them are passing. Therefore, the new commit will be created successfully and you can now git push
your changes to your remote repository. Note that in case at least one of the pre-commit
hooks fails, the creation of the commit will fail and you will be informed about which hooks have failed. Additionally, it will try to resolve the issues (if possible). If that’s not possible you will have to make the required amendments and re-commit.
For instance, say that our .pre-commit-config.yaml
file has additional new lines at the very end. pre-commit
hooks will fail and automatically resolve the issue for you:
Trim Trailing Whitespace............................Passed
Fix End of Files....................................Failed
- hook id: end-of-file-fixer
- exit code: 1
- files were modified by this hook
Fixing .pre-commit-config.yaml
Check Yaml.........................................Passed
Check for added large files........................Passed
Now that the issue has been resolved, all you have to do is to git commit
again and finally git push
.
Which pre-commit hooks to use for Python projects
Depending on what exactly you want to achieve you need to trigger certain actions before a commit is created. However, there are a few must-have pre-commit hooks that will make your life much easier when developing Python applications.
First, let’s define our pre-commit-config.yaml
file that would be suitable for most Python applications. Note that I consider the 4 pre-commit hooks of the sample configuration quite important, so I am about to include them as well.
# .pre-commit-config.yaml
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
args: [--maxkb=10000]
- id: double-quote-string-fixer
- id: mixed-line-ending
args: [--fix=lf]
- id: check-ast
- id: debug-statements
- id: check-merge-conflict
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.4
hooks:
- id: autopep8
We have already discussed about the first four hooks which are anyway part of the sample configuration that we explored in the previous section. Now let’s explore the remaining hooks in the file.
double-quote-string-fixer
: This hook, will replace double quotes ("
) with single quotes ('
) wherever this is applicable. When the double quotes are needed (say my_string = "Hello 'World'"
) this hook won’t replace them
mixed-line-ending
: This hook will check mixed line ending and will optionally replace line ending by CRLF (Carriage Return Line) and LF. In our example, we explicitly specify --fix
option so that the hook forces replacement of line ending by LF.
check-ast
: This hook will check whether the files ending in .py
parse as valid Python.
debug-statements
: This hook will check for debugger imports in all files ending in .py
. For Python 3.7+, it will also verify that no mention to breakpoint()
is made in the source code.
check-merge-conflict
: This will check for files that contain merge conflict strings.
autopep8
: Finally, this hook will automatically format all the files ending in .py
to conform to the PEP-8 styleguide.
For more details about the all the hooks available you can refer to this page.
How to run pre-commit hooks without creating a commit
If for any reason you want to run pre-commit
hooks without creating a commit, all you have to do is to run the following command
$ pre-commit run --all-files
This command is going to verify whether all the files of your repository align to the restrictions imposed by the pre-commit hooks. If everything meets the criteria you should see the following output
Trim Trailing Whitespace.............................Passed
Fix End of Files.....................................Passed
Check Yaml...........................................Passed
Check for added large files..........................Passed
How to bypass pre-commit hooks
At certain points, you may wish to do a git commit
and bypassing checks performed by pre-commit hooks. This can be done by providing --no-verify
option:
git commit -m "Bypassing pre-commit hooks" --no-verify
Conclusion
In this article, we explored the importance of pre-commit hooks and how they can benefit development teams so that they are able to maintain high-quality code that meets certain standards. Pre-commit hooks can help you save time by automating certain actions in your workflow. Additionally, we discussed how to install pre-commit hooks on your local machine and which specific hooks to enable in order to make your life easier when developing Python applications.