Search code examples
gitgit-checkoutgit-stagegit-index

Why does git checkout file behave like reset staged file followed by checkout unstaged file?


I've come across this statement in git documentation:

Checking out a file is similar to using git reset with a file path, except it updates the working directory instead of the stage

Link:https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting

Section: "Git Checkout File"

Now suppose I have a repo and a file test.txt in it

At first the working directory is clean:

On branch master
nothing to commit, working tree clean

Now I modify test.txt, run git add and git status shows now:

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test.txt

Now I run git checkout HEAD test.txt and get:

Updated 1 path from 58e7043

Output of git status:

On branch master
nothing to commit, working tree clean

According to the docs the version of test.txt in the index should have stayed the same and the version of it in the working directory should've changed back to its version in the commit to which HEAD points, resulting in different versions of the file between the working directory and the index --> in which case shouldn't git status output something ? But git status doesn't show that - why ?

Usually to go from staged file to clean working tree I'd have to use git reset HEAD <filename> followed by git checkout HEAD <filename> for that file but here it seems to do both ??

I'm confused

Edit - also what's interesting is that if after staging the file test.txt I run git checkout test.txt instead of git checkout HEAD test.txt I get:

Updated 0 paths from the index

Even though these 2 forms should be equivalent where the former defaults to HEAD as well (?)

I'm confused again


Solution

  • First of all, that is not the official documentation and this is a case, in my opinion, where Atlassian is very superficial on the comparison between the two commands. Sometimes, like this time, the same git command does very different operations depending on which options you use. On SO you can find good answers that go deep into the topic.

    Just to answer to your question, here is what the official documentation says about git checkout with a pathspec:

    Overwrite the contents of the files that match the pathspec. When the <tree-ish> (most often a commit) is not given, overwrite working tree with the contents in the index. When the <tree-ish> is given, overwrite both the index and the working tree with the contents at the <tree-ish>.

    You are in the second case, where the <tree-ish> is given (HEAD) and that is the expected behaviour: both the index and working directory are overwritten with the old version of test.txt.

    Instead, if you use git checkout test.txt, and test.txt is already staged, neither the working dir or the index change because you are basically replacing the working dir version with the index version, but are obviously the same.

    What Atlassian article is trying to say is that:

    • git checkout <pathspec> operates on the working dir mainly (on the index too if a <tree-ish> is provided)
    • git reset operates on the index only.

    The misunderstanding has arisen because in git reset the <tree-ish> defaults to HEAD. Instead, git checkout behaves differently if you specify a <tree-ish> or not.