Skip to main content
Git & GitHub · lesson 6 of 12
Git & GitHub beginner Lesson 6 of 12

Ignoring Files with .gitignore

Watch .gitignore actually hide matching files from git status, see it fail to hide a file already tracked, and debug pattern matches with git check-ignore.

By SkillByExample · Published

git gitignore

.gitignore only affects files Git hasn’t started tracking yet. This lesson shows both halves of that rule: files it successfully hides, and one it doesn’t.

1) Ignored files vanish from status — before they’re tracked

$ printf 'node_modules/\n*.log\n.env\n' > .gitignore
$ mkdir node_modules && echo '{}' > node_modules/pkg.json
$ echo "run 1" > app.log
$ printf 'DEBUG=1\nSECRET=xyz\n' > .env
$ git status -s
?? .gitignore

Three new files exist — node_modules/pkg.json, app.log, .env — but only .gitignore itself shows up as untracked. The other three match a pattern and are invisible to status, add -A, and diff alike.

2) A pattern added after tracking starts does nothing

$ echo "CACHE=on" >> config.py
$ echo "config.py" >> .gitignore
$ git status -s
 M config.py
?? .gitignore

config.py was already tracked from an earlier lesson. Adding it to .gitignore now doesn’t retroactively hide it — Git still reports the modification, because ignore rules only decide whether to start tracking a file, not whether to keep watching one it already tracks.

3) Debug which rule matched, with check-ignore

$ git check-ignore -v .env app.log node_modules/pkg.json
.gitignore:3:.env	.env
.gitignore:2:*.log	app.log
.gitignore:1:node_modules/	node_modules/pkg.json

Each line shows <file>:<line-number>:<pattern> followed by the path it matched — exactly which rule is responsible, useful when a file is unexpectedly missing (or unexpectedly present) and the .gitignore has grown past a few lines.

4) Commit the .gitignore itself

$ git checkout -- config.py
$ git add .gitignore
$ git commit -m "Add .gitignore"
$ git log --oneline -1
39e53d1 Add .gitignore

config.py’s stray edit from step 2 is discarded (it was only there to demonstrate the “already tracked” case) — only .gitignore itself, now four lines, is committed. .env, app.log, and node_modules/ stay untracked exactly as designed.

Practice

  1. Run git status -s --ignored to list ignored files explicitly, instead of the default behavior of hiding them completely.

    Solution
    !! .env
    !! app.log
    !! node_modules/

    !! marks an ignored path — a different marker from ?? (untracked) or M (modified).

  2. Untrack config.py properly with git rm --cached config.py, commit that, then confirm git status goes quiet on further edits to it.

  3. Add a negation pattern (!important.log) after a broader *.log rule in .gitignore, create important.log, and confirm git status shows it despite the earlier blanket rule.

  4. Create a nested node_modules/ inside a subdirectory and confirm the same top-level node_modules/ pattern still matches it — .gitignore patterns apply recursively from the directory they’re in unless anchored with a leading /.

  5. Add a leading slash (/config.py) to anchor a pattern to the repo root, then create a same-named file in a subdirectory and confirm the anchored pattern does not match it there.

Frequently Asked Questions

Why does .gitignore stop working for a file that's already tracked?
Ignore rules only apply to files Git doesn't yet know about. Once a file is tracked (committed at least once), Git watches it for changes regardless of what .gitignore says — the rule exists to keep new noise out, not to retroactively hide history.
How do I actually stop tracking a file after adding it to .gitignore too late?
Run git rm --cached <file> (keeps it on disk, removes it from Git's tracking) and commit that removal. From then on, the .gitignore entry takes effect because the file is untracked again.
What does the trailing slash in node_modules/ mean?
It restricts the pattern to directories only, matching the node_modules folder itself (and everything inside it) rather than also matching a plain file that happened to be named node_modules.
Is .gitignore the only place ignore patterns can live?
No — Git also reads a global ignore file (git config core.excludesFile) for machine-wide rules like editor swap files, and .git/info/exclude for local-only rules that aren't committed and shared. .gitignore is the one meant to be committed and shared with the team.