Git Hooks: Automate Code Quality Checks Before Every Commit

What Git Hooks Are

Git hooks are scripts that run automatically at specific points in the Git workflow. They live in .git/hooks/ and are not committed to the repository by default — so you need a way to share them with your team.

The most useful hooks:

HookWhen it runsCommon use
pre-commitBefore commit is createdLinting, formatting, tests
commit-msgAfter commit message is writtenEnforce message format
pre-pushBefore push to remoteRun full test suite
post-mergeAfter a mergeInstall new dependencies

---

Writing a pre-commit Hook

#!/usr/bin/env bash
# .git/hooks/pre-commit
set -euo pipefail

# Run ESLint on staged JS/TS files
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)
  

 || true)
if [ -n "$STAGED" ]; then
  echo "Running ESLint on staged files..."
  npx eslint $STAGED
fi

# Run Python formatting check on staged Python files
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py
  

 || true)
if [ -n "$STAGED_PY" ]; then
  python -m black --check $STAGED_PY
fi
chmod +x .git/hooks/pre-commit

---

Enforcing Commit Message Format

#!/usr/bin/env bash
# .git/hooks/commit-msg
MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,72}"

if ! echo "$MSG" | grep -qE "$PATTERN"; then
  echo "ERROR: Commit message must follow Conventional Commits format:"
  echo "  feat(scope): description"
  echo "  fix: description"
  echo "Got: $MSG"
  exit 1
fi

---

pre-push Hook for Running Tests

#!/usr/bin/env bash
# .git/hooks/pre-push
set -e

echo "Running tests before push..."
npm test -- --passWithNoTests

echo "Running type check..."
npx tsc --noEmit

This prevents broken code from reaching the remote — but be aware it adds latency to every push.

---

Sharing Hooks with Your Team

.git/hooks/ is not committed. Options:

Option 1: Symlinked hooks directory

mkdir -p .githooks
cp .git/hooks/pre-commit .githooks/
git config core.hooksPath .githooks    # use this directory for hooks

Commit .githooks/ to the repo. Each developer runs git config core.hooksPath .githooks after cloning (or automate this in a setup script).

Option 2: Husky (Node.js projects)

npm install --save-dev husky
npx husky init

Creates .husky/ directory. Add hooks there:

echo "npx eslint ." > .husky/pre-commit
chmod +x .husky/pre-commit

Husky integrates with npm prepare script so hooks install automatically on npm install.

Option 3: pre-commit (Python-based, language-agnostic)

pip install pre-commit
# .pre-commit-config.yaml
repos:
  - 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
  - repo: https://github.com/psf/black
    rev: 24.1.1
    hooks:
      - id: black
pre-commit install    # installs the pre-commit hook

---

Bypassing Hooks

git commit --no-verify    # skip pre-commit and commit-msg hooks
git push --no-verify      # skip pre-push hook

Hooks are a convenience — they run locally and can be bypassed. They are not a substitute for CI checks on the server.

Practice Git workflows in the ShellGenius Git Labs — the git-staging and git-gitignore challenges use real staging and workflow scenarios.