I Thought .gitignore Was Broken: Ignoring Already Committed Files
1. "Hey, Why Are You Still Following Me?"
At first, I thought Git was mocking me.
I created a secret.json file in the middle of a project, and since it obviously shouldn't go onto Git, I added it to .gitignore.
# .gitignore
node_modules/
dist/
secret.json <-- I definitely added this!
But when I typed git status, it still showed secret.json as modified. It even let me add and commit it.
"No, I told you to ignore it! Why aren't you listening?"
Frustrated, I changed the encoding of the .gitignore file to UTF-8, added newlines, and checked for typos ten times.
But Git stubbornly kept tracking that file. Like a stalker.
2. Git is a "Blacklist"
I solved this problem only after understanding Git's mechanism by comparing it to a "Club Blacklist".
.gitignore is the Blacklist held by the Guard at the club entrance.
The rule is simple: "People on this list cannot enter newly (Untracked -> Tracked)."
But what about my secret.json? It was already inside the club dancing (Tracked).
Writing its name in .gitignore meant "Stop him next time he tries to enter," not "Kick out the person who is already playing inside."
From Git's perspective, once a file starts being Tracked via git add even once, it doesn't even look at .gitignore. It's already under management (registered in Index).
3. The Solution: Forced Eviction
Okay, so we need to kick out secret.json that's playing inside the club.
But there's a catch. We must not delete the file itself.
I need that file on my local computer. (It probably contains API keys, so if I delete it, my app won't run).
So we need to remove it "only from Git's surveillance radar (Index/Staging Area)." The spell for this is the --cached option.
3.1 Evicting a Single File
# 1. Remove only from Git's tracking list (cache) (Local file survives)
git rm --cached secret.json
# 2. Now .gitignore takes effect
git status
# Currently looks Untracked, but hidden because of .gitignore
When you type this command, Git says "Okay, this file is no longer my jurisdiction" and takes its hands off.
Only then does the .gitignore rule start to apply, and the file becomes "Ignored."
3.2 Using Recursive Option for Folders
If you accidentally committed a huge folder like node_modules, you need the -r (recursive) flag.
git rm -r --cached node_modules/
And you must commit for it to take effect.
git commit -m "Stop tracking secret.json"
4. Cleaning Day: Resetting All Caches
Sometimes when I massively overhaul .gitignore, it's annoying to do git rm --cached one by one.
Or files like .DS_Store might be scattered everywhere.
In this case, I use a method to briefly send all club guests outside and make them re-enter.
# 1. Untrack ALL files (Bravely!)
# . (dot) means everything in current directory
git rm -r --cached .
# 2. Add all files again (Filtered by .gitignore rules this time)
git add .
# 3. Commit saying "gitignore applied"
git commit -m "Refresh .gitignore rules"
This method is really refreshing (Cathartic). All tangled gitignore issues get solved in one shot.
Just be aware that a record of "all files deleted and added again" might remain in the commit history once. (Team members might be surprised).
5. Common Mistakes & Tips
5.1 Folders vs Files
.gitignore syntax is surprisingly tricky.
config.js: Ignores config.js in the current folder AND all subfolders (Recursive).
/config.js: Ignores config.js only in the Project Root. (Subfolders tracked).
build: Ignores both file named build and folder named build.
build/: Ignores only the Folder build. (Recommended).
I initially just wrote build, but I worried it might be confused with a variable name or other files. When ignoring folders, make sure to append a slash (/) at the end.
5.2 Exceptions (!)
"I want to ignore all .json, but track package.json!"
In this case, use the exclamation mark (!).
*.json # Ignore all json
!package.json # But except this one (Track it)
Important: Order matters. The ignore rule (*.json) must come first, and the exception rule (!package.json) must come later. It executes top-to-bottom.
5.3 Debugging: Why is it ignored?
Sometimes you wonder, "I want to add this, why is it being ignored?"
There is a command to find the culprit rule.
git check-ignore -v ignored_file.js
# Output: .gitignore:3:*.js ignored_file.js
(Interpretation: Ignored because of *.js rule on line 3 of .gitignore)
6. Power User Patterns: Whitelisting
Usually, we blacklist files. But for high-security projects, Whitelisting (Ignore everything, allow few) is safer.
# 1. Ignore EVERYTHING
*
!*/
# 2. Allow only source code
!src/
!*.js
!*.ts
This prevents accidental leaks of any unexpected file types.
Double Asterisk Power (**):
**/debug.log: Matches debug.log in ANY subdirectory depth.
7. Advanced: Ignoring Local Changes Only (assume-unchanged)
Sometimes you have this scenario:
"I have a config.js that contains DB connection strings. The template needs to be committed, but my local password changes must NOT be committed."
Putting it in .gitignore removes the file from the repo, which isn't what we want.
In this case, you can tell Git, "Pretend you didn't see changes in this file."
# 1. Start ignoring changes
git update-index --assume-unchanged config.js
# 2. Stop ignoring (When you actually need to commit an update)
git update-index --no-assume-unchanged config.js
Now git status won't show your local edits. Be careful though, pulling updates from others might conflict or overwrite your ignored changes.
7. Prevention is Better: Husky & Lint-staged
Humans make mistakes. We forget .gitignore.
So it's best to check specifically when the Commit button is pressed, and block the commit if secrets or huge files are detected.
Use a tool called Husky.
// package.json
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{js,ts}": [
"eslint --fix", // Check code style
"git add"
]
}
}
If you integrate tools like detect-secrets here, code containing AWS keys simply won't commit. Prevent the cow from being lost, rather than fixing the stable later.
8. Global .gitignore (System-wide Settings)
As a developer, there are trash files that appear commonly in every project.
If you are a Mac user, the dreaded .DS_Store. If you use VS Code, .vscode.
Writing these in .gitignore for every single project is manual labor.
Create a Blacklist that applies to your entire computer.
# 1. Create file in home directory
touch ~/.gitignore_global
# 2. Add content (As much as you want)
echo ".DS_Store" >> ~/.gitignore_global
echo "*.log" >> ~/.gitignore_global
echo ".vscode/" >> ~/.gitignore_global
echo "Desktop.ini" >> ~/.gitignore_global
# 3. Tell Git to use this file as global config
git config --global core.excludesfile ~/.gitignore_global
Now, no matter what project you create, .DS_Store is automatically ignored. Quality of life improves.
9. The Secret File: .git/info/exclude
If you add todo.txt to .gitignore, everyone sees it.
"I want to ignore my personal messy files without polluting the project config."
Open .git/info/exclude in your project root.
It works exactly like .gitignore, but it is NOT committed. It's your personal, private ignore list.
10. Bonus: Case Sensitivity & Empty Folders
Wait, there are two more common issues related to tracking files that drive beginners crazy.
9.1 Git ignores Case Changes
Did you rename components to Components but Git shows no changes?
Windows and macOS file systems are Case Insensitive by default. Git respects this OS setting, so it thinks nothing changed.
Solution: Use git mv.
git mv components temp
git mv temp Components
9.2 Empty Folders are not tracked
Git only tracks files. It cannot track an empty directory.
If you want to keep the logs/ folder structure in the repo but keep it empty?
Create an empty file named .gitkeep inside it. (The name doesn't technically matter, but .gitkeep is the convention).
touch logs/.gitkeep
9.3 Weird Numbers instead of Filenames?
If git status shows broken filenames like \341\204\200..., Git is escaping non-ASCII characters.
To fix this and see proper Korean/Japanese/Emoji filenames:
git config --global core.quotepath false
Now your terminal will display filenames correctly.
Also, if you are working with a team, ensure everyone uses the same settings to avoid confusion.
10. One-Line Summary
.gitignore is just a doorman; it can't stop a Tracked File that's already inside. Kick it out with git rm --cached and make it go through immigration again.