How to undo git add before committing

You staged something you did not mean to. Taking it back out of the index is one command — but there is a very similar command one word away that throws the change itself away, so it is worth knowing which is which.

Unstaging

$ git restore --staged file.txt
staged           : "M  tracked.txt"   "A  untracked.txt"
restore --staged : " M tracked.txt"   "?? untracked.txt"
file still       : changed

The change is still in your file. Only the index was rolled back.

git restore arrived in Git 2.23. Every older answer says this instead, and it does exactly the same thing:

$ git reset HEAD file.txt

Both are fine. restore --staged is clearer about what it does, and reset is what you will meet in existing scripts.

Reading the result

git status --short prints two columns: the first is the index, the second is the working tree.

M   tracked.txt     staged
 M  tracked.txt     not staged
??  untracked.txt   not tracked at all

So M moving from the first column to the second is the unstage. That is the thing to look for; it is easier than remembering which command did what.

Note the file that was newly added goes back to ?? rather than to a modified state — it was never tracked, so unstaging returns it to being unknown to git.

The word that changes everything

$ git restore --staged file.txt     # unstage, keep the change
$ git restore file.txt              # DISCARD the change

Without --staged, restore restores the working tree from the index:

git restore tracked.txt   (no --staged)
  status : 'M  tracked.txt'
  file   : changed

Read that carefully — the file still says changed because it was staged first, and restore restored it from the index. So the staged version won and any later edit is gone. On an unstaged change, the same command silently reverts the file to its last committed state.

There is no undo. The discarded version was never committed, so there is nothing in the object database and nothing for git reflog to find. Of everything in this series, this and git clean are the two commands that genuinely lose work.

If you want both — unstage and discard — say so in two steps, so the destructive one is deliberate.

Unstaging everything

$ git restore --staged .

Same as the single-file form, applied to the whole tree. git reset with no paths does the same thing, which is why you see bare git reset in older answers — with no --hard it only touches the index, so it is safe.

Before the first commit it does not work

This one surprised me, and the demo corrected the article:

$ git reset HEAD a.txt
fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.

$ git restore --staged a.txt
fatal: could not resolve 'HEAD'

status : A  a.txt      <- still staged

I expected the newer command to cope. It does not. Both need a commit to reset the index to, and in a repository whose first commit has not happened yet there is no HEAD.

What works there:

$ git rm --cached a.txt
?? a.txt

git rm --cached removes the entry from the index and leaves the file alone, which is exactly what unstaging means when there is nothing to compare against.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).