为什么在git diff和git log之间,..和...的含义会颠倒?

3

以一个具有以下历史图表的示例repo为例:

$ git log --all --graph --oneline
* 691d454 (HEAD -> branch-b) branch-b-2
* c3bd488 branch-b
| * f5756ff (branch-a) branch-a-2
| * de00ec4 branch-a
|/  
* 5ad3b15 (root) root

现在,比较git diff branch-a..branch-b
diff --git a/f b/f
index af31a64..e7791f3 100644
--- a/f
+++ b/f
@@ -1 +1 @@
-branch-a-2
+branch-b-2

使用git diff branch-a...branch-b命令

diff --git a/f b/f
index d8649da..e7791f3 100644
--- a/f
+++ b/f
@@ -1 +1 @@
-root
+branch-b-2

使用 git log --oneline branch-a..branch-b 命令

691d454 (HEAD -> branch-b) branch-b-2
c3bd488 branch-b

使用 git log --oneline branch-a...branch-b 命令

691d454 (HEAD -> branch-b) branch-b-2
c3bd488 branch-b
de00ec4 branch-a
f5756ff (branch-a) branch-a-2

基本上,我们看到这个:
|------+--------------------------------------+--------------------------------------|
|      | branch-a..branch-b                   | branch-a...branch-b                  |
|------+--------------------------------------+--------------------------------------|
| log  | root -> branch-b                     | branch-a -> branch-b                 |
|      | (root, branch-b]                     | [branch-a, branch-b]                 |
|      | changes introduced in branch-b       | difference from branch-a to branch-b |
|------+--------------------------------------+--------------------------------------|
| diff | branch-a -> branch-b                 | root -> branch-b                     |
|      | [branch-a, branch-b]                 | (root, branch-b]                     |
|      | difference from branch-a to branch-b | changes introduced in branch-b       |
|------+--------------------------------------+--------------------------------------|

这意味着,如果您想查看从主分支派生出的分支中所做的更改作为提交列表,git log master..branch 将显示给您。但是,如果您想将这些相同的更改作为合并这些提交的差异来查看,则必须切换到 ...,如此操作:git diff master...branch。我经常发现自己在做这件事情,因此这是一个我一直想知道的小问题。
这是否存在设计原因?它似乎只是不一致性。

编辑:为了澄清,我知道在difflog.....的翻译分别是什么。我的问题可以重新表述为:是否更合理地将它们中的任何一个翻转,以使它们彼此一致?例如,如果git diff branch-a..branch-b意味着git diff $(git merge-base branch-a branch-b) branch-b,而git diff branch-a...branch-b意味着git diff branch-a branch-b,那么diff就与log一致。那么,为什么不是这种情况?

编辑2:在表格下面的段落中,我举了一个实际的例子,说明了git diff master...branchgit log master..branch的类比。对于另一种情况,如果您想获得将masterbranch分开的提交列表(即表示两者之间差异的提交),则应使用git log master...branch,如果您想要表示相同差异的diff,则应使用git diff master..branch

编辑3:我尝试使表格更清晰。使用括号和圆括号是数学区间符号

编辑4:调整了表格的位置,以便更好地适应。


也许我没有正确地阅读您的表格,但我不明白为什么 log branch-a..branch-b 的结果是 root -> branch-b?(也就是说,我在输出中没有看到 5ad3b15 - anthony sottile
@AnthonySottile 的意思是,它显示了从 rootbranch-b 的提交,但不包括 branch-a 的更改。如果 branch-b 有更多的提交,它们也会出现;所有从 rootbranch-b 的提交都不包括根节点。这也与使用 ...diff 相一致,因为它不包括 5ad3b15 的更改,否则您将看不到删除 root 行,只能看到向以前不存在的文件添加 branch-b 行。 - JoL
@JoL 对,我应该读完整个答案 :-D 抱歉 - zerkms
我添加了几个提交以帮助避免单行日志输出的混淆。 - JoL
2
我相信没有好的解释,这就是事实。你可以查看 Git 历史记录,看看是否有任何解释或讨论。 - max630
显示剩余2条评论
1个回答

1
他们没有翻转。相反,在git diff中,..具有特殊含义,即它没有任何含义,而git diff中的...具有特殊含义,这种含义是唯一的,其他Git命令中都找不到。
您特定的输入图形是...在编辑之前是这样的 :-) ...太小了,无法显示真正的差异。如果在两个分支分叉后有更多提交,则可以看到更多内容。
让我们首先看看git diff,因为它比较简单。在这些特定情况下,git diff所做的工作是-选择两个单独的提交并进行比较。
为了命名要比较的两个提交,我更喜欢写:
git diff <commit1> <commit2>

然而,如果您愿意,可以写成:

However, you can, if you prefer, write:

git diff <commit1>..<commit2>

