git:查看特定文件的提交更改

55
我想查看在第147行对于this文件所做的更改。所以我逐行查询了文件的提交记录:
git blame include/svl/itemset.hxx

这是git blame的精简输出结果。
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 145)     SfxItemPool*                GetPool() const { return m_pPool; }
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 146)     const sal_uInt16*           GetRanges() const { return m_pWhichRanges;
 }
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 147)     void                        SetRanges( const sal_uInt16 *pRanges );
d210c6ccc3 svl/inc/svl/itemset.hxx         (Xiaofei Zhang      2010-07-29 10:56:19 +0800 148)     void                        MergeRange( sal_uInt16 nFrom, sal_uInt16 nTo );
4b3a535ae3 include/svl/itemset.hxx         (Michael Stahl      2015-04-08 15:02:47 +0200 149)     const SfxItemSet*           GetParent() const { return m_pParent; }

现在我想看看提交SHA d210c6ccc3 对这些行做了什么更改。基本上,我想看看这个提交对文件所做的更改。所以我做了以下操作:
git show d210c6ccc3 include/svl/itemset.hxx

但是这似乎没有给我正确的输出,实际上什么都没有输出。有人可以建议我可能漏掉了什么吗?或者也许有其他更好的方法来了解选择的提交对文件所做的更改?

提交 d210c6ccc3 修改了 svl/inc/svl/itemset.hxx,而文件 include/svl/itemset.hxx 则被 4b3a535ae3 修改。但是你的提交+文件组合在责任记录中没有出现。也许中间有重命名或符号链接之类的东西? - rodrigo
如果那个提交没有修改文件,为什么它会出现在 git-blame 的输出中呢?据我所知,git blame 给出的是修改了这些行的行和提交。 - Rohan Kumar
我的猜测是include/svl目录已经被重命名为svl/inc/svl(或者反过来)。只需使用备用名称即可查看差异。 - rodrigo
4个回答

54

eftshift0的回答是正确的(我已经点赞了)。但这是为什么,还有一个可能出现问题的地方。

大多数情况下,git show commit -- path是正确的,并会显示:

  • 指定提交的日志消息,以及
  • 与其父级进行比较而生成的该特定文件的补丁。

该补丁与 git diff commit^1 commit -- path所生成的补丁相同。 (请注意,此处的^1后缀为文字文本,而commit部分是要替换的哈希ID。 后缀语法的含义是“查找第一个父提交”。 您可以将此后缀添加到大多数提交选择器中,但不能用于使用某些搜索模式的选择器。请参见 gitrevisions文档。)

有两个重要的例外。只有其中一个适用于此处,因为git blame通常不会追溯合并,它试图追踪进入合并的更改的。 但我还是想提一下它,因为git show在合并时的行为很有趣。:-)

如果使用git show查看合并提交,则默认情况下将显示组合差异(“所有父项”与合并提交的内容进行比较)。在这种情况下,您可能希望直接回退到git diff,以便您可以指定要比较的父项(^1^2,如果这是八达通合并,则可以使用更多级)。原因是组合差异有意忽略与一个父提交中的版本匹配的任何文件:这意味着从该点起,存储库中的任何内容都来自于合并的两个(或N> 2)“方面”中的一个。

使用git blame时,您正在寻找“谁改变了什么”。做合并的人通常不是做出更改的人,所以您应继续“穿过合并”以找到真正做出更改的人。

第二个例外是导致您在此处出现问题的原因,它实际上更多地涉及文件在开发过程中重命名git blame的工作方式。

当使用git blame分析更改文件时,例如include/svl/itemset.hxx,它会从指定的提交开始一次回退一个提交。如果您没有选择自己的起始点,则从当前提交HEAD开始。然后,它查看父提交(例如,通过git show)。例如,如果当前提交922e935c8812是普通提交,其父提交为22c6554c98e2,则它将比较提交922e935c881222c6554c98e2。如果22c6554c98e2有一个相同的文件名,那很可能是同一个文件...但如果没有,Git会尝试确定22c6554c98e2中哪个文件与include/svl/itemset.hxx是相同的文件。

在这种情况下,在提交b9337e22ce1d处发生了这件事。在提交b9337e22ce1d中有一个名为include/svl/itemset.hxx的文件,但在提交b9337e22ce1d^f4e1642a1761中,该文件被命名为svl/inc/svl/itemset.hxx。当从提交b9337e22ce1d回退到提交f4e1642a1761时,Git检测到此重命名,并且git blame会将新名称从提交f4e1642a1761带回提交d210c6ccc3

但是,当您运行git show d210c6ccc3时,Git直接跳转到d210c6ccc3(及其父级7f0993d43019)。它不再知道在HEAD中名为include/svl/itemset.hxx的文件在d210c6ccc3中被命名为svl/inc/svl/itemset.hxx。因此,您必须发现这一点,并将早期的名称传递给Git。

您可能会想知道如何找到这个。答案是使用git log --follow。对于git log--follow代码并不好,但它与git blame使用相同的代码,因此至少产生相同的答案。以下是我的操作:

