TL;DR

A developer walks through creating Rust pre-commit hooks and demonstrates multiple failure modes: hooks that check the working tree instead of the index, hooks that run across a repository and block rebases or commits that lack certain file types. The author argues pre-commit hooks are intrinsically fragile and recommends using pre-push hooks with specific safeguards instead.

What happened

The author created a small Rust project and iteratively built a pre-commit hook meant to enforce rustfmt formatting. The initial script ran rustfmt –check on files in the working tree, so formatting fixes made by rustfmt remained unstaged and the commit still contained unformatted code. The hook was revised to extract the index into a temporary directory with git checkout-index and run checks there, which caught unstaged formatting. That change then caused the hook to run across files already in the repo and fail on third-party code; the script was refined again to operate only on cached (staged) files using git diff –name-only –cached. During an interactive rebase, however, the hook still caused errors when commits on the rebasing branch had no Rust files or when hooks changed repository state. The author reports that common frameworks (including lint-staged and the pre-commit framework) don't fully solve these issues and concludes that pre-commit hooks create more problems than they prevent.

Why it matters

  • Hooks that run on the working tree can produce unstaged automatic fixes, leading to commits that differ from what a pre-commit check validated.
  • Pre-commit hooks can block common workflows like interactive rebase and merging, especially when hooks assume certain file types or modify commits.
  • Automatic or centralized installation of hooks is unreliable; contributors may have different hook versions or none at all, creating inconsistent developer experiences.
  • Tools that try to work around index vs working-tree differences (stash, –keep-index) alter repository state and can have side effects developers may not expect.

Key facts

  • A simple pre-commit script that runs rustfmt –check on *.rs operates on the working tree and will not detect unstaged formatting changes.
  • Using git checkout-index –prefix=<tmpdir>/ allows a hook to validate the staged index snapshot rather than the working tree.
  • Filtering the files to just those staged can be done with git diff –name-only –cached –no-ext-diff –diff-filter=d.
  • Running checks across the entire repository causes failures when existing files elsewhere are not formatted, blocking commits that touch unrelated files.
  • Hooks run during interactive rebase and when resolving merge conflicts; they can fail on commits that don't include the checked file types.
  • lint-staged and the pre-commit framework address some problems (for example, by stashing with –keep-index) but do not eliminate rebase-related or commit-modifying issues.
  • The author recommends pre-push hooks as a preferable alternative because they avoid many of the interactive and local-commit problems of pre-commit hooks.

What to watch next

  • Whether projects shift from pre-commit to pre-push hooks as a standard practice (not confirmed in the source).
  • Tooling improvements that let hooks reliably inspect the index without altering git state (not confirmed in the source).
  • Changes to popular hook frameworks to better handle rebases, interactive commits, and cross-branch validations (not confirmed in the source).

Quick glossary

  • pre-commit hook: A Git hook that runs before a commit is finalized; typically used to run linters, formatters, or other checks.
  • index (staging area): The Git area where changes are staged before being committed; distinct from the working tree on disk.
  • pre-push hook: A Git hook that runs before a push operation, which can validate commits just prior to sending them to a remote.
  • rustfmt: The canonical Rust code formatter commonly used to enforce code style in Rust projects.
  • rebase (interactive): A Git operation that reapplies commits onto a new base; interactive rebase lets users edit commit history and messages.

Reader FAQ

Are pre-commit hooks reliable for enforcing project checks?
According to the source, they are fragile in many workflows and can create more problems than they solve.

Does the pre-commit or lint-staged framework fix these problems?
The source says those frameworks address some issues (e.g., using –keep-index) but do not solve rebase-related failures or hooks that modify commits.

What is a practical alternative recommended in the source?
The author recommends using pre-push hooks, with safeguards such as running on the index, keeping checks fast, avoiding network or credentials, and not auto-installing hooks.

How can a hook check staged files rather than the working tree?
The source demonstrates using git checkout-index to export the index into a temporary directory and running checks against those files.

pre-commit hooks are fundamentally broken 2025-12-26 • git • workflows • devtools Let's start a new Rust project. $ mkdir best-fizzbuzz-ever $ cd best-fizzbuzz-ever $ cat main.rs fn main() {…

Sources

Related posts

By

Leave a Reply

Your email address will not be published. Required fields are marked *