Between the answers from axiac and Jay you have a pretty complete picture, but I'd like to cover the whole thing a bit differently. There are two git checkout
forms of interest here:
git checkout -- path
: the path
argument is treated as a file or directory—if it's a directory, it means all files within that directory, recursively—and Git copies the current copy of the file as found in the index out to your work-tree.
We'll say more about the index in a moment.
git checkout HEAD -- path
: the path
argument is treated the same way, but Git copies the current copy of the file as found in the HEAD
commit to both the index and your work-tree.
You can use a name other than HEAD
here: any commit specifier works, and Git copies that commit's copy of the file to both the index and your work-tree.
For git restore
, you have more flexibility. You can tell Git to copy a file:
- from the index, or from any commit (use the
--source
option to choose a source explicitly, or let the command figure one out on its own)
- to the index, or to your work-tree, or both (use the
--staged
option to copy to the index, and/or the --worktree
option to copy to the work-tree).
Note that if you use git checkout
and choose a commit, the file goes to both places: the index, and your work-tree. Copying "from the index, to the index" does not really do anything (and might net you a complaint since it seems like a weird thing to ask).
About the index and your work-tree
Your work-tree is pretty simple and obvious. Git stores each commit as a full snapshot: a complete copy of every file you've committed. But, files stored inside Git's commits are stored in a special, read-only, compressed, de-duplicated, Git-only format. This keeps the .git
directory (the repository proper) from getting too big too fast. Most commits really just re-use most previous files, which means the de-duplication works extremely well. For those times when it doesn't work so well, the compression usually takes care of the rest.1 On the other hand, because the files here are read-only and aren't ordinary files, you can't actually use them to get your work done. Git has to copy these files out, turning then back into ordinary files that you, and your computer, can use.
The useful, everyday-work copies of your files appear in what Git calls your work-tree or working tree. You can do anything you want with these files. Git doesn't actually use them! It does create them, having extracted the compressed Git-only stored files from a commit, but it doesn't make commits from them.
Git's index, which Git also calls the staging area or sometimes—rarely these days—the cache, holds a copy2 of every file that came out of the current commit and is now ready to go into the next commit you will make.3 When you run git commit
, Git just packages up whatever is in the index at that time. This is why you keep having to git add
a file. If you change a file, you haven't changed Git's copy of the file: you have only changed the work-tree copy. You use git add
to tell Git: Copy my updated work-tree file back into your index, replacing the old copy there.
Hence, the index always holds your proposed next commit. You use git add
, or if you need to remove files, git rm
, to update—or remove—these index copies of your files. They start out with the copies from the commit you've checked out: the current or HEAD
commit. So if you haven't used git add
, the index and HEAD
commit copies normally match.4 When they do match, the presence of the index copy tends to be invisible: git status
doesn't mention the index copy, for instance.
If you're curious, though, try running git ls-files --stage
(be prepared for a lot of output in a big repository!—this command doesn't run its output through a pager). This will show you every file in the index.
1The compressed, de-duplicated format doesn't work for some very large binary file formats. In this case Git can become bloated. This is where things like Git-LFS come in.
2Technically, the index doesn't hold an actual copy of the file, but rather a reference. The difference only shows up if you start using the low level commands git update-index
or git ls-files --stage
, which can inspect the index contents directly. For everyday use, though, you can just think of the index as holding a copy of the files. The index copy is in the frozen and de-duplicated format—i.e., pre-frozen and pre-de-duplicated, albeit overwrite-able—which makes committing go fast.
3The index also takes on an expanded role when you have to deal with merge conflicts. This answer does not cover this case.
4The word normally here is to take care of a lot of corner cases, including things like git reset --soft
and special varieties of git commit
that don't necessary use the regular index.