有很多Git文档是次优的,我们可以这么说。
重要的是要意识到每个Git提交都保存一个快照,而不是更改,因为这解释了Git在几种棘手情况下的行为。各种Git命令,包括
git diff
和
git log
,可以提取
两个快照并进行比较。将旧快照与新快照或“左侧”与“右侧”(因为您可以反转它并将新快照与旧快照进行比较)进行比较的结果是
差异或
补丁。
准备这样的差异/补丁的默认方法是生成一系列指令,如果按字面意义执行,将把每个左侧文件转换为相应的右侧文件。这些指令的一般形式为:
期望在左右两侧文件中都能查看此特定上下文,然后从左侧文件中删除任何-
行,并从右侧文件中添加任何+
行。如果左侧文件来自某个提交的(单数)
父级,而右侧文件来自提交本身,则告诉您有人在该文件中更改了什么。
毫无疑问,您已经看到了这个输出,并且它可能甚至有些意义。
然而,您正在阅读的文档是自动从多个输入片段编译的,并且您链接到的
git log
的描述是为在阅读
default 输出 git diff-tree
的另一个描述之后阅读而写的,其中包括此特定文本:
in-place edit :100644 100644 bcd1234 0123456 M file0
copy-edit :100644 100644 abcd123 1234567 C68 file1 file2
rename-edit :100644 100644 abcd123 1234567 R86 file1 file3
create :000000 100644 0000000 1234567 A file4
delete :100644 000000 1234567 0000000 D file5
unmerged :000000 000000 0000000 0000000 U file6
当然,
git log -p
根本不会产生那样的输出——因此
git log
文档中没有包括这一部分。但是,
git log -p
确实会产生与
git diff-tree -p
相同的输出。当
git diff-tree -p
文档的后面一部分使用“不生成上述描述的输出”这个短语时,它指的是
:100644 ...
这些东西。
回到
git log -p
显示提交历史记录以及完整差异信息的说法,这也是错误的。问题在于,完整信息对于
git log -p
来说过于复杂。具体来说,合并提交被定义为具有两个或多个父提交的任何提交。
每个提交都保存了所有文件的快照。但是每个提交还记录了一些父提交哈希 ID。大多数提交只有一个父提交。在这种特定且非常常见的情况下,
git log
可以将(单数)父提交放在左侧,将提交(也是单数)放在右侧运行
git diff
。这样,您就可以看到父级和子级之间的差异:该提交的作者在该提交中更改了什么。
但是有些提交有两个父级。这些提交被称为合并提交;
git merge
命令倾向于构建它们。(我们不能说它总是构建它们,因为——正如Git命令通常会做的那样——
git merge
实际上可以根据情况和一些命令行参数执行几个不同的任务。) 鉴于这种合并提交,
git log
不仅会选择一个父级,然后显示该父级的快照与提交的快照之间的差异。它不会选择两个父级并将它们进行比较——这通常不太明智,并且不会告诉您有关合并结果的任何信息——默认情况下,它甚至不会尝试同时比较所有三个提交。
相反,git log
对于一个双亲(或多于两个父级)的 合并 提交所做的是显示日志消息,然后不再显示差异。 在大多数情况下,这实际上是最实用的方法,这就是为什么 git log
这样做的原因。但这立即告诉我们,我们肯定没有得到完整的图片!
您可以从合并提交中获取差异
请注意,对于一个漂亮简单的线性提交链:
A <-B <-C ... <-F <-G <-H <--master
git log
做的是从最后一个提交开始(它有一个哈希 ID,但这里我只称之为
H
),显示其作者和日志消息,然后提取两个快照,一个来自父级
G
,一个来自
H
本身,并对它们进行差异比较。然后它向后移动到提交
G
。现在它显示了
G
的作者和日志消息,然后提取了
F
(
G
的父级)和
G
的快照并将它们进行差异比较。这个过程会重复,Git 会从子提交向父提交逐个向后移动。只有在合并时,
git log
才不会进行差异比较。
git show
命令非常类似于
git log
:它基本上执行
git log
的功能,但仅适用于
一个提交。也就是说,如果您将
git show
指向提交
G
的哈希ID,它将显示
G
的作者信息、日志消息以及从
F
到
G
的差异,但然后就停在那里了——它不会继续显示
F
。但是,如果您将
git show
指向
合并提交,则
有时它会显示差异。它显示的是
合并差异,稍后在这些手册页面中进行了描述。需要注意的是,合并差异
仍然有意省略一些内容。特别要注意文档中提到的(单独的)部分:
合并差异仅列出所有父级中修改过的文件。
这里的意图是帮助读者。有时候确实很有用,但文档并不是非常清晰。在这种情况下,为什么
git log
没有显示任何内容,而
git show
产生了一个组合差异,这一点并不清楚。
实际上,
git log
、
git show
和其他各种命令都可以执行这种特殊的组合差异操作。但默认情况下,
git log
不会这样做。你可以给
git log
提供
-c
或
--cc
标志(注意第一个是 "一个破折号,一个 c",第二个是 "两个破折号,两个 c"),以使
git log
为合并生成组合差异。
git show
命令默认使用
--cc
行为。
最后,注意你可以给
git log
和
git show
加上
-m
标志。在这种情况下,这两个命令将更加特别地处理合并:对于一个具有两个父节点
P1和
P2的合并提交
C,这两个命令实际上会运行以下命令:
git diff P1 C
git diff P2 C
在显示通常的头信息(作者和日志消息)之后。
在所有情况下,除非使用--graph
,否则git log
不会提供足够的信息来重现实际的提交图,这对于理解git merge
至关重要。但这是另一天的事情......