An Intro to pre-commit

You can use many great tools to help you in your software development journey. One such tool is pre-commit, a framework for managing and maintaining multi-language pre-commit hooks. You use pre-commit to run one or more tools before allowing you to commit your code locally. For example, you might run the Flake8 linter or the Ruff formatter on your Python code in GitHub Actions or some other CI. But rather than waiting for CI to run, you want to run those checks locally and automatically.

That is where pre-commit comes in. You tell pre-c0mmit what to run, and it will run right before it allows you to commit your code.If any of those checks fail, you must fix your code before committing it.

Installing pre-commit

pre-commit is a Python package, so you can install it using pip. Here’s the command you’ll need to run in your terminal:

pip install pre-commit

Once pre-commit is installed, you can confirm that it works by running the following:

pre-commit --version

Adding the git Hooks

The next step is to navigate to one of your local GitHub code bases in your terminal. Once inside one of your repos, you will need to run this command:

pre-commit install

This command installs pre-commit in your .git\hooks folder so that pre-commit runs whenever you commit. But how does pre-commit know what to run?

You have to define what pre-commit runs using a special YAML file. You’ll learn how in the next section!

Adding a pre-commit Configuration

You need to add a file named .pre-commit-config.yaml (note the leading period) into the root of your repo. If you want to generate a simple config file, you can run this command:

pre-commit sample-config

Here’s an example config for running Black on your code:

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    -   id: check-yaml
    -   id: end-of-file-fixer
    -   id: trailing-whitespace
-   repo: https://github.com/psf/black
    rev: 22.10.0
    hooks:
    -   id: black

Personally, I like to run the Ruff formatter and linter as well as a couple of defaults, so I use this config a lot:

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-added-large-files

- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.1.7
  hooks:
    # Run the linter.
    - id: ruff
    # Run the formatter.
    - id: ruff-format

When you add a new rule to pre-commit, you should run that rule against all the files in your repo so you don’t have any surprises later on. To do that, you need to run this command:

pre-commit run --all-files

Once you have run all your new rules against all your code files, you can start working on your next feature or bug fix. Then, when you run,  git commit the pre-commit hooks will run, and you’ll see if your code is good enough to pass.

Wrapping Up

There are TONs of hooks you can add to pre-commit. A lot of them are mentioned on the pre-commit website. You can add Mypy, pytest, and much, much more to your pre-commit hooks. Just don’t get too crazy, or they may take too long to run, and you’ll go nuts waiting for it.

Overall, running so many of your CI hooks locally is great because your machine is usually faster than waiting on a queue in CI. Give it a try and see what think!