Git的包文件是否是快照而不是快照?

Nat*_*ong 65 git version-control internals

Git与大多数其他版本控制系统之间的主要区别之一是,其他人倾向于将提交存储为一系列增量 - 一次提交与下一次提交之间的更改集.这似乎是合乎逻辑的,因为它是存储提交的最小可能信息量.但是,提交历史记录越长,比较修订范围所需的计算就越多.

相比之下,Git 在每个版本中存储了整个项目完整快照.这并没有使回购规模显着每个提交成长的原因是项目中的每个文件存储在Git的子目录中的文件,名为对其内容的哈希值.因此,如果内容未更改,则散列未更改,并且提交仅指向同一文件.还有其他优化.

所有这些对我来说都是有意义的,直到我偶然发现有关包文件的信息,Git定期将数据放入其中以节省空间:

为了节省空间,Git使用了packfile.这是一种格式,其中的Git只会保存在第二个文件已经改变,它的指针是类似文件的一部分.

这基本上不会回到存储增量吗?如果没有,它有什么不同?这如何避免Git遇到其他版本控制系统遇到的相同问题?

例如,Subversion使用增量,回滚50个版本意味着撤消50个差异,而使用Git,您可以获取适当的快照.除非git还在packfiles中存储50个差异...是否有一些机制说"在经过一些少量的增量后,我们将存储一个全新的快照",这样我们就不会堆积太大的变更集?Git还有什么可以避免增量的缺点?

Chr*_*sen 67

简介:
Git的包文件经过精心构建,可有效地使用磁盘缓存,并为常用命令和读取最近引用的对象提供"漂亮"的访问模式.


Git的包文件格式相当灵活(见文档/技术/包,format.txt,或将打包文件Git的社区图书).pack文件以两种主要方式存储对象:"undeltified"(获取原始对象数据并对其进行deflate-compress)或"deltified"(与某些其他对象形成delta,然后对生成的delta数据进行deflate-compress).存储在包中的对象可以是任何顺序(它们(不一定)必须按对象类型,对象名称或任何其他属性进行排序),并且可以对相同类型的任何其他合适对象进行分层对象.

Git的pack-objects命令使用几种启发式方法为常用命令提供出色的引用局部性.这些启发式控制既可以为分层对象选择基础对象,也可以控制对象的顺序.每种机制大多是独立的,但它们共享一些目标.

Git确实形成了delta压缩对象的长链,但是启发式方法试图确保只有"旧"对象位于长链的末端.delta基本缓存(其大小由core.deltaBaseCacheLimit配置变量控制 )会自动使用,并且可以大大减少需要读取大量对象(例如git log -p)的命令所需的"重建"次数.

Delta压缩启发式

典型的Git存储库存储了大量的对象,因此无法合理地将它们全部进行比较以找到将产生最小增量表示的对(和链).

增量基础选择启发式基于以下思想:在具有相似文件名和大小的对象中找到良好的增量基础.每种类型的对象都是单独处理的(即一种类型的对象永远不会被用作另一种类型的对象的delta基础).

出于delta base selection的目的,对象(主要)按文件名排序,然后按大小排序.进入此排序列表的窗口用于限制被视为潜在delta基础的对象数.如果在其窗口中的对象中没有找到对象的"足够好"的1增量表示,则该对象将不被增量压缩.

窗口的大小由--window=选项 git pack-objectspack.window配置变量控制.增量链的最大深度由--depth= 选项git pack-objectspack.depth配置变量控制.该--aggressive的选择git gc大大扩大双方的窗口尺寸和最大深度尝试创建一个较小的包文件.

文件名排序将具有相同名称(或至少类似的结尾(例如.c))的条目的对象聚集在一起.大小排序从最大到最小,因此删除数据的增量优先于添加数据的增量(因为删除增量具有较短的表示),因此较早的较大对象(通常较新)往往用纯压缩表示.

1 什么称为"足够好"取决于所讨论的物体的大小及其潜在的三角洲基数以及其产生的三角链的深度.

对象排序启发式

对象以"最近引用的"顺序存储在包文件中.重建最近历史记录所需的对象放在包中较早的位置,它们将靠近在一起.这通常适用于OS磁盘缓存.

所有提交对象按提交日期(最近的第一个)排序并存储在一起.此放置和排序优化了遍历历史图表并提取基本提交信息(例如git log)所需的磁盘访问.

树和blob对象从第一次存储(最近)提交的树开始存储.每个树都以深度优先的方式处理,存储任何尚未存储的对象.这将所有需要的树和blob放在一起重建最近的提交.任何尚未保存但后续提交所需的树和blob将按照排序的提交顺序存储在下一个.

最终对象排序受到delta基本选择的轻微影响,因为如果为delta表示选择了一个对象并且其基础对象尚未存储,则其基础对象将立即存储在deltified对象本身之前.这可以防止由于读取基本对象所需的非线性访问而导致的磁盘高速缓存未命中,该基础对象稍后将"自然地"存储在包文件中.


Gre*_*ill 7

在包文件中使用增量存储只是一个实现细节.在那个级别,Git不知道为什么或如何从一个版本更改为下一个版本,而只是知道blob B非常类似于blob A,除了这些更改C.因此它只会存储blob A并更改C (如果它选择这样做 - 它也可以选择存储blob A和blob B).

从包文件中检索对象时,增量存储不会向调用者公开.呼叫者仍然看到完整的blob.因此,Git的工作方式与没有增量存储优化的情况一样.

  • 压缩时blob之间的关系可能与修订历史建议/隐含的关系无关. (12认同)
  • Git*确定*限制可能需要应用多少增量来重新生成一个对象(并且btw delta表示对于树和blob一样有用),这是`git pack-objects`的`--depth`参数.一些看看实际得到的包装也表明Git要求delta非常小,因为它值得使用而不是压缩blob. (5认同)
  • 所有这些对大多数其他版本控制系统存储的增量同样如此...... (4认同)
  • 我知道这不会暴露给调用者,但是,为了检索给定的散列,Git可能必须采用blob并应用变更集; 它没有真正的快照,对吧?那么在这一点上它与SVN的区别在于它是不是必须堆积大量的增量才能到达历史中的特定点,因为它限制了blob上堆栈的增量数量? (2认同)
  • 我不确定它与SVN有什么不同,因为我不确定SVN是如何工作的.但我可以指出,它与Darcs(以及Darcs补丁理论)的不同之处在于包文件与git如何合并分支无关.Git重建了它合并的修订版的快照,它还重建了需要合并的任何旧修订版的快照,然后找出要做的事情.另一方面,Darcs存储补丁,并在需要工作目录时将它们组合在一起,并且可以根据不同的补丁集创建不同的工作目录. (2认同)