我正在尝试编写一个对每次提交执行检查的脚本,对于该检查,我需要知道提交的父级。检查后,我按照与父提交相同的程序。
我的问题是我多次遇到相同的提交 - 所以除非我的存储库中有一个循环,否则我可能做错了。
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
类似的东西,但是断言会在一段时间后触发。
为什么我使用此方法两次遇到相同的提交?我的假设是不正确的,跟随提交的父母允许我遍历完整的历史记录?
答案 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
但提交A
是B
和D
的父级。所以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 8320b1d的Jeff King (peff
)(2019年4月4日)。
(由Junio C Hamano -- gitster
--在commit d9d65e9中合并,2019年4月25日)
rev-list --parents -- pathspec
:使用revision
来容纳改写的父母此修补程序修复了
prio_queue
中的二次列表插入 pathspec限制与rewrite_one()
结合使用。发生的事情是这样的:
- 我们看到一些
--parents
触及了路径,因此我们尝试重写其父级。commit X
永远循环,重写父级,直到找到相关的父级(或打到根并确定没有父级)为止。繁重的工作由rewrite_one()
完成,它使用process_parent()
放弃父母。try_to_simplify_commit()
将任何中间父级放入process_parent()
列表中,照常按提交日期插入。因此,如果
&revs->commits
是最近的,并且有很多历史没有涉及到路径,那么我们可能会向commit X
添加很多提交。
在最坏的情况下,按提交日期插入为&revs->commits
, 二次方。我们很久以前就在fce87ae中试图解决此问题(在rewrite_one中修复二次性能。,2008-07-12,v1.5.6.6)。
在这种方案中,我们将最旧的提交缓存在列表中。如果要添加的新提交较旧,则可以在此处开始线性遍历。这在实践中通常效果很好,因为父母比其后代年龄大,因此我们在遍历时倾向于添加越来越大的提交。但这并不能保证,实际上,有一个简单的情况并非如此:合并。
想象一下,我们看一下合并的第一个父对象,并且看到一个非常老的提交(假设3岁)。在第二个父级上,当我们回顾3年的历史时,可能会有很多提交。一次父级提交污染了我们最早提交的缓存;当我们穿越大量的历史时,它将保持最古老的历史 必须回到缓慢而线性的添加到列表中的方法。天真的,人们可能会想到,与其缓存最旧的提交,不如从最后添加的提交开始。但这只会使某些情况变得更快,而另一些情况会变得更慢(事实上,虽然它使真实世界的测试用例变得更快,但在此处的性能测试中却表现不佳)。
从根本上讲,这些只是试探法。我们最坏的情况仍然是二次方,有些情况会逼近这一点。相反,让我们使用具有更好的最坏情况性能的数据结构。
将O(n)
换成其他代码会对整个代码库产生影响,但是我们可以利用一个事实:对于revs->commits
情况,实际上没有人需要看到rewrite_one()
中的那些提交。直到我们完成了整个列表的生成。这给我们留下了两个明显的选择:
我们可以生成列表 unordered ,该列表应为O(n),然后对其进行排序,总计为
revs->commits
。这是下面的“O(n log n)
”。我们可以将提交插入到单独的数据结构中,例如优先级队列。这是下面的“
sort-after
”。我希望
prio-queue
是最快的(因为它可以节省我们 将项目复制到链接列表的额外步骤),但令人惊讶的是sort-after
似乎要快一些。以下是在整个网络中使用所有三种技术的新
prio-queue
的时间安排 与p0001.6
相比,存储库很少:master