Skip to main content
ANVISoftware Solutions
Lesson 14 of 15Intermediate14 min

.gitignore

By the end of this lesson

Keep build output, dependencies and secrets out of the repository.

A .gitignore file is a list of paths and patterns that Git should leave alone. Anything matching stays in your folder and never appears in git status as something waiting to be added.

The file lives in the repository and is itself committed, so everyone working on the project ignores the same things. That is the point: without it, every person has to remember not to commit their build folder, and eventually one of them forgets.

Four categories belong in almost every .gitignore, for different reasons:

  • Build output — bin, obj, dist, compiled assets. Produced from the source, so storing it adds size and creates conflicts on files nobody edits by hand
  • Dependencies — node_modules, package folders. Described by a manifest and a lock file, which are what you commit instead
  • Environment and secret files — .env, local connection strings, credential files. These differ per person and must never be shared through the repository
  • Machine and editor noise — OS metadata files, editor caches, local test databases, log files. Yours alone, and of no use to anyone else

The pattern syntax, which is small enough to learn in one sitting:

bin/
A trailing slash matches directories only. Everything inside is ignored along with it.
*.log
An asterisk matches any run of characters within one path segment. This ignores every file ending in .log.
!keep-this.log
A leading exclamation mark re-includes something an earlier pattern excluded. Useful for one exception to a broad rule.
/config.local.json
A leading slash anchors the pattern to the folder containing the .gitignore, so it matches only at that level rather than anywhere in the tree.
**/temp/
A double asterisk crosses directory boundaries. This ignores a folder named temp at any depth.
.gitignore for a .NET solution
Text
# Build output
bin/
obj/
[Dd]ebug/
[Rr]elease/

# Dependencies restored from the manifest
packages/

# Local configuration and secrets - never commit these
.env
.env.local
appsettings.Development.json
appsettings.*.Local.json

# Editor and machine noise
.vs/
.idea/
*.user
.DS_Store

# Logs and local databases
*.log
*.db
*.db-shm
*.db-wal
Checking what is ignored, and fixing something already tracked
Shell
# Why is this file being ignored? Names the pattern and the line.
git check-ignore -v src/bin/Release/App.dll

# What is Git currently hiding from me?
git status --ignored

# A file was committed before it was ignored. Stop tracking it,
# but keep it on disk.
git rm --cached appsettings.Development.json
git commit -m "Stop tracking local development settings"

# Whole folder committed by mistake
git rm -r --cached obj/
git commit -m "Stop tracking build output"
  • git check-ignore -v answers the question that wastes the most time here: which pattern in which file is matching. Ignore rules can come from several places, and guessing is slow.
  • --cached is the important flag. git rm --cached removes the file from Git's tracking while leaving it in your folder. Without --cached, it deletes your file too.
  • After this commit, the file stops appearing in future commits. It does not disappear from the commits already made — every earlier commit still contains it, exactly as it was.
  • Adding a pattern to .gitignore has no effect on a file Git already tracks. Ignore rules apply to untracked files only, which is why the git rm --cached step is needed.

If a secret does get in, the first action is not a Git action. Rotate the credential — change the password, revoke the key, issue a new token. A secret that no longer works cannot be used, whatever remains in the history.

Then tell whoever is responsible for the system it belongs to, because they may need to check whether it was used. Cleaning the history comes after both of those, as a tidying exercise rather than the fix. Treating the history rewrite as the fix leaves a live credential exposed while you work on it.

Summary

  • A committed .gitignore gives everyone on the project the same ignore rules
  • Build output, dependencies, environment files and machine noise all belong in it
  • Ignore rules apply to untracked files only — use git rm --cached for something already tracked
  • A secret in one commit stays in history after deletion, and proper removal means rewriting every later commit across every clone
  • If a secret does get in, rotate the credential first; cleaning the history is tidying, not the fix

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Untrack something already committed

In a practice repository, commit a file called local-settings.json. Then add it to .gitignore and run git status.

Nothing changes. Explain why, then use git rm --cached to fix it and confirm the file is still on disk afterwards.

Show solution

Git already tracks the file, so the ignore rule does not apply to it. Ignore rules decide whether an untracked file is offered to you, not whether a tracked file continues to be tracked.

git rm --cached removes it from the index. The next commit records the removal, the file stays in your folder, and from then on the ignore rule keeps it out of git status.

The important detail to notice: every commit made before this still contains the file. That is the mechanism behind the warning about secrets, seen in a harmless case.

Shell
git add local-settings.json
git commit -m "Add local settings file"

# add local-settings.json to .gitignore, then:
git status                      # no change - still tracked

git rm --cached local-settings.json
git commit -m "Stop tracking local settings"
git status                      # now ignored, file still on disk

Think about it

Think about it

You notice that an API key was committed to a private repository six weeks ago and pushed. Eight people have cloned it.

List what you would do, in order, and say why rewriting the history is not the first item.

Show solution

Revoke or rotate the key first. Until that is done the credential works, and every minute spent on Git is a minute it stays usable.

Then report it to whoever owns the system, so they can check whether it was used. That is their decision to make with full information, not yours to skip.

Cleaning the history comes third, and it is genuinely awkward: eight clones, every commit after the leak gets a new identifier, and everyone has to re-clone or reset. It is worth doing to stop the value circulating, but it fixes exposure rather than risk.

The conclusion most teams reach is the one in this lesson. The cost of removal is so far above the cost of prevention that the effort belongs at the .gitignore end.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

You add a file to .gitignore, but it still shows up as modified in git status. Why?
A password was committed and pushed last month. You delete it and commit the deletion. What is the situation?

Saved in this browser only.