在脚本访问中通过提交父级跟踪Git历史记录两次同一提交

时间:2018-01-25 12:32:32

标签: git python-3.x

我正在尝试编写一个对每次提交执行检查的脚本,对于该检查,我需要知道提交的父级。检查后,我按照与父提交相同的程序。

我的问题是我多次遇到相同的提交 - 所以除非我的存储库中有一个循环,否则我可能做错了。

import subprocess

def parents(rev):
  args = ['git', 'rev-list', '--parents', '-n', '1', rev]
  output = subprocess.check_output(args, stderr=subprocess.PIPE).decode()
  items = output.split()
  return items[1:]  # First SHA is the ID of the revision that we passed into the command

revisions = parents('HEAD')
visited = set()
while revisions:
  rev = revisions.pop()
  assert rev not in visited, rev
  visited.add(rev)
  print(rev)  # TODO: Do check on commit
  revisions += parents(rev)

我希望这会打印出与git rev-list HEAD类似的东西,但是断言会在一段时间后触发。

为什么我使用此方法两次遇到相同的提交?我的假设是不正确的,跟随提交的父母允许我遍历完整的历史记录?

2 个答案:

答案 0 :(得分:2)

您看到的行为是git rev-list --parents命令固有的行为。考虑一个如下所示的存储库:

A--B--C
 \   /
   D

git log --oneline的输出可能是:

0000004 (HEAD -> master) Merge branch "mybranch"
0000003 B
0000002 D
0000001 A

但提交ABD的父级。所以B

$ git rev-list --parents -n1 B
0000003 0000001

对于D

$ git rev-list --parents -n1 D
0000002 0000001

您会看到提交A两次列出,这正是您在问题中触发问题的原因。

根据您尝试做的事情,最简单的解决方案可能是迭代git rev-list HEAD的输出,这只会列出一次提交。

答案 1 :(得分:0)

注意:在Git 2.22(2019年第二季度)中,git rev-list --parents仍将多次访问相同的提交,但会更快地完成访问,因为对“ {{1 }}”。

请参见commit 8320b1dJeff King (peff)(2019年4月4日)。
(由Junio C Hamano -- gitster --commit d9d65e9中合并,2019年4月25日)

  

rev-list --parents -- pathspec:使用revision来容纳改写的父母

     

此修补程序修复了prio_queue中的二次列表插入   pathspec限制与rewrite_one()结合使用。

     

发生的事情是这样的:

     
      
  1. 我们看到一些--parents触及了路径,因此我们尝试重写其父级。
  2.   
  3. commit X永远循环,重写父级,直到找到相关的父级(或打到根并确定没有父级)为止。繁重的工作由rewrite_one()完成,它使用process_parent()放弃父母。
  4.   
  5. try_to_simplify_commit()将任何中间父级放入process_parent()列表中,照常按提交日期插入。
  6.   
     

因此,如果&revs->commits是最近的,并且有很多历史没有涉及到路径,那么我们可能会向commit X添加很多提交。
  在最坏的情况下,按提交日期插入为&revs->commits,   二次方。

     

我们很久以前就在fce87ae中试图解决此问题(在rewrite_one中修复二次性能。,2008-07-12,v1.5.6.6)。
  在这种方案中,我们将最旧的提交缓存在列表中。如果要添加的新提交较旧,则可以在此处开始线性遍历。这在实践中通常效果很好,因为父母比其后代年龄大,因此我们在遍历时倾向于添加越来越大的提交。

     

但这并不能保证,实际上,有一个简单的情况并非如此:合并。
  想象一下,我们看一下合并的第一个父对象,并且看到一个非常老的提交(假设3岁)。在第二个父级上,当我们回顾3年的历史时,可能会有很多提交。一次父级提交污染了我们最早提交的缓存;当我们穿越大量的历史时,它将保持最古老的历史   必须回到缓慢而线性的添加到列表中的方法。

     

天真的,人们可能会想到,与其缓存最旧的提交,不如从最后添加的提交开始。但这只会使某些情况变得更快,而另一些情况会变得更慢(事实上,虽然它使真实世界的测试用例变得更快,但在此处的性能测试中却表现不佳)。
  从根本上讲,这些只是试探法。我们最坏的情况仍然是二次方,有些情况会逼近这一点。

     

相反,让我们使用具有更好的最坏情况性能的数据结构。
  将O(n)换成其他代码会对整个代码库产生影响,但是我们可以利用一个事实:对于revs->commits情况,实际上没有人需要看到rewrite_one()中的那些提交。直到我们完成了整个列表的生成。

     

这给我们留下了两个明显的选择:

     
      
  1. 我们可以生成列表 unordered ,该列表应为O(n),然后对其进行排序,总计为revs->commits。这是下面的“ O(n log n)”。

  2.   
  3. 我们可以将提交插入到单独的数据结构中,例如优先级队列。这是下面的“ sort-after”。

  4.   
     

我希望prio-queue是最快的(因为它可以节省我们   将项目复制到链接列表的额外步骤),但令人惊讶的是   sort-after似乎要快一些。

     

以下是在整个网络中使用所有三种技术的新prio-queue的时间安排   与p0001.6相比,存储库很少:

master