You edited files, the change didn't work out, and you want your working tree back to how it looked at your last commit. git discard local changes covers all of that — one file, a folder, the whole tree, staged edits, unstaged edits, and the brand-new files you never git added. The tool built for exactly this is git restore, shipped in Git 2.23 (August 2019) specifically to replace git checkout's overloaded, easy-to-misuse file-restore behavior. This guide is scoped tight: discarding uncommitted work, not rewriting commit history. If what you actually want to undo is already a commit — including on a branch other people have pulled — that's a different operation, covered in full in the guide to git reset --hard's every mode and recovery path. And if you typed "git revert file" hoping to fix an uncommitted edit, that command reverts commits, not files — the real distinction is explained in Diff Checker's git revert guide, and again below, since it's the single most common mix-up in this topic.

Quick Answer: Which Command Discards What

Every git discard local changes question reduces to two facts you need before picking a command: which area holds the change (working tree, staging index, or both), and whether the file is tracked at all. Match your situation below — full explanations for each row follow in the linked sections.

Situation Command Notes
Discard unstaged edits, one file git restore <file> Staged copy of the file, if any, is untouched
Discard unstaged edits, current directory down git restore . Scoped to cwd — a real gotcha, see below
Discard unstaged edits, entire repository git restore :/ Whole repo regardless of where you run it
Unstage a file, keep the edit git restore --staged <file> Index only — working tree edit survives
Discard staged and unstaged, one file git restore -SW <file> Full wipe for that file back to HEAD
Reset a file to an older commit or branch git restore --source=<ref> <file> Pulls content from that commit/branch
Remove new, untracked files git clean -fd restore and reset --hard never touch these
Wipe everything uncommitted, tracked and untracked git reset --hard HEAD then git clean -fd Blunt instrument — full treatment in the git reset hard guide
Cancel a merge/rebase/cherry-pick in progress git merge --abort etc. Different problem — nothing to discard yet
What Changed, and Where? Pick Your Command What changed, and where? Unstaged edit, tracked file Staged edit (git add already run) New file, never staged (untracked) Merge / rebase / cherry-pick in progress git restore <file> git restore --staged <file> git clean -fd only one that reaches it git merge --abort (etc.) -SW for staged + unstaged index → HEAD, edit kept -n to preview first rebase / cherry-pick / am too restore, checkout -- and reset --hard never touch untracked files git clean is the only command above that can delete a brand-new file
Match what changed and where it lives to the command that discards it — git clean is the only one of the four that can ever touch a file Git isn't tracking yet.

Two commands cover the vast majority of real git discard changes sessions: git restore <file> when you know exactly which file to throw away, and git restore :/ when you want a clean slate across the whole repository. Everything past this table is about the details that decide which row actually applies — and the one thing every row shares: none of this is a history rewrite. You're not undoing commits, you're discarding local changes that were never committed in the first place.

Before You Discard: Uncommitted Work Has No Undo

Read this before you run anything below. git reflog — the command everyone reaches for after a bad Git decision — records every position HEAD and your branches have held. It tracks commits. It has no idea what your working tree or staging area looked like five minutes ago, because uncommitted content was never a commit to begin with. When git restore, git checkout --, or git reset --hard discards an uncommitted edit, that content is not sitting in some recoverable history slot waiting for a reflog entry — it's simply gone, the way an unsaved document is gone if you close the editor without saving.

Two genuine, partial exceptions exist and are worth knowing about before you need them. Content that was staged with git add at some point became a real Git object (a blob) even if it was later discarded, and git fsck --lost-found can sometimes find those dangling objects before garbage collection cleans them up. And IDE local history — VS Code's "Local History" panel, JetBrains' "Local History" — keeps its own file snapshots independent of Git entirely, often including edits that never touched the staging area at all. Both are covered in detail, with the actual commands, in the recovery section further down. Treat both as a lucky break to check for, not a safety net to rely on — the honest default assumption is that discarding local changes in Git is a one-way door.

Where Your Changes Live: Working Tree, Index, HEAD

Every discard command in this guide moves content between three places, and knowing which direction each command copies from is the entire trick to using git restore correctly instead of guessing.

  • Working tree — the actual files on disk, the ones your editor shows you.
  • Index (staging area) — the snapshot built by git add, waiting for the next commit.
  • HEAD — your last commit, the tree Git considers "already saved."
