为什么此r1..r2修订版本范围的输出包含r2可以到达的提交?

时间:2019-04-16 01:31:40

标签: git git-log

在下图中,从r2(HEAD)和r1(75ec2933~1)都可以到达最后两个提交。

> git log --oneline --graph develop topic 75ec2933~1..HEAD
* 91cc860a (HEAD -> topic) 
* 1048e4d1 
* 1c28716e
| * f4a483cc (develop)
| *   b7cb53e6 
| |\
| |/
|/|
* | c7a197bd 
* | 3935a1a7 
| *   ad27a1fc 
| |\
| |/
|/|
* | 75ec2933 Merge branch 'develop' 
| * 5e55f38f 
|/
* 2effd96f          <--------------- 75ec2933~1 is r1
* ae6c987e 
* ecc2b546 

我希望最后两个提交不会成为输出的一部分,因为the git-log documentation说我们可以使用修订范围来“仅显示指定修订范围内的提交”。此外,修订范围文档中说about the r1..r2 notation

  

...您可以请求从r2可以到达的提交,但不包括^ r1 r2从r1可以到达的提交,可以将其写为r1..r2。

所以我的问题是为什么我们可以看到最近的两次提交,这些提交似乎可以从r1到达。

其他调查

事实证明,75ec2933~1不是2effd96f,而是887b3cfa。上图隐藏了该图,这导致我对r2感到困惑。

> git log ecc2b546..3935a1a7 --oneline --graph
* 3935a1a7
*   75ec2933 Merge branch 'develop' 
|\  
| * 2effd96f 
| * ae6c987e 
* 887b3cfa 
* 62e6be09 

1 个答案:

答案 0 :(得分:2)

我不得不在这里猜测一下(更新:已确认),但我认为我们有这种情况:

  • 提交75ec2933是合并提交,即具有两个父母。
  • 父1号有一些未知的哈希ID(更新:887b3cfa)。
  • 父母#2是2effd96f

在这种情况下,表达式75ec2933~1..HEAD排除父#1,但不排除父#2。您可以通过运行以下内容找出答案:

git rev-parse 75ec2933^@

(请注意,插入符号或帽子@后的^后缀)。对于产生的git log输出,有相当长的解释。但是,为了演示它,我将改用Git仓库作为Git本身,因为这是我很方便的。

示例

当我在Git的Git存储库中对另一个合并提交执行此操作时,会发生以下情况:

$ git rev-parse a562a11983^@
7fa92ba40abbe4236226e7d91e664bbeab8c43f2
ad6f028f067673cadadbc2219fcb0bb864300a6c

此处提交a562a11983与父母7fa92ba40aad6f028f06合并。

如果我在Git的Git存储库上运行git log --decorate --oneline --graph,则允许git log从提交b5101f9297开始(旧的master提示-我尚未更新自己的Git现在已经有数周的时间存储在Git的存储库中),结果从以下开始:

* b5101f9297 (HEAD -> master) Fourth batch after 2.20
*   a562a11983 Merge branch 'it/log-format-source'
|\  
| * ad6f028f06 log: add %S option (like --source) to log --format
* |   7fa92ba40a Merge branch 'js/add-e-clear-patch-before-stating'
|\ \  
| * | fa6f225e01 add --edit: truncate the patch file
* | |   371820d5f1 Merge branch 'bc/tree-walk-oid'
|\ \ \  
| * | | 974e4a85e3 cache: make oidcpy always copy GIT_MAX_RAWSZ bytes
| * | | ea82b2a085 tree-walk: store object_id in a separate member
| * | | f55ac4311a match-trees: use hashcpy to splice trees
| * | | 36775ab524 match-trees: compute buffer offset correctly when splicing
| * | | 0a3faa45b1 tree-walk: copy object ID before use
| | |/  
| |/|   
* | |   a6e3839976 Merge branch 'jt/upload-pack-deepen-relative-proto-v2'

使用git log --decorate --oneline --graph a562a11983^1..HEAD将其修剪为:

