“git show”与“git diff”的结果不同

7
我在一个git仓库中提交了一次修改,使用git show命令只显示有一个文件被修改,但是使用git diff命令却显示有两个文件被修改。
我创建了一个简单的示例仓库来展示相同的情况。
请参考以下链接: 在这个示例仓库中,我们有如下简单的历史记录:
*   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.

当我在例如SourceTree中查看提交c248261时,我可以看到fileAfileB被修改了。使用git diff得到相同的结果:

$ git diff --name-only c248261^..c248261
fileA
fileB
$

或使用缩写表示:

$ git diff --name-only c248261^!
fileA
fileB
$

当我尝试使用 git show 获取相同的信息时,只有其中一个文件显示出来:

$ 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
$

同样地,git diff-tree 没有显示任何内容:
$ git diff-tree c248261
$

请问有人能解释一下它们之间的区别吗?

我正在使用 git 版本 2.0.4。

3个回答

7

什么是git show

git show命令是用于显示git中对象信息的通用命令。

从手册中可以查到(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
...

因此,git show 的输出不同,因为这是一个合并提交而不是普通提交。

git diff-tree --cc 的 man 手册说:

--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.

进一步
-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.

请注意我在这里重申的最后一行:
Furthermore, it lists only files which were modified from all parents.

当使用git show查看合并提交时,它只会列出与该提交相比较的所有父提交所修改的文件。对于普通合并(即2个父提交的情况),这正好是被合并的文件。例如,如果您执行以下合并操作:

git checkout master
git merge feature
git show列出的文件是在合并之前同时修改了masterfeature的文件,并在合并提交中合并在一起的文件。
对于八爪鱼式合并——即多于两个父级的合并——结果文件必须与所有父级中的文件不同才能由git show显示。
请注意,使用git show查看合并提交时,还可能显示其他文件。例如,如果您调用git merge --no-commit,然后在提交之前执行以下任何操作,则这些文件也将由git show显示在合并提交中:
  • 添加新文件。
  • 删除现有文件。
  • 修改任何文件,包括已经被git merge暂存的文件。

结论

git show所做的是显示所谓的组合差异
运行命令git show在合并提交上的结果可以认为只显示了被合并的文件,而不是那些只是从其中一个父级简单地复制过来的文件。
这有点令人惊讶,因为很多人期望能够看到相对于刚刚合并的分支进行了哪些文件修改。
那么,如果您想查看任何一个父级之间的差异,该怎么办呢?
让我们最后再看一下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.

所以我们可以使用-m选项来获取两个差异,如下:

git show -m commit

这将为您提供与所有父提交逐个对比的差异。

要查看合并提交和您合并到其中的分支上一个提交之间的差异(即第一个父提交),请使用以下任一命令:

git show --first-parent commit
git diff commit^..commit
git diff commit^!

--first-parent选项真正属于git log,但也适用于git show。符号commit^!commit^..commit的缩写。

要查看合并提交和您合并的分支之间的差异(例如,查看您从另一个分支中省略了什么),请使用

git diff commit^2..commit
commit^2 表示 commit 的第二个父提交。
如果您有多个父提交(例如,一个章鱼合并),那么我相信您可以猜到如何对比第三、第四等提交。

1

从手册页(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.

因此,git show的输出不同,因为这是一个合并提交而不是一个简单的blob。 git diff-tree --cc的文档如下:

--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.

好的,现在变得有趣了。调用 git diff-tree c248261 没有返回任何内容。然而,git diff-tree --name-only c248261^ c248261 返回了预期的结果。我以为它们是等价的,但是阅读手册后我发现它们并不相同。 - mgd
git diff-tree 的 man 页面上说:“如果只有一个 <tree-ish> 给定,则将提交与其父提交进行比较(参见下面的 --stdin)。”,而在 --stdin 下,它写道:“当只给定一个提交时,将提交与其父提交进行比较。” 不确定这到底是什么意思。 - mgd
你想让我编辑这个问题吗(那么当我接受它作为正确答案时,你将获得积分)?我已经进行了一次编辑,正在等待审核。(我还有一些更正意见,之后会应用。)或者,我可以发布一个新的答案。 - mgd
如果您已经自己完成了正确答案的工作,请回答并接受它! - Useless
好的,我已经完成了。只是想给你点赞,因为你帮助我找到了正确的方向。 - mgd
显示剩余3条评论

0

通过查看提交信息中的提交c248261,你可能正在寻找的是一个合并提交。合并提交具有多个父级。

将后缀^添加到修订参数表示该提交对象的第一个父级。

因此,当你说:

$ git diff --name-only c248261^..c248261
fileA
fileB
$

它从提交的第一个父级进行差异比较。

你可以尝试执行以下操作:

$ git diff --name-only c248261^2..c248261

这将会给你以下结果:

fileA
--other files if exist--

在两个父提交合并后,会创建一个新的提交,即c248261,它只有一个文件具有差异,这是你在下面这段代码中看到的:

git show --name-only c248261

是的,这是一个合并提交,并且我知道它有两个父提交。我想要看到的是这个提交与其第一个父提交之间的修改文件 - 例如,在我合并到的分支中有什么改变。而且,与第一个父提交相比,这个合并提交实际上修改了两个文件 - 而不是一个。 - mgd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接