使用git log显示合并期间更改的文件

23

我正在执行以下命令:

git log --name-only –pretty="format:%H %s" -- *.sql --grep="JIRA-154"

以以下格式返回结果:

[commitid1] [comment]
path/to/file1/file1.sql
path/to/file2/file2.sql
path/to/file3/file3.sql

[commitid2] [comment]
path/to/file2/file2.sql
path/to/file4/file4.sql

输出已重定向到文件,格式恰好符合我的要求,但是合并提交是一个问题。作为合并的一部分而被更改的文件永远不会列出。而是得到类似以下的结果:

[commitid3] [merge comment]
[commitid4] [comment]
path/to/file3/file3.sql

显然我在这里理解错了些什么,因为我期望看到合并时更改的文件列表。是否有一种方法将这些文件包含在输出中?


“--grep =” JIRA-154 “应该放在”-- *.sql“之前,否则它没有任何效果。合并提交本身不涉及任何文件。” - ElpieKay
谢谢你提醒我 @ElpieKay - 我之前没有意识到这一点,确实很重要。 - Blue
2个回答

43

TL;DR

尝试在git log选项中添加-m选项。这使得Git“分离”每个合并,因此它将对每个父级别进行差异比较。git log找到合并,但是根本不查看其中的内容,而没有这个或其他类似的选项。

另外,正如ElpieKay评论所指出的那样,您需要在--之前放置--grep=<regexp>。还可以使用引号写"*.sql",以防止您的shell自行扩展星号(细节因不同shell而异,并取决于当前工作目录中是否有*.sql文件)。

长版本

正如Tim Biegeleisen所说,问题源于合并提交的特性。
通常情况下,为了展示提交中发生了什么变化,Git会运行一个简单的git diff parent self命令,其中parentself分别代表提交的父级和提交本身。无论是git log还是git show都是这样做的,只是在稍微不同的情况下有些微妙的区别。最明显的区别是git show默认每次都显示差异,而git log只有在给出-p或其他各种差异控制选项(例如--name-only)时才会进行差异比较。
合并操作则有所不同。

合并提交是具有两个父级的提交。这意味着git loggit show需要运行两个git diff命令。1实际上,git show确实运行了两个差异,但是默认情况下将它们转换为组合差异,该差异仅显示那些与两个父级版本不同的文件。但出于某种原因,git log默认情况下不会这样做。

即使在git log显示差异时,它的行为在合并时也特别奇怪(我甚至可以说是糟糕的)。虽然git log -pgit log --name-status在常规提交上运行(单个)差异,但它根本不运行在具有多个可见父级的提交上的差异,除非你强制它这样做。
仅使用-m总是有效的。该标志基本上告诉git log(和git show)将合并拆分为多个单独的“虚拟提交”。也就是说,如果提交M是具有父项P1P2的合并,则对于差异而言,Git会像存在父项P1的提交MP1一样,以及第二个提交MP2与父项P2一样。您将获得两个差异(以及差异头中的两个提交ID)。
在使用--first-parent选项后,git log会忽略合并提交的第二个(或更多)父提交,只保留一个父提交。这意味着git log将完全不跟踪侧边分支。因此,如果您不关心合并的其他方面的历史记录,可以使用-m --first-parent。这样可以获得与第一个父提交相比的单个差异,而不是每个父提交的差异。
(哪个父提交是第一个?好吧,它是您运行git merge时的HEAD。通常这是提交的“主线”,即“您的分支”的提交。但是,如果您的团队随意使用git pull,则可能不希望忽略合并的另一侧,因为git pull将他人的主线工作转换为小侧边分支的"foxtrot merges"。)

再次合并差异

除了-m外,您可以提供-c--cc(请注意,-c有一个破折号,而--cc有两个4)给git log,以使其生成组合差异,就像git show一样。但是,与所有组合差异一样,它忽略了在合并提交和任何一个父提交之间匹配的文件。也就是说,再次给出相同的合并M,这次Git将比较M vs P1M vs P2。对于任何文件F,其中M:FP1:FP2:F相同,Git根本不显示任何内容。
通常情况下,这正是您想要的。如果提交M中的文件F与两个父提交之一中的文件F匹配,那么这意味着该文件来自于该父提交。通常情况下,P1中的FP2中的F不匹配并不重要:无论是在P1还是P2中对F进行的任何更改都可能是历史上某个更早更改的结果,这就是我们应该注意到它的地方,而不是在合并M时。
这就是组合差异背后的逻辑。它并不适用于所有情况,这就是为什么存在-m的原因:将合并拆分成其组成部分。
1实际上有两个或更多,但“更多”是不寻常的;大多数合并提交只有两个父提交。具有两个以上父提交的合并提交称为“章鱼合并”。 2无论哪种方式,git loggit show都内置了大部分git diff,因此它们实际上不需要运行其他命令,但结果相同。 3我不知道原因,而且我只是在查看git log源代码时才了解到这种特定行为,试图解释为什么git log --name-status没有显示某些内容。 4这是因为--cc是一个“长”选项,在GNU选项解析中,所有像name-onlycc这样的长选项都会得到“两个”破折号,而所有“短”(一个字母)选项如p则只有“一个”破折号。

3
非常有用的信息和解释。然而,对于时间紧迫的求助者,我建议在你的回答开头加上“tl;dr add -m”。 - Martin
2
@Martin:也许吧;但是-m输出通常很长,如果你不知道它每个父级都会产生一个差异,那么它也会非常令人困惑。我猜我也可以把那个放进去,看看效果如何 :-) - torek

4
你看到这种行为的原因是在合并提交的情况下,会有两组变更文件,每组都来自于一个父提交。这里的一种选择是在运行git log命令时使用--first-parent -m选项:
git log --name-only --grep="JIRA-154" --first-parent -m –-pretty="format:%H %s" -- "*.sql"

这将告诉Git只关注正在发生合并的主分支,仅显示此提交所涉及的文件集。点击这里查看文档,点击这里阅读一篇优秀的博客文章。

1
在刚才的实验中(基于马丁对我的回答的TL;DR的要求),我发现git log --first-parent并不真正起作用(在我看来):它首先看到合并提交合并提交并禁用任何差异,然后进行“第一个父级”图形切割,然后标记要跳过的合并,无论如何都不会进行差异比较。将--first-parent-m组合使用可以正常工作并做正确的事情。请注意,--first-parentgit show交互得更好。 - torek
@torek请编辑一下,如果这个答案还有价值的话。如果没有,请告诉我,我可以删除它。 - Tim Biegeleisen

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