pre-commit

My team and I ran into a problem where Code Style violations were being caught too late: in CI or at Code Review. A shared .editorconfig clearly wasn’t enough on its own, since editorconfig can’t fully describe every nuance of formatting.

The obvious, well-known solution is to check code before it lands in source control. Everyone knows about git hooks, but over the past few years I haven’t run into teams that actually use them seriously.

In my view, there are two main problems with git hooks:

  • There’s no transparent way to install them when checking out a working copy.
  • A scripted response to a specific action (e.g. pre-commit) is driven by a single file in .git/hooks — that makes it hard for the team to keep hooks current.

To make pre-commit-hook setup easier, we used the well-known pre-commit tool. It’s written in python and uses a yaml configuration.

What are the advantages of this tool? Let’s list them:

  • A declarative description of the scenarios that need to run before changes hit history, kept in a single .pre-commit-config.yaml configuration file.
  • Strict pinning of the scenario versions — the key to reproducibility across every contributor and every machine.
  • The ability to run the whole set of checks independently of git via pre-commit run.
  • Simple installation of the scripts into your working copy with a single pre-commit install.

Installation

I recommend installing pre-commit with pipx. This approach has an undeniable advantage: each tool installed via pipx gets its own virtualenv, so the python packages it depends on won’t collide with each other in the system site-packages.

pipx install pre-commit

If that’s not a big deal for you, follow the installation instructions in the official documentation instead.

Example

Here’s what the .pre-commit-config.yaml for this blog looks like:

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-toml
  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.34.0
    hooks:
      - id: markdownlint-docker
        args:
          - --disable
          - MD034

I deliberately disabled the check that bans “bare” links in markdown — I consider that acceptable.

And here’s how you’d enable checking and formatting of import directives in Go using goimports:

repos:
  - repo: https://github.com/tekwizely/pre-commit-golang
    rev: 645165368711be670d1945c15060e746f5ed15c6
    hooks:
      - id: go-imports
        args:
          - -local
          - git.your-company.tld
          - -w

Once you’ve described .pre-commit-config.yaml at the root of your repository, run:

pre-commit install

That will wire the hooks into your working copy.

If you want to check your changes before running git commit, run pre-commit run:

pre-commit run

The output will show the issues, if there are any.

Hopefully pre-commit makes it easier for you to start using git hooks — if you actually need them, of course. Good luck!

References