Git show和Git log --numstat输出的区别

7

我正在比较使用相同选项的git show和git log。我会在不同的提交中得到不同的结果。我还没有详细查看文档以找出原因,但我猜测这与每个命令如何解释修订列表以及提交图形是有关的?例如,在FFmpeg上进行演示:

案例1 git log:

> git log -n1 --numstat --format='%H' 00049f193d07cec0409069bc51d0dcb8ab9da837
00049f193d07cec0409069bc51d0dcb8ab9da837

案例1 git show:

> git show -n1 --numstat --format='%H' 00049f193d07cec0409069bc51d0dcb8ab9da837
00049f193d07cec0409069bc51d0dcb8ab9da837

4       0       libavcodec/mpegaudiodecheader.c

案例2 git日志:

> git log -n1 --numstat --format='%H' 001d668d40b5f87d19271c7d5521368b5187425b
001d668d40b5f87d19271c7d5521368b5187425b

2       5       libavformat/dvenc.c
2       6       libavformat/gxfenc.c
5       0       libavformat/internal.h
2       5       libavformat/movenc.c
2       5       libavformat/mxfenc.c
7       0       libavformat/utils.c

案例2 git show:

> git show -n1 --numstat --format='%H' 001d668d40b5f87d19271c7d5521368b5187425b
001d668d40b5f87d19271c7d5521368b5187425b

2       5       libavformat/dvenc.c
2       6       libavformat/gxfenc.c
5       0       libavformat/internal.h
2       5       libavformat/movenc.c
2       5       libavformat/mxfenc.c
7       0       libavformat/utils.c

基本上,我对于情况1为什么日志中省略了已更改的文件(libavcodec/mpegaudiodecheader.c),而show命令包括它们感到困惑,然后在情况2中,输出是相同的感到困惑。

参考:

> git diff --numstat 00049f193d07cec0409069bc51d0dcb8ab9da837 00049f193d07cec0409069bc51d0dcb8ab9da837^
0       4       libavcodec/mpegaudiodecheader.c
2个回答

6

Joseph K. Strauss的回答是正确的(这是一个合并提交),但不够完整。缺少的信息散布在Git文档和源代码中,这就是为什么它们很难找到的原因。

首先,00049f193d07cec0409069bc51d0dcb8ab9da837 确实是一个合并提交。它的两个父节点分别是d832020bd853f84b96a3fdf3e0a385d8492ec8c8fcbcc561e0fdc95a7dd48b92db53846726aec27e(我们不需要知道它们的确切编号,但也可以记录下来以显示其“合并性”)。

git show文档给了我们一个提示:

任何生成差异的命令都可以使用-c--cc选项,在显示合并时生成组合差异。这是使用git-diff(1)git-show(1)显示合并时的默认格式。还要注意,您可以给这些命令之一加上-m选项,以强制生成与合并的各个父节点不同的差异。

这里缺少的是对-c--cc本身的描述,在这两个链接的手册页面中都找不到,但在git diff-tree的手册中可以找到。然而,在我们去那里之前,值得先去git diff文档,在那里我们发现了这个:

"git-diff-tree"、"git-diff-files"和"git-diff --raw"可以使用-c--cc选项,为合并提交生成差异输出。[删节示例]
请注意,组合差异仅列出从所有父级修改的文件。

(这里我加粗了一下,因为它非常重要;我们马上就会再次看到它,在那里我还会使用加粗。)现在我们可以跳回到git diff-tree,在那里我们找到了 -c--cc的实际描述:

-c
      此标志更改合并提交的显示方式(这意味着只有在命令给出一个 tree-ish --stdin时才有用)。 它同时显示每个父节点与合并结果之间的差异,而不是一次显示父节点和结果之间的成对差异(这是-m选项执行的操作)。 此外,它仅列出从所有父节点修改的文件

--cc
      此标志更改合并提交补丁的显示方式,类似于-c选项。 它意味着-c-p选项,并通过省略在父节点中其内容仅有两种变体且合并结果选择其中一个而没有进行修改的不相关段来进一步压缩补丁输出。 当所有段都不相关时,提交本身和提交日志消息将不显示,就像在任何其他“空差异”情况下一样。

请注意,这告诉我们--ccgit show的默认设置,但没有提到git log。事实证明,git log默认只抑制合并差异输出,而git show设置了--cc。前者似乎没有在任何地方记录,但可以在Git源代码中的builtin / log.c和< b>revision.c 中找到:

[revision.c]
void init_revisions(struct rev_info *revs, const char *prefix)
{
        memset(revs, 0, sizeof(*revs));

        revs->abbrev = DEFAULT_ABBREV;
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
[snip]

这将设置默认操作以忽略合并(revs->ignore_merges = 1),对于所有命令都是如此;需要处理合并的命令需要清除该标志(这也在Documentation/technical/api-revision-walking.txt中有说明)。 git showgit log(以及其他几个命令)都是在builtin/log.c中实现的,其中包含以下部分内容:
static void log_setup_revisions_tweak(struct rev_info *rev,
                                      struct setup_revision_opt *opt)
{
        if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) &&
            rev->prune_data.nr == 1)
                DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES);

        /* Turn --cc/-c into -p --cc/-c when -p was not given */
        if (!rev->diffopt.output_format && rev->combine_merges)
                rev->diffopt.output_format = DIFF_FORMAT_PATCH;

        /* Turn -m on when --cc/-c was given */
        if (rev->combine_merges)
                rev->ignore_merges = 0;
}

