Skip to content

Git Hooks: Automate Your Development Workflow

DodaTech Updated 2026-06-22 5 min read

In this tutorial, you'll learn about Git Hooks: Automate Your Development Workflow. We cover key concepts, practical examples, and best practices.

Git hooks are executable scripts that Git triggers on committing, pushing, or merging to enforce code quality and catch issues before they reach your repository.

In this tutorial, you'll learn Git hooks — scripts that run automatically at key points in Git's workflow. Hooks let you enforce code quality, run tests, and catch issues before they ever reach your repository. By the end, you'll write custom hooks and use popular hook management frameworks.

flowchart LR
  A[git commit] --> B[pre-commit hook]
  B --> C{Pass?}
  C -->|No| D[Hook aborts commit]
  C -->|Yes| E[Commit message editor]
  E --> F[commit-msg hook]
  F --> G{Pass?}
  G -->|No| H[Hook aborts commit]
  G -->|Yes| I[post-commit hook]
  I --> J[Commit recorded]

Client-Side vs Server-Side Hooks

Hook Type Location Trigger Common Uses
pre-commit Local Before commit message Lint, format, check secrets
prepare-commit-msg Local Before commit editor opens Auto-generate messages
commit-msg Local After message is written Validate message format
post-commit Local After commit completes Notify, log
pre-push Local Before push Run tests, check branches
pre-receive Server Before server accepts push Enforce policies
update Server Per-branch on push Branch-specific rules
post-receive Server After push accepted Deploy, notify

Writing a Pre-Commit Hook

Create a pre-commit hook that prevents committing debug statements:

#!/bin/bash
# .git/hooks/pre-commit

echo "Running pre-commit checks..."

if git diff --cached --name-only | grep -q "\.js$"; then
  if git diff --cached | grep -q "debugger\|console\.log"; then
    echo "ERROR: Remove debugger/console.log before committing"
    exit 1
  fi
fi

echo "Pre-commit checks passed"
exit 0

Make it executable:

chmod +x .git/hooks/pre-commit

Now try to commit a file with console.log:

echo "console.log('test')" > app.js
git add app.js
git commit -m "Add app.js"

Expected output:

Running pre-commit checks...
ERROR: Remove debugger/console.log before committing

Writing a Commit-Msg Hook

Validate commit messages follow a conventional format:

#!/bin/bash
# .git/hooks/commit-msg

# Pattern: type(scope): description
pattern="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}$"

if ! grep -qE "$pattern" "$1"; then
  echo "ERROR: Commit message must match: type(scope): description"
  echo "Example: feat(login): add OAuth support"
  exit 1
fi
exit 0

Using Hook Management Tools

Shared hooks via a tools directory:

# Create a hooks directory in your repo
mkdir -p .githooks
git config core.hooksPath .githooks

Now all hooks in .githooks/ are part of your repository and shared across clones.

Server-Side Hooks

Server-side hooks run on the Git server during push operations:

Hook Trigger Use Case
pre-receive Before any refs are updated Enforce project-wide policies
update Per-ref, before each branch/tag update Branch-specific rules
post-receive After all refs are updated Deploy, notify, trigger CI

Sample pre-receive hook that blocks non-fast-forward pushes:

#!/bin/bash
# Server-side pre-receive hook
while read oldrev newrev refname; do
  if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
    # Check if this is a force push (non-fast-forward)
    if ! git merge-base --is-ancestor "$oldrev" "$newrev"; then
      echo "ERROR: Non-fast-forward push rejected for $refname"
      exit 1
    fi
  fi
done

Pre-Push Hook for Testing

Run tests before pushing:

#!/bin/bash
# .githooks/pre-push

echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
  echo "ERROR: Tests failed. Push aborted."
  exit 1
fi

Expected output on failure:

Running tests before push...
npm test failed: 1 test failing
ERROR: Tests failed. Push aborted.

Common Errors

Error Cause Fix
Hook ignored Not executable chmod +x .git/hooks/hook-name
Permission denied Wrong shebang line Use #!/bin/bash or #!/usr/bin/env bash
Hook runs despite failure Missing exit 1 Always return non-zero on failure
Hook not triggered Wrong filename Name must be exact: pre-commit, not precommit
Skipped hooks --no-verify flag used Educate team not to skip
Hook pulls image every time Docker-based hooks inefficient Use host tools or cache Docker images
core.hooksPath ignored Old Git version Requires Git 2.9+
Binary file conflicts Hooks checked in as .sample Remove .sample extension

Practice Questions

What are Git hooks?

Git hooks are executable scripts that Git automatically runs at specific points in the Git workflow. They allow you to enforce policies, run checks, and automate tasks before or after Git commands like commit, push, and merge.

How do I share hooks with my team?

Store your hooks in a .githooks directory within your repository and configure git config core.hooksPath .githooks. This makes hooks part of the repo so every clone gets them. Alternatively, use frameworks like Husky or lefthook.

What is the difference between pre-commit and pre-push hooks?

pre-commit runs before the commit is recorded, making it ideal for quick checks like linting and formatting. pre-push runs before sending commits to the remote, suitable for running full test suites that might take longer.

Can hooks be bypassed?

Yes. Developers can skip hooks with git commit --no-verify or git push --no-verify. Hooks are a convenience, not a security measure. For enforced policies, use server-side hooks or CI/CD checks.

What languages can I write hooks in?

Any language that can be executed — bash, Python, Ruby, Node.js, Go. The hook file just needs a valid shebang line and executable permission. Bash is most common for simple hooks, Python for complex logic

Challenge

Write a pre-commit hook that checks for hardcoded API keys, passwords, and tokens in staged files. Use regex patterns to detect API_KEY=, password=, and secret =. Block the commit if any match. Also write a commit-msg hook that enforces the Conventional Commits format. Create both hooks and test them with sample files.

Real-World Task

In a team repository managed by GitHub, implement a pre-commit hook that runs a linting tool (like ESLint for JavaScript or flake8 for Python) and a pre-push hook that runs the full test suite. Store hooks in .githooks/ and configure core.hooksPath. Push the hooks directory so all team members get the same checks. This setup is used at DodaTech for DodaZIP development to ensure every commit meets quality standards.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro