Ignoring Files with .gitignore

Reviewed & published by Brayan K

Not everything belongs in version control — build outputs, dependencies, secrets, and editor junk should stay out. By the end of this lesson you'll write .gitignore patterns confidently, use negation, untrack files you already committed, and set up a global ignore.

Part of the free Git course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ Writing .gitignore

A .gitignore file lives at your repo root. Each line is a pattern: a bare name ignores one file, *.log ignores every file with that extension, and a trailing slash like build/ ignores a whole directory. Lines starting with # are comments. Commit the .gitignore so your whole team shares the rules.

# .gitignore is a plain text file at your repo root. Each line is a
# PATTERN of files Git should ignore (never track, never nag you about).

# A comment — Git skips lines starting with #
# Ignore a single specific file
secrets.env

# Ignore every file with a given extension (the * wildcard)
*.log

# Ignore a whole directory (the trailing slash means "directory")
node_modules/
build/

# Ignore everything in a folder but a specific file inside it
logs/*
!logs/.gitkeep

2️⃣ Patterns, Globs & Negation

Patterns give you fine control. A leading slash anchors to the root, ** matches across any number of directories, and a leading ! negates an earlier rule to re-include a file. Test any path with git check-ignore -v <path> — it prints the matching rule, or nothing (exit code 1) if the path isn't ignored.

# Pattern cheat-sheet — test any path with: git check-ignore -v <path>

*.tmp            # any file ending in .tmp, anywhere
/config.local    # a leading slash anchors to the repo ROOT only
build/           # the build directory (trailing slash = directory)
**/cache         # a "cache" folder at ANY depth (** = any directories)
logs/**/debug.log  # debug.log under logs/, however deeply nested

# NEGATION — re-include something an earlier rule ignored
*.log            # ignore all logs...
!important.log   # ...except this one (the ! un-ignores it)

3️⃣ Untracking an Already-Committed File

The single biggest gotcha: .gitignore only affects untracked files. If a file is already tracked, adding it to .gitignore does nothing — Git keeps committing it. To stop, untrack it with git rm --cached, which removes it from Git's index but keeps it on disk. Then add it to .gitignore and commit.

# ⚠️ The #1 gotcha: .gitignore only affects UNTRACKED files.
# If a file is ALREADY tracked, adding it to .gitignore does nothing —
# Git keeps committing it. You must untrack it first.

# Stop tracking the file but KEEP it on disk
git rm --cached secrets.env

# Now add it to .gitignore so it stays ignored, then commit the change
echo "secrets.env" >> .gitignore
git commit -m "Stop tracking secrets.env"

# To untrack a whole folder, add -r (recursive)
git rm -r --cached build/

4️⃣ A Global gitignore

Some clutter — editor swap files, .DS_Store, IDE folders — appears in every project but shouldn't pollute each project's shared .gitignore. Set up a global ignore once with git config --global core.excludesfile, and it applies to all your repos.

Your turn. Ignore all logs but keep one, then untrack a file you committed by mistake. Fill in the three blanks.

# 🎯 YOUR TURN — replace each ___ then run it in your terminal.
# Goal: ignore build junk but keep one important log.

# 1) Ignore every .log file (one pattern)
echo "___" >> .gitignore        # 👉 the glob that matches all .log files

# 2) But re-include important.log (negation)
echo "___important.log" >> .gitignore   # 👉 the character that negates

# 3) You already committed debug.log by mistake — untrack it (keep on disk)
git ___ --cached debug.log      # 👉 the command that untracks a tracked file

# ✅ Expected: "git check-ignore debug.log" prints it (ignored), while
#    "git check-ignore important.log" prints nothing (the ! kept it tracked).

Common Errors (and the fix)

Pro Tips

📋 Quick Reference

Pattern / CommandMeaning
*.logIgnore all files ending in .log
build/Ignore the build directory
/config.localIgnore only at the repo root
**/cacheIgnore "cache" at any depth
!important.logNegate — re-include this file
git rm --cached fileUntrack a file, keep it on disk
git check-ignore -v pathShow which rule ignores a path

Mini-Challenge: Clean Up a Messy Repo

No commands given this time — just the plan. Write a .gitignore that keeps only your real source files tracked.

# 🎯 MINI-CHALLENGE: clean up a messy repo.
# No commands given — just the plan. Run each step in your terminal.
#
# 1. Create files: app.js, debug.log, node_modules/lib.js, .env
# 2. Write a .gitignore that ignores *.log, node_modules/, and .env
# 3. Run "git status" — only app.js and .gitignore should show as untracked
# 4. Pretend .env was already committed: untrack it with git rm --cached
# 5. Prove each rule with "git check-ignore -v <path>"
#
# ✅ Expected: status shows only app.js + .gitignore; check-ignore confirms
#    debug.log, node_modules/lib.js, and .env are all ignored.

