cheat-sheets

pre-commit

pre-commit is a framework that runs linters and formatters on staged files before each git commit, using hooks pinned in a .pre-commit-config.yaml at the repo root. It catches problems locally, and the same config can run in CI. Hooks here cover shell (ShellCheck and shfmt), Markdown, YAML and secrets. Git basics are in Git.

Documentation: pre-commit.com, supported hooks list

Install and Use #

COMMANDDESCRIPTION
uv tool install pre-commitInstall (or pipx install pre-commit, pip install pre-commit, brew install pre-commit)
pre-commit --versionVerify
pre-commit installInstall the git hook into .git/hooks/pre-commit
pre-commit install --hook-type commit-msgAlso install a hook for commit messages
pre-commit runRun hooks on staged files
pre-commit run --all-filesRun on every file (do this first after adding hooks)
pre-commit run <HOOK_ID>Run one hook
pre-commit run <HOOK_ID> --all-filesRun one hook everywhere
pre-commit run --files <FILE>...Run on specific files
pre-commit run --from-ref origin/master --to-ref HEADRun on files changed in a branch (CI)
pre-commit autoupdateBump every rev to the latest tag
pre-commit autoupdate --repo <REPO_URL>Bump one repo
pre-commit autoupdate --freezePin to commit SHAs instead of tags
pre-commit cleanDelete cached hook environments
pre-commit gcRemove unused cached repos
pre-commit uninstallRemove the git hook
git commit --no-verifySkip hooks for one commit (emergencies only)
SKIP=<HOOK_ID> git commit -m "msg"Skip specific hooks, comma-separated

When a hook modifies files, the commit is aborted. Review the changes, git add them and commit again.

Config Reference #

yaml
# .pre-commit-config.yaml
default_install_hook_types: [pre-commit, commit-msg]
default_stages: [pre-commit]
fail_fast: false
exclude: ^(vendor/|\.venv/)

repos:
  - repo: <REPO_URL>
    rev: <TAG_OR_SHA>
    hooks:
      - id: <HOOK_ID>
        args: [--option]
        files: \.sh$
        exclude: ^legacy/
        types: [shell]
        additional_dependencies: []
KEYDESCRIPTION
repoGit repository containing the hook (or local / meta)
revTag or commit SHA to use; pin it, never use a branch name
idWhich hook from the repository
argsExtra command-line arguments
files / excludeRegex filters on file paths
types / types_orFilter by file type (python, shell, yaml, markdown)
stagespre-commit, pre-push, commit-msg, manual
pass_filenamesSet false for hooks that scan the whole repo
always_runRun even when no matching file changed
verbosePrint hook output even on success

Example Config #

yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
        args: [--allow-multiple-documents]
      - id: check-merge-conflict
      - id: check-added-large-files
      - id: detect-private-key
      - id: check-executables-have-shebangs
      - id: check-shebang-scripts-are-executable

  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.11.0.1
    hooks:
      - id: shellcheck
        args: [-x]

  - repo: https://github.com/scop/pre-commit-shfmt
    rev: v3.14.1-1
    hooks:
      - id: shfmt
        args: [-i, "2", -ci]

  - repo: https://github.com/igorshubovych/markdownlint-cli
    rev: v0.50.0
    hooks:
      - id: markdownlint
        args: [--fix]

  - repo: https://github.com/adrienverge/yamllint
    rev: v1.38.0
    hooks:
      - id: yamllint
        args: [-c, .yamllint.yml]

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

The rev values above are examples from the time of writing. Run pre-commit autoupdate to move them to current releases and let Renovate keep them fresh (it supports pre-commit configs).

HOOKPURPOSE
pre-commit-hooksWhitespace, EOF newline, YAML/JSON syntax, merge-conflict markers, large files, private keys
shellcheck-pyShellCheck without a system install
pre-commit-shfmtshfmt without a system install
markdownlint-cliMarkdown style (config in .markdownlint.yaml)
yamllintYAML style (config in .yamllint.yml)
gitleaksSecret scanning of staged changes

Related config files:

yaml
# .yamllint.yml
extends: default
rules:
  line-length: disable
  truthy: { check-keys: false }
  document-start: disable
yaml
# .markdownlint.yaml
default: true
MD013: false     # line length
MD033: false     # inline HTML
MD041: false     # first line heading

Local Hooks #

Run any command already in your repo as a hook:

yaml
  - repo: local
    hooks:
      - id: compose-config
        name: docker compose config
        entry: docker compose -f compose.yml config -q
        language: system
        files: ^compose\.ya?ml$
        pass_filenames: false

language: system uses whatever is on PATH; other languages (python, node, docker, script) build their own environment.

CI #

yaml
# GitHub Actions
name: pre-commit
on: [push, pull_request]
jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: pre-commit/action@v3.0.1
yaml
# Woodpecker CI
steps:
  - name: pre-commit
    image: python:3.12
    commands:
      - pip install pre-commit
      - pre-commit run --all-files --show-diff-on-failure

--show-diff-on-failure prints what a fixing hook changed. Cache ~/.cache/pre-commit between runs to save time.

Tips #

  • Run pre-commit run --all-files once when adding a new hook, and commit the resulting fixes on their own.
  • Use stages: [manual] for slow hooks and run them with pre-commit run --hook-stage manual.
  • gitleaks only finds secrets in what you commit; rotate any secret that has ever been pushed.
  • A hook failing in CI but not locally usually means different hook versions or a missing pre-commit install.
  • See also Bash Recipes for the scripts that local hooks often call.
Search all sheets

Tip: / or Ctrl/Cmd+K to open, Esc to close.