You ran git add, and now you want the file back out of the staging area — without
touching its content, without rewriting history, without gambling on whether the wrong command
just wiped your edit. undo git add almost always means one thing: unstage the
file. The short answer is git restore --staged <file>, which moves the file out
of the index and leaves the edit sitting right where you left it, in the working tree. This guide
covers every real shape that question takes — one file, every file, a file you deleted, a Git
version too old for restore, a brand-new repo with no commits yet, and the one
lookalike command, git rm --cached, that does something different on purpose. If what
you actually want is to throw the edit away entirely rather than just unstage it, that's a
separate operation covered in
Diff Checker's guide to discarding local
changes — this article never deletes content, only moves it between Git's three areas.
Quick Answer: How to Undo git add
Every git unstage question — sometimes phrased as git cancel add — comes down to
picking the right row below: what you staged, how much of it, and which Git version you're
running. git restore --staged <file> is the modern default; git reset
HEAD <file> is the classic form that has worked in every Git release since Git 1.0.
Neither one touches file content — both only move an entry between the index and HEAD.
# Unstage one file — recommended, Git 2.23+ (August 2019)
git restore --staged <file>
# Unstage one file — classic syntax, works on every Git version
git reset HEAD <file>
# Unstage everything staged
git restore --staged :/
git reset | Situation | Command | What Changes | Destructive? | Min Git Version |
|---|---|---|---|---|
| Unstage one file, keep the edit | git restore --staged <file> | Index only | No | 2.23 (Aug 2019) |
| Unstage one file, classic syntax | git reset HEAD <file> | Index only | No | Any |
| Unstage everything | git restore --staged :/ or bare git reset | Index only | No | 2.23+ / Any |
| Unstage a staged deletion | git restore --staged <file> | Index only — file stays missing on disk | No | 2.23+ |
| Unstage part of a file | git restore --staged --patch <file> | Index, selected hunks only | No | 2.23+ |
| No commits yet (unborn branch) | git reset -- <file> or git rm --cached <file> | Index only | No | Any |
| Stop tracking the file for good | git rm --cached <file> | Index now and every future commit | Semi — untracks going forward | Any |
| Already committed the add | git reset --soft HEAD~1 | Moves HEAD, unstages the commit's changes | Rewrites history (safe if unpushed) | Any |
The same request shows up under a lot of different names. git unstage,
git cancel add, git undo git add, git undo add
file, git undo added files, reverse git add,
git revert add, and the plain opposite of git add are all the
same intent phrased differently — none of
them are separate Git commands, and none of them require anything beyond restore
--staged or reset HEAD. A second cluster drops the space entirely:
git unadd, git unadd files, git un add files,
and git unadd one file — again, not real Git subcommands, just the plain-English
name for unstaging. A third cluster describes the action instead of naming it: git add
remove, git add git remove, remove files from git add,
git remove added files, and git remove from added all mean "take
this file out of what git add just staged." Every one of them resolves to the same
two commands above — the only exception is how to cancel git add and commit,
which describes committing after adding, covered separately in the
already-committed section below, since by then the fix is a
different command entirely.
Where Undo Happens: Working Tree, Index, HEAD
git add copies a file's current content from the working tree into the index — the
staging area, the snapshot Git will use for the next commit. Every unstage command in this guide
runs that copy backward: it overwrites the index entry with whatever HEAD already
has, discarding the staged snapshot. The opposite of git add, in that literal
sense, isn't one dedicated command — it's git restore --staged (or git reset
HEAD), because add's direction is working tree → index, and undoing it means
index ← HEAD.
That's the reassurance worth stating plainly, because it's the real question behind most undo git
add searches: unstaging never touches file content on disk. Your edit, your new lines, your
deleted lines — none of it changes when you run git restore --staged or git
reset HEAD. Only the index entry moves. Run git status immediately after and
the file reappears under "Changes not staged for commit" instead of "Changes to be committed" —
same content, different bucket. If you actually want the content gone too, that's an entirely
different, genuinely destructive operation, covered in full in
the git reset --hard guide, and it's worth reading the
difference twice before copying a reset command from memory.
git restore --staged: The Modern Way to Unstage
git restore shipped in Git 2.23, released August 2019, specifically to split
"restore files" away from the overloaded git checkout command. Its
--staged flag (short form -S), documented in
Git's official
git restore reference, is the direct, modern answer to undo git add: it
resets the index entry for a path back to what HEAD has, and stops there.
# Unstage a single file
git restore --staged <file>
# Short flag, same result
git restore -S <file>
# Unstage every staged file in the repo (not just the current directory)
git restore --staged :/
# Unstage everything from the current directory down only — narrower than :/
git restore --staged .
That last distinction matters if you run the command from inside a subdirectory: .
scopes to the current directory downward, while :/ covers the whole repository
regardless of where you're standing. Confusing the two is an easy way to think you unstaged
everything when part of the tree is still staged.
git reset HEAD: The Classic Way to Unstage
Before Git 2.23, git reset HEAD <file> was the only way to git unstage a file,
and it still works identically today — Git doesn't remove working commands. Given a path,
reset behaves like --mixed unconditionally: it moves the index entry
back to HEAD and never touches the working tree, regardless of which mode flag you'd
normally reach for — a rule stated explicitly in
Git's official git
reset reference.
# Unstage one file — works on every Git version ever released
git reset HEAD <file>
git reset -- <file>
# Unstage everything staged
git reset
git reset HEAD $ git reset HEAD file.txt
Unstaged changes after reset:
M file.txt reset and restore --staged produce the identical end state for this
case; which one you reach for is a matter of which Git version you're running and which syntax
your fingers already know, not a difference in outcome.
Which Git Version Do You Need?
git restore, including its --staged and --patch flags,
requires Git 2.23.0, released August 16, 2019, alongside git switch — both were
carved out of git checkout's overloaded behavior. Run git --version if
you're not sure which side of that line you're on.
git --version
# git version 2.23.0 or newer → git restore --staged works
# older than 2.23 → use git reset HEAD <file> instead git reset HEAD <file> has no such cutoff — it has worked since Git's earliest
releases and needs no version check at all. On a shared CI image, an older Linux distro's default
package, or a machine you don't control, reset HEAD is the safer default to script
against; restore --staged is the one worth using in your own terminal once you know
your Git is current.
Unstage One File vs Everything
Both commands scale from a single path to the whole repository — the only thing that changes is the target.
# One file
git restore --staged src/api/client.ts
git reset HEAD src/api/client.ts
# Multiple named files
git restore --staged src/api/client.ts src/api/types.ts
# Everything staged, whole repo
git restore --staged :/
git reset
# Everything staged, current directory down only
git restore --staged .
A bare git reset (no path, no --hard/--soft) always means
"unstage everything and stop" — it's the most common way people git unstage all without typing
the longer restore --staged :/ form. Verify either version worked with a quick
git status: staged entries move from "Changes to be committed" to "Changes not staged
for commit," and a fully unstaged tree shows nothing under the first heading at all.
Unstaging a Deleted File (It Won't Reappear)
git add doesn't just stage new content — running it after rm <file>
(or after deleting a file in your editor) stages the deletion itself. Unstaging that deletion is
where most guides go quiet, and it has a real gotcha: undoing the stage does not bring the file
back.
$ rm file.txt
$ git add file.txt
$ git status --short
D file.txt
$ git restore --staged file.txt
$ git status --short
D file.txt
Notice the status line moved from D (staged deletion) to
D (unstaged deletion) — the D shifted columns, but it's still
there. file.txt is still missing on disk; only its removal is no longer staged for
the next commit. git reset HEAD file.txt lands in the identical spot and even prints
Unstaged changes after reset: D file.txt to confirm it. Neither command
inspects the working tree at all — they only ever touch the index — so a missing file stays
missing until you explicitly ask for it back:
# Bring the file's content back to disk too, after unstaging the deletion
git restore file.txt
# Or in one step — unstage and restore the working tree copy together
git restore -SW file.txt
That two-step shape — unstage first, then separately restore the working tree — is exactly the
model from the earlier section: git add moved the deletion working tree → index, and
undoing it one arrow at a time means the working tree arrow needs its own command.
Unstage Part of a File: Patch Mode
git add -p famously stages a file hunk by hunk. Both unstage commands have a matching
interactive flag for undoing exactly that — pulling specific hunks back out of the index while
leaving the rest of the file staged.
# Modern syntax, Git 2.23+
git restore --staged --patch <file>
git restore -S -p <file>
# Classic syntax, works on every Git version
git reset --patch <file>
git reset -p <file>
Either command walks the staged hunks in the file one at a time and asks Unstage this hunk
[y,n,q,a,d,e,?]? — y unstages that hunk, n leaves it staged,
s tries to split it into smaller hunks, and e opens a manual edit if the
automatic split isn't fine-grained enough. Nothing about patch mode touches the working tree; a
hunk you unstage this way simply becomes an unstaged edit in the same file, sitting right next to
whatever hunks are still staged.
Fresh Repo, No Commits Yet: When HEAD Doesn't Exist
On a brand-new repository — git init, one git add, zero commits — both
standard unstage commands fail, because both resolve against HEAD, and
HEAD doesn't point anywhere yet. Verified directly, against Git 2.50.1:
$ git init demo
$ cd demo
$ echo "hello" > file.txt
$ git add file.txt
$ git reset HEAD file.txt
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
$ git restore --staged file.txt
fatal: could not resolve HEAD
Two different error messages, same underlying cause — neither command has a commit to reset the
index against. This is worth flagging precisely because it's easy to assume git restore
--staged is the safe universal answer; on an unborn branch it fails just like reset
HEAD does, only with different wording. Two commands sidestep the problem entirely, because
neither one needs a resolvable HEAD:
# Works on an unborn branch — no revision argument at all, just a pathspec
git reset -- file.txt
# Works too — removes the path from the index directly
git rm --cached file.txt git reset -- <file> works specifically because -- tells Git that
everything after it is a path, not a revision — with no revision token to resolve, Git never tries
to look up HEAD and simply clears the path from the index. git rm --cached
<file> sidesteps the issue a different way: it never consults HEAD at
all, it just removes the entry from the index directly. Once that first commit exists, this entire
edge case disappears — git reset HEAD <file> and git restore --staged
<file> both work normally from the second commit onward.
git rm --cached: A Different Command Entirely
git add remove, git add git remove, and git remove added
files often lead people here — and it's worth being precise about what git rm
--cached actually does, because it's not a synonym for unstaging. git rm --cached
<file> removes the file from the index and stops Git from tracking it going forward,
while leaving the file itself untouched on disk. That's a different intent from unstaging:
unstaging says "not in this commit, but still tracked"; rm --cached says "stop
tracking this file, permanently, starting now."
# Stop tracking a file, keep it on disk (e.g. you just realized .env was staged)
git rm --cached .env
# Same, recursively for an entire directory
git rm -r --cached node_modules
# Committing after this makes the "stop tracking" permanent
git commit -m "Stop tracking .env"
The case where this distinction actually matters: you ran git add ., it swept up a
file that should never be tracked at all — a .env, a build directory, a local config
— and simple unstaging isn't enough, because the very next git add . will just stage
it again. git rm --cached combined with adding the path to .gitignore is
the actual fix; git restore --staged alone only buys you until the next add.
Unstage Wildcards and Whole Directories
Both commands accept any pathspec Git understands — globs, directories, multiple arguments at once, not just a single filename.
# Unstage every .log file staged anywhere in the repo
git restore --staged "*.log"
# Unstage everything staged under one directory
git restore --staged src/
# Same, classic syntax
git reset HEAD src/
# Multiple patterns at once
git restore --staged "*.log" "*.tmp" build/
Quote glob patterns like "*.log" so your shell passes the literal asterisk to Git
instead of expanding it against files in the current directory first — an unquoted
*.log only matches files that already exist in your current working directory, which
silently misses staged deletions and files sitting in subdirectories.
Unstaging in VS Code, GitHub Desktop, Sourcetree, GitKraken, JetBrains
Every GUI Git client wraps the same two commands from above; the button labels differ, the underlying operation doesn't.
- VS Code — Source Control panel, hover a file under "Staged Changes," click the minus (−) icon to unstage it, or click "Unstage All Changes" at the top of that group.
- GitHub Desktop — uncheck a file's checkbox in the Changes list to unstage it before committing; there's no separate confirmation step.
- Sourcetree — select one or more files in the "Staged files" pane and click Unstage, or drag them back down into the "Unstaged files" pane.
- GitKraken — click a staged file in the commit panel to move it back to "Unstaged Files," or use the "Unstage All" link above the list.
- JetBrains IDEs (IntelliJ, WebStorm, PyCharm) — in the Commit tool window, uncheck a file to unstage it, or right-click and choose "Rollback" only if you actually mean to discard the edit — that button is destructive, unlike the checkbox.
That JetBrains distinction is worth a second look: "unstage" and "rollback" sit next to each other in the same context menu, and only one of them is reversible in the sense this whole guide has been describing. Before comparing a staged file against its committed version in any of these tools, pasting both versions into a dedicated diff view makes the actual content difference obvious before you click anything.
Already Committed? Undo git commit Instead
how to cancel git add and commit describes a different moment than everything above — the add already happened, and so did the commit. At that point there's no staged entry left to unstage; the change is sitting in a commit, and undoing it is a commit-level operation, not an index-level one.
# Undo the last commit, keep the changes staged
git reset --soft HEAD~1
# Undo the last commit, keep the changes unstaged (back to a plain edit)
git reset --mixed HEAD~1
# Undo the last commit AND its content — destructive, use with care
git reset --hard HEAD~1 --soft is the closest analogue to "undo the add and the commit but keep everything
staged" — it moves HEAD back one commit and leaves the index untouched, so the files
show up right back under "Changes to be committed," exactly where they were before you ran
git commit. The full mechanics, including what to do if the commit was already
pushed, are in the dedicated guide to undoing the last
commit. If the commit itself is fine and only its message or a small addition needs fixing,
git commit --amend is the narrower tool — no history
rewind needed. And if you'd rather set the whole thing aside instead of deciding right now,
git stash shelves staged and unstaged changes alike onto a
recoverable stack without touching the commit at all. A pile of rough checkpoint commits made
while sorting this out can be squashed into one afterward, so
committing early to be safe doesn't cost you clean history later.
git restore vs git reset vs git rm --cached vs git revert
Four commands, four different jobs, and mixing them up is the single most common way someone means
to unstage a file and ends up either deleting content or misunderstanding what "revert" even
applies to here. git restore --staged and git reset HEAD unstage — index
only, and content never leaves the working tree either way. git rm --cached untracks
— index now and every future commit, working tree untouched, but the file will vanish from the
repo the moment you commit that change. git revert doesn't apply to this problem at
all: it creates a new commit undoing a previous commit, and a staged-but-uncommitted
git add was never a commit to begin with — git revert add is a
common way to phrase "undo git add," but the command it names does something else entirely. The
table below lines up what each command touches, how destructive it is, and when you'd actually
reach for it.
| Command | What It Touches | Touches Working Tree? | Destructive? | Typical Use |
|---|---|---|---|---|
git restore --staged <file> | Index only | No | No | Unstage a file, modern syntax |
git reset HEAD <file> | Index only | No | No | Unstage a file, classic syntax |
git rm --cached <file> | Index now and every future commit | No | Semi — untracks going forward | Stop tracking a file entirely |
git revert <commit> | Creates a new commit undoing a prior commit | Only via the new commit it creates | No — adds history, doesn't remove it | Undo a commit already shared with others |
Verify: git status and git diff --staged
Two commands confirm an unstage did what you expected, before you commit anything else.
# Confirms which bucket each file is in right now
git status
# Shows exactly what's still staged — empty output means nothing is staged
git diff --staged
git diff --cached # identical, older alias
# Shows what's changed but NOT staged — this is where an unstaged edit lands
git diff git diff --staged is the one worth running right after any unstage: if it prints
nothing, the index is clean and matches HEAD exactly. If a file you meant to fully
unstage still shows up there, you likely only unstaged part of it in patch mode, or targeted the
wrong path. For a closer look at a specific staged hunk than the terminal's plus/minus output
gives you, pasting the git diff --staged output for one file next to the plain
git diff output for the same file into
a side-by-side diff view makes it obvious at a
glance whether the staged and working copies have actually diverged, or whether they're identical
and the unstage is complete.
Frequently Asked Questions
What's the difference between git reset and git restore for unstaging a file?
For unstaging specifically, none — git reset HEAD <file> and git
restore --staged <file> produce the identical result: the file's index entry is
reset to match HEAD, and the working tree is untouched either way. The real
difference is scope and Git version. git reset without a path can also move
branches and rewrite history — that's the destructive form people confuse with the harmless
one. git restore only ever restores files and requires Git 2.23 (August 2019) or
newer; reset has no version requirement at all.
How do I undo git add before I commit?
Run git restore --staged <file> (or git reset HEAD <file>
on older Git) for one file, or git restore --staged :/ (or a bare git
reset) to unstage everything. Either one moves the file back to "Changes not staged for
commit" — your edit stays exactly as it was, just no longer scheduled for the next commit.
Can I undo git add for one file only?
Yes. Both git restore --staged <file> and git reset HEAD
<file> take a single path and only touch that file's index entry — every other
staged file stays staged exactly as it was. Name multiple paths to unstage a specific subset,
or use --patch on either command to unstage individual hunks within one file
while leaving the rest of that same file staged.
How do I remove files from the staging area?
"Remove files from git add" and "unstage" name the same operation, because the staging area
is the index. Use git restore --staged <file> for one path,
git restore --staged :/ for everything staged, or git reset HEAD
<file> on Git older than 2.23. Nothing leaves your disk — the entries move from
"Changes to be committed" back to "Changes not staged for commit." The one case that needs a
different command is a file that should never have been tracked at all, such as a
.env: there, git rm --cached <file> plus a
.gitignore entry is the real fix, because unstaging alone only holds until the
next git add .
How do I cancel git add and commit if I already committed?
Unstaging doesn't apply anymore at that point — the change is inside a commit, not the index.
git reset --soft HEAD~1 undoes the last commit and puts its changes right back
into the staging area, closest to the original "just add, don't commit" state. git
reset --mixed HEAD~1 does the same but leaves the changes unstaged instead. Never run
--hard for this unless you genuinely want the content gone too — it deletes the
commit's changes from the working tree as well.
Does git rm --cached delete the file from my computer?
No — that's the entire point of the --cached flag. git rm --cached
<file> removes the file from Git's index (so it's no longer tracked and won't be
included in the next commit) while leaving the actual file untouched on disk. Running plain
git rm <file> without --cached is the destructive version — it
stages a deletion and removes the file from your working tree in the same step.