Skip to content

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 Tree
before rule .env .env .env
after rule .env .env .env

Adding this line does not alter any of those three copies:

.env

Git 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.

For a path present in HEAD, this command restores its Index entry from HEAD:

Terminal window
git restore --staged .env

The older spelling has the same important boundary here:

Terminal window
git reset HEAD .env

Neither 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:

Terminal window
git rm --cached .env

The 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:

Terminal window
printf '.env\n' >> .gitignore
git add .gitignore
git rm --cached .env
git status --short
git diff --staged

A typical short status is:

M .gitignore
D .env

Before committing, verify that the local file still exists. In Git Bash:

Terminal window
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.

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 policy

An example template might contain:

APP_ENV=local
DB_HOST=127.0.0.1
DB_DATABASE=app
DB_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.

  • Expecting a new ignore rule to stop tracking an existing path.
  • Confusing “no staged change” with “not present in the Index.”
  • Using git restore --staged when the goal is to remove the Index entry.
  • Omitting --cached and accidentally deleting the Working Tree file too.
  • Assuming deletion from the next commit removes the path from old history.
  • Treating .gitignore as secret protection rather than an addition policy.
  • Committing real values to .env.example.
  • Ignoring dependency lock files without considering reproducibility.
.gitignore → affects ordinary untracked-path discovery
tracked path → remains tracked despite a matching ignore rule
git restore --staged → restore Index entry from HEAD
git rm --cached <path> → stage removal from repository; preserve local file
.env → normally ignored
.env.example → normally tracked with safe placeholders
composer.lock → normally tracked in an application
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.