Three Areas, Two Directions of Copy git restore --staged <file> git restore <file> HEAD last commit already saved Index staging area built by git add Working Tree files on disk what your editor shows git restore -SW <file> both directions at once — file matches HEAD exactly
git restore --staged copies HEAD into the index; git restore copies the index into the working tree. Combine both directions with -SW and the file matches HEAD exactly, staged and unstaged copies discarded together.

git restore <file> copies the index's version of a file into the working tree — that's why it only discards unstaged edits: whatever was already staged is what reappears. git restore --staged <file> copies HEAD's version into the index — that unstages a file without touching the working tree at all, since the working tree was never the target of that copy. Combine both directions and you discard everything: working tree and index both reset to HEAD, with no trace of either the staged or unstaged edit left. This mental model — which area, copied from where — is the fuller version of the same three-tree model laid out for commit-level operations in the git reset --hard guide; the difference here is that every command in this article stops at "restore a file," never "move HEAD to a different commit."

git restore: The Modern Way to Discard Local Changes

git restore shipped in Git 2.23.0, released August 2019, alongside git switch — both were carved out of git checkout, which historically did two unrelated jobs (switch branches, restore files) behind one ambiguous command name. restore shipped labeled experimental at first and dropped that label in later Git releases as its flag set stabilized. git checkout -- <file> still works today and is not deprecated — Git does not remove working commands — but restore is the form worth defaulting to going forward, since its name says what it does and it can't accidentally switch your branch the way a mistyped checkout argument once could. The four forms below cover essentially every git discard local changes case you'll hit in practice; the complete flag list lives in the official git-restore documentation.

# Discard unstaged edits — working tree restored from the index
git restore <file>

# Unstage — index restored from HEAD, working tree edit kept
git restore --staged <file>

# Discard both — staged and unstaged, file matches HEAD exactly
git restore --staged --worktree <file>
# short flags: -S -W, or combined: -SW
git restore -SW <file>

# Pull content from a specific commit or branch instead of HEAD/index
git restore --source=HEAD <file>
git restore --source=main <file>

One scope gotcha worth flagging immediately, because it surprises people who assume "restore" means "restore the repo": git restore . only affects the current directory downward, exactly like most Git commands that accept a path. Run it from inside src/api/ and files elsewhere in the repo are untouched. git restore :/ is the form that covers the entire working tree regardless of where you're standing when you run it — the two are not interchangeable, and reaching for . when you meant :/ is an easy way to think you discarded everything when you actually discarded part of it.

The other rule worth internalizing now, because it applies to every command in this guide: neither restore, nor checkout --, nor reset --hard ever touches files Git isn't tracking. New files you created but never staged are invisible to all three. That's covered fully in its own section below, since it's the single most common surprise in this entire topic.

Discard Changes in a Single File

Clear up the naming confusion first, because it's the reason this section exists: git revert file, git revert single file, git revert one file, git revert individual file, git revert specific file, and how to revert change in file are all phrasings of the same intent — "put this one file back to how it looked before" — but git revert is the wrong command for every one of them. git revert creates a new commit that undoes a previous commit; it has no concept of an uncommitted edit sitting in your working tree, because there's no commit there to revert yet. If the change you want gone is still uncommitted, the correct command is git restore. The full mechanics of what revert actually does, for the case where the change really is already a commit, are in the dedicated git revert guide.

# Discard an uncommitted, unstaged edit to one file
git restore <file>

# Same result, older syntax — still valid, not deprecated
git checkout -- <file>

That's the direct answer to git restore a single file. The phrasing git reset single file, git reset one file, and git reset individual file usually points at the same intent from a different angle, but "reset" is worth being precise about here: git reset <file> (no --hard, no --soft) only unstages — it never touches the working tree, regardless of which mode flag you'd normally reach for, because a reset given a file path behaves like --mixed unconditionally. It does not discard an unstaged edit the way restore does. If you want the working tree content gone too, git reset alone won't get you there for a single file — git restore is still the command.

git reset file to head — meaning "make this file's content match HEAD exactly, discarding whatever's staged and whatever's unstaged" — is git restore -SW <file>:

$ git status
Changes to be committed:
  modified:   src/api/client.ts
