考虑这个简单的python代码,它演示了一个非常简单的版本控制设计:
def build_current(history):
current = {}
for action, key, value in history:
assert action in ('set', 'del')
if action == 'set':
current[key] = value
elif action == 'del':
del current[key]
return current
history = []
history.append(('set', '1', 'one'))
history.append(('set', '2', 'two'))
history.append(('set', '3', 'three'))
print build_current(history)
history.append(('del', '2', None))
history.append(('set', '1', 'uno'))
history.append(('set', '4', 'four'))
print build_current(history)
for action, key, value in history:
if key == '2':
print '(%s, %s, %s)' % (action, key, value)
请注意,通过使用历史记录列表,您可以在曾经存在的任何状态下重建当前字典。我认为这是一个“前向构建”(缺少一个更好的术语)因为要构建当前字典,必须从头开始并处理整个历史列表。我认为这是最明显和最直接的方法。
正如我所听到的,早期版本控制系统使用了这种“前向构建”过程,但它们并不是最佳的,因为大多数用户更关心构建的最新版本。此外,当用户只关心查看最新版本时,用户不希望下载整个历史记录。
我的问题是,在版本控制系统中存储历史记录还有哪些其他方法?也许可以使用“向后构建”?这可能允许用户仅下载最近的修订版而不需要整个历史记录。我还seen用于存储历史记录的几种不同格式,即:变更集,快照和补丁。变更集,快照和补丁之间有什么区别?
在现有的流行版本控件中,他们如何存储历史记录以及各种设计的优势是什么?
答案 0 :(得分:11)
你提到了这3种存储(文件) - 历史的方法:
Subversion在FSFS存储库中使用正向增量(也称为修补程序),在BDB存储库中使用向后增量。 请注意,这些实现具有不同的性能特征:
前进的增量在提交时很快,但在结账时很慢(因为 “当前”版本必须重建)
后向增量快速检出,但提交速度慢 必须构造增量来构造新的当前并将之前的“当前”重写为一堆增量
另请注意,FSFS使用"skipping delta"算法,可最大限度地减少重建特定版本的跳转。然而,这个跳过的delta不是像mercurials快照一样的大小优化;它只是最小化了构建完整版本所需的“修订”数量,无论总体大小如何。
这是一个包含9个版本的文件的小ascii艺术(从规范中复制):
0 <- 1 2 <- 3 4 <- 5 6 <- 7
0 <------ 2 4 <------ 6
0 <---------------- 4
0 <------------------------------------ 8 <- 9
其中“0 < - 1”表示修订版1的delta基础是修订版0。
N个版本的跳转次数最多为log(N)。
对FSFS的一个非常好的影响是旧版本只会写一次,之后只会通过进一步的操作来阅读。 这就是为什么subversion存储库非常稳定的原因:只要你的硬盘上没有硬件故障,即使上次提交中发生了一些损坏,你也应该能够获得一个工作存储库:你仍然拥有所有较旧的版本。
在BDB Backend中,您不断在checkins / commits上重写当前版本,这使得此过程容易出现数据损坏。此外,当您仅将全文存储在当前版本中时,破坏提交时的数据可能会破坏历史记录中的大部分内容。
答案 1 :(得分:8)
我认为颠覆在向后构建方面做了一些尝试。但我可以更好地解释我所知道的:Mercurial快照。
Mercurial使用正向构建方案。但是为了使每个修订版能够轻松地重建,有重新同步点:每次重建修订版所需的所有增量的大小都大于全文的两倍时,将存储全文本版本(压缩快照),所有后续增量都是相对于这个新快照计算的。
这意味着您永远不需要阅读超过文本大小3倍的内容来检索任何修订版本。
您可以找到更多详情in the Hg Book。
答案 2 :(得分:4)
作为一个更通用的答案,您需要将DVCS(集中式VCS,如CVS,SVN,Perforce,ClearCase等)与DVCS(Distributed VCS, like Git or Mercurial)区分开来。
它们涉及different workflows and usage。
特别是,CVCS 客户端与其服务器之间的数据交换将比使用DVCS(在推送或拉动所有回购时确实需要delta)更重要。
这就是为什么delta对于CVCS中的大多数操作非常重要,这对于某些操作以及DVCS中的不同原因来说非常重要。
Deltas在Eric Sink的两本书中有描述:
存储库=文件系统*时间
树是文件夹和文件的层次结构。三角洲是两棵树之间的差异。理论上,这两棵树不需要相关。然而,在实践中,我们计算它们之间差异的唯一原因是因为其中一个来自另一个。一些开发人员从树N开始并进行了一次或多次更改,从而生成树N + 1。
我们可以将delta视为一组变化。实际上,许多SCM工具正是为了这个目的而使用术语“变更集”。变更集只是表示两棵树之间差异的变化列表。
delta意义很重要(参见this thread):前向增量或反向增量。
某些SCM工具使用某种折衷设计。在一种方法中,我们不是仅存储一个完整的树并将每个其他树表示为三角形,而是沿途再撒上几棵树。
您可以在Eric Raymond's Understanding Version-Control Systems中看到“旧”VCS的演变。
许多现代版本控制工具使用二进制文件增量进行存储库存储 一种流行的文件增量算法称为vcdiff 它输出已更改的字节范围列表。这意味着它可以处理任何类型的文件,二进制文件或文本。作为辅助优势,vcdiff算法同时压缩数据。
不要忘记,增量管理也会对为表示历史记录而创建的Directed Acyclic Graphs (DAGs)产生影响(请参阅“Arrows direction in ProGit book”和inconvenient behind DAG)。
您可以找到有关delta管理的细节:
Veracity支持两种DAG:
树DAG保留文件系统中目录结构的版本历史记录。 DAG的每个节点代表整个树的一个版本。
数据库(或“db”)DAG保留数据库的版本历史记录或记录列表。 DAG的每个节点代表完整数据库的一个状态。
最后几点说明VCS的第三代(第四代?)必须处理不仅分发文件(树)而且分发数据库(用于各种目的)