How-to

The .gitignore File: How to Ignore Files in Git and Untrack Ones Already Committed

Updated: · Skillaria.Top

A .gitignore file at the root of a repository lists patterns for files and folders Git should not track: dependencies, build output, logs, secrets. It only applies to untracked files, so a file that is already committed has to be removed from the index with git rm --cached.

A gitignore file is how you tell Git: "leave these files alone, don't show them in git status, and don't stage them with git add .". The rules live in a plain text file called .gitignore, which is committed along with the project and applies to the whole team. The key catch: ignore rules only affect files Git isn't tracking yet.

What usually goes into .gitignore

  • Dependencies: node_modules/, vendor/, .venv/ — the package manager restores them.
  • Build output: dist/, build/, *.class, __pycache__/.
  • Logs and temporary files: *.log, tmp/, *.swp.
  • Secrets and local settings: .env, keys, local config files.
  • OS and editor clutter: .DS_Store, Thumbs.db, .idea/ — although these are better kept in a global ignore file, covered below.

The rule of thumb: the repository should contain what people write, not what programs generate or what depends on a particular machine.

How to create a .gitignore file

It's an ordinary text file at the root of the repository. Create it, add one pattern per line, and commit it:

cat > .gitignore <<'EOF'
# dependencies
node_modules/
# build output
dist/
# logs
*.log
# secrets
.env
EOF

git add .gitignore
git commit -m "chore: add .gitignore"

Ready-made templates for popular languages and frameworks are available in the github/gitignore repository, and GitHub and GitLab let you pick one when you create a project. Use them as a starting point and remove what you don't need.

.gitignore pattern syntax

