Git Hooks & Automation 🪝
Git hooks are scripts that run automatically at specific points in your Git workflow — before a commit, before a push, or after a merge. By combining Git hooks with tools like Husky, lint-staged, and commitlint, you can automate code quality enforcement so that every commit is linted, formatted, and follows your team’s conventions.
🎯 What Are Git Hooks?
Section titled “🎯 What Are Git Hooks?”Git hooks are scripts stored in the .git/hooks/ directory of a repository. They run automatically when triggered by Git operations:
| Hook | Trigger | Common Use |
|---|---|---|
pre-commit | Before a commit is created | Lint and format staged files |
commit-msg | After commit message is entered | Validate commit message format |
pre-push | Before pushing to remote | Run tests |
post-merge | After a merge completes | Run npm install |
post-checkout | After switching branches | Run npm install |
The challenge is that .git/hooks/ is not tracked by Git, so sharing hooks across a team requires a tool like Husky.
🐶 Husky Setup
Section titled “🐶 Husky Setup”Husky makes Git hooks easy to manage and share with your team.
Initialize Husky
Section titled “Initialize Husky”npx husky initThis command:
- Installs
huskyas a dev dependency - Creates a
.husky/directory - Adds a
preparescript topackage.jsonthat runshuskyonnpm install - Creates a sample
pre-commithook
Your package.json now includes:
{ "scripts": { "prepare": "husky" }}🔍 Pre-Commit Hooks with lint-staged
Section titled “🔍 Pre-Commit Hooks with lint-staged”Running linters on your entire codebase before every commit is slow. lint-staged solves this by running tools only on files that are staged for commit.
Install lint-staged
Section titled “Install lint-staged”npm install --save-dev lint-stagedConfigure lint-staged
Section titled “Configure lint-staged”Add the configuration to package.json:
{ "lint-staged": { "*.{ts,html}": [ "eslint --fix" ], "*.{ts,html,scss,json,md}": [ "prettier --write" ] }}Update the Pre-Commit Hook
Section titled “Update the Pre-Commit Hook”Edit .husky/pre-commit:
npx lint-stagedNow when you run git commit:
- lint-staged identifies files staged for commit
- ESLint runs on
.tsand.htmlfiles with auto-fix - Prettier formats all supported file types
- If any tool reports an unfixable error, the commit is aborted
- Fixed files are automatically re-staged
Example Workflow
Section titled “Example Workflow”# Stage your changesgit add src/app/user/user.component.ts
# Commit — lint-staged runs automaticallygit commit -m "feat: add user component"
# Output:# ✔ Preparing lint-staged...# ✔ Running tasks for staged files...# ✔ *.{ts,html} — eslint --fix# ✔ *.{ts,html,scss,json,md} — prettier --write# ✔ Applying modifications from tasks...# ✔ Cleaning up...# [main abc1234] feat: add user component📝 Commit Message Conventions
Section titled “📝 Commit Message Conventions”Consistent commit messages make your Git history readable and enable automated changelogs.
Conventional Commits
Section titled “Conventional Commits”The Conventional Commits specification defines a standard format:
<type>(<scope>): <subject>
<body>
<footer>Types:
| Type | Description |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation changes |
style | Formatting (no code change) |
refactor | Code change that neither fixes nor adds |
perf | Performance improvement |
test | Adding or fixing tests |
build | Build system or dependency changes |
ci | CI/CD configuration changes |
chore | Other changes (tooling, etc.) |
Examples:
feat(auth): add login with Google OAuthfix(user-profile): prevent crash when avatar URL is nulldocs(readme): update installation instructionsrefactor(api): replace callbacks with async/awaitperf(dashboard): lazy load chart library✅ commitlint Setup
Section titled “✅ commitlint Setup”commitlint validates commit messages against the Conventional Commits format.
Install commitlint
Section titled “Install commitlint”npm install --save-dev @commitlint/cli @commitlint/config-conventionalConfigure commitlint
Section titled “Configure commitlint”Create commitlint.config.js in your project root:
module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert', ], ], 'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']], 'subject-max-length': [2, 'always', 72], 'body-max-line-length': [1, 'always', 100], },};Add the commit-msg Hook
Section titled “Add the commit-msg Hook”Create the hook file .husky/commit-msg:
npx --no -- commitlint --edit $1How It Works
Section titled “How It Works”# ❌ Rejected — no type prefixgit commit -m "add login feature"# ⧗ input: add login feature# ✖ subject may not be empty [subject-empty]# ✖ type may not be empty [type-empty]
# ❌ Rejected — wrong typegit commit -m "feature: add login"# ✖ type must be one of [feat, fix, docs, ...] [type-enum]
# ✅ Acceptedgit commit -m "feat(auth): add login feature"🚀 Pre-Push Hooks
Section titled “🚀 Pre-Push Hooks”Run tests before pushing to catch issues early:
Create .husky/pre-push:
npx ng test --no-watch --code-coverage📦 Complete Setup from Scratch
Section titled “📦 Complete Setup from Scratch”Here’s the full setup for a new Angular project:
Step 1: Install Dependencies
Section titled “Step 1: Install Dependencies”npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional prettierStep 2: Initialize Husky
Section titled “Step 2: Initialize Husky”npx husky initStep 3: Configure lint-staged
Section titled “Step 3: Configure lint-staged”Add to package.json:
{ "lint-staged": { "*.{ts,html}": [ "eslint --fix" ], "*.{ts,html,scss,json,md}": [ "prettier --write" ] }}Step 4: Set Up Hooks
Section titled “Step 4: Set Up Hooks”Update .husky/pre-commit:
npx lint-stagedCreate .husky/commit-msg:
npx --no -- commitlint --edit $1Create .husky/pre-push:
npx ng test --no-watchStep 5: Configure commitlint
Section titled “Step 5: Configure commitlint”Create commitlint.config.js:
module.exports = { extends: ['@commitlint/config-conventional'],};Step 6: Verify Everything Works
Section titled “Step 6: Verify Everything Works”# Test pre-commit hookecho "test" >> README.mdgit add README.mdgit commit -m "test: verify git hooks"# Should pass lint-staged and commitlint
# Test commit message validationgit commit --allow-empty -m "bad message"# Should fail commitlint🔄 Integration with CI/CD
Section titled “🔄 Integration with CI/CD”Git hooks enforce quality locally, but CI/CD is the final safety net. Your CI should run the same checks:
name: Quality Checkson: [pull_request]
jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Needed for commitlint
- uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm'
- run: npm ci
# Validate commit messages - name: Validate commits run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to HEAD
# Lint - name: Lint run: npx ng lint
# Format check - name: Check formatting run: npx prettier --check .
# Test - name: Test run: npx ng test --no-watch --code-coverage