Git Hooks
Git Hooks
Section titled “Git Hooks”Git hooks are scripts that run automatically at specific points in the Git workflow — before a commit, after a merge, before a push, and more. They’re stored in .git/hooks/.
Hook Types
Section titled “Hook Types”| Hook | Trigger | Common Uses |
|---|---|---|
pre-commit | Before committing | Run linter, format code, check for secrets |
commit-msg | After commit message is written | Validate message format |
prepare-commit-msg | Before message editor opens | Prepend issue number |
post-commit | After commit is created | Notify, trigger CI locally |
pre-push | Before push | Run tests |
pre-rebase | Before rebase | Safety checks |
Writing a Hook
Section titled “Writing a Hook”Hooks are executable scripts in .git/hooks/. Create pre-commit:
#!/bin/sh# Run ESLint on staged TypeScript filesSTAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.ts$')
if [ -n "$STAGED" ]; then npx eslint $STAGED if [ $? -ne 0 ]; then echo "ESLint failed — fix errors before committing" exit 1 fifi# Make it executablechmod +x .git/hooks/pre-commitHusky (Shareable Hooks)
Section titled “Husky (Shareable Hooks)”The problem with raw Git hooks: .git/hooks/ isn’t committed. Husky stores hooks in the repo and registers them via npm prepare.
npm install -D huskynpx husky initThis creates a .husky/ directory and a prepare script in package.json.
pre-commit with lint-staged
Section titled “pre-commit with lint-staged”Only lint the files that are staged (not the whole project):
npm install -D lint-staged.husky/pre-commit:
npx lint-stagedpackage.json:
{ "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{js,jsx}": ["eslint --fix"], "*.{css,scss}": ["prettier --write"] }}commit-msg — Enforce Conventional Commits
Section titled “commit-msg — Enforce Conventional Commits”npm install -D @commitlint/cli @commitlint/config-conventionalcommitlint.config.js:
export default { extends: ['@commitlint/config-conventional'] };.husky/commit-msg:
npx --no -- commitlint --edit $1Now commit messages must follow the format:
feat: add user authenticationfix: resolve null pointer in paymentchore: upgrade dependenciesdocs: update API referencepre-push — Run Tests
Section titled “pre-push — Run Tests”.husky/pre-push:
#!/bin/shnpm testSkipping Hooks
Section titled “Skipping Hooks”# Skip hooks for a commit (use sparingly — breaks CI guarantees)git commit --no-verify -m "Emergency fix"git push --no-verifyHook Examples
Section titled “Hook Examples”Check for debug statements
Section titled “Check for debug statements”#!/bin/sh# .husky/pre-commit — find leftover console.log / debugger
if git diff --cached | grep -E '^\+.*(console\.log|debugger)' > /dev/null; then echo "Found console.log or debugger in staged changes." exit 1fiPrepend branch name to commit message
Section titled “Prepend branch name to commit message”#!/bin/shBRANCH=$(git symbolic-ref --short HEAD)JIRA=$(echo $BRANCH | grep -oE 'PROJ-[0-9]+')
if [ -n "$JIRA" ]; then sed -i "1s/^/$JIRA: /" "$1"fiConventional Commits
Section titled “Conventional Commits”A commit message specification:
<type>(<scope>): <description>
[optional body]
[optional footer]Types: feat, fix, docs, style, refactor, test, chore, perf
Benefits:
- Automated changelog generation
- Semantic version bumping
- Clear commit history
- Better PR reviews