PatternWhat it ignores
*.logall files with the .log extension in any folder
build/a build folder at any level (trailing slash = directories only)
/buildonly build at the root (a leading slash anchors it to the .gitignore's folder)
docs/*.pdfPDFs directly in docs, but not in docs/old/
docs/**/*.pdfPDFs in docs and all its subfolders
**/temptemp in any folder
!keep.lognegation: do not ignore keep.log
file?.txt? matches any single character
[ab].txta.txt or b.txt
# commenta comment line

An important detail: a pattern with no slash in it (config.json) matches at any level, while a pattern with a slash at the start or in the middle (/config.json, src/config.json) is relative to the folder containing that .gitignore.

Negating patterns with an exclamation mark

*.log
!important.log

The gotcha: you can't "re-include" a file if its parent folder is ignored. Git doesn't descend into ignored folders, so this won't work:

logs/
!logs/keep.txt     # does not work

The right way is to ignore the folder's contents, not the folder itself:

logs/*
!logs/keep.txt

The same trick is used to keep an empty folder in a repository: Git doesn't store empty directories, so you put a .gitkeep file inside and allow only that file.

Other places to define ignore rules

LocationApplies toCommitted to the repo
.gitignore at the root and in subfoldersthe whole teamyes
.git/info/excludethis clone onlyno
global core.excludesFileall your repositoriesno

It makes sense to move your own OS and IDE clutter into a global ignore file so you don't pollute the shared .gitignore:

git config --global core.excludesFile ~/.gitignore_global
echo ".DS_Store" >> ~/.gitignore_global
echo ".idea/" >> ~/.gitignore_global

If core.excludesFile isn't set, Git reads ~/.config/git/ignore if it exists.

Why .gitignore is not working

The most common complaint: "I added the file to .gitignore, but Git still shows changes." The cause is almost always the same — the file is already tracked. Ignore rules have no effect on files that have ever been committed.

To check which rule matches (or why none does), use git check-ignore:

git check-ignore -v logs/app.log
# .gitignore:3:*.log	logs/app.log

The output shows the file and the matching rule's line. Empty output means the file isn't ignored. Other causes of trouble are trailing spaces in a pattern or the wrong file encoding (for example, UTF-16 when the file was created from PowerShell).

How to remove an already committed file from the repo: git rm --cached

The git rm command removes a file from both the index and the disk. With the --cached flag it only removes it from the index: the file stays on your machine but is no longer tracked.

echo ".env" >> .gitignore
git rm --cached .env
git commit -m "chore: stop tracking .env"
git push

For a folder you need -r:

git rm -r --cached node_modules/

If there are many rules and it's unclear what has already made it into the repository, you can rebuild the whole index: git rm -r --cached ., then git add . and commit. Check git status before committing — files removed from the index will show up there.

Warning: secrets stay in the history

git rm --cached removes the file from future commits, not past ones. If a password or token got into the repository, anyone with a clone can find it in the history. Treat the secret as compromised and rotate it. You can rewrite history with tools like git filter-repo, but that changes the hashes of all subsequent commits and needs to be coordinated with the team.

Another consequence: after a git pull, a file removed from the index with --cached will be deleted from your teammates' disks. For .env, warn the team in advance or keep an .env.example template in the repository.

git rm: deleting files

git rm old.py              # delete from disk and the index
git rm -r legacy/          # delete a folder
git rm -n '*.tmp'          # dry run: show what would be deleted
git rm -f changed.py       # delete a file with uncommitted changes

If you delete a file with plain rm, Git sees it as "deleted", and you still have to stage the deletion with git add or git rm. git rm does both steps at once. Without -f it refuses to delete a file with uncommitted changes — a safeguard against losing work.

git mv: renaming and moving files

git mv utils.py helpers.py
git mv helpers.py src/helpers.py

git mv is shorthand for mv + git rm of the old path + git add of the new one. Git doesn't record renames: it detects them from content similarity when showing git status, git log --follow and git diff. That's why a rename via git mv and a plain mv followed by git add -A produce the same result.

A practical tip: rename a file and change its contents in separate commits. If a file changes a lot along with the rename, Git may not recognize it as the same file, and the --follow history will break.

To change only the case of a file name on Windows and macOS (case-insensitive file systems), use git mv readme.md README.md — Git may not notice a simple rename done in a file manager.

Rule order and precedence

Within a single file, rules are read top to bottom, and the last matching one wins. So the negation !keep.log must come after the *.log rule, or it will be overridden. Across files, a .gitignore in a subfolder takes precedence over the root one, while the global ignore file and .git/info/exclude have the lowest priority.

Example .gitignore for a web project

Below is a working example for a Node.js project with a Python backend. Note the comments: six months from now nobody will remember why a given rule was added.

# --- dependencies ---
node_modules/
.venv/

# --- build output and caches ---
/dist
/build
__pycache__/
*.pyc
.pytest_cache/
coverage/

# --- logs ---
*.log
npm-debug.log*

# --- environment and secrets ---
.env
.env.*
!.env.example

# --- user uploads, but keep the folder ---
uploads/*
!uploads/.gitkeep

Both negation tricks are used here: .env.* hides all local environment variants while the .env.example template stays in the repository, and the uploads folder exists in every clone thanks to .gitkeep.

What you shouldn't ignore

Overdoing it is harmful too. Dependency lock files — package-lock.json, poetry.lock, composer.lock — are usually committed in applications: they guarantee that every developer and the CI install the same package versions. Shared formatting settings (.editorconfig, linter configs) belong in the repository as well.

IDE settings are a judgment call. The .vscode/ folder is often ignored entirely, but sometimes recommended extensions and build tasks are committed: .vscode/*, then !.vscode/extensions.json. It's a team decision — the main thing is not to keep personal paths and keys in a shared repository.

Scenario: you accidentally committed node_modules

A classic beginner mistake: running git add . before creating .gitignore, and tens of thousands of files end up in the commit. The fix takes three steps:

echo "node_modules/" >> .gitignore
git rm -r --cached node_modules
git commit -m "chore: remove node_modules from the repository"

If the commit hasn't been pushed yet, it's cleaner to rewrite it so the heavy files don't stay in the history: git reset --soft HEAD~1, then the same commands and a new commit. Once pushed, the files are in the history, and the clone size won't shrink without rewriting history.

To avoid this in the future, create .gitignore before your first git add and get into the habit of checking git status before committing: a suspiciously long list of new files is a reason to stop.

Summary

A .gitignore is the first file worth committing in a new project: dependencies, build output, logs and secrets don't belong in the repository. Ignore rules only apply to untracked files, so anything already added is removed with git rm --cached, and rules are checked with git check-ignore -v. Use git rm and git mv to delete and rename files so the changes go straight into the index. Working with the index and your first repository is covered hands-on in the Git course.

FAQ

Should I commit the .gitignore file itself?

Yes, so the rules apply to the whole team; put personal exclusions in .git/info/exclude or your global ignore file.

Can a project have more than one .gitignore file?

Yes, you can put a .gitignore in any subfolder; its rules apply relative to that folder and add to the root rules.

How do I commit a file that is ignored?

Use git add -f path/to/file; after that the file is tracked and .gitignore no longer affects it.

How do I list all ignored files in a project?

Run git status --ignored or git ls-files --others --ignored --exclude-standard.

How do I temporarily stop tracking changes to a committed file?

Use git update-index --skip-worktree file, but it's a local setting and not a substitute for .gitignore.

Learn this topic on video

This topic is covered in depth with practice in the course “Git from Scratch to Confident Developer”.

Go to course