Article

Git Pre-Commit Hooks: Your Code’s Quality Guardian

5 min read
Originally published on Medium. Read, clap or comment on Medium ↗

How a simple Git feature can transform your development workflow and catch bugs before they reach production

Picture this: It’s 2 AM, you’re rushing to deploy a critical fix, and you accidentally commit code with a glaring syntax error. The CI pipeline fails, your team gets paged, and what should have been a quick fix turns into an hour-long debugging session. Sound familiar?

This scenario plays out in development teams worldwide, but it doesn’t have to be your story. Enter Git pre-commit hooks — your automated quality control system that catches issues before they ever leave your local machine.

What Are Pre-Commit Hooks?

Pre-commit hooks are scripts that Git runs automatically before each commit is finalized. Think of them as your code’s personal bouncer — they check everything at the door before allowing entry to your repository.

When you run git commit, these hooks spring into action, performing checks like:

If any check fails, the commit is rejected, giving you a chance to fix issues before they become someone else’s problem.

The Real-World Impact

Before Pre-Commit Hooks

Sarah, a senior developer at a fintech startup, shares her experience: “We were spending 30% of our code review time on formatting issues and catching obvious bugs. Reviews that should have taken 10 minutes were taking an hour.”

After Implementation

“Now our reviews focus on architecture, logic, and business requirements. We’ve reduced our average review time by 40% and caught 85% of common issues before they reach the pipeline.”

Setting Up Your First Pre-Commit Hook

Let’s start with a simple example that prevents commits with debugging statements:

#!/bin/sh
# .git/hooks/pre-commit
# Check for console.log statements
if git diff --cached --name-only | grep -E '\.(js|jsx|ts|tsx)$' | xargs grep -l 'console\.log' 2>/dev/null; then
echo "Error: console.log statements found. Please remove them before committing."
exit 1
fiecho "Pre-commit checks passed!"
exit 0

Make it executable:

chmod +x .git/hooks/pre-commit

The Pre-Commit Framework: Supercharging Your Hooks

While writing custom hooks is powerful, the pre-commit framework takes things to the next level. It’s like having a curated app store for code quality tools.

Installation and Setup

pip install pre-commit

Create a .pre-commit-config.yaml file:

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

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3.9

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8

Install the hooks:

pre-commit install

Power-User Configurations

JavaScript/TypeScript Projects

repos:
- repo: local
hooks:
- id: eslint
name: eslint
entry: npx eslint --fix
language: system
types: [javascript]

- id: prettier
name: prettier
entry: npx prettier --write
language: system
types_or: [javascript, jsx, ts, tsx, json, css, scss, markdown]

- id: jest
name: jest unit tests
entry: npm test --
language: system
pass_filenames: false
always_run: true

Security-Focused Setup

repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets

- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit

- repo: https://github.com/gitguardian/ggshield
rev: v1.18.0
hooks:
- id: ggshield
language: python
stages: [commit]

Advanced Techniques

Conditional Hooks Based on File Changes

repos:
- repo: local
hooks:
- id: database-migration-check
name: Check database migrations
entry: ./scripts/check-migrations.sh
language: system
files: '^migrations/'

- id: docker-build-test
name: Test Docker build
entry: docker build -t test-build .
language: system
files: '^(Dockerfile|requirements\.txt)$'
pass_filenames: false

Custom Hook for Commit Message Validation

#!/bin/sh
# .git/hooks/commit-msg
commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}'if ! grep -qE "$commit_regex" "$1"; then
echo "Invalid commit message format!"
echo "Format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore"
echo "Example: feat(auth): add OAuth2 integration"
exit 1
fi

Team Adoption Strategies

The Gradual Approach

Start with non-intrusive hooks like formatting and gradually add more comprehensive checks:

Week 1: Basic formatting (Prettier, Black)
Week 2: Add linting (ESLint, Flake8)
Week 3: Include security checks
Week 4: Add test requirements for specific files

Making It Stick

  1. Document Everything: Create a team guide explaining each hook’s purpose
  2. Provide Easy Setup: Include installation in your project’s README
  3. Allow Bypasses: Sometimes urgency requires --no-verify, and that's okay
  4. Gather Feedback: Regularly review which hooks help vs. hinder productivity

Performance Optimization

Pre-commit hooks should be fast. Here are strategies to keep them speedy:

Cache Dependencies

repos:
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
language: system
types: [python]
require_serial: true
additional_dependencies: [types-requests]

Run Only on Changed Files

hooks:
- id: eslint
name: eslint
entry: npx eslint
language: node
types: [javascript]
files: \.(js|jsx)$
# Only runs on staged JavaScript files

Common Pitfalls and Solutions

The “It Works on My Machine” Problem

Issue: Different team members have different tool versions
Solution: Pin exact versions in your config and use Docker for consistency

The “Too Slow” Complaint

Issue: Hooks take too long, developers get frustrated
Solution: Profile your hooks, run only necessary checks, use caching

The “False Positives” Frustration

Issue: Hooks fail on legitimate code
Solution: Fine-tune configurations, provide clear override instructions

Measuring Success

Track these metrics to show pre-commit hooks’ value:

The Future of Pre-Commit Hooks

Emerging trends include:

Conclusion: Your Code Quality Safety Net

Pre-commit hooks aren’t just about catching bugs — they’re about creating a culture of quality and consistency. They free your team to focus on solving real problems instead of hunting formatting issues and obvious errors.

Start small, measure the impact, and gradually build a comprehensive quality gate that fits your team’s workflow. Your future self (and your teammates) will thank you when that 2 AM deployment goes smoothly because the real issues were caught hours earlier, safely on your local machine.

The best part? Once set up, pre-commit hooks work silently in the background, like a guardian angel for your codebase. They’re the invisible infrastructure that makes good teams great and great teams unstoppable.

Ready to implement pre-commit hooks in your project? Start with the basic setup above and gradually add more sophisticated checks as your team becomes comfortable with the workflow. Remember: the goal isn’t perfection — it’s progress.


Follow me for more deep dives into developer productivity tools and techniques.

← All articles Discuss on Medium ↗