After staging a file(tracked file) I realized it needed some changes, so to unstage it I used
git rm --cached <filename>
. Consequently, I lost all the changes. Is there a way to undo it?

- 63
- 1
- 4
-
1`gir rm --cached` does _not_ unstage a file in Git, it unstages the addition of that file to the repository. What changes did you lose, and are you certain that you really lost anything? – Tim Biegeleisen Oct 24 '19 at 11:10
4 Answers
git rm --cached
only removes the new file from the index and not from the working tree. Be careful doing it that way. If this was an existing file you just staged the deletion of the file.
You can add it again with git add <filename>
.
A more common way of unstaging changes is to use git reset HEAD <filename>
.
But... it's not necessary to unstage a file to add further changes. You can just make further changes and stage those again using git add <filename>
.

- 1,144
- 1
- 9
- 17
Git's use of the phrase staged changes or to stage some changes can get very confusing.
To avoid getting confused, here is what you need to know:
Git has a thing that Git calls, variously, the index, the staging area, or sometimes—rarely now—the cache. These three names all refer to one single thing. Here, I'll just call it the index.
The index initially contains a copy1 of each file that is in the commit that you checked out. (This current commit is also called
HEAD
.) These files are in a Git-only format. You can't see these files. (This is a slight overstatement—there are some special Git tricks to see them—but normally they're invisible.) The files that you can see are in your work-tree, which holds ordinary everyday files.When you make your next commit, Git will use the files that are in the index at that time. That is, when you run
git commit -m "some message"
(or aftergit commit
without-m
collects a message from you), Git will then, at that time, package up all the files that are in the index at that time, and use those to make the new commit.Hence, if you've changed or replaced a file in your work-tree, you must first copy it back into the index. Otherwise Git will just re-use the original file.
When you run git add file
, Git takes the work-tree copy, compresses it into the special Git-only format, and stuffs the compressed copy into the index. Now the index copy no longer matches the HEAD
copy, but it does match the work-tree copy.
If you change the work-tree copy again, now all three copies are different! At this point git status
will tell you that you have "changes staged for commit" and also "changes not staged for commit".
You can change the work-tree copy to anything at any time. This has no effect on the index copy.
You can remove the work-tree copy without removing the index copy, using any tool on your computer that removes a file. You can also remove the index copy without removing the work-tree copy, using git rm --cached
. And, you can remove both copies using git rm
. None of this affects any existing commit—no existing commit can ever be changed! But removing the index copy does affect the next commit, because when you run git commit
, Git will package up all the files that are in the index at that time, so if the file is gone from the index, it won't be in the next commit.
Since what's in the index is what will be in the next commit, that's a pretty good description of the index: The index is, mostly anyway, what will go in your next commit. The index does have some additional roles to play, especially during merges, but we won't cover those here at all.
1Technically, the index actually holds references to the copies. But the effect is pretty much the same as if the index held actual copies, at least until you start diving into how to manipulate the index directly using git update-index
.
How to "unstage changes"
To get the file from the current (HEAD
) commit back into the index in the form it has in the current commit, you can use git reset -- file
. The --
here is only needed if the file name resembles a git reset
option or a branch name. For instance, if you have a file named master
and a branch name master
, git reset master
looks like you want to use the branch name, so you must write git reset -- master
to tell git that you mean the file master
.
So: git reset -- file
copies the file from the HEAD
commit into the index. This is true even if you removed the index copy: reset will just put it back. It's also true if the file isn't in the HEAD
commit. If you've added file F
to the index, and then decide it should not be in the index after all, you can git reset -- F
and that removes it from the index.
(You can also git rm --cached F
if you prefer: that has the same effect at this point. But if you just want to make the index copy match the HEAD
copy, use git reset
because that works for both cases: there is a HEAD
copy, or there isn't.)
How do you know what is staged?
Again, you can't actually see the copy of the file that is in your index. So how do you know whether it matches some other copy? The answer is: git status
tells you, in a short and useful way.
Let's say you have three files in your work-tree called README.md
, main.py
, and util.py
. The first two of these files came out of the HEAD
commit, and you've just created util.py
yourself now, so it's not in the HEAD
commit and not in the index.
The git status
command runs two comparisons:
First, it compares every file in
HEAD
to every file in the index. So this comparesHEAD:README.md
(the HEAD copy) to:README.md
(the index copy).2 Then it comparesHEAD:main.py
to:main.py
.For each file that is the same,
git status
says nothing.For each file that is different,
git status
says that there are changes staged for commit. If the file is completely gone from the index, you've staged a delete. If the file in the index is totally new, you've staged a newly-added file.So, if you know what was in the
HEAD
commit, you now know which files are the same in the index: anythinggit status
did not mention.Next, having told you what's different or not in
HEAD
vs index,git status
goes on to compare each file in the index to each file in the work-tree. So here, it will compare:README.md
toREADME.md
,:main.py
tomain.py
, and discover that there's autil.py
that's not in the index.For each file that is the same,
git status
says nothing.For each file that is different,
git status
reports changes not staged for commit. That's right becausegit commit
isn't going to use the work-tree, it's just going to use the index.For a file like
util.py
, that's in the work-tree but not in the index,git status
reports it as an untracked file. That's what an untracked file is: a file that is in the work-tree, but not in the index.
Note that if you remove a file from the index, it is instantly untracked! If you git add
the file so that it's in the index, well, now it's tracked.
2This funny HEAD:file
and :file
syntax is specific to Git itself, and only works with some Git commands. One of those is git show
: you actually can see the index copy of README.md
using git show :README.md
, for instance. Since git show
is a user-oriented command, you can do this with pretty good safety: git show HEAD:file
, git show :file
, and git show <commit:>file
are all pretty good ways to see a specific copy of a specific file that's been turned into its internal Git form, either saved in a commit, or ready to go into a commit.
You can also see the names of all the index copies of files using git ls-files
. In a big repository, this prints a lot of names! It is not meant for normal everyday use and is not a very user-friendly command, though.
Commits read the index; git checkout
writes to it
To make a new commit, you simply copy the right files into the index and run git commit
. The index already has all the files from the current commit, so you only need to update any files that you want to be different, or add any new files you want, or remove any files you want to delete. Then you git commit
and make a new commit. This new commit is now the HEAD
commit, and now the HEAD
commit and the index match.
If the HEAD
commit and the index match, and those two match the work-tree, then everything is "clean" and you can easily switch to another commit, using, e.g.:
git checkout otherbranch
(or the new Git 2.23 git switch
command, which is like a friendly variant of git checkout branch
that doesn't have all the other Git tools shoved into one command—git checkout
can do somewhere between three and seven different jobs, depending on how you count).
This checkout, assuming it succeeds,3 has to:
- fill your index with all the files from the tip of
otherbranch
(and remove any irrelevant ones); - fill your work-tree with all the files from the tip of
otherbranch
(and remove any irrelevant ones); and - make the name
HEAD
refer to the tip commit ofotherbranch
.
Once that's all done, your HEAD
commit, index, and work-tree again all match.
3The git checkout
command can sometimes succeed *even if the index and work-tree are not "clean". Clean is a poorly defined term here, but to see more about when git checkout
will let you switch branches while carrying modifications, see Checkout another branch when there are uncommitted changes on the current branch.
Conclusion
You can't see the index copies of files! What you can and should do is use git status
, which compares the HEAD
files to the index copies. In a big project, with hundreds, thousands, or even millions of files, Git will say nothing about almost all of them. It will only mention the ones that are different. That's much more useful.
Whenever your HEAD
, index, and work-tree all match, git status
says nothing. When git status
sees some things that don't match, it prints file names under changes staged for commit and/or under changes not staged for commit depending on whether it's the HEAD
and index copies that disagree, or the index and work-tree copies that disagree, or both.
git add
copies files into the index. If they were already there before, well, now they're overwritten with the copy from the work-tree. If they weren't there before, well, now they are.
git rm
removes files from the index. Without --cached
, it also removes the same files from the work-tree. With --cached
, it leaves the work-tree copies alone—which is fine for now, but a later git checkout
might need to remove the work-tree copies!
Git will, in general, try fairly hard not to destroy any work-tree files with precious data. What's "precious", though? Well, if work-tree file util.py
has been added and committed and matches HEAD:util.py
and :util.py
, at this point, util.py
is not precious any more, because if you want it, it's there in the commit. So you can git checkout
an old commit that doesn't have util.py
and Git will feel totally safe in removing util.py
from your work-tree. Just git checkout
the latest command it's back.
Some Git commands, including git reset --hard
, will destroy work-tree files. The theory behind git reset --hard
is that you want to reset both the index and your work-tree. The default mode, which is git reset --mixed
, resets only the index copies.4 That's why git reset -- file
"un-stages" the file—copies HEAD:file
into :file
–but doesn't touch the work-tree copy of file
.
4Peculiarly, git reset --soft
, git reset --mixed
, and git reset --hard
only allow you do do every file. When you want to do just one file, using git reset -- file
, it always uses --mixed
. For other cases, you have to use one of the many alternate modes of git checkout
. Git 2.23 introduces a new command, git restore
, that is intended to make this less confusing. Time will tell whether it does: git restore
is technically still experimental.

- 448,244
- 59
- 642
- 775
You need to add again. Here is a sample set of commands.
echo testmodify>> FOO //Appends "testmodify" to FOO
git add FOO // Stage FOO to commit
git rm --cached FOO // Unstage FOO
git add FOO // Stage FOO
FOO still contains "testmodify"

- 4,788
- 1
- 12
- 28
As per my experience. I ran this command "git rm --cached ." and my whole project files got removed so before pushing any commit, I stash the code. Simple and easy way to get rid of this issue.
git stash