Back

Automating Code Checks with Git Pre-Commit Hooks

Automating Code Checks with Git Pre-Commit Hooks

Every developer knows the frustration of pushing code only to have CI/CD pipelines fail due to formatting issues or linting errors. What if you could catch these problems before they even reach your repository? Git pre-commit hooks offer a powerful solution that runs automated checks locally, saving time and maintaining consistent code quality across your team.

This article walks you through setting up git pre-commit hooks using the pre-commit framework, configuring essential tools like ESLint, Prettier, and Black, and implementing best practices that keep your development workflow smooth and efficient.

Key Takeaways

  • Git pre-commit hooks run automated checks before commits, preventing common issues from reaching your repository
  • The pre-commit framework simplifies hook management with YAML configuration and language-agnostic support
  • Speed optimization and gradual adoption are crucial for team acceptance
  • Alternative tools like Husky and Lefthook offer different approaches for specific needs

What Are Git Pre-Commit Hooks?

Git hooks are scripts that Git executes before or after events such as commit, push, and receive. A git pre-commit hook specifically runs after you stage changes but before Git creates the commit. If the hook exits with a non-zero status, Git aborts the commit, giving you a chance to fix issues first.

While you can write hooks as simple shell scripts in .git/hooks/, managing them becomes complex as your project grows. That’s where the pre-commit framework shines—it provides a standardized way to manage and share hooks across your team.

Why Use the Pre-Commit Framework?

The pre-commit framework solves several pain points with native Git hooks:

  • Easy installation and updates: Hooks are versioned and installed via a simple YAML configuration
  • Language agnostic: Run Python linters, JavaScript formatters, and shell scripts from one config
  • Selective execution: Hooks run only on changed files, keeping commits fast
  • Team consistency: Share the same .pre-commit-config.yaml file across your entire team

Setting Up Pre-Commit in Your Project

First, install the pre-commit package:

pip install pre-commit

Create a .pre-commit-config.yaml file in your project root. Here’s a practical configuration that covers common needs:

repos:
  # Basic file fixes
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  # Python formatting with Black
  - repo: https://github.com/psf/black
    rev: 24.2.0
    hooks:
      - id: black
        files: \.py$

  # Python type checking with Mypy
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies: [types-all]
        files: \.py$

  # JavaScript/TypeScript with Prettier
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        files: \.(js|jsx|ts|tsx|json|css|md)$

  # JavaScript/TypeScript linting with ESLint
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.56.0
    hooks:
      - id: eslint
        files: \.(js|jsx|ts|tsx)$
        additional_dependencies:
          - eslint-config-standard
          - eslint-plugin-react

  # Run project tests
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: pytest
        language: system
        pass_filenames: false
        always_run: true

Install the git hooks:

pre-commit install

Now your hooks will run automatically on every commit. To run them manually on all files:

pre-commit run --all-files

Optimizing Hook Performance

Speed matters—slow hooks discourage developers from committing frequently. Follow these practices:

Run hooks only on staged files: The pre-commit framework does this by default, but ensure your custom hooks respect this pattern.

Use fast tools: Choose Ruff over Flake8 for Python, or Biome over ESLint for JavaScript when speed is critical.

Skip expensive checks locally: Reserve comprehensive test suites for CI/CD. Local hooks should focus on quick wins like formatting and basic linting.

Parallelize when possible: Some tools support parallel execution. For example, pytest can run tests in parallel with pytest-xdist.

Team Adoption Best Practices

Getting your team to adopt pre-commit hooks requires thoughtful implementation:

Start small: Begin with non-intrusive hooks like trailing whitespace removal. Add stricter checks gradually.

Document the setup: Include installation instructions in your README. Make it part of your onboarding process.

Handle environment differences: Use language-agnostic hooks where possible. For language-specific tools, ensure your .pre-commit-config.yaml specifies compatible versions.

Provide escape hatches: Sometimes developers need to bypass hooks. The --no-verify flag skips all hooks:

git commit --no-verify -m "Emergency fix"

Use this sparingly and document when it’s appropriate.

Alternative Approaches

While pre-commit is the most popular framework, alternatives exist:

Husky: Popular in JavaScript projects, integrates well with npm scripts.

Native Git hooks: Direct shell scripts in .git/hooks/ offer maximum control but lack portability.

Lefthook: Fast, cross-platform alternative with parallel execution support.

For most teams, pre-commit offers the best balance of features, performance, and ecosystem support.

Common Pitfalls and Solutions

Hook failures on CI: Ensure CI environments have all necessary dependencies installed. Consider running pre-commit run --all-files as a CI step.

Platform-specific issues: Test hooks on all platforms your team uses. Use Docker for consistent environments if needed.

Merge conflicts in config files: Keep .pre-commit-config.yaml changes minimal and atomic. Update hook versions in separate commits.

Conclusion

Git pre-commit hooks transform code quality from a reactive process to a proactive one. By catching issues before they enter your repository, you save time, reduce context switching, and maintain higher standards across your codebase. Start with basic formatting hooks, gradually add linters and type checkers, and always prioritize speed to keep your team’s workflow smooth.

The key to successful adoption lies in balancing thoroughness with developer experience. Fast, focused hooks that provide immediate value will become an indispensable part of your team’s workflow.

FAQs

Yes, pre-commit excels at monorepos. You can configure different hooks for different file patterns and directories. Use the files and exclude keys in your configuration to target specific languages or folders within your repository.

Use language version managers like nvm or pyenv alongside pre-commit. Alternatively, specify language_version in your hook configuration or use Docker-based hooks to ensure consistent environments across all developers' machines.

Without pre-commit installed, hooks won't run locally but your code will still commit. To enforce standards, run pre-commit checks in your CI pipeline as a safety net and make pre-commit installation part of your project setup documentation.

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay