· 6 min read
Unstaging Files in Git: Why `git restore --staged` Fails with `fatal: could not resolve HEAD`
Two commands seem to unstage files in Git `git rm --cached` and `git restore --staged`. One throws `fatal: could not resolve HEAD` on a fresh repo. Here's why, traced step by step.
A student ran git restore --staged notes.txt on a brand-new repo yesterday and got this:
fatal: could not resolve HEAD
The same command works fine on every other repo they’ve touched. So why does it blow up here, and what should they have run instead?
This post walks through both cases fresh repo and repo with commit history and traces what happens in each of Git’s three areas at every step. By the end, the error message should read like a sentence, not a riddle.
TL;DR
| Command | What it actually does | When to use it |
|---|---|---|
git rm --cached <file> | Removes the file from the index. Working tree untouched. | Unstaging a file that has never been committed. |
git restore --staged <file> | Copies the file’s content from the last commit back into the staging area. | Unstaging a file in a repo that has at least one commit. |
The second command needs a “last commit” to copy from. No commit → no source → error.
Git’s three areas (quick refresher)
Every file in a Git project moves between three places:
| Area | What lives here | Standard Git name |
|---|---|---|
| Working area | The file as it exists on disk right now. | working tree |
| Staging area | The snapshot you’ve marked “ready for the next commit.” | index |
| Commit area | The latest snapshot you actually saved. | HEAD (the latest commit) |
Movement between them:
git add git commit
Working area ─────────▶ Staging area ─────────▶ Commit area
▲
│ git restore --staged
│ (copies FROM Commit area)
│
└────────────────────
git restore --staged is essentially the reverse arrow of git commit: it pulls from the commit area back into the staging area. Hold onto that the error message falls out of this one fact.
Scenario A —> Fresh repo, no commits yet
You just ran git init. There is no commit history. You create notes.txt with the text Hello World.
Working area → Hello World
Staging area → (empty)
Commit area → (empty — no commits yet)
Stage it:
git add notes.txt
Working area → Hello World
Staging area → Hello World
Commit area → (empty)
Now you change your mind. You want to unstage. Two commands seem to fit. Try the more familiar one first:
git restore --staged notes.txt
# fatal: could not resolve HEAD
Park that error for a moment. Try the other one:
git rm --cached notes.txt
Working area → Hello World (your file is still here)
Staging area → (empty)
Commit area → (empty)
That worked. The file is back to being untracked exactly the state you were in before git add. No data lost.
But why did git restore --staged fail? To answer that, we need to see what it does when it succeeds.
Scenario B —> Repo with existing commits
Same file, but this time you committed once first.
Working area → Hello
Staging area → Hello
Commit area → Hello (committed once)
Now edit the file to Hello world and stage the change:
# After editing the file on disk
git add notes.txt
Working area → Hello world
Staging area → Hello world
Commit area → Hello (unchanged —> the commit is still the old version)
Three different states across three areas. Unstage with:
git restore --staged notes.txt
Here is the exact question Git asks itself:
What should the staging area look like after this command?
Its answer is always the same:
It should look exactly like the last commit.
So Git copies the content of notes.txt from the last commit back into the staging area:
Working area → Hello world (still has your new edit)
Staging area → Hello (reset to match the last commit)
Commit area → Hello
Notice what did not happen: your working file is untouched. You still have Hello world on disk. Only the staged version was rolled back.
That’s git restore --staged in one sentence: copy from the last commit into the staging area.
Back to the error
Now Scenario A makes sense. On a fresh repo, when you ran git restore --staged notes.txt:
Working area → Hello World
Staging area → Hello World
Commit area → ??? ← there is no last commit
Git asked the same question “what should the staging area look like?” and the answer is the last commit. But there is no last commit. HEAD doesn’t point at anything yet. So Git gives up:
fatal: could not resolve HEAD
It’s not a bug. It’s Git refusing to copy from a source that doesn’t exist.
Watch out: git rm --cached is not a general-purpose unstage
This is the trap. git rm --cached works for “unstaging” only in the fresh-repo case. On a file that has already been committed, the same command does something completely different it stages the file’s deletion:
# notes.txt is already committed
git rm --cached notes.txt
git status
# Changes to be committed:
# deleted: notes.txt
# Untracked files:
# notes.txt
If you commit after that, Git will record a deletion of the file in the repo (the file stays on your disk, but the next commit will say “this file is gone”). That is almost never what someone trying to “unstage” actually wants.
So the rule is sharper than “use whichever”:
- No commits yet →
git rm --cachedis the right tool.git restore --stagedcannot work here. - At least one commit →
git restore --stagedis the right tool. Do not usegit rm --cachedunless you genuinely want to stage a deletion.
Try this yourself
In a scratch folder:
git inita new repo. Create a file,git addit, then trygit restore --staged— watch it fail. Read the full error message.- Unstage the same file with
git rm --cached. Confirm withgit statusthat the file is back to untracked. - Now
git addit again andgit commitonce. - Edit the file,
git addagain, thengit restore --staged. Watch it succeed. Confirm withgit diff(working vs staging) andgit diff --staged(staging vs commit) what actually got rolled back. - Finally, try
git rm --cachedon the now-committed file. Readgit statuscarefully — you’ll seedeleted:rather than an unstage. That’s the trap to remember.
If you can predict git status between every step, you understand the staging area better than most working Git users.
Cohort Notes — monthly
What we shipped, what the cohort is building, and when the next batch starts. One short email a month. No spam.