Search code examples
gitgit-resetgit-stage

Remove staged file and reset to original version


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?


Solution

  • Yes:

    git checkout HEAD -- file
    

    does the trick.

    Longer version with a lot of background

    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 index

    Using 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 complicated

    The 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 too

    The 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.