Gitignore and Hooks¶
Tell Git what never belongs in the repo, and run automatic checks before commit or push. Commands: Git Command Set (e.g.
git rm --cached).
See also: Git Command Set · Commit Conventions · Collaboration
How to read this page¶
- Concepts — what
.gitignoreis; build intuition first - Overview — rules, commands, and hook type tables
- Scenarios — problem-driven walkthroughs with examples
- Symptom lookup — at the bottom for quick fixes
What .gitignore does¶
.gitignore is a plain-text rule file in the repository. Matching paths are not picked up by default git add and usually do not show as untracked in git status—unless they were already tracked.
In plain terms: “Git pretends these paths do not exist.”
Effect: Applies to untracked paths (or paths you untrack). It does not remove files already committed from history by itself.
Overview¶
.gitignore rule cheat sheet¶
| Pattern | In plain terms | Scenario |
|---|---|---|
foo/ | Ignore a directory | Rule syntax |
*.log | Match extension at any depth | Rule syntax |
/build | Repo root only | Rule syntax |
**/tmp | Any depth | Rule syntax |
!path | Un-ignore a path | Rule syntax, Keep one file |
# | Comment | Rule syntax |
Nested .gitignore | Rules stack with parent | Monorepo nested |
core.excludesfile | Machine-wide ignore file | Global ignore |
Git commands used in this doc¶
| Command / config | Job | Scenario | Command set |
|---|---|---|---|
git check-ignore -v <path> | Show matching rule | Nested | — |
git rm --cached | Untrack, keep on disk | Already tracked | git rm |
git rm -r --cached | Untrack a tree | Already tracked | git rm |
git restore --staged | Unstage | Secrets staged | git restore |
git config --global core.excludesfile | Global ignore path | Global ignore | git config |
git config core.hooksPath | Versioned hooks dir | Team hooks | git config |
git commit --no-verify | Skip commit hooks | Skip hooks | git commit |
git push --no-verify | Skip push hooks | Skip hooks | git push |
chmod +x .git/hooks/* | Make hooks executable | pre-commit, etc. | — |
Hook types cheat sheet¶
| Hook | When | Typical job | Scenario |
|---|---|---|---|
pre-commit | Before git commit | Lint, format, block large files | pre-commit |
commit-msg | After message, before commit | Message format, block trailers | commit-msg |
pre-push | Before git push | Tests, branch checks | pre-push |
post-checkout | After branch switch | Install deps, clear caches | — |
post-merge | After merge | npm install, migration hints | — |
Full list: git-scm githooks.
Team hook tooling¶
| Approach | In plain terms | Scenario |
|---|---|---|
core.hooksPath | Version .githooks/ in the repo | Team hooks |
| Husky / Lefthook | Register on npm install | Team hooks |
| CI / PR checks | Catch --no-verify bypasses | Team hooks |
Scenario: first .gitignore on a new project¶
Typical uses:
- Node / frontend: dependencies,
dist, build caches - Python: virtualenvs,
__pycache__ - Editors:
.idea,.vscode(sometimes keep shared recommendations) - OS junk:
.DS_Store
# Dependencies and build output
node_modules/
dist/
.next/
out/
# Python
.venv/
**/__pycache__/
# Logs
*.log
# Editor (keep team VS Code extension list)
.vscode/*
!.vscode/extensions.json
.idea/
# macOS
.DS_Store
Note
Commit .gitignore itself so teammates get the same rules after clone.
Scenario: rule syntax that actually works¶
| Pattern | Meaning | Example |
|---|---|---|
foo/ | Ignore directory foo | node_modules/ |
*.log | Any .log file in any folder | npm-debug.log |
/build | Only at repo root | Does not match src/build |
**/tmp | tmp at any depth | a/b/tmp |
!path | Negation—un-ignore | See below |
# | Comment |
In plain terms: “* matches extensions, leading / anchors to root, ! pulls paths back out of ignore.”
Scenario: nested .gitignore in a monorepo¶
Common layout: root for global noise, packages/apps for local build artifacts.
repo/
├── .gitignore # node_modules, .DS_Store
├── apps/web/.gitignore # apps/web/.next
└── packages/api/.gitignore
Effect: Rules stack; deeper files add/refine behavior. When confused, see which rule wins:
Scenario: global ignore on your machine¶
Skip .DS_Store in every repo without copying rules:
git config --global core.excludesfile ~/.config/git/ignore
# In ~/.config/git/ignore:
# .DS_Store
# *.swp
In plain terms: “Personal clutter I never want to commit anywhere.”
Effect: Local to your Git config; nothing is shared via the remote unless already tracked.
Scenario: rules exist but files stay tracked¶
.gitignore does not untrack committed files. Classic mistake: commit dist/, add ignore later—Git still tracks it.
In plain terms: “The file is already on the books; ignore alone is not enough.”
Effect: git rm --cached updates the index only; files stay on disk; next commit stops tracking them on the remote.
# Stop tracking one file, keep on disk
git rm --cached config.local.json
# Whole tree
git rm -r --cached dist/
git status
git commit -m "chore: stop tracking dist and local config"
Warning
After you stop tracking, teammates’ pulls may delete their tracked copies depending on merge outcome. Coordinate in the PR description.
Scenario: secrets or huge files staged by mistake¶
Typical uses:
.envwith API keys stagedgit add .grabbednode_modulesor gigabytes of data
# Unstage; working tree unchanged
git restore --staged .env
echo ".env" >> .gitignore
# Not pushed yet: amend or reset (see git-commands reset / amend)
# Already pushed: rotate secrets + history rewrite (BFG / filter-repo)—deleting the file is not enough
Warning
Secrets pushed to a remote are compromised. Rotate credentials first; history cleanup is secondary.
Scenario: ignore a folder except one file¶
In plain terms: “Ignore the whole directory, then ! the one file you need in Git.”
Scenario: monorepo .gitignore patterns¶
Typical Node + Turbo monorepo (many open templates follow this shape):
node_modules/
dist/
.next/
.turbo/
*.tsbuildinfo
**/src/**/*.js
**/src/**/*.js.map
!**/src/**/*.config.js
.venv/
releases/
Typical uses:
- Ignore
.pnpm-storeafter local installs - Use
!packages/foo/generated/in a subfolder when one package must track generated output
What Git hooks are¶
Hooks are scripts Git runs before or after actions like commit or push. They live under .git/hooks/ (names like pre-commit, commit-msg—no extension, must be executable).
In plain terms: “Automated bouncers at the door of commit and push.”
Effect: Non-zero exit code aborts the Git operation. See Hook types cheat sheet above.
Scenario: lint / format before commit (pre-commit)¶
Typical uses:
- ESLint / Prettier on staged files
- Block debug
console.logfrom landing on main
When it runs: During git commit, before the commit object is created.
Teams often use Husky or Lefthook to version hook scripts and wire them on npm install instead of hand-copying into .git/hooks.
Scenario: validate commit messages (commit-msg)¶
Typical uses:
- Enforce commit conventions (
feat:,fix:, etc.) - Reject auto-injected
Co-authored-by: Cursor ...trailers
#!/bin/sh
# .git/hooks/commit-msg
MSG_FILE="$1"
MSG=$(cat "$MSG_FILE")
echo "$MSG" | grep -q 'Co-authored-by: Cursor' && {
echo "error: Cursor Co-authored-by trailer is not allowed"
exit 1
}
head -1 "$MSG_FILE" | grep -q . || {
echo "error: empty commit subject"
exit 1
}
exit 0
When it runs: After the message is written, before the commit is finalized.
Note
If the project ships scripts/git-hooks/install.sh (or .ps1), run it once after clone to install shared hooks.
Scenario: tests before push (pre-push)¶
Typical uses:
- Block pushes while unit tests fail
- Quick smoke test before updating a shared branch
When it runs: During git push, before objects are sent to the remote.
Scenario: hook blocked me—skip or fix?¶
In plain terms: “The bouncer said no—you used --no-verify to walk past anyway.”
Typical uses:
- Temporary local WIP (still avoid on shared branches)
- Hook false positive during an emergency hotfix
Warning
Do not make --no-verify a habit on shared branches. Fix the hook or the code; CI should still catch bypasses on PR.
Scenario: share hooks with the team¶
.git/hooks is not cloned by default. Common approaches:
core.hooksPath→ versioned folder in the repo
- Husky / Lefthook → declared in
package.json, registered on install - CI as backstop → hooks are local first line; PR checks catch
--no-verify
repo/
├── .githooks/
│ ├── pre-commit
│ └── commit-msg
├── .gitignore # do not ignore .githooks
└── package.json
Quick lookup: symptom → action¶
| Symptom | Try first |
|---|---|
| New file should not be in repo | Add to .gitignore → check git status |
| Ignored but still shows modified | Already tracked → git rm --cached |
| Which rule matched? | git check-ignore -v <path> |
| Commit blocked by hook | Read error, fix code; --no-verify only if justified |
| Teammate has no hooks | Document install; enforce in CI |
| Secret was pushed | Rotate credentials + history cleanup |
More commands: Git Command Set.