# your commands here

🎉 Lesson Complete!

Practice quiz

What is the purpose of a .gitignore file?

  • To delete files
  • To list your commits
  • To tell Git which untracked files to ignore
  • To store passwords safely

Answer: To tell Git which untracked files to ignore. .gitignore lists patterns of files Git should not track or show as untracked.

What does the pattern `*.log` match?

  • Every file ending in .log
  • Only a file literally named *.log
  • Files in a log/ folder
  • Nothing

Answer: Every file ending in .log. The * wildcard matches any characters, so *.log matches all files with the .log extension.

What does a trailing slash mean, as in `build/`?

  • Ignore a file named build
  • Ignore files starting with build
  • It's invalid syntax
  • Ignore only the build directory (and its contents)

Answer: Ignore only the build directory (and its contents). A trailing slash restricts the match to directories.

What does a leading `!` do, as in `!important.log`?

  • Forces deletion
  • Negates a previous ignore — re-includes the file
  • Comments out the line
  • Matches hidden files

Answer: Negates a previous ignore — re-includes the file. ! negates an earlier pattern, so a file otherwise ignored becomes tracked again.

A file is ALREADY tracked. Adding it to .gitignore...

  • Has no effect until you run git rm --cached on it
  • Immediately untracks it
  • Deletes it
  • Causes an error

Answer: Has no effect until you run git rm --cached on it. .gitignore only affects untracked files; you must git rm --cached an already-tracked file to stop tracking it.

What does `git rm --cached secrets.env` do?

  • Deletes the file from disk
  • Adds it to the next commit
  • Stops Git tracking the file but keeps it on disk
  • Ignores all .env files

Answer: Stops Git tracking the file but keeps it on disk. --cached removes the file from the index (untracks it) while leaving the working-tree copy intact.

Lines starting with `#` in .gitignore are...

  • Negations
  • Comments
  • Directory patterns
  • Errors

Answer: Comments. A # at the start of a line marks a comment in .gitignore.

Which sets up a GLOBAL gitignore for all your repos?

  • git ignore --global
  • echo to .gitignore-all
  • git global-ignore
  • git config --global core.excludesfile ~/.gitignore_global

Answer: git config --global core.excludesfile ~/.gitignore_global. Setting core.excludesfile points Git at a personal ignore file applied across all repositories.

What does `**` mean in a pattern like `logs/**/debug.log`?

  • Exactly one directory
  • Any number of directories (including none)
  • Only hidden directories
  • A literal asterisk

Answer: Any number of directories (including none). ** matches across multiple directory levels.

Where should project-specific ignore rules normally live?

  • In your home folder only
  • In the commit message
  • In a .gitignore file committed at the repo root
  • In a branch name

Answer: In a .gitignore file committed at the repo root. A committed .gitignore at the repo root shares the rules with the whole team.

Continue this course

Frequently asked questions

Why isn't my .gitignore working?

The most common reason is that the file is already tracked. .gitignore only affects untracked files — once Git is tracking something, adding it to .gitignore does nothing. Run git rm --cached <file> to untrack it (the file stays on disk), then commit. Other causes: a typo in the pattern, or the rule being overridden by a later negation. Use git check-ignore -v <path> to see exactly which rule matches.

What's the difference between *, **, and a trailing slash?

A single * matches any characters within one path segment, so *.log matches files ending in .log. ** matches across directory levels, so logs/**/debug.log matches debug.log however deeply it's nested under logs/. A trailing slash like build/ restricts the match to a directory rather than a file of the same name.

How do I ignore everything in a folder except one file?

First ignore the folder's contents, then negate the file you want to keep: logs/* on one line, !logs/.gitkeep on the next. Order matters — the negation must come after the broader ignore rule. A common use is committing an empty placeholder file (often named .gitkeep) so Git keeps an otherwise-ignored directory in the repo.

What is git rm --cached and when do I use it?

git rm --cached <file> removes a file from Git's index (stops tracking it) while leaving the actual file on your disk. You use it when you accidentally committed something that should have been ignored — like a secrets file or a build artifact. After untracking it, add the file to .gitignore and commit, so it stays out of the repo going forward.

Should I use a project .gitignore or a global one?

Use both, for different things. A project .gitignore lives at the repo root, is committed, and is shared with the whole team — it should list project-specific outputs like build/ or dist/. A global gitignore (set with git config --global core.excludesfile) applies to all your repos and should hold your personal clutter like editor swap files and OS junk (.DS_Store), which don't belong in any shared project file.

Related lessons