Changes not staged for commit:
  modified:   src/api/client.ts

$ git restore -SW src/api/client.ts
$ git status
nothing to commit, working tree clean

That single command discards both copies at once — the staged version and the unstaged edit on top of it — and leaves the file byte-for-byte identical to HEAD. It's the file-scoped equivalent of a full reset --hard, just contained to one path instead of the entire repository.

One File, Three Commands: What Each One Touches command → restore <file> reset <file> restore -SW <file> Index (staged copy) untouched reset to HEAD unstages the file reset to HEAD staged edit discarded Working tree overwritten from index unstaged edit is gone untouched edit stays on disk overwritten matches HEAD exactly All three move only HEAD → index → working tree for the one file you name Only reset <file> is non-destructive — it changes what's staged, never what's on disk
git restore <file> only ever touches the working tree; git reset <file> only ever touches the index, and never destroys anything; git restore -SW <file> touches both, leaving the file byte-for-byte identical to HEAD.

Discard All Local Changes at Once

git restore all, git remove all changes, git clear local changes, git throw away local changes, git get rid of local changes, and remove local changes git are all the same request: wipe every uncommitted edit across the whole repository, not just one file. The same git restore flags from the sections above apply — the only thing that changes is the target, a pathspec covering everything instead of one file.

# Discard every unstaged edit in the whole repo (not just cwd down)
git restore :/

# Also unstage everything — index reset to HEAD, working tree kept as-is
git restore --staged :/

# Full wipe — every staged and unstaged edit, entire repo, back to HEAD
git restore -SW :/

git restore -SW :/ is the most literal answer to git discard changes and git undo local changes when "changes" means everything currently modified and tracked: after it runs, git status reports a clean working tree with nothing staged, and every tracked file matches its last commit exactly. It does not touch new files you haven't staged even once — that's the untracked-files gap covered next — and it does not remove commits, since there's no commit involved in the operation at all.

git reset --hard HEAD reaches the same tracked-file end state through a different mechanism — it moves the branch pointer to itself while forcing the index and working tree to match — and for a lot of people it's the more familiar command to type. It's covered in full, including every mode and every recovery path, in Diff Checker's git reset --hard guide; the short version here is that it's the blunter instrument of the two. restore only ever touches files you point it at and never moves any Git reference, which is why a git restore all sweep is the safer default when the goal is specifically "discard edits," not "move my branch."

Unstage Without Losing Work: git restore --staged

Unstaging is the one operation in this guide that's non-destructive by design: it moves a change out of the index and back into the working tree as an unstaged edit, and nothing gets deleted in the process.

# Unstage one file — the edit stays in the working tree
git restore --staged <file>

# Equivalent, older syntax
git reset HEAD -- <file>
git reset -- <file>

# Unstage everything
git restore --staged :/

This is the accurate reading of reset files git and the unstaging sense of git reset file to head: resetting a file's index entry back to what HEAD has, without touching the file on disk at all. It's a completely different operation from git reset --hard, even though both share the word "reset" — one restores an index entry from a commit, the other moves your entire branch and wipes the working tree. Confusing the two is exactly how someone means to unstage a file and accidentally discards it instead, so it's worth reading the flag list twice before running a reset command you copied from memory.

Two Lanes: Which Copy Each Command Touches copy → git restore --staged <file> git restore <file> Index (staged copy) reset to HEAD unstaged — nothing lost untouched Working tree untouched — edit stays overwritten from index unstaged edit is gone Both only ever touch the file(s) you name — no other file, no Git reference, moves The one destructive cell here: an unstaged edit git restore discards has no reflog
git restore --staged only ever resets the index — the unstaged edit in the working tree survives. git restore only ever overwrites the working tree from the index — if that unstaged edit was never staged, this is the step that throws it away.

Untracked Files: Only git clean Removes Them

Every command covered so far — restore, checkout --, reset --hard — operates on files Git already knows about. New files sitting in your working tree that were never git added are, from Git's point of view, not part of the repository yet, so none of those commands see them, let alone delete them. The only command that removes untracked files is git clean.

# Dry run — show what would be deleted, delete nothing
git clean -n

# Actually delete untracked files AND untracked directories
git clean -fd

# Also delete files matched by .gitignore (build output, .env, node_modules — careful)
git clean -fdx

