Overview
Symptom: you add patterns to .gitignore, but Git still shows changes for files you expected to be ignored. Root cause: .gitignore only affects untracked files. Anything already in the index remains tracked until explicitly removed from the index.
This guide shows how to untrack already tracked files safely, verify ignore patterns, and handle history if sensitive data was committed.
Quickstart (most cases)
- Edit .gitignore to include the patterns you want.
- Remove tracked files from the index without deleting your working copies:
# Stop tracking paths that now match .gitignore
# Replace PATHSPEC with a file, directory, or pattern
git rm -r --cached PATHSPEC
# Stage the .gitignore change and the index removals
git add .gitignore
# Commit the changes
git commit -m "Stop tracking ignored files"
Examples of PATHSPEC:
- A single file: secrets.env
- A directory: build/
- Everything matching current .gitignore: . (see performance notes before using)
Minimal working example
# Start a demo repo
mkdir demo && cd demo
git init
echo "temp logs" > app.log
mkdir config && echo "API_KEY=abc" > config/dev.env
echo "(initial)" > README.md
git add . && git commit -m "initial"
# Now decide to ignore logs and env files
echo "*.log" >> .gitignore
echo "config/*.env" >> .gitignore
git status # You'll still see app.log and config/dev.env tracked
# Fix: untrack but keep files locally
git rm --cached app.log config/dev.env
git add .gitignore
git commit -m "ignore logs and env; untrack already-tracked files"
git status # Clean; future changes to those files are ignored
Step-by-step: robust fix
- Verify your .gitignore pattern matches
- Check which rule applies:
git check-ignore -v path/to/file
- If nothing prints, the file is not ignored by your patterns. Adjust .gitignore until this command shows a matching rule and source file.
- Untrack the affected files
- Untrack specific paths:
# Preview first (dry run)
git rm -r -n --cached build/ config/*.env
# Execute
git rm -r --cached build/ config/*.env
- To untrack everything currently ignored (use with care on large repos):
# Preview
git ls-files -ci --exclude-standard -z | xargs -0 -n50 echo
# Execute
git ls-files -ci --exclude-standard -z | xargs -0 git rm --cached -r
- Commit the changes
git add .gitignore
git commit -m "stop tracking ignored files"
- Push and coordinate
- Push to the remote and notify teammates; they may need to pull and possibly prune their indexes similarly.
git push
If sensitive data is already in history
Untracking stops future tracking but does not remove past commits. To purge secrets or large files from history, rewrite the repository history, then force-push and rotate credentials.
- Using git filter-repo (preferred if available):
# Remove file(s) from all history
git filter-repo --path secrets.env --invert-paths
# Or remove by glob
git filter-repo --path-glob "config/*.env" --invert-paths
git push --force --all
git push --force --tags
- Using git filter-branch (built-in, slower):
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch secrets.env' \
--prune-empty --tag-name-filter cat -- --all
git push --force --all
git push --force --tags
After history rewrites:
- Invalidate caches and artifacts that may hold the data (CI, Docker layers, artifact stores).
- Rotate any leaked credentials.
Pattern tips that often cause “not working”
- Leading slash anchors to repo root:
/build/ignores build/ only at the root. - A trailing slash matches directories:
logs/. - Use
!to unignore a subset:logs/**then!logs/keep.log. - Whitespace matters; escape spaces or quote in shell when testing.
- .gitignore applies to the index; it does not delete files.
Alternatives and scope of ignoring
- Repository-level: .gitignore (committed, shared with team).
- Local-only (not shared): .git/info/exclude.
- Global ignore for your user:
git config --global core.excludesFile ~/.gitignore_global
Use local or global ignores for machine-specific files (e.g., editor backups) rather than committing them to the repo.
Pitfalls and gotchas
- Forgetting to commit .gitignore: teammates won’t benefit until it’s committed.
- Using
git rmwithout--cached: this deletes files from disk; always include--cachedwhen you only want to untrack. - Negative rules order: later
!rules can override earlier ignores; keep patterns ordered and specific. - Submodules: .gitignore does not ignore submodule directories; remove the submodule entry from .gitmodules and the index instead.
- assume-unchanged/skip-worktree: these flags hide local changes but do not ignore new files and are not shared. Use only for local, temporary workflows.
# Local-only: keep file tracked but stop seeing changes (not shared)
git update-index --skip-worktree path/to/file
# Undo
git update-index --no-skip-worktree path/to/file
Performance notes
- Prefer targeted untracking over repo-wide operations. Use pathspecs to limit scope.
- Always dry-run first:
git rm -r -n --cached PATHSPEC. - On very large repos, generating full file lists can be slow. Consider:
- Untracking only top-level directories known to be ignored (e.g., build/, dist/).
- Running commands from the narrowest subdirectory.
- Avoid
git rm -r --cached .on monorepos unless you’ve verified the list with a dry run.
Helpful commands overview
| Task | Command |
|---|---|
| Verify a rule | git check-ignore -v PATH |
| Untrack file/dir | git rm --cached [-r] PATH |
| Preview untracking | git rm -n --cached [-r] PATH |
| List cached ignored | git ls-files -ci --exclude-standard |
FAQ
Why does .gitignore not affect already tracked files?
- Because .gitignore only determines which untracked files Git will consider for adding. The index keeps tracking until you remove entries from it.
How do I ignore a file but keep my local edits and still track it for others?
- You cannot share that state. Use
--skip-worktreelocally if you must, but it’s fragile. Prefer refactoring the file into a template with a local override.
- You cannot share that state. Use
Will
git rm --cacheddelete my files?- No. It removes paths from the index only. Your working tree files remain.
Do I need to run these commands on every machine?
- Teammates pulling your commit will stop tracking the paths too, but local caches may persist. If they had local modifications, they might need to run similar commands or resolve conflicts.
How do I remove a secret from all history?
- Rewrite history with
git filter-repoorgit filter-branch, force-push, and rotate the secret. Untracking alone is not sufficient.
- Rewrite history with