$ git log --oneline --follow --name-status include/svl/itemset.hxx
00aa9f622c29 Revert "used std::map in SfxItemSet"
M       include/svl/itemset.hxx
afaa10da2572 make SfxItemSet with SAL_WARN_UNUSED
M       include/svl/itemset.hxx
[snip]
a7724966ab4f Bin comments that claim to say why some header is included
M       include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx

还有一个更早的重命名。以下是另一种更短的方法,用于查找重新命名的内容:

$ git log --oneline --follow --diff-filter=R --name-status include/svl/itemset.hxx
b9337e22ce1d execute move of global headers
R100    svl/inc/svl/itemset.hxx include/svl/itemset.hxx
e6b4345c7f40 #i103496#: split svtools in two libs, depending on whether the code needs vcl or not
R100    svtools/inc/svtools/itemset.hxx svl/inc/svl/itemset.hxx

如果d210c6ccc3出现在e6b4345c7f40之前,您将需要使用更早的路径名称。但是,提交d210c6ccc3e6b4345c7f40的后代,而不是祖先。


1当合并发生更改时,在根本意义上,这确实需要同时跟随两个(或所有)输入提交。然而,Git目前无法做到这一点:git log --followgit blame实际上只遍历最终版本的文件“来自”的任一父级。也就是说,如果我们查看包含合并文件F的典型合并提交M,则有两个输入M^1:FM^2:F,以及一个输出M:F。如果M:FM^1:F相同,我们应该完全忽略M^2:F:所有M:F的内容都来自提供M^1:F的人。如果M:FM^2:F相同,我们应该完全忽略M^1:F:所有M:F的内容都来自提供M^2:F的人。

请注意,这仅适用于每次一个文件,即使如此,它只在文件完全匹配两个输入之一时才起作用。否则,我们应该查看组合差异,以查看文件如何从两个输入中修改。这就是组合差异和历史简化背后的逻辑。但在某些罕见的情况下,它过于简化,因此有时会出错。


奇怪的是,在macOS上我已经通过MacPorts安装了版本为2.35.1的git,但是“git show commit -- path”没有显示任何内容。但是,“git diff commit^1 commit -- path”返回了正确的差异。 - kkpattern
@kkpattern:给定的提交是否是合并提交?当您运行 git show -m commit -- path 时会发生什么?(请参阅答案中以“如果您查看合并提交”开头的段落。) - torek
@kkpattern:git rev-parse <commit>^@ 的输出是什么?在 Windows 系统上呢? - torek
两者都返回两个提交哈希值。Windows git客户端会多返回一个空行,但我认为这并没有什么影响。在macOS上,git show commit命令可以正常工作。但是添加-- path却不起作用。 - kkpattern
@kkpattern:好的,所以这确实是一个合并提交。如果默认的组合差显示了该文件,那么添加“--path”应该不会有任何问题,尤其是它会导致什么错误。当显示任何组合差异时,它们是否完全相同? - torek
显示剩余3条评论

25

你必须在那个版本中提供路径:

git show d210c6ccc3 -- svl/inc/svl/itemset.hxx

就这样吧


1
这将仅显示提交消息、作者和日期,而不是文件中的更改。 - Peter Reid
3
@PeterReid:不,Edmundo是正确的(尝试克隆存储库,虽然它有点大)。如果d210c6ccc3是合并提交,则您通常是正确的,但它不是。 - torek
如果这是一个合并提交,内容来自于其中一个父级,则您仍将看到真正添加这些行的修订版本,而不是合并修订版本....或者如果这些行是在合并时添加的(比如为了解决冲突或者是在合并后某些代码没有完整_)... 这种情况会在某些情况下发生),则合并修订版本将显示在责备输出中,使用显示时将在合并修订版本上为文件看到一些输出。 - eftshift0
@Edmundo:没错,我在这里有点过于简洁了。有时候人们可能会遇到一个合并,但不是通过 git blame 来查看,而是想要从那个提交中 git show 出一个文件,但如果它不是组合差异的一部分,git show 就不会显示该文件。如果通过 git blame 找到了合并,那么更改将成为组合差异的一部分,因此 git show 最终仍然会显示该文件。 - torek
没错...但我猜你可以把它选为“唯一”的答案,对吧?那就够了 :-D - eftshift0
显示剩余2条评论

4
你可以使用

标签来

git diff d210c6ccc3^ d210c6ccc3 include/svl/itemset.hxx

查看某个提交(d210c6ccc3)相对于其前一个提交(d210c6ccc3^)所做的更改。

其中,d210c6ccc3^是“基础”,d210c6ccc3是您要与基础进行比较的提交。

如果像您在问题评论中提到的那样,您查看了错误的提交,则替换上面的SHA值即可。

这将给出文件内容的diff输出。您所提供的git show命令将仅输出修改了指定提交中的文件的提交消息。


0

尝试使用 git diff <commit> -- include/svl/itemset.hxx


7
你能否在回答中加入一些解释? - sanyassh

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