pre-commit hooks run before each commit. If a hook exits non-zero, the commit is aborted.
Native: .git/hooks/pre-commit (shell script). Per-machine; not committed.
Portable: tools that share hooks across the team:
- husky (Node ecosystems):
.husky/pre-commit is committed.
- lefthook (Go binary, fastest).
- pre-commit (Python framework with rich plugin ecosystem).
Common pre-commit checks:
- Lint (ESLint, Ruff, golangci-lint)
- Format (Prettier, Black, gofmt)
- Type-check (TypeScript)
- Secret scanning (gitleaks, detect-secrets)
- Test the impacted files only
Keep them fast (<2 seconds). Slow hooks lead to --no-verify culture, which defeats the purpose.