* b5101f9297 (HEAD -> master) Fourth batch after 2.20
* a562a11983 Merge branch 'it/log-format-source'
* ad6f028f06 log: add %S option (like --source) to log --format

请注意,此图形形状看起来要简单得多!我已经删除了提交a562a11983,但没有删除ad6f028f06,因此看起来提交a562a11983有一个父项ad6f028f06,尽管实际上有两个。实际上, git log --graph欺骗了我们。

在深入研究git log本身的具体细节之前,还需要注意一些其他事项。首先,gitrevisions notation中的语法r1..r2等效于r2 ^r1。实际上,如果我们使用git rev-parse来扩展 语法,那就是我们所看到的:

git rev-parse a562a11983^1..HEAD
b5101f929789889c2e536d915698f58d5c5c6b7a
^7fa92ba40abbe4236226e7d91e664bbeab8c43f2

HEAD是以b5101开头的提交哈希,而a562a11983^1(后缀^和数字1)是以7fa92b...开头的提交。 '在此处使用插入符号^作为后缀,而不是作为前缀; 插入符号作为前缀表示 ,即排除修订,但插入符号作为后缀引入了许多其他gitrevisions指定符之一,例如@{commit},当然还有特定父级的数字选择。 / p>

另一个事实是,每个提交记录的零个或更多父哈希ID。大多数提交具有完全一个父ID。您在存储库中进行的第一个提交没有父项,原因很简单,因为它不能有任何父项:新提交的父级ID必须是现有的有效提交哈希ID。没有父母的提交称为 root commit 。某些提交(通常由git merge进行的提交有两个父级,您可以进行具有三个或更多个父级的多臂章鱼合并)。根据定义,具有两个或多个父哈希ID的任何提交都是合并提交

由于大多数提交都有一个父提交,因此我们通常从这样的提交链的末尾开始,通常以诸如master之类的分支标签标记,然后我们可以一次向后进行一次提交:< / p>

... <-F <-G <-H   <-- master (HEAD)

在这里,存储在分支名称master中的哈希ID用大写字母H表示。我们说名称master 指向哈希ID为H的提交。提交H本身存储其父提交G的哈希ID,该哈希ID存储提交F的哈希ID,依此类推。因此,通过从H开始并向后退回到G,然后再回到F,依此类推,Git可以向我们展示历史记录-可以从到达的提交- em>名称master

最后一项是git log实际上采用了许多起点(我们可能希望将它们称为终点,但是Git可以逆向工作)。每个指定修订的参数(而不是那些由于用前缀^否定而消除修订的参数)都提供了这样的起点。如果您没有提供任何起点,则git log将使用HEAD作为起点。

git log如何浏览历史记录,然后显示图形

如果我们有一条简单的线性链,例如:

...--F--G--H   <-- master (HEAD)

然后,如果我们想模仿git log,我们的工作就很容易。我们从提交H开始并显示它。现在我们已经完成了H的工作,因此我们向后退了一步,回到了其父级G。我们显示G,然后回到F。我们重复此过程,直到达到没有父级且允许我们停止的根提交为止,或者直到用户退出git log

但是假设我们有一个包含合并提交的图形:

       I--J
      /    \
...--H      M--N   <-- master (HEAD)
      \    /
       K--L

我们将首先显示提交N,然后走到M并显示它。 1 然后,我们走到...等等,走吧到J还是L

git log要做的是,保留尚未显示的提交的优先级队列还要遍历提交图一次提交一次。因此,当您在没有其他参数的情况下运行git log或以HEADmaster作为参数运行时,git log会将提交N放入队列。

如果队列中只有一个提交,则工作很容易:将一个提交从队列中取出,显示出来,然后将其父级放入队列,如果在此之前{{ {1}}(通常是这种情况)。当队列中有个以上提交时,git log将从队列的 front 中获取一个,即优先级最高的一个。

因此,如果您运行git log,则Git所做的工作会将所有三个起始点放入优先级队列。由于您的实际命令是:

git log <start-point-1> <start-point-2> <start-point-3>

我们有三个起点,分别是git log --oneline --graph develop topic 75ec2933~1..HEAD develop),f4a483cctopicHEAD是对某些哈希ID的否定引用) 。事实证明,75ec2933~1HEAD都命名为提交topic,因此队列中只有两个提交而结束。

