In a 2017 VS Code issue, someone described clicking Discard Changes after seeing roughly five thousand files in the Source Control view. They said the action removed their files, including work they could not find in the Recycle Bin. A screenshot of the issue circulated again, this time described as a pull request. It was an issue, but the distinction does not make the failure less interesting. My first reaction was short: we need git trash.
The name is slightly misleading. This is less about adding another verb to Git than about a missing promise in our tools: before a tool destroys local work, it should know how to give that work back.
What Git Can Actually Recover
Git gives us several places where a file can exist. They look similar in an editor’s Source Control panel, but they have very different recovery properties.
- Committed content is in the repository’s history. If a tracked file is deleted or changed, we can usually restore its committed version.
- Staged content has been copied into the index. It may differ from both the working file and the last commit.
- Unstaged content lives in the working tree. Git can show a diff against the index, but the current bytes have not necessarily been saved as a Git object.
- Untracked content is only a file on disk as far as ordinary Git history is concerned.
git statuscan tell us that it exists; that does not mean Git has a copy.
This is why “it’s in a Git repository” is a poor synonym for “it’s backed up.” A three-month-old file that was never added may be less recoverable than a file committed five minutes ago. git restore can reconstruct content from the index or a commit. It cannot reconstruct bytes that Git never recorded. git clean, meanwhile, removes untracked files from the working tree; its -n option only previews that removal.
An editor compresses these different states into one list of changes. That is convenient for staging, but dangerous for deletion. The word “discard” can mean “restore this tracked file from the index” or “remove this untracked file from disk.” The UI may look like one operation while the consequences are not the same.
What a Trash Operation Would Need to Save
A useful git trash would be a recoverable discard, not simply an alias for git clean.
Before changing the working tree, it would capture the exact state being removed: file contents, paths, and whether each file was tracked, staged, untracked, or ignored. It would then record a durable recovery entry and only perform the discard after that entry had been written successfully. If saving fails, deletion should fail too.
Recovery also has to be selective. I might want one deleted file back without restoring every other change from that afternoon. If a new file now occupies the old path, the tool should report a conflict and offer a different destination instead of silently overwriting it. A useful entry would show when the discard happened, which paths it affected, and how much data it saved.
There are hard edges here. Repositories can contain gigabytes of generated files; ignored paths can hold secrets; a remote development environment may not have a desktop Recycle Bin. A trash system needs explicit size limits, retention rules, and clear handling for those cases. “Moved to Trash” should never be displayed when the bytes were actually deleted permanently.
This is also why an operating-system Trash is only a partial answer. It can help with an untracked file that is removed as a file. A tracked file’s discarded edits are usually replaced with older contents in place; the removed version is not necessarily a deleted file for the operating system to catch.
What We Have Today
The current VS Code documentation makes the distinction explicit. Discarding a tracked file restores its staged version, or the committed version if nothing is staged. Discarding an untracked file removes it. Untracked files can go to the Recycle Bin or Trash when git.discardUntrackedChangesToTrash is enabled and the environment supports it, but that is not guaranteed. The docs suggest checking Timeline local history or the system Trash after a mistake, while warning that neither is a guaranteed backup. The 2017 report should therefore not be read as a description of every current VS Code setup.
For a command-line workflow, I would first inspect the actual boundary:
1 | git status --short |
These commands show different things: working-tree status, unstaged changes, staged changes, and the untracked paths a clean operation would remove. None of them saves the work. When I need to set aside a whole working state, git stash push -u can record tracked changes and untracked files before cleaning the tree. The -u flag does not include ignored files; -a does, with a much broader effect. A stash is useful, but it is an explicit temporary snapshot, not an automatic undo button for every discard.
The simpler long-term habit is still to commit work in small increments. Yet “remember to commit” cannot be the entire design answer. We use discard precisely when we are exploring, reorganizing, or uncertain whether something deserves a commit. Those are the moments when a reversible operation is most valuable.
Git is very good at remembering what we deliberately put into its history. A trash can would remember what we were about to throw away. The difference is small in the interface and enormous when we need to undo one click.