Tour of Git
Advanced Git / Lesson 5.6

Git Hooks — Automation

Git hooks are scripts that run automatically at specific points in the Git workflow. They let you enforce standards, run tests, and automate tasks without relying on developer discipline.

How Hooks Work

Hooks live in the .git/hooks/ directory. When you initialize a repo, Git creates sample hooks with a .sample extension. Remove the extension to activate them:

.git/hooks/
  pre-commit.sample     →  rename to pre-commit to activate
  commit-msg.sample     →  rename to commit-msg to activate
  pre-push.sample       →  rename to pre-push to activate

Hooks can be written in any language — Bash, Python, Ruby, Node.js — as long as the file is executable.

Most Useful Hooks

pre-commit

Runs before a commit is created. Use it for:

  • Linting — check code style
  • Formatting — auto-format staged files
  • Secrets detection — prevent committing API keys
#!/bin/sh
# .git/hooks/pre-commit

# Run linter on staged files
npm run lint --staged
if [ $? -ne 0 ]; then
  echo "Lint failed. Fix errors before committing."
  exit 1
fi

If the script exits with a non-zero code, the commit is aborted.

commit-msg

Runs after you write a commit message. Use it to enforce message format:

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

# Require commit messages to start with a type prefix
if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore):" "$1"; then
  echo "Commit message must start with: feat:, fix:, docs:, etc."
  exit 1
fi

pre-push

Runs before pushing to a remote. Use it for:

  • Running the full test suite
  • Preventing pushes to protected branches
#!/bin/sh
# .git/hooks/pre-push

# Run tests before pushing
npm test
if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi

Hook Tools

Managing hooks manually is tedious. Popular tools help:

  • Husky (JavaScript) — manages hooks via package.json
  • pre-commit (Python) — multi-language hook framework
  • lefthook (Go) — fast, language-agnostic hook manager

Example with Husky:

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}

Client-Side vs Server-Side

TypeWhereExamples
Client-sideDeveloper's machinepre-commit, commit-msg, pre-push
Server-sideGit serverpre-receive, post-receive, update

Server-side hooks enforce rules that cannot be bypassed. Client-side hooks can be skipped with --no-verify.

Common Patterns

  • Lint on commit — catch style issues early
  • Run tests on push — prevent broken code from reaching the remote
  • Enforce commit messages — keep history clean and searchable
  • Auto-format — apply Prettier/Black on staged files
  • Check branch names — enforce naming conventions like feature/, fix/

Limitations

  • Hooks are not pushed with the repo (they live in .git/hooks/)
  • Developers must set them up locally (or use tools like Husky)
  • Client-side hooks can be bypassed with git commit --no-verify