Why are there two ways to unstage a file in Git?
Solution 1
git rm --cached <filePath>
does not unstage a file, it actually stages the removal of the file(s) from the repo (assuming it was already committed before) but leaves the file in your working tree (leaving you with an untracked file).
git reset -- <filePath>
will unstage any staged changes for the given file(s).
That said, if you used git rm --cached
on a new file that is staged, it would basically look like you had just unstaged it since it had never been committed before.
Update git 2.24
In this newer version of git you can use git restore --staged
instead of git reset
.
See git docs.
Solution 2
git rm --cached
is used to remove a file from the index. In the case where the file is already in the repo, git rm --cached
will remove the file from the index, leaving it in the working directory and a commit will now remove it from the repo as well. Basically, after the commit, you would have unversioned the file and kept a local copy.
git reset HEAD file
( which by default is using the --mixed
flag) is different in that in the case where the file is already in the repo, it replaces the index version of the file with the one from repo (HEAD), effectively unstaging the modifications to it.
In the case of unversioned file, it is going to unstage the entire file as the file was not there in the HEAD. In this aspect git reset HEAD file
and git rm --cached
are same, but they are not same ( as explained in the case of files already in the repo)
To the question of Why are there 2 ways to unstage a file in git?
- there is never really only one way to do anything in git. that is the beauty of it :)
Solution 3
Quite simply:
-
git rm --cached <file>
makes git stop tracking the file completely (leaving it in the filesystem, unlike plaingit rm
*) -
git reset HEAD <file>
unstages any modifications made to the file since the last commit (but doesn't revert them in the filesystem, contrary to what the command name might suggest**). The file remains under revision control.
If the file wasn't in revision control before (i.e. you're unstaging a file that you had just git add
ed for the first time), then the two commands have the same effect, hence the appearance of these being "two ways of doing something".
* Keep in mind the caveat @DrewT mentions in his answer, regarding git rm --cached
of a file that was previously committed to the repository. In the context of this question, of a file just added and not committed yet, there's nothing to worry about.
** I was scared for an embarrassingly long time to use the git reset command because of its name -- and still today I often look up the syntax to make sure I don't screw up. (update: I finally took the time to summarize the usage of git reset
in a tldr page, so now I have a better mental model of how it works, and a quick reference for when I forget some detail.)
Solution 4
This thread is a bit old, but I still want to add a little demonstration since it is still not an intuitive problem:
me$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: to-be-added
# modified: to-be-modified
# deleted: to-be-removed
#
me$ git reset -q HEAD to-be-added
# ok
me$ git reset -q HEAD to-be-modified
# ok
me$ git reset -q HEAD to-be-removed
# ok
# or alternatively:
me$ git reset -q HEAD to-be-added to-be-removed to-be-modified
# ok
me$ git status
# On branch master
# Changes not staged for commit:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: to-be-modified
# deleted: to-be-removed
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# to-be-added
no changes added to commit (use "git add" and/or "git commit -a")
git reset HEAD
(without -q
) gives a warning about the modified file and its exit code is 1 which will be considered as an error in a script.
Edit: git checkout HEAD to-be-modified to-be-removed
also works for unstaging, but removes the change completely from the workspace
Update git 2.23.0: From time to time, the commands change. Now, git status
says:
(use "git restore --staged <file>..." to unstage)
... which works for all three types of change
Solution 5
if you've accidentally staged files that you would not like to commit, and want to be certain you keep the changes, you can also use:
git stash
git stash pop
this performs a reset to HEAD and re-applies your changes, allowing you to re-stage individual files for commit. this is also helpful if you've forgotten to create a feature branch for pull requests (git stash ; git checkout -b <feature> ; git stash pop
).
Senthess
Updated on January 17, 2022Comments
-
Senthess over 2 years
Sometimes git suggests
git rm --cached
to unstage a file, sometimesgit reset HEAD file
. When should I use which?EDIT:
D:\code\gt2>git init Initialized empty Git repository in D:/code/gt2/.git/ D:\code\gt2>touch a D:\code\gt2>git status # On branch master # # Initial commit # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # a nothing added to commit but untracked files present (use "git add" to track) D:\code\gt2>git add a D:\code\gt2>git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: a # D:\code\gt2>git commit -m a [master (root-commit) c271e05] a 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a D:\code\gt2>touch b D:\code\gt2>git status # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # # b nothing added to commit but untracked files present (use "git add" to track) D:\code\gt2>git add b D:\code\gt2>git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: b #
-
Pierre de LESPINAY over 11 yearsI would say
git rm --cached
unstages the file but doesn't remove it from the working directory. -
Kaz over 10 yearsThis is a bit like
cvs rm
to locally undocvs add
all over again. -
soupdog over 10 yearsThanks, wasn't entirely clear from the first two answers (probably just my ignorance on terminology) that git reset left the modifications in the file locally (as opposed to git checkout which would revert them).
-
Roman Starkov over 10 yearsTo remove a file staged for addition so that it is no longer staged can surely be called "unstaging a file staged for addition", right? The end result is not a staged deletion, that's for sure, hence I think the misunderstanding is totally understandable.
-
sootsnoot over 9 yearsBoth the accepted answer and this one are great, and explain why you would use one vs the other. But they don't directly answer the implicit question of why does git suggest two different methods. In the first case in the OP's example, a git init has just been done. In that case, git suggests "git rm --cached" because at that point there are no commits in the repository and so HEAD is not valid. "git reset HEAD -- a" produces: "fatal: Failed to resolve 'HEAD' as a valid ref."
-
neonmate almost 9 yearsIt's
git rm <file> --cached
-
waldyrious over 8 yearsI really don't think the edit of Aug 4 2015 to this answer was an overall improvement. It might have fixed technical correctness (I don't feel qualified to evaluate that), but I'm afraid it made the tone of the answer much less accessible, by introducing language like "unsets the imperative to begin tracking a currently-untracked file", and using jargon like "index" and "HEAD", precisely the kind of stuff that scares off beginners. If someone can, please edit to restore a more newcomer-friendly language.
-
Simon Robb over 8 yearsAgree with @waldyrious. The original answer might not have been straight out of the git textbook, but it answered the question at a sufficient technical level. Technical details should have been clarified in comments, not as an edit that obscured the original intent.
-
Adrien Be about 8 yearsSo typically, one would use
git rm --cached <filePath>
to remove some file(s) from the repo after realizing it should have never been in the repo: so most likely running this command & then adding the relevant files togitignore
. Am I correct? -
Jun Jiang about 8 yearsgit reset HEAD {filename}
-
waldyrious almost 8 yearsI've reverted the edit. I believe there's been sufficient validation by the community (in the previous comments and the votes on them) that the edit was detrimental to the clarity of the answer.
-
Tom Hale almost 8 yearsNote @DrewT warns that if using
rm --cached
and pushing, anyone pulling the same branch will have the file(s) actually removed from their working tree. -
waldyrious almost 8 years@TomHale thanks for the heads-up. I added an additional note to the answer to point this out.
-
Miles Rout over 7 yearsThis is actually the only answer that properly answers the question, IMO. It actually answers the question, which is not 'what are the differences between 'git rm --cached' and 'git reset HEAD' but 'why does git inconsistently give both as options?', the answer being that there's no HEAD to reset to when you
git init
for the first time. -
jimh about 7 yearsthanks so much, but some people may want extra clarification that git rm does not remove the file with --cached as in @waldyrious's answer
-
cyrus zhou almost 7 yearsThis is a clean solution and much less worrisome than typing "git rm"
-
John Deighan over 6 yearswith 'git checkout', wouldn't you lose all changes that you made to the file?That's not the same thing as unstaging a file, unless I'm misunderstanding.
-
Gabriel Staples over 5 years@Ryan Stewart, is the
--
ingit reset -- <filePath>
really necessary? This answer just usesgit reset <file>
: stackoverflow.com/a/348234/4561887. -
Nunchucks over 5 yearsgit rm --cached tells git to stop tracking a file... that's not what I wanted. I wanted to unstage a file just for now, not stop tracking it all together. :(
-
cweekly about 5 years
git stash
has other related benefits, because it creates entries in the reflog which are then available in the future. when in doubt, go ahead and do agit stash
(e.g.git stash save -u "WIP notes to self"
(the '-u' is to include any new/untracked files in the stash commit) ... then trygit reflog show stash
to see the list of stash commits and their sha's. I recommend a shell alias likealias grs="git reflog show stash"
-
Mr. Unnormalized Posterior about 5 years
git reset -- <filePath>
is soft reset, unstages the file! -
milosmns about 5 yearsWith this many votes on both question and answer, I would say that apparently we want to have an
unstage
command ingit
. -
yucer over 4 years"git status" advises now: use "git restore --staged <file>..." to unstage
-
Tobias Gaertner over 4 yearsJust a sidenote: after "git stash" and then "git stash pop" also all files that where staged are unstaged now.
-
Oto Shavadze over 4 years
there is never really only one way to do anything in git. that is the beauty of it
- Hmm... why ? it is always great, when there is only one obvious way. this saves a lot of our time and memory in brain )) -
Rhialto supports Monica about 4 yearsIt is still annoying that the "index" is here called "cache" (
--cached
option) and ingit restore --staged
it is called yet something else. The original name "index" is of course the most misleading one, but after that, couldn't they settle on "staging area" immediately? -
dhana1310 about 4 yearsIt works with both the options
--stage
as well as--staged
. -
scubbo almost 4 yearsI wish I could upvote this every time I refer back to it.
-
Luis Mauricio almost 4 yearsYou should put a warning in the begining about the version, because old version deletes the files in the new versions
-
Luis Mauricio almost 4 years@DanielAlder sry, I just retested, it does not delete, my mistake.
-
ScottyBlades almost 3 yearsidk why everyone else likes so much complexity. This is a good answer.
-
tobi_s over 2 yearsWhy can't we have
git stage
andgit unstage
instead of adding ever new variations of obscure commands? -
Gregory Stein about 2 yearsWorth mentioning that you'd need a git version > 2.23.0 to use
git restore