If I have a file in the staging area (showing in git diff --cached
) and I want to remove it completely how do I do that?
Doing
git reset HEAD -- file
git checkout -- file
does it but is there 1 command for these 2 actions?
Yes:
git checkout HEAD -- file
does the trick.
There are many (too many, sometimes) things to know about this. First, the index (also known as the staging area or the cache) holds all files-to-be-committed at all times. In fact, each file's presence in the index is what means the file will be committed, in the form it has in the index. This is why you have to keep doing git add file
all the time: Git won't copy it from the work-tree into the index, overwriting the old index version, until you tell Git to do so.
When you initially check out some commit, the index normally holds a copy of every file that's in that commit as well. There are some exceptions (see Checkout another branch when there are uncommitted changes on the current branch for details), but usually, the initial setup is:
HEAD index work-tree
-------------------------------
README.md README.md README.md
file1.txt file1.txt file1.txt
and so on, with all three versions of each file matching.
There are some slight but important differences between each copy, though:
The copy in the commit, in HEAD
, is read-only. Nothing can change this copy. (Of course, HEAD
itself can change to be another, different, commit; the different commit can have a different copy of the file, or maybe not have the file at all.) The committed copy of the file is in a special Git-only format.
The copy in the index / staging-area is read/write. This copy is also in the special Git-only format, though. You can at any time copy a different version of that file into the index, or even remove the index entry.
The copy in the work-tree is in your computer's normal format. You can do anything you like with it, subject to whatever limits your computer imposes.
What git status
does—well, one of the many things it does—is to run two comparisons:
What's in HEAD
vs what's in the index. Whatever is different, Git lists as staged for commit.
What's in the index vs what's in the work-tree. Whatever is different, Git lists as not staged for commit.
This means you don't have to wade through huge lists of everything that's the same; you only see what's different.
git add
= copy from work-tree to indexUsing git add path
copies the work-tree version into the index. That's pretty simple! Of course, Git being Git, there are more varieties of git add
, but we will just ignore them for now. :-)
git reset
= ... well, it's complicatedThe git reset
command does too many different things. However, if you stick with git reset -- path
, it simplifies a lot: it means copy from the HEAD
commit to the index. The work-tree copy remains untouched.
git checkout
= copy from ... well, it's complicated tooThe git checkout
command, like git reset
, does too many different things. However, if you stick with these two forms, we get two things that are easy to explain:
git checkout -- path
copies from the index to the work-tree.
git checkout HEAD -- path
copies from the HEAD
commit to the index, and then from the index to the work-tree.
There's one option missing here: there is no easy way to copy from HEAD
commit to the work-tree, bypassing the index. (There are a few ways to do this but they have certain caveats.)
You cannot write on the HEAD
version, so there is no way to copy into HEAD
. Instead, you will run git commit
, which makes a new commit, freezing the index copy (of every file!) forever. The new commit then becomes the HEAD
commit. The fact that all the files are already in their final form in the index, when you run git commit
, is part of what makes git commit
so fast.