# Delete ONLY gitignored files, leave other untracked files alone
git clean -fdX

-f (force) is required for clean to delete anything at all — clean.requireForce defaults to true specifically so a bare git clean can't wipe files by accident. -d extends the deletion to untracked directories, not just loose files. Lowercase -x widens scope to include anything .gitignore excludes — build artifacts, .env files, dependency folders — which is powerful and also the fastest way to lose something you meant to keep if your ignore rules are broad. Uppercase -X flips that around: it removes only the gitignored files and leaves every other untracked file untouched, useful for clearing build output specifically without touching new source files you haven't staged yet. The full flag reference, including the -e exclude pattern, is in the official git-clean documentation.

git clean Scope: Default, -x and -X -x — untracked AND gitignored files git clean -fd untracked files & dirs (not gitignored) — default -X ONLY gitignored files Never reach untracked files, any flag git restore git checkout -- git reset --hard restore, checkout -- and reset --hard operate only on tracked files — regardless of any flag, none of them can see or delete an untracked file
Default git clean -fd covers untracked files and directories; -x widens that scope to also sweep up gitignored files; -X narrows to only the gitignored ones. restore, checkout -- and reset --hard never cross into any of these — git clean is the only door into untracked territory.

Run git clean -n before every real invocation. There is no reflog for deleted untracked files and no commit object backing them in the common case — once clean removes a file that was never staged, it's gone the same way an unsaved document is gone, exactly the warning from the top of this guide.

Reset a File to master, main or Any Commit

Sometimes the target isn't HEAD — it's how a file looked on a different branch, or several commits back. git reset a file to master and git reset single file to main both describe the same move: pull one file's content from another ref into your current working tree, without checking out that branch or merging anything.

# Pull a file's content from the main/master branch tip
git restore --source=main <file>
git restore --source=master <file>

# Pull from a specific commit
git restore --source=a1b2c3d <file>

# Older syntax, same effect
git checkout main -- <file>

By default this only updates the working tree, leaving the pulled-in content unstaged relative to your current HEADgit status shows it as a modification, ready to review or commit. Add --staged (git restore --source=main -S <file>) if you also want it staged immediately. Nothing about this command switches your branch, merges history, or affects any file besides the one(s) you named — it's a narrow, single-file version of what a merge or checkout would otherwise do to the whole tree.

git restore vs git checkout vs git reset vs git revert

git revert vs reset is the comparison people usually ask about, but it's incomplete without restore and clean in the same table — those four plus stash cover essentially every "undo something in Git" scenario, and mixing them up is the single biggest cause of losing work you meant to keep.

Command What It Touches Scope Reversible? Typical Use
git restore Working tree and/or index, per flags File(s) you name No — no reflog for uncommitted content Discard a specific uncommitted edit
git checkout -- <file> Working tree (and index, with a ref) File(s) you name No — same caveat as restore Older syntax, still valid, same effect as restore
git reset --hard Branch pointer, index, entire working tree Whole repo Commits: yes, via reflog. Uncommitted edits: no Wipe everything uncommitted / move branch to a commit
git revert Creates a new commit undoing a prior commit One or more existing commits Yes — revert the revert Undo a commit already shared or pushed
git clean Untracked files (and ignored files with -x) Files never staged No — no reflog, no commit backing them Remove build output or stray new files
git stash Uncommitted changes moved to a hidden stack Whole tree or a pathspec Yes — apply/pop brings it back Set work aside temporarily instead of discarding it

The line that resolves git revert vs reset directly: revert is the only one of the six that adds history instead of rewriting or discarding it, which is exactly why it's the safe choice on a branch other people already have — nobody's history diverges, since nothing already-shared gets removed. reset --hard, by contrast, removes commits from the branch entirely, which is fine locally but dangerous on anything shared. Neither one is about uncommitted edits at all; for those, the top three rows — restore, checkout --, and clean — are the only commands that actually apply.

git undo checkout splits into two different questions depending on what "checkout" meant. If you switched branches with git checkout <branch> — the mechanics of that move, local and remote, are in the git checkout remote branch guide — and want to go back, git switch - (or git checkout -) returns to whatever branch you were on before — a pure navigation move, nothing about your files changes. If instead you ran git checkout -- <file> and want the discarded edit back, that's the other question this entire guide answers: in general, you can't — see the recovery section for the narrow exceptions worth checking anyway.

