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.