91cc860a选项会稍微修改优先级队列。默认设置是具有最高 date 的提交(即,距离将来最远或过去最少的提交)位于队列的最前面。对于--graph--graph,这条规则已经到位,但是增加了一条附加规则:在显示了将要显示的所有子项之前,无法显示任何父提交。在这种情况下,由于--topo-order91cc860a没有父/子关系,因此额外的例外此时不起作用。

因此f4a483cc从这两个日期较晚的日期开始,也就是git log91cc860a。 Git使用单个HEAD打印此提交,并找到其父级topic,该父级进入队列。 *也比1048e4d1更新,因此Git接下来显示它。它是上一次提交的直接父项,因此现在该展示该提交了。这样会持续一段时间,以便我们看到:

1048e4d1

f4a483cc具有父元素* 91cc860a (HEAD -> topic) * 1048e4d1 * 1c28716e ,而1c28716ec7a197bd的祖先,因此无论日期如何,都不得显示。 Git现在开始显示c7a197bd,这是一个普通的提交:

f4a483cc

f4a483cc的父级是| * f4a483cc (develop) ,因此f4a483cc进入了队列。该提交具有b7cb53e6作为祖先,因此Git接下来显示b7cb53e6

c7a197bd

...和b7cb53e6本身是一个合并,将其父项| * b7cb53e6 b7cb53e6放入队列。但是c7a197bd已经在队列中,因此什么也没发生。

现在ad27a1fc位于队列的 front ,因此c7a197bd显示了它。它是c7a197bd的第一个也是唯一的父级,也是git log的第二个父级,因此1c28716e以这种有点时髦的方式显示它:

b7cb53e6

向右延伸的下肢显示出该第二亲本。直脚将最终连接到git log --graph的第一个父级。

相同的模式持续了一段时间,但随后我们遇到了不幸的情况:

| |\
| |/
|/|
* | c7a197bd 

这时,Git显示了提交b7cb53e6(有两个父级,* | 3935a1a7 | * ad27a1fc | |\ | |/ |/| * | 75ec2933 Merge branch 'develop' | * 5e55f38f |/ * 2effd96f <--------------- ??? 是父级#1,75ec2933是父级#2)。 Git 本来应该887b3cfa放入队列,但我们告诉它不要:2effd96f的意思是887b3cfa,这意味着不要显示{{ 1}} ,将其排除在队列之外。因此,在显示了^75ec2933~1之后,该队列包含了提交哈希ID ^887b3cfa887b3cfa。 Git显示了75ec2933,这使其可以继续前进到5e55f38f。当2effd96f显示一个时,甚至没有看到第二个截止父对象,因此它错误地绘制了该图,就好像该父对象不存在一样。< sup> 2


1 值得一提的是:当5e55f38f显示普通提交时,如果2effd96f有效,它会将提交与其(单)父对象进行对比,以显示作为更改,它实际上是快照。但是,当它遇到合并提交时,它不知道要使用哪个父对象作为差异,因此根本不用理会差异!您可以通过其他git log --graph选项强制其显示一个或多个差异。

2 公平地说,此时的内存中表示可能没有具有第二个父对象。 git log代码包含一些用于简化历史记录的“父重写”代码,这也可能在此处触发。


结论

我通常会告诉人们,如果-p显示出奇怪的结果,他们应该添加git log以使其既绘制图形(还是粗略的ASCII近似值)并在行走时遵循图形拓扑通过提交,以便显示通常很关键的父子关系。不幸的是,当您使用否定符将图形的某些部分剪裁掉时,这可能会使图形绘制代码陷入绘制谎言中。可能应该将图形绘制代码显示为真实的多亲情况,因此被迫绘制最后一部分,如下所示:

git log

但事实并非如此,因此除非/直到有人可以将其添加到Git中,否则我们只需要提防此类情况。