Discarding Changes in VS Code, GitHub Desktop and GitLab

github undo local changes and github remove local changes are both searches worth disambiguating first: github.com, the website, has no concept of your local working tree at all — it only ever sees what you've pushed. What people actually mean is GitHub Desktop, the separate desktop client, which does operate on your local files — and there, as in every other GUI, git undo local changes still resolves to the same restore and clean commands running underneath the buttons.

  • VS Code — Source Control panel, hover a modified file, click the discard (↩) icon to revert that one file; use "Discard All Changes" at the top of the Changes group to discard everything at once. Both call the same git restore/checkout -- machinery under the hood.
  • GitHub Desktop — right-click a changed file and choose "Discard Changes…" for one file, or use the Branch menu's "Discard all changes" for everything in the working directory.
  • GitLabgitlab revert single file is not a native one-click button the way "Revert" on a whole merge request is; GitLab's MR-level revert creates a revert commit for the entire MR, not a per-file option. The practical way to revert a single file inside a merge request is the same command-line move covered above — git restore --source=<sha> <file>, then commit and push — or editing the file back directly through GitLab's Web IDE.

All three are UI wrappers around the same underlying commands this guide covers; the wording and icon placement drift with product updates, but the operation they trigger — restore this file from the index or from HEAD — does not.

git abort changes: Cancelling a Merge, Rebase or Cherry-Pick

git abort changes usually means something different from everything above: not "discard an edit," but "stop a multi-step Git operation that's currently in progress and put everything back the way it was before it started." Git has a dedicated --abort flag for exactly that, on every command that can leave you mid-operation with conflict markers in your files.

git merge --abort
git rebase --abort
git cherry-pick --abort
git am --abort

Each of these restores the repository to its state right before that specific operation began — conflict markers disappear, the working tree returns to normal, and nothing about the attempt is left behind. They only work while the operation is actually in progress; run git status first if you're not sure ("You are currently merging" or "interactive rebase in progress" confirms it). The merge case specifically, including what to do when --abort itself refuses to run, has its own full guide at canceling or undoing a merge safely, and the cherry-pick case — including what --abort rolls back when you're picking a commit from another branch — has its own walkthrough. Once nothing is in progress, "abort my changes" just means the plain discard commands from earlier sections — there's no operation left to abort, only edits left to throw away.

Can You Get Discarded Changes Back?

Usually not, and it's worth being direct about that rather than hedging. git reflog is the tool everyone reaches for after a Git mistake, and it genuinely does save people after a bad reset --hard against the wrong commit — but it tracks commit history, not working-tree or index snapshots. Uncommitted content that restore, checkout --, or reset --hard discarded was never a commit, so there is no reflog entry pointing back to it.

Can You Get It Back? A Decision Flow Content already discarded — get it back? Was it ever staged with git add at some point? yes no git fsck --lost-found searches dangling blob objects before garbage collection prunes them Check IDE Local History VS Code Timeline / JetBrains Local History — outside Git entirely Partial rescue — not a guarantee Both cost nothing to try — but treat every discard above as final until proven otherwise
Two branches worth checking before giving up: content staged at some point may survive as a dangling blob findable with git fsck --lost-found; content that never touched the index depends entirely on IDE-level local history. Neither is a guarantee.

Two things are worth checking anyway, since both cost nothing to try:

# Find dangling (unreferenced) commit and blob objects
git fsck --lost-found

# Inspect a candidate object
git show <sha>

git fsck --lost-found can locate content specifically if it was staged with git add at some point, even if it was later unstaged and discarded — staging creates a real Git blob object, and that object can survive as "dangling" (unreferenced by any commit or branch) until garbage collection eventually prunes it — Git's gc.pruneExpire default is 2 weeks. Content that lived only in the working tree and was never staged even once never became a Git object in the first place, so there is nothing for fsck to find — this is the precise line between "might be recoverable" and "definitely gone."

The second worth checking is entirely outside Git: IDE local history features — VS Code's "Local History" (Timeline view, or the dedicated Local History extension), JetBrains IDEs' built-in "Local History" — snapshot file contents on save, independent of any Git operation. These have saved people's uncommitted work more than once, but they're editor-specific, time-limited, and not something to plan around — check them because it's free, not because it's guaranteed. The realistic takeaway for all of this: treat every discard command in this guide as final at the moment you run it, and use the habits in the next section to avoid needing recovery in the first place.

