Have you ever found yourself in a situation where you pushed a commit to a branch for review, only to discover you made a spelling mistake, used outdated syntax, or forgot to lint your code again after making that one last change?
I've wasted countless hours in the past doing exactly this! It is slow and time-consuming to identify an issue after the fact and it also slows down other folks in your team performing the code review. There must be a better way!
Pre-commit solves this problem by providing pre-commit hooks that run after every commit which solves all of these common, repeatable problems that machines are good at solving.
However finding an awesome new tool to enhance your workflow is only half the challenge, convincing one or more teams to adopt it another.
Using a tool - without imposing it upon a team
Using pre-commit
requires a .pre-commit-config.yaml
to be placed in a repository however to avoid updating the .gitignore
file for every new repository you wish to interactive with, the best solution i found was to add the following to a global .gitignore
.
> vim ~/.gitignore
.pre-commit-config.yaml
.codespellrc
This allows for the file to be ignored from all repositories I interact with, without the risk of potentially committing it or needing to update the repository to use it. This is especially useful when I need to add confirmation files for some of the additional pre-commit hooks that I'm using such as codespell
.
Finally within your repository you can create and use use pre-commit:
---
# .pre-commit-config.yaml
# ========================
#
# pre-commit clean
# pre-commit install
# pre-commit install-hooks
#
# precommit hooks installation
#
# - pre-commit autoupdate
#
# - pre-commit run black
#
# continuous integration
# ======================
#
# - pre-commit run --all-files
#
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- id: check-merge-conflict
- id: sort-simple-yaml
- id: fix-encoding-pragma
args: ["--remove"]
- id: forbid-new-submodules
- id: mixed-line-ending
args: ["--fix=lf"]
description: Forces to replace line ending by the UNIX 'lf' character.
- id: check-added-large-files
args: ["--maxkb=500"]
- id: no-commit-to-branch
args: [--branch, master]
- id: check-yaml
- id: check-json
files: ^tests/app/
- id: pretty-format-json
args: ["--no-sort-keys", "--autofix"]
files: ^tests/app/
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: https://github.com/ambv/black
rev: 21.5b1
hooks:
- id: black
language_version: python3.9
- repo: https://github.com/PyCQA/bandit
rev: 1.7.0
hooks:
- id: bandit
description: Security oriented static analyser for python code
exclude: tests/|scripts/
args:
- -s
- B101
- repo: https://github.com/codespell-project/codespell
rev: v2.1.0
hooks:
- id: codespell
name: codespell
description: Checks for common misspellings in text files.
entry: codespell
language: python
types: [text]
- repo: https://github.com/asottile/pyupgrade
rev: v2.19.4
hooks:
- id: pyupgrade
If you also want to use codespell
, you will need to add a .codespellrc
file to the root of your repository:
[codespell]
quiet-level = 3
count =
check-hidden =
check-filenames =
enable-colors =
builtin = clear,rare,informal,code,names
ignore-words-list = pullrequest
Now that pre-commit is configured, you can install and run it:
pre-commit install
pre-commit run --all
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Debug Statements (Python)................................................Passed
Check for merge conflicts................................................Passed
Sort simple YAML files...............................(no files to check)Skipped
Fix python encoding pragma...............................................Passed
Forbid new submodules....................................................Passed
Mixed line ending........................................................Passed
Check for added large files..............................................Passed
Don't commit to branch...................................................Passed
Check Yaml...............................................................Passed
Check JSON...........................................(no files to check)Skipped
Pretty format JSON...................................(no files to check)Skipped
Check hooks apply to the repository..................(no files to check)Skipped
Check for useless excludes...........................(no files to check)Skipped
black....................................................................Passed
bandit...................................................................Passed
codespell................................................................Passed
pyupgrade................................................................Passed
Lastly, if pre-commit is ever blocking your workflow and for any reason you don't want to run it, you can append a --no-verify
flag onto your commit to skip it.
git commit -m "do the thing" --no-verify
Now all your future commits are protected from common gotcha's and save time in code review.
Top comments (1)
Hi Kyle,
Great article, I only just recently discovered pre-commit myself and did a short write up myself.
I am working on my own pre-commit hooks, currently only having two:
spellcheck
prohibit-commit-main
When I went over your configuration I discovered:
no-commit-to-branch
, which does somewhat the same as the last one in my list, so I will be change to be using that instead.Anyway I noted that your example output, somewhat broke due to the
'
(apostrophe). I do not see this as a serious issue, but in case you did not know you can overwrite the text output and avoid the single apostrophe in: "Don't commit to branch", which is the text emitted default by the hook.And it will output:
Thanks for the article, I learn something new and expanded my toolbox.