Reputation: 98
git show filename
diplays a diff, while
git show branch:path/to/filename
displays the content of the file.
I look in the help (git show --help
) and what I understood is that it should
default to HEAD, i.e. git show HEAD:filename
.
But that produces the content of the file as I expected. Does anyone have any insight?
Upvotes: 5
Views: 315
Reputation: 48083
Thanks, @LeGEC, @IMSoP, and @VonC for the additional answers. I have chosen LeGEC's for the bonus, and I'm also going to provide a sort of a "distilled" answer here. (This is an informal answer; see the others for more precise details.)
The confusion is, how could
git show filename
not show you, like, that file? Especially given that
git show revision:filename
does show you the file? And the answer (as is so often the case with git) is that you were thinking too narrowly, there's actually more going on, and once you look at the bigger picture things make more sense.
The first part of the answer is that git show
is obviously
not just for showing files. If you entered
git show revision
you would clearly want it to show you that revision, and showing
diffs is a great and appropriate way to show a revision. So,
sometimes git show
shows files, and sometimes it shows diffs,
and this shouldn't be surprising.
(And sometimes it shows other things, like trees.)
And then, the second part of the answer is that, like many git
commands, git show
has the option to boil down some potentially
voluminous output to just the files you care about.
As a different example, with git diff
, you can say
git diff revision
to show you diffs with respect to a certain revision, or you can say
git diff revision filename
to limit the diffs to just that filename. Similarly, with git show
, you can say
git show revision filename
to, again, limit the revision's diffs to just that filename.
But this is then the answer to the original question: When you say
git show filename
it's a shortcut for
git show HEAD filename
and shows you just the diffs in the latest revision that apply to the filename.
Or in other words, the surprising behavior of
git show filename
falls out of git's naming conventions, and the fact that there's
an implicit HEAD
in there, making this an instance of the
git show
-showing-revisions form, not
git show
-showing-files.
Upvotes: 3
Reputation: 52226
I was surprised, by reading git help show, to see no mention of extra [<path>...]
arguments. The description mentions git diff-tree
, and although git help diff-tree mentions the existence of [<path>...]
, there is no description of what it does.
This is (IMHO) clearly a shortcoming in git help show
.
Nevertheless, much like git diff
or git log
, git show <object>...
can be extended to git show <object>... [<path>...]
, or the non ambiguous git show <object>... -- <path>...
, which applies on commit objects to narrow what is displayed from these commits. Specifying <path>...
has no effect on git show
when displaying a tree or a blob.
A point that is mentioned in the doc is that when naming a directory (a tree), only the content of that directory is shown (not a diff), and when naming a file (a blob), only the content of that file is shown (also not a diff).
Naming such things is described in git help gitrevision
:
# some ways to name a tree:
git show <commit>:path/to/dir # path from the top-level
git show <commit>:./dir # path relative to your pwd
git show <commit>: # the top-level of your repo at <commit>
git show <commit>^{tree}
# some ways to name a blob:
git show <commit>:full/path/to/file
git show <commit>:./rel/path/to/file
One an extra piece of information which, to my knowledge, isn't entirely worded in git doc:
for commands such as git diff
, git log
, git show
tries to guess, from the names you pass on the command line, what is an object and what is a filtering path.
Meaning: when you type git show foo/bar
if foo/bar
happens to match a branch or a tag name, it will display that object (and its diff...), and not resort to the default HEAD
if foo/bar
matches no branch or tag but matches a path on disk, it will try to treat it as a filtering path on the default HEAD
commit, as in git show HEAD foo/bar
; this will show a diff, narrowed to path foo/bar
if the string actually contains a :
, as in git show foo:bar
or git show :foo/bar
, git
will unambiguously treat this as a blob or tree name (with foo:bar
: object located at bar
within commit foo
, with :foo/bar
, object located at foo/bar
within the default HEAD
commit) ; this will display the plain content, and no diff
You can remove the guesswork by putting a --
in your command line:
# left of '--' : object names ('foo' and 'bar')
# right of '--' : filtering paths ('baz' and 'buzz')
git show foo bar -- baz buzz
If you don't provide a --
in your command line, and a name can be ambiguous, you would see an error message looking like that:
# with a file named 'master' on disk:
$ git show master
fatal: ambiguous argument 'master': both revision and filename
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Back to the OP's question :
git show filename
falls in point 2. above,git show HEAD:path/to/filename
falls in point 3.As stated in point 2., the only (small but impactful) thing to correct in the OP's question is that git show filename
defaults to git show HEAD -- filename
, not to git show HEAD:filename
.
Upvotes: 6
Reputation: 98005
tl;dr
git show example.txt
means git show HEAD -- example.txt
HEAD
, not example.txt
example.txt
argument is used to first filter the "list" of revisions, and then filter the diff displayedexample.txt
was not changed in the latest commit), or a summary of the latest commit with everything other than example.txt
ignoredTo 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 toHEAD
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?
<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 inexample.txt
" (the <path>
argument).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.<path>
You can demonstrate this is what's happening with a few variations:
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.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.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:
<revision-range>
, which is HEAD:example.txt
, and decides what type it references.Upvotes: 3
Reputation: 30966
git show <path>
is equivalent to git show HEAD <path>
.
Without <path>
, it prints the log message and the diff of changed files of the head commit.
With <path>
, it prints the log message and the diff of the specified path of the head commit if the file is changed in the commit. If not, nothing is printed.
Upvotes: 2