5 Git Hooks to Instantly Improve Your Code Quality

John AbioyeJohn Abioye
5 min read

Introduction

If you’ve ever accidentally pushed broken code, forgotten to run tests before committing, or struggled with enforcing consistent style across your team, Git hooks can be your secret weapon.

Git hooks are customizable scripts that run automatically in response to specific Git events like committing, pushing, or merging. Think of them as little gatekeepers that ensure your code meets certain quality standards before it leaves your local machine.

In this article, we’ll explore five powerful Git hooks you can implement today to instantly level up your code quality. Whether you’re a solo developer, part of a growing team, or managing a DevOps pipeline, these hooks will help you catch issues earlier, enforce consistency, and save time in code reviews.

Let’s dive in.


1. Pre-commit Hook: Run Linters Automatically

Problem: Developers often forget to run linters before committing. This leads to inconsistent formatting and stylistic issues that clutter code reviews.

Solution: Use the pre-commit hook to run your linter before each commit.

Example: For a JavaScript project using ESLint, create a .git/hooks/pre-commit file with the following:

#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Please fix the above issues."
  exit 1
fi

Make it executable:

chmod +x .git/hooks/pre-commit

Now, every time a developer tries to commit, ESLint will run first. If there are any linting errors, the commit will be blocked until they’re fixed.

Why it matters: It enforces code style early and prevents unclean code from reaching the repository.

Pro Tip: Use a tool like Husky to manage Git hooks more easily across teams and repositories.


2. Pre-push Hook: Run Tests Before Pushing

Problem: Pushing code without running tests can lead to broken builds in CI/CD pipelines.

Solution: Use the pre-push hook to run your test suite before allowing any push.

Example: For a Python project with pytest:

#!/bin/sh
pytest
if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi

Make it executable:

chmod +x .git/hooks/pre-push

Now, if any tests fail, Git will prevent the push.

Why it matters: This practice ensures that only tested and working code is pushed to the shared repository, reducing the chances of integration issues.

Pro Tip: Combine this with coverage checks to enforce a minimum code coverage threshold.


3. Commit-msg Hook: Enforce Commit Message Standards

Problem: Inconsistent or unclear commit messages make it harder to understand project history.

Solution: Use the commit-msg hook to validate commit messages.

Example: Enforce Conventional Commits format:

#!/bin/sh
commit_msg=$(cat "$1")
pattern="^(feat|fix|docs|style|refactor|test|chore)\: .+$"

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "Invalid commit message. Use Conventional Commits (e.g., 'feat: add login button')"
  exit 1
fi

Make it executable:

chmod +x .git/hooks/commit-msg

Why it matters: Structured commit messages improve readability, automate changelogs, and help with semantic versioning.

Pro Tip: Integrate this with semantic-release or commitizen for even more automation.


4. Pre-rebase Hook: Block Rebasing Protected Branches

Problem: Accidentally rebasing protected branches like main or production can lead to catastrophic consequences.

Solution: Use the pre-rebase hook to block rebases on critical branches.

Example:

#!/bin/sh
branch=$(git symbolic-ref HEAD | sed 's!refs/heads/!!')

if [ "$branch" = "main" ] || [ "$branch" = "production" ]; then
  echo "Rebasing on $branch is not allowed."
  exit 1
fi

Make it executable:

chmod +x .git/hooks/pre-rebase

Why it matters: Prevents rewriting history on branches where integrity must be preserved.

Pro Tip: Add more branches to the list depending on your workflow.


5. Applypatch-msg Hook: Enforce Patch Consistency

Problem: Applying patches manually can lead to inconsistent or malformed commit messages.

Solution: Use the applypatch-msg hook to ensure patches follow your commit guidelines.

Example: Reuse the commit-msg validation logic:

#!/bin/sh
commit_msg=$(cat "$1")
pattern="^(feat|fix|docs|style|refactor|test|chore)\: .+$"

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo "Patch commit message does not follow the Conventional Commits format."
  exit 1
fi

Make it executable:

chmod +x .git/hooks/applypatch-msg

Why it matters: Keeps your commit history clean and structured, even when importing patches from external sources.

Pro Tip: If you’re working in an open-source environment, this hook ensures that contributions maintain your project’s standards.


Bonus: Centralizing Git Hooks with Husky

Managing Git hooks across multiple machines and contributors can be tricky. That’s where Husky comes in.

Husky lets you define Git hooks inside your package.json or via a configuration file, and ensures they’re set up automatically when contributors install dependencies.

Example package.json snippet:

"husky": {
  "hooks": {
    "pre-commit": "npm run lint",
    "pre-push": "npm test",
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

You can even use tools like lint-staged to run linters only on staged files, improving speed and precision.


Conclusion

Git hooks are an underrated but powerful way to enforce code quality, streamline development workflows, and prevent costly mistakes. By automating linting, testing, commit validation, and branch protection, you shift quality control to the earliest point possible: the developer’s machine.

Start small. Implement one or two of these hooks, observe the improvements, and scale from there. Soon, your team will be shipping cleaner, more consistent code with fewer surprises in production.

Whether you're a seasoned DevOps engineer or a developer trying to build better habits, these five Git hooks are simple steps that can make a massive impact.

Want to go further? Explore pre-merge hooks with server-side enforcement, GitHub Actions for continuous workflows, or even building custom hook runners for language-specific needs.

Your future self (and your team) will thank you.

0
Subscribe to my newsletter

Read articles from John Abioye directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

John Abioye
John Abioye