Skip to content
ohsudo, Home
CI/CD

.gitignore not working: why Git still tracks the file, and how to fix it

.gitignore is ignored because the file is already tracked, the pattern is wrong or the file has the wrong encoding. Find the cause with git check-ignore -v.

Beginner4 min readApplies to Git 2.x · Linux · macOS · Windows (Git for Windows · WSL) · GitHub · GitLab

by OhSudo ··

Part of a learning path:Git for everyday work

You added node_modules/, .env or *.log to .gitignore, and git status still shows the file as modified — or it is still pushed with every commit. .gitignore isn’t broken. In almost every case it’s one of these:

  1. The file is already tracked. .gitignore only affects files Git doesn’t track yet.
  2. The pattern doesn’t match the path you think it matches.
  3. The .gitignore file itself can’t be read — wrong place, or saved as UTF-16 by PowerShell.

One command tells you which: git check-ignore -v shows the line that ignores a file, or nothing if no line does.

  1. A new fileuntracked
  2. .gitignorepattern matches it(works)
  3. Already tracked filesnot affected: git rm --cached(breaks here)
.gitignore only affects files Git doesn't track yet. A file that was committed once stays tracked until you remove it from the index.

TL;DR

Terminal window
git check-ignore -v path/to/file # which .gitignore line matches (no output = none)
git ls-files --error-unmatch path/to/file # succeeds if Git tracks the file
git rm --cached path/to/file # stop tracking it, keep it on disk
git rm -r --cached node_modules # the same for a folder
git commit -m "Stop tracking ignored files"

1. The file is already tracked

This is the cause nine times out of ten. Once a file has been committed, Git keeps tracking it — .gitignore only decides what new, untracked files to leave out. Check:

Terminal window
git ls-files node_modules | head -n 3 # any output: Git tracks files in there
git ls-files -ci --exclude-standard # every tracked file your .gitignore says should be ignored

Remove it from the index (Git’s list of tracked files) without deleting it from your disk, and commit:

Terminal window
git rm -r --cached node_modules
git commit -m "Stop tracking node_modules"

From now on, .gitignore applies to it. To do this for everything at once — after a big .gitignore update — remove the whole index and add everything back; the files your .gitignore matches are left out:

Terminal window
git rm -r --cached .
git add .
git commit -m "Apply .gitignore"

Warning

git rm --cached deletes the file for everyone else. When your teammates pull that commit, Git removes the file from their working folders — it’s a deletion in the history. For something like a shared config/local.yml, warn them first, or commit a config/local.example.yml they can copy. And if the file contains secrets, ignoring it now doesn’t remove it from history: change the secret first.

2. The pattern doesn’t match

Patterns are relative to the folder of the .gitignore they’re in, and a few characters change their meaning:

Pattern Matches
debug.log debug.log in any folder
/debug.log only debug.log next to this .gitignore
build/ folders named build, anywhere — not a file named build
docs/*.md .md files directly in docs/, not in docs/api/
docs/**/*.md .md files in docs/ and every folder below it
!keep.log re-includes a file an earlier pattern excluded

Test a path instead of guessing — the file doesn’t even need to exist with --no-index:

Terminal window
git check-ignore -v --no-index logs/app.log
.gitignore:3:*.log logs/app.log

That reads: line 3 of .gitignore, the pattern *.log, ignores logs/app.log.

One rule surprises everybody: you can’t re-include a file if its folder is excluded. With

config/
!config/app.example.yml

config/app.example.yml stays ignored, because Git doesn’t even look inside an excluded folder. Exclude the folder’s contents instead:

config/*
!config/app.example.yml

3. The .gitignore file itself is the problem

  • Wrong place or name. It must be named exactly .gitignore (not gitignore, not .gitignore.txt — Windows Explorer likes to hide extensions) and live in the repository or in a folder of it. Patterns in it start from that folder.
  • UTF-16 from PowerShell. In Windows PowerShell 5.1, echo "node_modules/" > .gitignore writes the file as UTF-16, which Git doesn’t read as text — so none of the patterns match. PowerShell 7 writes UTF-8 and doesn’t have this problem. Check and fix it:
Terminal window
file .gitignore # "UTF-16" = the problem (Git Bash, WSL, Linux, macOS)
Terminal window
(Get-Content .gitignore) | Set-Content -Encoding utf8 .gitignore # convert it (Windows PowerShell)
  • A rule elsewhere. Patterns can also come from .git/info/exclude (this clone only) or a global file (git config --global core.excludesFile). git check-ignore -v shows which file the matching line is in.

4. Tracked, but you want to ignore your local changes

Sometimes the file must stay in the repository — a shared config with defaults — but you change it locally and don’t want to commit that. .gitignore can’t do that. Tell Git to leave your copy alone:

Terminal window
git update-index --skip-worktree config/settings.json # ignore my local changes
git update-index --no-skip-worktree config/settings.json # undo

Better long term: commit a template (settings.example.json), ignore the real file, and have everyone copy the template.

Start from a complete .gitignore

Most “it’s still tracked” stories begin with a .gitignore that was added after the first commit. Add it first, for your whole stack — the .gitignore & .dockerignore Generator writes one for your languages, editors and operating systems, with the right exceptions (.env.example, the Gradle wrapper, .terraform.lock.hcl) kept in Git.

Shell, we have a problem!

Similar, but different: The following paths are ignored by one of your .gitignore files appears when you run git add on an ignored file — Git is doing what you asked. Use git add -f file if you really want it tracked. And a rejected push after cleaning up is a different story: git push rejected (fetch first).

$ ./practice.sh

Practice this in the terminal

Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.

Open the terminal →

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles