Ad – 728×90
🔀 Workflows

.gitignore & .gitattributes – Controlling What Git Tracks

Not everything in your project folder belongs in Git. Log files, build artifacts, environment secrets, IDE settings, OS-generated files — these should never be committed. .gitignore tells Git to completely ignore specified files and patterns. .gitattributes controls how Git handles line endings, binary files, and merge strategies. These two files are essential configuration for any professional project.

⏱️ 15 min read 🎯 Beginner 📅 Updated 2026

What Is .gitignore?

A .gitignore file lists patterns. Any file or directory matching a pattern is invisible to Git — it won't appear in git status, won't be staged by git add ., and won't be committed. Place the file in the root of your repository and commit it — everyone who clones your repo gets the same ignore rules.

⚠️
Never commit .env files or secrets

Environment files containing API keys, database passwords, or OAuth secrets must always be in .gitignore. Once a secret is committed, it's in Git history forever — even if you delete the file later. If you accidentally commit a secret, rotate it immediately and consider the old key compromised.

Pattern Syntax

.gitignore
# Comments start with #

# Ignore all .log files anywhere in the project
*.log

# Ignore a specific directory (trailing slash = directory only)
node_modules/
dist/
build/
.cache/

# Ignore a specific file
.env
.env.local
.env.production

# Negate a pattern (do NOT ignore this, even if a previous rule matches)
!important.log
!src/generated/keep-this.js

# Ignore files in a specific directory
logs/*.log

# Double-star = match any number of directories
**/temp/
**/*.pyc

# Ignore files only in the root directory (leading slash = root-relative)
/config.local.json

# Ignore all files in any directory named "coverage"
coverage/

Common .gitignore Patterns

.gitignore (starter template)
# Dependencies
node_modules/
vendor/

# Environment files
.env
.env.local
.env.*.local

# Build output
dist/
build/
out/
.next/
.nuxt/

# Python
__pycache__/
*.pyc
*.pyo
*.egg-info/
.venv/
venv/

# Logs
*.log
npm-debug.log*
yarn-error.log

# OS generated files
.DS_Store
Thumbs.db
*.swp
*~

# IDE / Editor
.idea/
.vscode/settings.json
*.sublime-project
*.sublime-workspace

# Test coverage
coverage/
.nyc_output/

# Temporary files
*.tmp
*.temp
.cache/
Ad – 336×280

Nested .gitignore Files

You can have multiple .gitignore files — one in the root and additional ones in subdirectories. A nested .gitignore applies rules only to files in its directory and below:

Directory structure
my-project/
  .gitignore          ← rules for entire project
  src/
    .gitignore        ← rules only for src/ and its subdirs
  tests/
    .gitignore        ← rules only for tests/ and its subdirs

What If the File Is Already Tracked?

Adding a file to .gitignore does NOT untrack it if Git is already tracking it. You must explicitly remove it from Git's tracking (without deleting the actual file):

Bash
# Untrack a single file (remove from Git index, keep on disk)
git rm --cached .env

# Untrack an entire directory recursively
git rm --cached -r node_modules/

# Untrack all files that now match .gitignore (nuclear option)
git rm --cached -r .
git add .
# Now only unignored files are staged

# Commit the change
git commit -m "chore: remove tracked files that should be ignored"

# Verify it worked
git ls-files | grep .env  # should return nothing
💡
Debug why a file is being ignored (or not)

git check-ignore -v <file> tells you exactly which line in which .gitignore file is responsible for ignoring a particular path. Invaluable for debugging unexpected behavior.

.gitattributes

.gitattributes gives Git metadata about files. The most important use case is controlling line endings across Windows, macOS, and Linux developers:

.gitattributes
# Ensure all text files use LF line endings in the repo
# (converts CRLF → LF on commit, converts back on Windows checkout)
* text=auto

# Force specific file types to LF
*.js text eol=lf
*.ts text eol=lf
*.jsx text eol=lf
*.tsx text eol=lf
*.css text eol=lf
*.html text eol=lf
*.json text eol=lf
*.sh text eol=lf

# Mark binary files (prevents Git from mangling them)
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff2 binary
*.pdf binary

# Custom merge strategy for generated files (never conflict, always use ours)
package-lock.json merge=ours
yarn.lock merge=ours

Global .gitignore

Machine-specific ignores (your IDE, your OS) shouldn't be in the project's .gitignore — that forces your preferences on the whole team. Use a global gitignore for personal machine-level patterns:

Bash
# Create and configure a global gitignore
touch ~/.gitignore_global
git config --global core.excludesFile ~/.gitignore_global

# Add your machine-specific patterns
echo ".DS_Store" >> ~/.gitignore_global
echo ".idea/" >> ~/.gitignore_global
echo "*.swp" >> ~/.gitignore_global
echo "Thumbs.db" >> ~/.gitignore_global

# Verify the setting
git config --global core.excludesFile
ℹ️
Use gitignore.io for project templates

Visit gitignore.io (also gitignore.io) and enter your tech stack (e.g., "Node, React, macOS, Windows, VS Code") to get a comprehensive, pre-built .gitignore file. GitHub also provides templates when creating a new repository — select your language to get a starter file.

📋 Summary

  • .gitignore lists patterns for files Git should never track — always commit this file.
  • Pattern syntax: *.log (wildcard), dir/ (directory), !file (negate), **/temp (any depth).
  • Never ignore: node_modules/, .env, dist/, __pycache__/, .DS_Store.
  • Already tracked? Use git rm --cached <file> to untrack without deleting.
  • Debug ignore rules: git check-ignore -v <file>
  • .gitattributes controls line endings (text=auto, eol=lf) and marks binary files.
  • ~/.gitignore_global for machine-specific ignores — configure with git config --global core.excludesFile.

FAQ

I added a file to .gitignore but git status still shows it. Why? +

Because Git is already tracking the file. .gitignore only prevents untracked files from being added. Once a file is tracked (i.e., it has been committed at least once), Git continues tracking it regardless of .gitignore. The fix: run git rm --cached <file> to remove it from the index (without deleting it from disk), then commit the change. The file will then be properly ignored going forward.

Should .vscode/ be in .gitignore? +

It depends on what's in it. Personal settings like settings.json (themes, font size, personal keybindings) should be in your global gitignore, not the project's. However, project-specific settings like .vscode/launch.json (debug configurations) and .vscode/extensions.json (recommended extensions) can be valuable to share with the team and should be committed. A common approach: .vscode/* in .gitignore with !.vscode/extensions.json and !.vscode/launch.json negation rules.

Why is .gitattributes important for cross-platform teams? +

Windows uses CRLF (\r\n) line endings, while macOS and Linux use LF (\n). Without .gitattributes, a Windows developer who edits a file changes every line (from LF to CRLF), creating a huge diff even if only one character was logically changed. * text=auto in .gitattributes tells Git to normalize line endings in the repository to LF and convert for checkout based on the OS. This keeps diffs clean and prevents "noise" in pull requests caused purely by line ending differences.

Can I use wildcards to ignore files at any depth? +

Yes — use ** to match any number of directory levels. For example, **/*.log ignores all .log files anywhere in the project tree, while *.log (without **) at the root of a .gitignore file also ignores them at any depth when placed in the root .gitignore. For directories: **/node_modules/ ignores a node_modules directory at any depth — useful in monorepos with nested packages.