tl;dr
git show example.txt means git show HEAD -- example.txt
- The object shown is
HEAD, not example.txt
- The
example.txt argument is used to first filter the "list" of revisions, and then filter the diff displayed
- So the result is either empty (if
example.txt was not changed in the latest commit), or a summary of the latest commit with everything other than example.txt ignored
To figure this out, I went to the source. It turns out that git show actually shares a lot of its implementation with git log, including how it processes its command-line arguments.
That means the synopsis from the git log manual page can give us some clues:
git log [<options>] [<revision-range>] [[--] <path>…]
Note that the revision range and path are both optional, and the -- is only needed if it's ambiguous which is meant. Under <revision-range>, we learn that:
When no <revision-range> is specified, it defaults to HEAD
So assuming there is a file called "example.txt", but no branch or tag called that, the following are all equivalent:
git log HEAD -- example.txt
git log HEAD example.txt
git log -- example.txt
git log example.txt
In all four cases, the <revision-range> is HEAD, and the <path> is example.txt.
In git log, the meaning of <path> is:
Show only commits that are enough to explain how the files that match the specified paths came to be.
In other words, while the <revision-range> decides what branch of history to look through, <path> decides what commits inside that history should be examined.
It also has another effect, which is crucial to understanding the next section: when asked to show changes introduced by a commit, it only considers the changes to <path>. For instance, if you run:
git log --oneline --name-status example.txt
You'll get something like this:
93f11d8429 Do the needful
M example.txt
e4d79ce24c Make some changes
M example.txt
2ce2aff50e Reformat all files to use non-breaking spaces lol
M example.txt
To see all the other files in each commit, you have to pass the --full-diff option.
Now, back to git show. Although it doesn't give the same synopsis, it actually uses the same argument parsing, so for our same hypothetical repo, the following are equivalent:
git show HEAD -- example.txt
git show HEAD example.txt
git show -- example.txt
git show example.txt
So what does this actually do?
- It processes the
<revision-range> (HEAD) looking for a single commit; but because it's using the machinery from git log, what it actually gets is a list with one item in
- It "simplifies" this list based on whether each commit "contributed to the history of
example.txt" (the <path> argument).
- As long as
example.txt was actually changed in the current HEAD commit, it will still have one commit, and proceed with its display, starting with the summary of the commit.
- It then prints a diff of the changes introduced by the commit, but only the changes made to
<path>
You can demonstrate this is what's happening with a few variations:
- If you run
git show HEAD -- example-2.txt, and example-2.txt exists, but wasn't modified in the most recent commit, you get an empty output: the revision is filtered out at step 2.
- The same is true if
example-2.txt doesn't exist at all - it still processes step 1 successfully, so there's no error. Running git show example-2.txt would instead complain that it can't tell if example-2.txt was intended as a branch or a file, since it can't find either.
- If you run
git show --full-diff example.txt, you'll get the same output as git show HEAD, as long as example.txt was changed in the most recent commit. The option changes step 4 to be "show all changes in the selected revision, regardless of <path>".
Finally, what about the format that does show the content?
git show HEAD:example.txt
git show :example.txt
In this case, HEAD:example.txt or :example.txt is a single argument, which can be interpreted as a <revision-range> according to the general revision parser:
A suffix : followed by a path names the blob or tree at the given path in the tree-ish object named by the part before the colon.
For git log, this is valid but meaningless; but for git show, it leads to a completely different code path:
- It looks at the
<revision-range>, which is HEAD:example.txt, and decides what type it references.
- Seeing that it references a blob, it decides to show the content of that blob.