Deleting and Moving Files
Deleting or renaming a tracked file changes the Working Tree first. A normal commit still records the Index, so the repository does not record that change until the Index represents it. The same three-area model used for edits and new files therefore applies to deletions and moves.
1. Deleting a file from the filesystem
Section titled “1. Deleting a file from the filesystem”Suppose old.php exists in the latest commit and the Index:
HEAD Index Working Treeold.php old.php old.phpDeleting it in VS Code or with a filesystem command changes only the Working Tree initially:
rm old.phpHEAD Index Working Treeold.php old.php missingShort status shows the deletion in the second column:
D old.phpThe leading space means the Index has no staged change. D in the second
column means the Working Tree is missing the file represented in the Index.
A normal git commit does not record this deletion yet because it reads the
Index rather than copying the current directory directly.
2. Stage a manual deletion
Section titled “2. Stage a manual deletion”Stage the specific path after deleting it manually:
git add old.phpDespite its name, git add does not mean only “add a new file.” It updates
the selected Index path to represent the current Working Tree state. When the
path is absent, that update stages its deletion.
For tracked changes across a suitable scope, this form can also stage modifications and deletions:
git add -uThe staged state becomes:
HEAD Index Working Treeold.php staged as deleted missingand short status becomes:
D old.phpD in the first column means the deletion is selected for the next commit.
The file still exists in older commits; the new snapshot records that the
path is absent.
3. Delete and stage with git rm
Section titled “3. Delete and stage with git rm”Git can combine the filesystem deletion and Index update:
git rm old.phpThis command:
- removes
old.phpfrom the Working Tree; - stages removal of its Index entry.
It normally produces the same final state as:
rm old.phpgit add old.phpInspect the result before committing:
git status --shortgit diff --stagedFor a tracked directory, recursive removal requires an explicit recursive form:
git rm -r obsolete/Review the target and staged diff carefully before removing many paths.
4. Repository-only removal with --cached
Section titled “4. Repository-only removal with --cached”Sometimes the repository should stop tracking a file while the application still needs a local copy:
git rm --cached debug.logCompare the two forms:
| Command | Index | Working Tree |
|---|---|---|
git rm debug.log |
removal staged | local file removed |
git rm --cached debug.log |
removal staged | local file preserved |
The --cached form targets the Index boundary. Add a suitable ignore rule if
the preserved local path should not return as untracked or be added again.
Neither form removes versions stored in older commits.
5. Safety when local changes exist
Section titled “5. Safety when local changes exist”Git can reject git rm when removing the path would discard content that is
not safely represented in both HEAD and the Index. Treat that refusal as a
safety signal, not an obstacle to bypass immediately.
Inspect first:
git statusgit diff -- old.phpgit diff --staged -- old.phpDecide whether the local work should be committed, copied elsewhere, or
intentionally discarded. Do not add -f merely to silence the refusal; a
forced removal can destroy uncommitted content.
6. Move or rename with git mv
Section titled “6. Move or rename with git mv”This command moves a file in the Working Tree and stages the corresponding Index change:
git mv old.php UserService.phpA typical short status is:
R old.php -> UserService.phpgit mv is convenient, but it is not required for Git to recognize a rename.
The following workflow can reach the same staged result:
mv old.php UserService.phpgit add -ABefore staging a manual move, status commonly exposes the raw Working Tree facts:
D old.php?? UserService.phpAfter staging, Git compares the removed and added content and may present the pair as a rename.
7. Rename detection is an inference
Section titled “7. Rename detection is an inference”A commit normally does not store an operation saying “rename this path.” It stores a new snapshot: the old path is absent, and the new path exists. Diff and history commands compare snapshots and infer a rename when deleted and added content is sufficiently similar.
Inspect the inference with:
git diff --staged --summaryExample:
rename old.php => UserService.php (100%)100% means the compared file content is identical. If the file is heavily
rewritten while being moved, Git may display a deletion and an addition
instead. The committed snapshot is still correct; only the comparison’s
presentation differs.
This also explains why different diff options or similarity thresholds can present the same pair of snapshots differently. Rename detection is analysis, not permanent rename metadata inside the commit.
8. A backend refactoring example
Section titled “8. A backend refactoring example”Suppose a Laravel service is moved and renamed:
app/Services/Payment.php→ app/Billing/PaymentService.phpA focused sequence is:
git mv app/Services/Payment.php app/Billing/PaymentService.phpgit status --shortgit diff --staged --summarygit diff --stagedUpdate namespaces, imports, service-container bindings, and tests as separate content edits, then stage and review them. Rename detection does not prove the application still works; run the relevant test suite after the refactor.
9. Common mistakes
Section titled “9. Common mistakes”- Assuming a filesystem deletion is automatically included in a normal commit.
- Reading
D fileandD fileas the same state. - Thinking
git addcannot stage a deletion. - Forgetting that
git rmdeletes the Working Tree file too. - Using
git rmwhengit rm --cachedis required to preserve a local file. - Forcing removal without inspecting uncommitted work.
- Believing
git mvwrites permanent rename metadata into the commit. - Treating a detected rename as proof that application references were updated.
10. Summary
Section titled “10. Summary”filesystem delete → Working Tree changes firstgit add <deleted-path> → stage the deletiongit rm <path> → delete locally and stage removalgit rm --cached <path> → stage removal; preserve local filegit mv <old> <new> → move locally and stage the resultmanual move + git add -A → can produce the same staged snapshotrename display → inferred by comparing similar content11. Knowledge check
Section titled “11. Knowledge check”Why does a plain commit not record a filesystem-only deletion?
A normal commit records the Index. Until the deleted path is staged, its Index entry still represents the old file.
When should git rm –cached be used?
When the next snapshot should stop tracking a path but the Working Tree copy must remain on the machine.
Does a commit contain a permanent rename operation?
Normally no. It records the new snapshot, and Git later infers renames by comparing removed and added content.