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 &
For your 1st question "What is git-restore?":
git-restore is a tool to revert non-commited changes. Non-commited changes are: a) changes in your working copy, or b) content in your index (a.k.a. staging area).
This command was introduced in git 2.23 (together with the git-switch) to separate multiple concerns previously united in git-checkout.
git-restore can be used in three different modes, depending on whether you like to revert work in the working copy, in the index, or both.
git restore [--worktree] <file>
overwrites <file> in your working copy with the contents in your index (*). In other words, it reverts your changes in the working copy. Whether you specify --worktree
or not does not matter because it is implied if you don't say otherwise.
git restore --staged <file>
overwrites <file> in your index with the current HEAD from the local repository. In other words, it unstages previously staged content. In so far, it is indeed equivalent to the old git reset HEAD <file>
.
To overwrite both, the working copy and the index with the current HEAD, use git restore --staged --worktree --source HEAD <file>
. This version does both: revert your working copy to HEAD and unstage previously staged work.
For your 2nd question "What's the difference between git-restore and git-reset?":
There are overlaps between these two commands, and differences.
Both can be used to modify your working copy and/or the staging area. However, only git-reset can modify your repository. In this sense, git-restore seems the safer option if you only want to revert local work.
There are more differences, which I can't enumerate here.
(*) A file not add
ed to the index is still regarded to be in the index, however in it's "clean" state from the current HEAD revision.
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:
git reset
cases.As reset, restore and revert documentation states:
There are three commands with similar names:
git reset
,git restore
andgit revert
.
git-revert
is about making a new commit that reverts the changes made by other commits.git-restore
is 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-reset
is about updating your branch, moving the tip in order to add or remove commits from the branch. This operation changes the commit history.
git reset
can also be used to restore the index, overlapping withgit restore
.
So:
To restore a file in the index to match the version in HEAD (this is the same as using
git-reset
)git restore --staged hello.c
or you can restore both the index and the working tree (this the same as using
git-checkout
)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.
See discussion.
See commit e701bab (08 Jan 2020) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 09e393d, 22 Jan 2020)
restore: invalidate cache-tree when removing entries with --staged
Reported-by: Torsten Krah
Signed-off-by: Jeff KingWhen "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 incheckout_worktree()
, which callsremove_marked_cache_entries()
. That actually drops the entries from the index, as well as invalidating the cache-tree and untracked-cache.But with
--staged
, we never callcheckout_worktree()
, and theCE_REMOVE
entries 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 ofcheckout_worktree()
; that function needs to iterate over theCE_REMOVE
entries (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 restore actually 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.
See commit 088018e (05 May 2020) by Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit 4c2941a, 08 May 2020)
restore: default to HEAD when combining --staged and --worktree
Signed-off-by: Eric Sunshine
Reviewed-by: Taylor BlauBy default, files are restored from the index for
--worktree
, and from HEAD for--staged
.When
--worktree
and--staged
are combined,--source
must 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
--source
requirement, though documented, is not actually enforced.)However, HEAD is also a reasonable default for
--worktree
when combined with--staged
, so make it the default anytime--staged
is used (whether combined with--worktree
or not).
So now, this works:
git restore --staged --worktree
git restore -SW