When I want to unstage a staged file, all of my Git tutorials show something like:
$ git add * $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README modified: CONTRIBUTING.md
This hint tells us to use
git resetfor unstaging a staged file.
But instead, in my terminal, I see:
git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) renamed: cat.js -> catcat.js renamed: tolendo.gogo -> tolendo.txt Untracked files: (use "git add <file>..." to include in what will be committed) readme (copy).md tolendo (copy).txt zing (copy).html
My terminal tells me to use
git restore --stagedbut the tutorials, as well as Git’s website, tell me to use
git reset HEAD.
I have no idea about the new
restorecommand. I tried Google to find the difference between
git restorebut nothing seemed to fit my question.
I have presented
git restore (which is still marked as “experimental”) in “How to reset all files from working directory but not from staging area?“, with the recent Git 2.23 (August 2019).
It helps separate
git checkout into two commands:
- one for files (
git restore), which can cover
- one for branches (
git switch, as seen in “Confused by git checkout“), which deals only with branches, not files.
As reset, restore and revert documentation states:
There are three commands with similar names:
git-revertis about making a new commit that reverts the changes made by other commits.
git-restoreis about restoring files in the working tree from either the index or another commit.
This command does not update your branch.
The command can also be used to restore files in the index from another commit.
git-resetis about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
git resetcan also be used to restore the index, overlapping with
To restore a file in the index to match the version in HEAD (this is the same as using
git restore --staged hello.c
or you can restore both the index and the working tree (this the same as using
git restore --source=HEAD --staged --worktree hello.c
or the short form which is more practical but less readable:
git restore -s@ -SW hello.c
With Git 2.25.1 (Feb. 2020), “
git restore --staged” did not correctly update the cache-tree structure, resulting in bogus trees to be written afterwards, which has been corrected.
restore: invalidate cache-tree when removing entries with –staged
Reported-by: Torsten Krah
Signed-off-by: Jeff King
git restore --staged” removes a path that’s in the index, it marks the entry with
CE_REMOVE,but we don’t do anything to invalidate the cache-tree.
In the non-staged case, we end up in
checkout_worktree(), which calls
remove_marked_cache_entries(). That actually drops the entries from the index, as well as invalidating the cache-tree and untracked-cache.
--staged, we never call
checkout_worktree(), and the
CE_REMOVEentries remain. Interestingly, they are dropped when we write out the index, but that means the resulting index is inconsistent: its cache-tree will not match the actual entries, and running “
git commit” immediately after will create the wrong tree.
We can solve this by calling
remove_marked_cache_entries()ourselves before writing out the index. Note that we can’t just hoist it out of
checkout_worktree(); that function needs to iterate over the
CE_REMOVEentries (to drop their matching worktree files) before removing them.
One curiosity about the test: without this patch, it actually triggers a BUG() when running git-restore:
BUG: cache-tree.c:810: new1 with flags 0x4420000 should not be in cache-tree
But in the original problem report, which used a similar recipe,
git restoreactually creates the bogus index (and the commit is created with the wrong tree). I’m not sure why the test here behaves differently than my out-of-suite reproduction, but what’s here should catch either symptom (and the fix corrects both cases).
With Git 2.27 (Q2 2020), “
git restore --staged --worktree” now defaults to take the contents out of “HEAD”, instead of erring out.
restore: default to HEAD when combining –staged and –worktree
Signed-off-by: Eric Sunshine
Reviewed-by: Taylor Blau
By default, files are restored from the index for
--worktree, and from HEAD for
--sourcemust be specified to disambiguate the restore source, thus making it cumbersome to restore a file in both the worktree and the index.
(Due to an oversight, the
--sourcerequirement, though documented, is not actually enforced.)
However, HEAD is also a reasonable default for
--worktreewhen combined with
--staged, so make it the default anytime
--stagedis used (whether combined with
So now, this works:
git restore --staged --worktree git restore -SW