Safer Habits: Stash, Branch, Diff Before You Discard

Three habits catch most "I discarded the wrong thing" moments before they happen, and none of them cost more than a few extra seconds.

Stash instead of discard when you're not fully sure. git stash takes the exact same uncommitted changes and parks them on a recoverable stack instead of throwing them away — git stash pop brings them back later. It's the reversible version of everything this guide covers, and the right default whenever "discard" really means "I don't need this right now" rather than "I'm certain this is wrong."

Commit first, clean up after, when the edit might be worth keeping. A rough commit is trivially undoable — undoing the last commit or amending it with git commit --amend takes one command, and unlike a discarded working-tree edit, a committed one always has a reflog entry. When in doubt about whether an edit is "done" enough to throw away, committing it first costs nothing and buys a real undo path — and a pile of rough checkpoint commits can be squashed into one before the branch goes anywhere, so the safety net costs nothing in history noise either.

Look before you discard. Before running git restore or git reset --hard on something you're not 100% sure about, pull the committed version out for comparison:

git show HEAD:path/to/file.ts > /tmp/committed-version.ts

Diff Checker, a free Chrome extension (also usable at diffchecker.pro), can't touch Git directly — it doesn't know what a commit or a working tree is — but it's built for exactly this kind of side-by-side text comparison: paste your current, about-to-be-discarded file into one editable pane and the committed version from git show into the other. Its Monaco-based editor renders a side-by-side or unified diff view with syntax highlighting across 17 languages, recomputing live as you edit either side, entirely in your browser with nothing uploaded. You see exactly what the discard is about to remove, and if a specific hunk turns out to be worth keeping, you can copy it back into your file before running the command instead of after wishing you had. For comparing two whole files this way in more detail, see the guide to diffing two files directly.

Frequently Asked Questions

Is git checkout -- <file> deprecated now that git restore exists?

No. git checkout -- <file> still works exactly as before and Git has no plans to remove it. git restore, introduced in Git 2.23 (August 2019), is the recommended replacement because it does one job — restoring files — instead of overloading the same command that also switches branches. New scripts and habits should prefer restore for clarity, but existing checkout -- usage in scripts or muscle memory is not broken and needs no urgent migration.

How do I discard changes to just one file without touching anything else?

git restore <file> discards unstaged edits to that single file and leaves every other modified file untouched. If the file also has staged changes, add --staged --worktree (short form -SW) to discard both the staged and unstaged copies at once: git restore -SW <file>. Neither form affects any other file in the repository, staged or not.

Does git revert undo uncommitted local changes to a file?

No — this is the most common mix-up in this whole topic. git revert creates a new commit that undoes the effect of a previous commit; it operates on commit history and has no concept of your working tree or staging area. If you typed "git revert file" or "how to revert change in file" meaning "put this file back to how it looked before I edited it," the command you want is git restore <file> for unstaged edits, or git restore --source=<commit> <file> to pull the file's content from a specific earlier commit or branch.

Can I get back changes I discarded with git restore or git reset --hard?

Usually no. git reflog only records where HEAD and branches have pointed — it tracks commits, not working-tree contents, so it cannot help recover uncommitted edits that restore, checkout --, or reset --hard threw away. Two partial exceptions exist: content that was git add-ed at some point became a real Git object (a blob) and can sometimes be found with git fsck --lost-found before garbage collection runs, and IDE local history features (VS Code's Local History, JetBrains' Local History) often keep their own separate snapshots regardless of Git. Both are rescues worth trying, not guarantees — content that was only ever in the working tree and never staged leaves no Git object behind at all.

How do I remove new files that git restore and git reset --hard don't delete?

Run git clean. Neither git restore, git checkout --, nor git reset --hard ever touches untracked files — files Git isn't tracking at all. git clean -n shows what would be deleted without deleting anything; git clean -fd actually removes untracked files and directories; adding -x also removes files matched by .gitignore, while -X removes only gitignored files and leaves other untracked files alone. Always run the -n dry run first, since git clean has no reflog and no undo.