如何存储和计算版本控制历史记录?

时间:2012-01-11 18:26:03

标签: python git svn version-control mercurial

考虑这个简单的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用于存储历史记录的几种不同格式,即:变更集,快照和补丁。变更集,快照和补丁之间有什么区别?

在现有的流行版本控件中,他们如何存储历史记录以及各种设计的优势是什么?

3 个答案:

答案 0 :(得分:11)

你提到了这3种存储(文件) - 历史的方法:

  1. 补丁:补丁是(通常是文本的,但也可以是二进制补丁)表示两个文件之间的差异。它是unix命令 diff 的输出,可以通过unix命令 patch 应用。许多版本控制系统都使用补丁来存储文件的历史记录(例如SVN,CVS,GIT ..)。有时,这些补丁在技术上被称为“delta”,希腊字母"Δ"描述了两件事的不同。
  2. 变更集:变更集是将“属于一起”的变更合并到单个实体中的不同文件的术语。并非所有版本控制系统都支持变更集(最值得注意的是CVS和SourceSafe)。开发人员使用变更集来避免破坏的构建(例如:在一个文件中更改方法的签名,在第二个文件中更改调用。您需要同时进行两个更改才能运行程序,否则会出错)。 See also here for the difference between changeset and patch
  3. 快照:此文件/文件系统的状态的完整副本到此时间点。它们通常非常大,其使用取决于性能特征。快照始终是补丁列表的冗余,但要更快地检索信息,有时版本控制系统会混合或组合补丁和快照
  4. 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的第三代(第四代?)必须处理不仅分发文件(树)而且分发数据库(用于各种目的)