这些都是完全相同的东西,无论何时。Git只是假装你根本没有写过“..”。无论你在这里使用哈希ID、分支名称(如“master”)还是远程跟踪名称(如“origin/master”),或者任何混合使用,这都是真实的。
请注意,两个提交名称或哈希ID的顺序很重要:git diff的输出是一组指令,告诉你如何更改第一个提交,以便在遵循所有指令的情况下获得第二个提交。
如果你写:
git diff <commit1>...<commit2>

Git仍会比较两个提交:2 Git将找到这两个给定提交之间的合并基础,然后将该合并基础提交与第二个列出的提交进行比较。也就是说,这与shell样式扩展相同:
git diff $(git merge-base <commit1> <commit2>) commit2

在这里,与之前一样,顺序很重要:合并基础是相同的3,无论两个提交的顺序如何,但是该合并基础将与第二个列出的提交进行比较。


1嗯,几乎总是这样!如果在命令行上列出第三个提交,它会像使用了三个点的语法一样。我认为这些是git diff中的一个错误,尽管总有可能有人依赖它们。

2这里还有另一个特殊情况,它绝对是一个错误:如果有多个合并基础,git diff会生成一个组合差异。这不是三个点语法的意图,这就是为什么它是一个错误。

3这假设您没有触发脚注2中的错误。当有多个提交的单个合并基础时,顺序无关紧要,但当有多个合并基础时,输入的顺序可能会影响输出的顺序。


对于git log和大多数其他Git命令,.....具有非常不同的含义。正如您现在无疑所知道的那样,<commit1>..<commit2><commit2> ^<commit1>的简写。我们可以通过git rev-parse看到这一点:

$ git rev-parse master..origin/maint
468165c1d8a442994a825f3684528361727cd8c0
^ccdcbd54c4475c2238b310f7113ab3075b5abc9c

这里,origin/maint 转换为第一个(未否定的)哈希 ID,而 master 转换为第二个(否定的)哈希 ID。它们告诉 Git,在遍历提交图时,应选择从 468165c1d8a442994a825f3684528361727cd8c0(正引用)可达的提交,同时拒绝从 ccdcbd54c4475c2238b310f7113ab3075b5abc9c(负引用)可达的提交。这意味着所有这样的提交,可能是许多提交。像 git log 这样的命令,通过遍历提交图,将显示所有选定的提交。
三个点的符号更加棘手,但在内部转换为相同类型的正引用和负引用:
$ git rev-parse master...origin/maint
468165c1d8a442994a825f3684528361727cd8c0
ccdcbd54c4475c2238b310f7113ab3075b5abc9c
^468165c1d8a442994a825f3684528361727cd8c0

在这里,两个正的引用是与origin/maintmaster相关联的哈希ID,而负的引用则是由这些起点选择的子图重新连接的点的结果。在这种特定情况下只有一个这样的点,因此这与我们即将看到的git merge-base输出相匹配,尽管一些分叉和合并方式复杂的子图可能具有多个这样的点:
$ git merge-base master origin/maint
468165c1d8a442994a825f3684528361727cd8c0

这些复杂的图表可能导致多个合并基地,尽管实现...的代码比找到合并基地的代码要简单。

如果你有一个像这样的图表:

     C--D   <-- br1
    /
A--B
    \
     E--F   <-- br2

三个点的语法 br1...br2 选择像 git log 这样遍历图形的命令中的提交 C-DE-F,但对于 git diff 分别选择提交 BF(仅适用于 git diff)。两个点的语法br1..br2选择像git log这样的命令中的提交E-F,并分别比较git diff中的提交DF(仅适用于git diff)。

事实上,我已经知道它们的翻译。我想我的问题可以重新表述为:“将diff或log的.....翻转,使它们彼此一致,这样不是更有意义吗?”我将编辑这个问题。 - JoL
请查看最后一段:使用 .. 这种方式时,git diff 不会像 git log 一样,因为您最终会在 git diff 中比较提交 DF,但在 git log 中根本看不到提交 D - torek
对于最后一段和你的评论,我的问题是“为什么?”例如,为什么在使用“..”时git log不能看到D,但在使用“...”时可以,或者交换diff的含义,以便logdiff彼此一致,这样就不需要在最后一段描述difflog之间的区别了。 - JoL
这里总会存在一些根本性的差异,因为git log将遍历所有选定的提交记录。如果有五个提交记录是从br2可达但从br1不可达的,那么git log br1..br2将显示这五个提交,但git diff只会比较两个提交记录。我认为A..B排除了A并包含了B的原因是为了使它类似于半开区间,如(1..4]。但最终,这些图形选择运算符并类似于半开区间,所以这个想法可能是错的。我还会认为允许git diff A..B就是错误的... - torek
因为这意味着 git diff A B,Git 可以强制用户编写它。但 Git 作者并没有选择这样做。 - torek
如果两者都被翻转,那么不会有任何区别,这就是我的意思。git diff将显示代表这5个提交组合的差异,就像现在使用br1...br2一样。 - JoL

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