loading bytebasex.com

Understanding .gitignore: What to Ignore and Why

Learn how .gitignore works, master pattern syntax, fix tracked files, and prevent sensitive data leaks in your Git repository.

You clone a fresh repository, run npm install, and a few minutes later git status shows hundreds of changed files, all inside a node_modules folder nobody wanted tracked in the first place. Or worse: you commit a .env file with real API keys in it, push it, and now that secret is sitting in your project's history forever, even after you delete the file in a later commit.

Both of these are exactly what .gitignore exists to prevent, and both of them are still common, because most people copy a generic template once and never actually learn how the file works.

Understanding .gitignore: What to Ignore and Why

Welcome to BytebaseX. I'm Suptojit Modak, and in this guide, I'll cover what .gitignore actually does, the pattern syntax that trips people up, what to do when ignoring a file too late doesn't seem to work, and a couple of habits that will save you from ever accidentally committing something you really didn't want to.

What .gitignore Actually Does

A .gitignore file is a plain text file, placed at the root of your repository (or in any subfolder), that tells Git which files and folders to leave alone. Files matching a pattern in it won't show up in git status, won't get included when you run git add ., and won't accidentally get committed.

.gitignore only affects files Git isn't already tracking. It has zero effect on files that are already committed, which is the single biggest source of confusion around this file.

That distinction matters enough that we'll come back to it in its own section, because "I added it to .gitignore but it's still showing up" is one of the most common Git questions there is.

A Basic Example

Create a file named exactly .gitignore (yes, starting with a dot, no filename before the extension) in your project root:

# Dependencies
node_modules/

# Environment variables
.env

# Build output
dist/
build/

# Editor and OS files
.vscode/
.DS_Store

Save it, and from that point forward, Git will act as if node_modules, .env, dist, build, and those editor/OS files simply don't exist in your working directory. They stay on your disk, you can still use them normally, but Git stops paying attention to them entirely.

Pattern Syntax: What the Symbols Actually Mean

Most confusion with .gitignore comes down to not knowing exactly what each pattern matches. Here's what actually happens with each one:

PatternWhat it matches
filename.txtA file with this exact name, anywhere in the repository
/filename.txtA file with this name, only at the repository root
foldername/A folder with this name (and everything inside it), anywhere
*.logAny file ending in .log, in any folder
logs/*.logFiles ending in .log, only directly inside a folder named logs
!important.logAn exception: un-ignores a file that a broader pattern above it would otherwise catch
**/tempA folder or file named temp, at any depth in the project

The leading slash is worth calling out specifically, since it's easy to miss. Without it, config.json matches a file with that name anywhere in your project, including inside subfolders. With a leading slash, /config.json only matches that exact file sitting at the project root, and a config.json buried three folders deep would be tracked normally.

The Exception Rule, and the Trap Inside It

The ! prefix lets you carve out an exception to a broader ignore rule:

logs/*
!logs/.gitkeep

This ignores everything inside the logs folder except a placeholder file called .gitkeep, a common trick for keeping an otherwise-empty folder tracked in Git, since Git doesn't track empty folders on their own.

Warning: There's a specific trap here: you cannot re-include a file if one of its parent folders is already ignored. If you write logs/ (ignoring the whole folder) instead of logs/* (ignoring its contents), then !logs/.gitkeep underneath it won't work, because Git never even looks inside a fully ignored folder to check for exceptions. If you need exceptions to work, ignore the folder's contents with a wildcard rather than the folder itself.

"I Added It to .gitignore But It's Still Tracked"

This is the single most common .gitignore problem, and it happens because of the rule mentioned earlier: .gitignore only stops Git from tracking new files. If a file was already committed before you added it to .gitignore, Git keeps tracking it, ignore rule or not, because as far as Git is concerned, that file is already part of the project.

To actually stop tracking a file without deleting it from your disk, you need to explicitly untrack it:

git rm --cached .env

The --cached flag is the important part here. Without it, git rm deletes the file from your disk too. With it, Git only removes the file from tracking; the file itself stays exactly where it is, untouched, on your computer. Commit this change, and from that point forward, your .gitignore rule for that file will actually take effect.

git rm --cached .env
git commit -m "Stop tracking .env file"
Error to avoid: git rm --cached only removes the file going forward. It does not erase the file from your project's earlier commit history. If a real secret, like an API key or password, was ever committed, treat that credential as compromised and rotate it immediately, rather than assuming removing the file now makes it safe. Fully scrubbing a file from Git history is a separate, more involved process, and even then, anyone who already cloned or forked the repo may still have a copy.

A Live Demo: Which Files Actually Get Ignored

Here's a simple way to test your understanding. Click a pattern below and see which files in a sample project would be ignored versus tracked.

📁 node_modules/express.js
📄 error.log
📄 src/debug.log
📄 config.json
📄 src/config.json
📄 index.js

Click a pattern above to see what it matches.

Language and Framework Templates

Rather than writing a .gitignore from scratch every time, most ecosystems have well-maintained standard templates covering the common build artifacts and dependency folders for that language. A few starting points worth knowing:

  • Node.js: node_modules/, .env, npm-debug.log*, dist/
  • Python: __pycache__/, *.pyc, venv/, .env
  • Java: *.class, target/, .gradle/
  • General web projects: .DS_Store, Thumbs.db, .vscode/, .idea/

Rather than memorizing all of these, you can generate a properly formatted .gitignore file for your exact stack using our own free Gitignore Generator — pick your language or framework, and it puts together a ready-to-use file covering the common cases for that ecosystem.

The Global .gitignore: One File for Every Project

Some files, like your editor's settings folder or your operating system's own clutter files (.DS_Store on macOS, Thumbs.db on Windows), have nothing to do with any specific project. They show up because of your setup, not the codebase. Rather than repeating the same handful of entries in every single repository's .gitignore, set up one global ignore file that applies everywhere:

git config --global core.excludesfile ~/.gitignore_global

Create that file and add your personal, environment-specific entries to it:

# ~/.gitignore_global
.DS_Store
Thumbs.db
.vscode/
.idea/
*.swp

This applies across every repository on your machine automatically, without cluttering each project's own .gitignore with things that are really about your setup, not the project itself. It also means you don't need to remember to add these entries every time you start a new project.

Checking Whether a File Is Being Ignored

If you're not sure why a file is or isn't showing up in git status, don't guess. Ask Git directly:

git check-ignore -v path/to/file.txt

If the file is ignored, this prints exactly which .gitignore file and which line is responsible for it:

.gitignore:3:node_modules/    node_modules/some-package/file.txt

If the file isn't ignored, the command prints nothing at all. This is genuinely useful when you have .gitignore files at multiple levels of a project (a root one plus per-folder ones) and can't immediately tell which rule, if any, is catching a specific file.

Common Mistakes Worth Avoiding

Ignoring a File After It's Already a Problem

As covered above, .gitignore has no effect on files already tracked. If you're setting up a new project, add your .gitignore file before your very first commit, or at minimum before you run git add . for the first time. Retrofitting it onto an existing repository works, but it takes the extra git rm --cached step covered earlier.

Committing Secrets Before Remembering to Ignore Them

This is the higher-stakes version of the mistake above. If you're setting up any project that will eventually need API keys, database passwords, or similar credentials, add .env (or whatever your framework's convention is) to .gitignore as one of the very first things you do, ideally before you even create the file with real secrets in it.

Being Too Broad

A pattern like *.json might seem convenient for ignoring a config file, but it also silently ignores package.json, package-lock.json, and any other JSON file your project actually needs tracked. Be as specific as the situation allows, especially with wildcards.

Quick Reference

What you wantHow to do it
Ignore a specific filefilename.txt
Ignore a file only at the root/filename.txt
Ignore an entire folderfoldername/
Ignore by file extension*.log
Make an exception to a broader rule!filename.txt
Stop tracking an already-committed filegit rm --cached filename
Check why a file is being ignoredgit check-ignore -v filename
Set up ignore rules for every projectgit config --global core.excludesfile ~/.gitignore_global

Frequently Asked Questions

Can I have more than one .gitignore file in a project?

Yes. You can place a .gitignore file in any subfolder, and its rules apply to that folder and everything inside it, in addition to whatever the root .gitignore already covers. This is useful for a subfolder with its own specific build artifacts that don't apply to the rest of the project.

Does .gitignore work retroactively on files I already committed?

No, and this is the most common misunderstanding about this file. Adding a pattern to .gitignore only affects files Git isn't tracking yet. For a file that's already committed, you need to explicitly stop tracking it with git rm --cached filename, then commit that change.

I accidentally committed a secret. Is deleting it in a new commit enough?

No. Deleting a file in a new commit removes it from the current state of the project, but the secret still exists in your repository's history, in the earlier commit where it was added. Treat any committed credential as compromised and rotate it. Removing it from Git history entirely is a separate, more involved process, and even that doesn't help if someone already cloned the repo before the cleanup.

What's the difference between .gitignore and git stash for hiding changes?

They solve completely different problems. .gitignore permanently tells Git to never track certain files at all. A stash is temporary and applies to changes Git is already tracking, letting you set aside edits you plan to bring back shortly. If you want Git to genuinely forget a file exists, that's .gitignore. If you want to temporarily shelve work you're already tracking, that's git stash, covered in a separate guide.

Why does my .gitignore file not seem to work at all?

The two most common causes: the file the pattern is meant to catch was already committed before the .gitignore rule existed (fix with git rm --cached), or there's a typo in the pattern itself. Run git check-ignore -v filename to see exactly whether, and why, a specific file is being ignored.

Conclusion

A well-set-up .gitignore file is one of those things that quietly prevents problems rather than solving dramatic ones, which is probably why it doesn't get much attention until something slips through. Get the basic patterns down, remember that it only affects untracked files, and set up a global ignore file once so you're not repeating the same setup on every new project.

If you're starting a new project right now, our Gitignore Generator is the fastest way to get a solid starting file for your specific stack, rather than writing one from memory.

About the author

Suptojit Modak
I'm Suptojit Modak, a web developer and the person behind BytebaseX — a blog with tutorials, guides, and free resources for developers and bloggers, built to be simple and easy to follow.

Instagram · GitHub · Facebook

Post a Comment