如果选择了组合差异选项,这就可以显示合并的内容。而对于 git show 命令:

static void show_setup_revisions_tweak(struct rev_info *rev,
                                       struct setup_revision_opt *opt)
{
        if (rev->ignore_merges) {
                /* There was no "-m" on the command line */
                rev->ignore_merges = 0;
                if (!rev->first_parent_only && !rev->combine_merges) {
                        /* No "--first-parent", "-c", or "--cc" */
                        rev->combine_merges = 1;
                        rev->dense_combined_merges = 1;
                }
        }
        if (!rev->diffopt.output_format)
                rev->diffopt.output_format = DIFF_FORMAT_PATCH;
}

所以,git show会检查是否指定了-m参数。如果没有,则内部打开-m,然后除非有三个显式选项之一:-c--cc--first-parent,否则打开--cc。前两个选项很有道理(不要覆盖用户的设置),但第三个选项很奇怪。(也许这是为了避免后面出现问题,例如我们进行联合差异比较,但只引入了一个父级ID。)
仍然不是很明显的是它们的区别在哪里:
$ git log --no-walk --numstat 00049f193d07cec0409069bc51d0dcb8ab9da837 
[snip output: log message, with no diff-stats]
$ git show --numstat 00049f193d07cec0409069bc51d0dcb8ab9da837
[snip log message]
4       0       libavcodec/mpegaudiodecheader.c

如果我们在git log命令中加入-m选项(以清除rev->ignore_merges标志),则可以获得针对两个父提交的numstat差异。 如果我们此外再加上--cc,那么我们将得到与git show相同的结果:
$ git log -m --cc --no-walk --numstat 00049f193d07cec0409069bc51d0dcb8ab9da837
[snip log message]
4       0       libavcodec/mpegaudiodecheader.c

稍加思考,现在清楚为什么我们只看到一个文件:它是唯一一个来自两个父版本都有变化的文件。这与任何合并差异的约束条件相同,并且确实,用相同的git log--cc替换为-c会产生相同的结果。
底线就是,如果没有-m-c--ccgit log会打印合并的日志消息,但从不尝试显示合并提交与其父版本之间的差异。如果没有这些选项中的任何一个,git show将设置--cc。这大约只有一半文档说明了。

非常好,谢谢你提供了非常全面的答案。我已经确认这是正确的,但我还是有点困惑,所以我想澄清一下——默认情况下(使用--cc选项),git show仅显示“所有父级中修改的文件”(与使用-c或--cc的git log相同)?再次查看第1种情况(合并提交)的提交,根据我的测试,似乎git showgit log -c/--cc(如Joseph K Strauss的答案所述)仅显示第一个父级的numstat/diff。 - Mike
这是比较输出的情况,既使用git log -m的输出,又使用git diff --numstat直接比较提交与其父级,还要单独查看每个父级提交的git log --numstat(为了简洁起见,我将省略演示,但如果需要可以包含在内)。我认为我正在寻找的行为是“仅从所有父级修改的文件”情况。对于这个提交,似乎git show或git log -c/--cc并没有产生这种行为(也许在这种情况下一个空的numstat是正确的),但也许我误解了什么。 - Mike
@Mike:这里的问题在于,“从所有父级修改的文件”和“从第一个父级修改的文件”是同一组文件,对于这个特定的提交:diff(merge,p1) => 音频文件;diff(merge, p2) => 许多文件,包括音频文件。要区分这些情况,您需要进行合并,例如,diff(merge,p1) => 文件A,文件B;diff(merge,p2) => 文件B,文件C。然后,“所有父级”仅为文件B,而“与p1相比”或“与p2相比”则有所不同。(还要注意,log没有任何标志,根本不会显示任何差异,即使使用了--numstat。) - torek
你说得对。我打错了grep命令,并没有仔细查看文件列表。我认为我也被这个事实所困惑了,在这种情况下,git show -c <commit>git show -c --numstat <commit>是不一致的。第一个父级有8行添加/13行删除的差异,而第二个父级在audiothing.c中有4行添加的差异(每个父级修改不同的行)。git show -c显示组合差异,但添加--numstat使它看起来只有4行的差异(这就是为什么我认为它正在查看第一个父级)。在这种情况下,我期望+12/-13。 - Mike
实际上,我想我会期望在这种情况下得到一个空的差异,并且仅包括所有父级中有所修改的(例如,在文件B中与p1 p2 (... & pn)不同的行),但是我想也许这种粒度水平很难或者可能具有奇怪的边缘情况,并且以文件级别的粒度是合理的。 - Mike

2

看起来你的第一次提交(00049f193d07cec0409069bc51d0dcb8ab9da837)是一个合并。因此,它会假设你不关心看到提交的统计信息,但会显示与第一个父提交的差异的numstat。

我试图找到相关文档,但没有找到。


这在一定程度上可以解释事情,但是git loggit show应该都默认使用合并的差异,包括--numstat。 (添加-m以对每个父提交进行单独的差异)。所以奇怪的是它们之间的输出会发生变化(它们完全省略文件并不奇怪,只是奇怪的是git show --numstat 没有)。 - torek
事实证明,我有些微妙的错误:git loggit show选择了不同的合并差异选项。 - torek

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