| |

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 .env files
  • 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. .gitignore is 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
PatternIgnores
*.logEvery .log file, anywhere
node_modules/That directory and its contents
.envThe .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/file overrides 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:

PatternMatches
*.tmpFiles ending in .tmp
.*Files starting with .
?Any single character
[abc]Any of a, b, c

Directory-only:

PatternEffect
folder/Ignores the directory and its contents
folderIgnores 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:

PatternMatches
foofoo at any depth
/foofoo 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, foo matches foo everywhere. /foo matches 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; a src/ folder might not. Nested .gitignore files 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 --cached is essential: If a file was committed before .gitignore was set up, adding it to .gitignore does nothing. git rm --cached untracks 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 .gitignore either 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

PatternIgnores
*.logFiles ending in .log
node_modules/Directory
.envA specific file
/fooOnly at root
!fileUn-ignore
# commentComment

Anchoring

PatternMatches
foofoo anywhere
/foofoo at root only
foo/Directory foo anywhere
/foo/Directory foo at root
**/foofoo at any depth

Common Patterns

PatternPurpose
node_modules/Node dependencies
dist/Build output
.envEnvironment variables
*.logLog files
.DS_StoremacOS Finder
.vscode/VS Code settings
__pycache__/Python bytecode
target/Java build output

Useful Commands

CommandPurpose
git ls-filesList tracked files
git check-ignore -v FILEShow matching rule
git rm --cached FILEUntrack (keep local)
git status --ignoredShow ignored files
git clean -XdnPreview ignored file deletion

.gitignore vs .gitattributes

FilePurpose
.gitignoreWhich files to ignore
.gitattributesHow to treat files

Managing Already-Committed Files

StepCommand
1. Add to ignoreecho "file" >> .gitignore
2. Untrackgit rm --cached file
3. Commitgit commit -m "Untrack file"

Common Pitfalls

PitfallProblemSolution
File already tracked.gitignore has no effectgit rm --cached
No trailing slashMatches files tooAdd / for directories
Missing root anchorMatches deep in treeUse /pattern
Ignored parentNegation failsCan’t un-ignore inside ignored parent
Secrets committedLeak before ignoreRotate keys, then untrack
Comment missingUnclear intentAdd a comment
Case sensitivity.ENV vs .envGit 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

CommandPurpose
touch .gitignoreCreate the file
git check-ignore -v FILEShow matching rule
git rm --cached FILEUntrack, keep local
git status --ignoredShow ignored files
git ls-filesList tracked files
git clean -XdnPreview ignored deletion

Key takeaways:

  • .gitignore tells Git which files not to track
  • Patterns support wildcards, negation (!), and anchoring (/)
  • .gitignore only 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 FILE to see why a file is ignored
  • Use git status --ignored to see the full picture
  • Use git ls-files to confirm what’s actually tracked
  • Patterns apply to the current directory and below — nested .gitignore files override parents
  • Never commit secrets.env, API keys, credentials
  • Start from a stack-specific template, then customize
  • Use .gitattributes for binary handling and line-ending rules
  • Test your rules — a wrong .gitignore either 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!