Git 4e 🧩 Ignoring Files
Every repository generates files that should never be committed — build artifacts, dependency folders, editor settings, secrets. .gitignore is how Git is told to look the other way. It’s the file that keeps a repository clean, focused, and safe.
Key point: .gitignore only affects untracked files. If a file is already committed, adding it to .gitignore doesn’t remove it — Git keeps tracking it. You have to untrack it first, then ignore it.
What is .gitignore?
.gitignore is a plain text file that tells Git which files and directories to ignore. It lives at the root of the project — or in any subdirectory — and applies to that directory and everything beneath it.
Ignore rules exist for three reasons:
- Cleanliness — no build output, no dependency folders, no temp files
- Safety — no secrets, no API keys, no
.envfiles - Consistency — no editor-specific files cluttering diffs
Without .gitignore, every node_modules/ folder would be committed — hundreds of megabytes of reproducible dependencies in every clone.
Why it matters: A repository should contain source code and configuration — not generated files.
.gitignoreis how you enforce that rule.
Basic .gitignore syntax
Create the file at the project root:
touch .gitignore
Then add patterns, one per line.
Simple patterns:
*.log
node_modules/
.env
| Pattern | Ignores |
|---|---|
*.log | Every .log file, anywhere |
node_modules/ | That directory and its contents |
.env | The .env file at this level |
Negation:
build/
!build/index.html
The ! prefix un-ignores. So build/ ignores the whole folder, but !build/index.html brings one file back into tracking.
Comments:
# Ignore node_modules
node_modules/
Lines starting with # are comments — they help future readers understand why a pattern exists.
Why negation matters: Sometimes you want to ignore a directory but keep one specific file.
!path/to/fileoverrides the earlier ignore, as long as the file’s parent directories aren’t also ignored.
Common patterns by technology
The patterns differ by stack. These are the ones you’ll use most.
Node.js:
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
.env
.env.local
.env.*.local
dist/
build/
.vscode/
.idea/
*.swp
Python:
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv/
db.sqlite3
media/
staticfiles/
.env
Java:
target/
*.jar
*.war
*.ear
.DS_Store
.idea/
*.iml
*.iws
Each stack has its own set of generated files — dependencies, compiled output, test artifacts, environment files. The patterns match what that toolchain produces.
Why per-stack lists help: You don’t have to remember every generated file. Standard lists exist for every ecosystem. Start from one, then customize.
Advanced pattern matching
Gitignore patterns support wildcards and special syntax.
Wildcards:
| Pattern | Matches |
|---|---|
*.tmp | Files ending in .tmp |
.* | Files starting with . |
? | Any single character |
[abc] | Any of a, b, c |
Directory-only:
| Pattern | Effect |
|---|---|
folder/ | Ignores the directory and its contents |
folder | Ignores any file or directory named folder |
/folder/ | Only at the repository root |
The trailing slash changes meaning. folder/ matches only directories named folder. folder matches both files and directories.
Recursive directory matches:
*/node_modules/
*/build/
Ignores node_modules and build at any depth.
Ignore everything except specific files:
*
!important.txt
!src/
The * ignores everything. The negations bring back specific files and directories.
Anchoring:
| Pattern | Matches |
|---|---|
foo | foo at any depth |
/foo | foo only at root |
foo/ | Directory foo at any depth |
/foo/ | Directory foo at root only |
Anchoring gives you control over where a pattern applies.
Why anchoring matters: In a big repository,
foomatchesfooeverywhere./foomatches only at root. That distinction prevents accidental matches deep in the tree.
Real-world .gitignore examples
Complete Node.js .gitignore:
# Logs
logs
*.log
npm-debug.log
yarn-debug.log
yarn-error.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage
coverage
.coverage
.nyc_output
# Dependencies
node_modules/
jspm_packages/
bower_components
# Build output
dist/
build/
.next/
.nuxt/
.vuepress/dist
# TypeScript
typings/
# Cache
.npm
.eslintcache
.cache
# Environment
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
Python Django .gitignore:
# Byte-compiled
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
build/
dist/
*.egg-info/
.installed.cfg
*.egg
# Django
*.log
local_settings.py
db.sqlite3
# Flask
instance/
.webassets-cache
# Jupyter
.ipynb_checkpoints
# pyenv
.python-version
# pipenv
Pipfile.lock
# Celery
celerybeat-schedule
Both lists cover the artifacts of their ecosystems — generated files, caches, dependencies, environment config, editor settings.
Why full examples help: The patterns are common across projects of the same stack. Copy a good starting list, then prune and add.
Special .gitignore features
Force binary handling via .gitattributes:
.gitignore decides what to track; .gitattributes decides how Git treats it.
*.png binary
*.jpg binary
*.gif binary
These tell Git not to try line-ending conversion or diffing on binary files.
Set line endings:
*.txt text eol=lf
*.js text eol=lf
Every .txt and .js file is normalized to LF.
Negation with exceptions:
*.tmp
!important.tmp
!temp.log
Ignore all .tmp files, but keep those two.
Why .gitattributes matters: Git isn’t perfect at detecting binary files. Forcing the flag prevents corruption and diff noise. Line-ending rules prevent “whole file changed” diffs when collaborators use different systems.
Managing multiple .gitignore files
Git reads .gitignore from every directory — top to bottom. Rules cascade.
Root .gitignore:
node_modules/
.env
Subdirectory .gitignore (e.g., src/.gitignore):
/src/
/src/node_modules/
The subdirectory’s rules apply within that subtree. They can also un-ignore something the parent ignored:
!/src/node_modules/
That negation brings src/node_modules back into tracking, overriding the root’s node_modules/.
Why nested ignores exist: Different parts of a repository may need different rules. A
docs/folder might ignore generated HTML; asrc/folder might not. Nested.gitignorefiles keep rules local.
Practical commands for .gitignore
Five commands cover daily use.
List tracked files:
git ls-files
Shows what Git actually tracks. If a file you wanted ignored shows up here, .gitignore isn’t working — probably because the file was already committed.
Check whether a file is ignored:
git check-ignore -v FILE
Shows the specific rule that ignores the file — the file path, line number, and pattern.
Stop tracking a file but keep it locally:
git rm --cached FILE
Removes the file from Git’s index but leaves it on disk. Add it to .gitignore first, then run this, and commit — the file becomes untracked.
See ignored files:
git status --ignored
Shows the files Git is ignoring alongside normal status.
Add a pattern:
echo "*.tmp" >> .gitignore
Appends a rule without opening an editor.
Why
git rm --cachedis essential: If a file was committed before.gitignorewas set up, adding it to.gitignoredoes nothing.git rm --cacheduntracks it while keeping the local copy. Then commit and the ignore rule takes effect.
Best Practices
1. Start with a template. Every ecosystem has standard .gitignore files. github.com/github/gitignore has one for almost every stack.
2. Keep it organized. Group related patterns together with comments. A .gitignore with 50 unsorted lines is unreadable.
3. Test thoroughly. Use git status --ignored to confirm patterns work. Check new files with git check-ignore -v.
4. Update regularly. New tools add new artifacts. When you add a build tool, add its output to .gitignore.
5. Document exceptions. Negation rules especially need comments — otherwise future readers won’t know why one file is tracked inside an ignored folder.
6. Agree as a team. A shared .gitignore prevents one developer from committing .idea/ while another commits .vscode/.
Why discipline matters here: A bad
.gitignoreeither commits junk or ignores something important. Both are painful. Take the extra minute to do it right.
Complete Example Session
# ============================================
# PART 1: CREATE .GITIGNORE
# ============================================
touch .gitignore
# ============================================
# PART 2: ADD BASIC PATTERNS
# ============================================
cat > .gitignore << 'EOF'
# Dependencies
node_modules/
# Build output
dist/
build/
# Logs
*.log
# Environment
.env
.env.local
# IDE
.vscode/
.idea/
EOF
# ============================================
# PART 3: VERIFY
# ============================================
git status
# [ Untracked files: ]
# [ .gitignore ]
# (node_modules and other files aren't listed)
# ============================================
# PART 4: CHECK A FILE
# ============================================
git check-ignore -v node_modules/express/index.js
# [ .gitignore:2:node_modules/ node_modules/express/index.js ]
# ============================================
# PART 5: FILE ALREADY TRACKED
# ============================================
git add .env
git commit -m "Oops, committed .env"
# [ The file is tracked ]
# .gitignore won't help until we untrack it
git rm --cached .env
# [ rm '.env' ]
git commit -m "Remove .env from tracking"
# [ Commit created, .env remains on disk ]
# Now .gitignore prevents re-adding
git status
# [ .env is not listed ]
# ============================================
# PART 6: SEE IGNORED FILES
# ============================================
git status --ignored
# [ Ignored files: ]
# [ node_modules/ ]
# [ .env ]
# [ dist/ ]
# ============================================
# PART 7: NESTED GITIGNORE
# ============================================
mkdir -p src
cat > src/.gitignore << 'EOF'
# Ignore compiled JS in src
*.js
!important.js
EOF
# ============================================
# PART 8: ADD A NEGATION
# ============================================
cat >> .gitignore << 'EOF'
# Ignore all .tmp but keep this one
*.tmp
!important.tmp
EOF
# ============================================
# PART 9: TRACKED FILES
# ============================================
git ls-files
# [ .gitignore ]
# [ src/.gitignore ]
# [ README.md ]
# (no node_modules, no .env)
# ============================================
# PART 10: GITATTRIBUTES
# ============================================
cat > .gitattributes << 'EOF'
*.png binary
*.jpg binary
*.gif binary
*.txt text eol=lf
*.js text eol=lf
EOF
git add .gitattributes
git commit -m "Add .gitattributes for binary and line-ending rules"
Quick Reference
Basic Syntax
| Pattern | Ignores |
|---|---|
*.log | Files ending in .log |
node_modules/ | Directory |
.env | A specific file |
/foo | Only at root |
!file | Un-ignore |
# comment | Comment |
Anchoring
| Pattern | Matches |
|---|---|
foo | foo anywhere |
/foo | foo at root only |
foo/ | Directory foo anywhere |
/foo/ | Directory foo at root |
**/foo | foo at any depth |
Common Patterns
| Pattern | Purpose |
|---|---|
node_modules/ | Node dependencies |
dist/ | Build output |
.env | Environment variables |
*.log | Log files |
.DS_Store | macOS Finder |
.vscode/ | VS Code settings |
__pycache__/ | Python bytecode |
target/ | Java build output |
Useful Commands
| Command | Purpose |
|---|---|
git ls-files | List tracked files |
git check-ignore -v FILE | Show matching rule |
git rm --cached FILE | Untrack (keep local) |
git status --ignored | Show ignored files |
git clean -Xdn | Preview ignored file deletion |
.gitignore vs .gitattributes
| File | Purpose |
|---|---|
.gitignore | Which files to ignore |
.gitattributes | How to treat files |
Managing Already-Committed Files
| Step | Command |
|---|---|
| 1. Add to ignore | echo "file" >> .gitignore |
| 2. Untrack | git rm --cached file |
| 3. Commit | git commit -m "Untrack file" |
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| File already tracked | .gitignore has no effect | git rm --cached |
| No trailing slash | Matches files too | Add / for directories |
| Missing root anchor | Matches deep in tree | Use /pattern |
| Ignored parent | Negation fails | Can’t un-ignore inside ignored parent |
| Secrets committed | Leak before ignore | Rotate keys, then untrack |
| Comment missing | Unclear intent | Add a comment |
| Case sensitivity | .ENV vs .env | Git is case-sensitive on Linux |
Real-World Examples
1. Create .gitignore
touch .gitignore
The first step in every project.
2. Ignore node_modules
node_modules/
Prevents committing hundreds of MB.
3. Ignore build output
dist/
build/
Generated files don’t belong in version control.
4. Ignore environment files
.env
.env.local
Never commit secrets.
5. Ignore a specific file
config/local.json
One file, one path.
6. Un-ignore a file
build/
!build/index.html
Keep one file inside an ignored folder.
7. Check a file
git check-ignore -v .env
Shows which rule applies.
8. See ignored files
git status --ignored
Full picture.
9. Untrack an already-tracked file
git rm --cached .env
git commit -m "Stop tracking .env"
After adding it to .gitignore.
10. Verify tracked files
git ls-files
Confirms nothing unexpected is tracked.
11. Nested .gitignore
# src/.gitignore
*.js
!important.js
Local rules for a subdirectory.
12. Binary file handling
# .gitattributes
*.png binary
*.jpg binary
Prevents Git from trying to text-diff them.
13. Line endings
# .gitattributes
*.txt text eol=lf
*.sh text eol=lf
Forces Unix line endings.
14. Preview ignored file deletion
git clean -Xdn
Shows what would be removed — safe to preview.
15. Add a pattern quickly
echo "*.tmp" >> .gitignore
Fast inline addition.
Visual: How .gitignore Works
┌──────────────────────────────────────────────┐
│ Working directory │
│ │
│ src/ ← tracked │
│ package.json ← tracked │
│ README.md ← tracked │
│ │
│ node_modules/ ← ignored │
│ dist/ ← ignored │
│ .env ← ignored │
│ *.log ← ignored │
│ │
├──────────────────────────────────────────────┤
│ .gitignore │
│ │
│ node_modules/ │
│ dist/ │
│ .env │
│ *.log │
│ │
└──────────────────────────────────────────────┘
Visual: Pattern Matching
┌──────────────────────────────────────────────┐
│ Pattern: *.log │
│ │
│ Matches: │
│ app.log │
│ logs/error.log │
│ src/debug.log │
│ │
├──────────────────────────────────────────────┤
│ Pattern: /build/ │
│ │
│ Matches: │
│ build/ │
│ │
│ Does not match: │
│ src/build/ │
│ │
├──────────────────────────────────────────────┤
│ Pattern: build/ │
│ │
│ Matches: │
│ build/ │
│ src/build/ │
│ deep/nested/build/ │
│ │
└──────────────────────────────────────────────┘
Visual: The Untrack Workflow
┌──────────────────────────────────────────────┐
│ 1. .env is already committed │
│ │
│ 2. Add to .gitignore │
│ echo ".env" >> .gitignore │
│ │
│ 3. Still tracked — ignore has no effect │
│ │
│ 4. Untrack it │
│ git rm --cached .env │
│ │
│ 5. Commit │
│ git commit -m "Untrack .env" │
│ │
│ 6. File stays on disk, no longer tracked │
│ │
└──────────────────────────────────────────────┘
Visual: Negation Rules
┌──────────────────────────────────────────────┐
│ build/ │
│ !build/index.html │
│ │
│ Result: │
│ │
│ build/ │
│ ├── app.js ← ignored │
│ ├── styles.css ← ignored │
│ └── index.html ← tracked │
│ │
├──────────────────────────────────────────────┤
│ *.log │
│ !important.log │
│ │
│ Result: │
│ │
│ app.log ← ignored │
│ important.log ← tracked │
│ │
└──────────────────────────────────────────────┘
Visual: Nested .gitignore
┌──────────────────────────────────────────────┐
│ project/ │
│ ├── .gitignore │
│ │ node_modules/ │
│ │ │
│ ├── src/ │
│ │ ├── .gitignore │
│ │ │ *.js │
│ │ │ !important.js │
│ │ │ │
│ │ ├── main.ts ← tracked │
│ │ ├── helper.js ← ignored │
│ │ └── important.js ← tracked │
│ │ │
│ └── node_modules/ ← ignored by root │
│ │
└──────────────────────────────────────────────┘
Summary
| Command | Purpose |
|---|---|
touch .gitignore | Create the file |
git check-ignore -v FILE | Show matching rule |
git rm --cached FILE | Untrack, keep local |
git status --ignored | Show ignored files |
git ls-files | List tracked files |
git clean -Xdn | Preview ignored deletion |
Key takeaways:
.gitignoretells Git which files not to track- Patterns support wildcards, negation (
!), and anchoring (/) .gitignoreonly affects untracked files — committed files stay tracked- To ignore a tracked file: add to
.gitignore,git rm --cached, then commit - Use
git check-ignore -v FILEto see why a file is ignored - Use
git status --ignoredto see the full picture - Use
git ls-filesto confirm what’s actually tracked - Patterns apply to the current directory and below — nested
.gitignorefiles override parents - Never commit secrets —
.env, API keys, credentials - Start from a stack-specific template, then customize
- Use
.gitattributesfor binary handling and line-ending rules - Test your rules — a wrong
.gitignoreeither commits junk or hides important files - Keep it organized with comments and grouping
- Agree as a team on what’s ignored
Remember: A clean repository is a maintainable one. .gitignore is your filter — keep generated files, dependencies, secrets, and editor artifacts out of tracking. Start with a template, test as you go, and update it when new tools arrive. Once you’ve untracked a file and ignored it, Git stops noticing it entirely — which is exactly the point.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!