Ignoring Files
A good ignore file keeps Git focused on source files and project decisions. You use it to filter out build output, dependency caches, machine-local clutter, and accidental secrets before they become part of history.
What ignoring means
Git has one job during ordinary work: notice which project files have changed so you can stage and commit the intentional ones. Ignore rules tell Git which untracked paths should stay quiet during that process.
An ignore rule is a pattern that says, "when this path is untracked, do not show it in normal status output and do not add it by accident with broad commands like git add .."
The word untracked is the hinge. If Git already tracks a file, adding a matching rule later will not make Git forget it. Ignore rules are a filter for new paths, not an undo button for paths already in the index.
Source code, configuration examples, lockfiles your project policy wants committed, tests, docs, and any file needed to rebuild or understand the project.
Compiled output, dependency folders, logs, editor scratch files, temporary archives, local environment files, credentials, and generated artifacts.
Where ignore rules live
Git reads ignore patterns from several places. Use the location that matches the audience for the rule: the whole team, only this clone, or every repository on your machine.
| Place | Commit it? | Use it for |
|---|---|---|
.gitignore |
Yes | Rules the project should share, such as dist/, node_modules/, target/, *.pyc, or framework caches. |
.git/info/exclude |
No | Local rules for one repository, such as a personal scratch file or editor output that should not affect teammates. |
core.excludesFile |
No | Global rules across your machine, commonly OS files, editor backups, and personal tooling files. |
A repository can have more than one .gitignore. A file in a subdirectory applies to paths under that directory, and deeper rules can override broader ones. In practice, one root .gitignore is enough for many projects; nested files are useful when a subproject has its own build output.
git config --global core.excludesFile ~/.config/git/ignore
Pattern syntax
A .gitignore file is line-oriented. Blank lines are separators. Lines beginning with # are comments. The useful work happens through simple glob-style patterns.
| Pattern | Meaning | Example match |
|---|---|---|
*.log |
Any name ending in .log, below this ignore file. |
server.log, logs/debug.log |
/TODO |
Only TODO next to this .gitignore, not every nested TODO. |
TODO |
build/ |
Any directory named build under this level. |
build/app.js, src/build/out.o |
docs/*.pdf |
PDFs directly in docs, but not deeper. |
docs/guide.pdf |
docs/**/*.pdf |
PDFs anywhere under docs. |
docs/api/v1/guide.pdf |
!keep.log |
Re-include a path that an earlier pattern ignored. | keep.log |
# compiled objects and logs
*.o
*.log
# dependency and build directories
node_modules/
dist/
# a root-only file
/local.sqlite
Directories and depth
Slashes decide where a pattern can match. A trailing slash says "directory only." A leading slash anchors the pattern relative to the directory containing the .gitignore. A slash in the middle makes the pattern relative to that ignore file's directory.
The difference is easiest to see with the same word in three shapes:
| Pattern | Matches | Does not match |
|---|---|---|
cache |
A file or directory named cache anywhere below this level. |
A path with a different final name. |
cache/ |
Directories named cache anywhere below this level. |
A regular file named cache. |
/cache/ |
The cache directory next to this .gitignore. |
src/cache/. |
Use a trailing slash for generated directories: dist/, coverage/, .cache/. It communicates intent and avoids accidentally ignoring a real file with the same name.
Negation and re-including files
A pattern beginning with ! reverses an earlier ignore rule. This is useful when you ignore a broad class of files but intentionally keep one example, fixture, or placeholder.
*.env
!.env.example
The ordering matters: Git uses the last matching rule at the relevant precedence level. Put the broad ignore first, then the exception.
You cannot re-include a file inside a directory that Git has already ignored wholesale, because Git stops descending into ignored directories for performance. Ignore the directory contents instead when you need exceptions.
# too broad if you want to keep something inside logs/
# logs/
# ignore contents, then re-include the placeholder
logs/*
!logs/.gitkeep
Tracked files are different
The most common surprise is adding a rule and still seeing the file in git status. That usually means the file is already tracked. Ignore rules do not remove files from history or from the index.
To stop tracking a file while keeping your local copy, remove it from the index with git rm --cached, then commit both the removal-from-tracking and the ignore rule.
git rm --cached app.log
printf "app.log\n" >> .gitignore
git add .gitignore
git commit -m "Stop tracking generated log file"
Use that command carefully. It does not delete your working copy when you pass --cached, but the next person who pulls your commit will see Git remove that tracked file from their checkout unless it is recreated locally. That is usually correct for build output and logs; it is not correct for real source files.
What to ignore
A healthy ignore file is boring. It should describe outputs and local state, not hide uncertainty. If a file is needed to build, test, deploy, or understand the project, think twice before ignoring it.
Good candidates
- Generated build output:
dist/,build/,target/, object files, bundles, and compiled classes. - Dependency caches: folders like
node_modules/when dependencies are restored from a manifest and lockfile. - Runtime clutter: logs, pid files, coverage output, temporary databases, and local caches.
- Machine-local files: OS metadata, editor backup files, and per-user scratch files.
- Secrets and private env:
.env, key files, tokens, local credentials, and generated certificates.
Files that need a policy
Lockfiles, generated documentation, snapshots, schema files, and checked-in assets are project decisions. Some teams commit them because they make builds reproducible; others regenerate them. The ignore file should reflect the policy, not silently decide it.
Ignoring a secret prevents future accidental adds, but it does not erase a secret already committed. If a token or key was committed, rotate it and clean the history with the team's approved procedure.
Checking why a file is ignored
When Git ignores something you expected to stage, ask Git which rule matched. git check-ignore -v prints the source file, line number, pattern, and path.
git check-ignore -v dist/app.js
A typical answer looks like this:
.gitignore:12:dist/ dist/app.js
If the command prints nothing, no ignore pattern matched that untracked path. If the file is tracked and you are debugging why it slipped in earlier, add --no-index to test the ignore rules without consulting the index.
git check-ignore -v --no-index path/to/file
Common pitfalls
Adding .gitignore after the accidental commit. If the file is tracked, the ignore rule will not affect it. Use git rm --cached for generated files you want to stop tracking.
Ignoring too much with a directory rule. A rule like logs/ prevents exceptions inside that directory. Use logs/* followed by a negated exception if you need to keep a placeholder.
Treating ignore rules as security. They reduce accidental staging; they do not protect files from being read, copied, or committed with git add -f. Keep secrets out of the repository directory when possible.
Copying a giant template without review. Templates are useful starts, but every ignored pattern is a small project policy. Remove rules that hide files your team actually wants to see.
Worked examples
Example 1: A small Node project
You want to commit source, package metadata, and the lockfile, but ignore dependencies, build output, coverage, logs, and private environment values.
node_modules/
dist/
coverage/
*.log
.env
!.env.example
The important choice is what is missing: do not ignore package.json or the lockfile if your team uses it for reproducible installs.
Example 2: Keep a directory with one placeholder
Git does not track empty directories. A common pattern is to ignore the runtime contents of a directory while keeping a placeholder file that explains why the directory exists.
uploads/*
!uploads/.gitkeep
This works because uploads/ itself is not ignored; only its contents are. Git can still see the exception.
Example 3: Stop tracking a committed secret file
Suppose .env was committed by mistake. First rotate any secret values in the file. Then stop tracking the file and add a shared rule so it does not return.
git rm --cached .env
printf ".env\n!.env.example\n" >> .gitignore
git add .gitignore
git commit -m "Ignore local environment file"
This prevents future ordinary adds. It does not remove the old secret from earlier commits, so rotation is the first step, not a footnote.
Example 4: Find the rule hiding a file
You try to add docs/build/report.pdf, but Git says nothing is there. Ask for the matching rule:
git check-ignore -v docs/build/report.pdf
If the output is .gitignore:8:docs/**/*.pdf docs/build/report.pdf, the rule is broad enough to catch generated PDFs at any depth under docs. Now you can decide whether to add an exception or move the generated report outside the tracked source area.
Example 5: Split team rules from personal rules
Your editor creates scratch.local files, but the project does not need to know about your editor. Put that in the local exclude file instead of the shared .gitignore.
scratch.local
Save shared .gitignore entries for paths every clone is expected to generate or protect.
Sources & further reading
-
The authoritative pattern syntax, precedence order, configuration locations, notes, and examples for Git ignore files.
-
A beginner-friendly chapter that places ignore files inside the daily loop of status, staging, diffing, and committing.
-
GitHub's practical guide to repository ignore files, global ignore files, and local excludes.
-
The exact reference for debugging ignore matches with
git check-ignore -vand--no-index. -
A curated template collection for common languages, frameworks, tools, operating systems, and editors.