"git show" does not return the same result as "git diff"
I have a commit in a git repository where it git show
says only one file has changed, but git diff
displays two files as changed.
I've created a simple sample repository that demonstrates the same behavior.
In the sample repo, we have this simple story:
* c248261 (HEAD, origin/master, master) Merged feature into master.
|\
| * d23c497 (feature) Modified fileA and fileB in feature.
* | 06a7f5e Modified fileA and fileC in master.
* | 9cd1a6e Merged feature into master.
|\ \
| |/
| * aed2e5e Modified fileA and fileB in feature.
* | c6e4fe7 Mofified fileC in master.
* | 19ed298 Merged feature to master.
|\ \
| |/
| * c0f2abc Added fileB and modified fileA in feature.
* | 47c67cf Added fileC in master.
|/
* 56a9b73 Added fileA in master.
When I look at commit c248261 for example. SourceTree, I see that fileA
and fileB
changed. Usage git diff
gives the same result:
$ git diff --name-only c248261^..c248261 fileA fileB $
or using shorthand notation:
$ git diff --name-only c248261^! fileA fileB $
When I try to get the same information with git show
, only one of the two files appears:
$ git show --name-only c248261
commit c2482616b6b6781d0580ec1008ef7d0ab5f73a70
Merge: 06a7f5e d23c497
Author: ...
Date: Fri Aug 15 16:19:02 2014 +0200
Merged feature into master.
fileA
$
Likewise, git diff-tree
it shows nothing:
$ git diff-tree c248261 $
Can someone explain the difference please?
I am using git version 2.0.4.
source to share
What is it git show
?
Command git show
is a general command for displaying information about an object in git
.
From the man page ( git help show
):
Shows one or more objects (blobs, trees, tags and commits).
...
For commits it shows the log message and textual diff.
It also presents the merge commit in a special format as produced by
git diff-tree --cc
...
So the output git show
is different because it is more of a commit than a simple commit.
The man page git diff-tree --cc
says:
--cc
This flag changes the way a merge commit patch is displayed, in a
similar way to the -c option. It implies the -c and -p options and
further compresses the patch output by omitting uninteresting hunks whose
the contents in the parents have only two variants and the merge result
picks one of them without modification. When all hunks are uninteresting,
the commit itself and the commit log message is not shown, just like in
any other "empty diff" case.
Further
-c
This flag changes the way a merge commit is displayed (which means
it is useful only when the command is given one <tree-ish>, or
--stdin). It shows the differences from each of the parents to the
merge result simultaneously instead of showing pairwise diff
between a parent and the result one at a time (which is what the -m
option does). Furthermore, it lists only files which were modified
from all parents.
Please take a look at this last line, which I am repeating here:
Furthermore, it lists only files which were modified from all parents.
This way, git show
when merging, the commit will only list the files that have changed when comparing the commit against all the parents of the merge. For a normal merge (i.e. 2 parents), these are exactly the files that were merged. If, for example, you do this merge:
git checkout master
git merge feature
The files listed git show
are files that have changed both in master
and feature
before the merge and have been merged together in the merge commits.
To merge octopus, that is, more than two parents, the resulting file must be different from the all-parent file to be displayed git show
.
Note that other files may also appear when used git show
in merge compilation. If, for example, you call git merge --no-commit
, and then before performing any actions, then these files will also show up git show
in merge compilation:
- Add a new file.
- Delete existing file.
- Modify any file - including any of the files already shipped with git merge.
Conclusion
What it does git show
is display a so called combined diff.
The result of executing a command git show
when compiling a merge can be thought of as showing only the files that have been merged, not files that have simply been left unchanged by either parent.
This is a bit surprising as many people expect to see which files have changed compared to the branch you just merged into.
So what if you want to see the difference between both parents?
Let's take a look at the man page git show
:
COMBINED DIFF FORMAT
Any diff-generating command can take the `-c` or --cc option to produce
a combined diff when showing a merge. This is the default format when
showing merges with git-diff(1) or git-show(1). Note also that you can
give the `-m' option to any of these commands to force generation of
diffs with individual parents of a merge.
Thus, we can use the option -m
to get both differences like this:
git show -m commit
This will give you a difference in relation to all parents one by one.
To see the difference between a merge commit and a previous commit on the branch you merged into (i.e. the first parent), use one of the following commands:
git show --first-parent commit
git diff commit^..commit
git diff commit^!
The parameter --first-parent
does apply to git log
, but also works for git show
. The designation commit^!
is an abbreviation for commit^..commit
.
To see the difference between a merge commit and the branch you merged into (for example, to see what you left outside of another branch), use
git diff commit^2..commit
The designator commit^2
means the second parent element commit
.
If you have more than two parents (i.e. merging octopuses), I'm sure you can guess how to distinguish from 3rd, 4th, etc.
source to share
c248261
Looking at the commit message for commit , the commit you are looking for is probably a merge commit. A merge negotiator has more than one parent.
The ^ suffix to the revision parameter denotes the first parent of that commit object.
So when you say
$ git diff --name-only c248261^..c248261 fileA fileB $
It performs a diff from the first parent commit c248261
.
You may try,
$ git diff --name-only c248261^2..c248261
which will give you the following output:
fileA
--other files if exist--
After merging the two parents, a new commit will be created, i.e. c248261
which has only one file with differences, which you see when you say,
git show --name-only c248261
source to share
From the man page ( git help show
)
For commits it shows the log message and textual diff.
It also presents the merge commit in a special format as produced by
git diff-tree --cc
...
For plain blobs, it shows the plain contents.
So the output git show
is different because it is more of a commit than a simple blob. The docs for git diff-tree --cc
say:
--cc
This flag changes the way a merge commit patch is displayed, in a
similar way to the -c option. It implies the -c and -p options and
further compresses the patch output by omitting uninteresting hunks whose
the contents in the parents have only two variants and the merge result
picks one of them without modification. When all hunks are uninteresting,
the commit itself and the commit log message is not shown, just like in
any other "empty diff" case.
source to share