Tracked Files and .gitignore
An ignore rule prevents ordinary discovery of an untracked path. It does not remove a path that Git already represents in the Index. This distinction matters when a generated file or local environment file was committed before the project adopted the correct ignore rule.
1. Ignore rules do not change tracking state
Section titled “1. Ignore rules do not change tracking state”Suppose .env exists in the latest commit:
HEAD Index Working Treebefore rule .env .env .envafter rule .env .env .envAdding this line does not alter any of those three copies:
.envGit still compares the tracked Working Tree file with its Index entry, so a
later edit remains visible in git status. .gitignore is a discovery policy,
not an instruction to delete Index entries or rewrite history.
This behavior is deliberately explicit. A broad new rule such as *.json
must not silently stage the removal of tracked project files.
2. Unstaging is not untracking
Section titled “2. Unstaging is not untracking”For a path present in HEAD, this command restores its Index entry from
HEAD:
git restore --staged .envThe older spelling has the same important boundary here:
git reset HEAD .envNeither command removes the tracked path. They only undo a staged difference
by making the Index copy match HEAD again. A newly added file that has never
existed in HEAD is different: unstaging it can leave it untracked because
there is no HEAD entry to restore.
3. Stop tracking while preserving the local file
Section titled “3. Stop tracking while preserving the local file”Use an explicit Index-only removal:
git rm --cached .envThe resulting state is:
HEAD Index Working Tree.env staged as deleted .env still exists--cached targets the Index and preserves the Working Tree file. The staged
deletion means the next commit will no longer contain .env. Earlier commits
remain unchanged and still contain their historical version.
Record the shared rule and the tracking-state change together:
printf '.env\n' >> .gitignoregit add .gitignoregit rm --cached .envgit status --shortgit diff --stagedA typical short status is:
M .gitignoreD .envBefore committing, verify that the local file still exists. In Git Bash:
test -f .env && echo ".env still exists"After the commit, the new HEAD has no .env entry. The local copy is now
untracked, and the committed ignore rule normally keeps it out of status and
broad adds.
4. Safe environment-file policy
Section titled “4. Safe environment-file policy”A backend application normally separates the local values from a safe, tracked template:
.env ignored; real local/runtime values.env.example tracked; required names and safe placeholders.gitignore tracked; shared repository policyAn example template might contain:
APP_ENV=localDB_HOST=127.0.0.1DB_DATABASE=appDB_USERNAME=DB_PASSWORD=.env.example helps developers, CI, and deployment tooling discover required
configuration. It does not replace the real runtime .env, and it must not
contain real passwords, tokens, or private keys.
5. Removing tracking does not remove history
Section titled “5. Removing tracking does not remove history”git rm --cached changes the next snapshot. It does not erase the file from
older commits. If a real credential was committed, assume it may have been
copied and rotate or revoke it immediately. A later history-cleanup operation
cannot make the old credential trustworthy again.
Rewriting old history is a separate, disruptive operation: affected commits and their descendants receive new IDs, and collaborators must coordinate their local repositories. That procedure belongs to the later security and history-rewriting lessons.
6. Generated files and dependency lock files
Section titled “6. Generated files and dependency lock files”Do not ignore a file merely because a tool generated it. Decide whether the repository needs the artifact or the exact decision the tool made.
For a typical Laravel application:
| Path | Usual policy | Reason |
|---|---|---|
vendor/ |
Ignore | composer install recreates installed packages |
.env |
Ignore | Environment-specific values and possible secrets |
.env.example |
Track | Safe configuration contract for the project |
composer.json |
Track | Declares dependency constraints and scripts |
composer.lock |
Track | Reproduces the exact tested dependency versions |
Ignoring composer.lock blindly can let developers, CI, and production
resolve different dependency versions that all satisfy the broader
constraints. Applications normally commit the lock file for reproducibility.
7. Common mistakes
Section titled “7. Common mistakes”- Expecting a new ignore rule to stop tracking an existing path.
- Confusing “no staged change” with “not present in the Index.”
- Using
git restore --stagedwhen the goal is to remove the Index entry. - Omitting
--cachedand accidentally deleting the Working Tree file too. - Assuming deletion from the next commit removes the path from old history.
- Treating
.gitignoreas secret protection rather than an addition policy. - Committing real values to
.env.example. - Ignoring dependency lock files without considering reproducibility.
8. Summary
Section titled “8. Summary”.gitignore → affects ordinary untracked-path discoverytracked path → remains tracked despite a matching ignore rulegit restore --staged → restore Index entry from HEADgit rm --cached <path> → stage removal from repository; preserve local file.env → normally ignored.env.example → normally tracked with safe placeholderscomposer.lock → normally tracked in an application9. Knowledge check
Section titled “9. Knowledge check”Why does a modified tracked file still appear after adding an ignore rule?
Git already has an Index entry for it. Ignore rules govern ordinary discovery of untracked paths; they do not remove existing Index entries.
Why does git restore –staged .env not untrack a file present in HEAD?
It restores the Index entry from HEAD, where .env already exists. The path
therefore remains represented in the Index and remains tracked.
What survives git rm –cached .env?
The Working Tree file and all older committed versions survive. Only removal from the next snapshot is staged.