首页 > 解决方案 > git标签的存储效率低吗?

问题描述

我想知道 git 标签的存储是否效率低下。

我认为标签只是指向变更集的“指针”,就磁盘使用而言,它应该非常有效且很小。

但是,使用我新的 git 存储库:

  1. 推送所有分支 (8) 和所有变更集 (20489) 总共需要 ~ 110MB (在 Gitlab 中显示)
  2. 推送不添加任何额外变更集(仍然是 20489)的所有标签(1444)突然需要大约 150MB

这很奇怪。没想到仅仅因为“指针”就有如此巨大的增长。

有没有人有任何线索或可能的解释?

谢谢

标签: git

解决方案


TL;博士

标签通常非常有效。正如ElpieKay 在评论中总结的那样,您必须有一些对象——可能是提交,但任何对象都可以——可以从标签访问,但不能从分支访问。

标签——无论是轻量级的还是带注释的;我们稍后会区分它们——指向任意 Git 对象,而不是变更集。当我们说到这里时,我们真正的意思是包含以下内容的哈希 ID:所有 Git 对象都有一个哈希 ID,作为它们的“真实名称”,它作为所有 Git 对象的键值存储中的键

在这个主 Git 数据库中有四种类型的对象。它们是提交blob带注释的标记对象。提交充当快照,但它们本身仅包含少量元数据,包括提交者的姓名和电子邮件地址以及时间戳;提交的提交的哈希 ID、日志消息和存储的对象的哈希 ID。树对象是最终通过子树和 blob 对象提供快照的对象。

名称——Git 称之为引用引用——落入各种命名空间。两个大的是分支名称like ,实际上在名称空间 ( ) 中,以及标签名称like ,实际上在名称空间 ( ) 中。即使名称拼写相同,名称空间也可以防止名称发生冲突。每个名称都包含一个哈希 ID,名称到哈希 ID 键值存储是构成 Git 存储库的另一个主要数据库。masterrefs/heads/*refs/heads/masterv1.2refs/tags/*refs/tags/v1.2

分支名称被限制为仅指向提交对象。标签名称可以直接指向提交对象。这样的标签称为轻量级标签。或者,标签名称可能指向带注释的标签对象。该对象本身指向其他(任意)对象,尽管标记名称指向提交是非常典型的。指向带注释标记对象的标记名称是带注释标记

对象数据库形成有向无环图

提交包含(即指向)其他提交哈希 ID。任何对象一旦生成就无法更改,任何对象的哈希 ID 都无法预测。1 因此,新的提交只会指向现有的提交。每个提交也被赋予一个唯一的散列 ID(即,没有提交超过一次)。这意味着提交图本身,通常一次增长一个提交,从来没有任何周期:所有提交箭头“向后指向”之前的提交。

提交还包含树哈希 ID,树包含更多树哈希 ID 以及 blob 哈希 ID。这些也是有向的和非循环的,尽管树哈希 ID 不必是唯一的(例如,两个不同的提交可以共享同一个快照)。

带注释的标记对象可以包含任何其他对象的 ID,但与提交一样,带注释的标记对象具有唯一的哈希 ID,并且只允许指向现有对象。所以这些同样不会在图中添加循环。


1哈希 ID 是对象内容的加密校验和,包括对象的类型。从技术上讲,如果您在该问题上花费了足够的计算能力,则可以预测或故意产生哈希冲突。然而,Git 也以其他方式禁止循环。


名称数据库充当 DAG 的入口点,允许垃圾收集

结果是,如果我们在存储库中选择任何对象,我们可以从该对象跟踪到所有可访问的其他对象并获得一个子图。如果我们使用名称数据库(分支和标签名称以及所有其他 Git 引用——有些特别狡猾,例如存储在索引中的 blob 哈希 ID)作为我们进入对象数据库的入口点,并将所有可访问的对象涂成绿色暂时,我们可以让 Git 遍历整个对象数据库并丢弃任何无法访问的对象(然后删除颜色,它在 Git 中实际上保存在内存中,而不是在磁盘上)。

然而,可到达的对象集取决于我们使用的名称!如果我们省略所有标签名称,我们可能会有一些对象——通常是一些提交链——否则无法访问。

仅获取和推送副本可到达的对象

作为一般规则,git fetch并且git push- 以及运行的初始提取 -git clone仅复制那些从正在使用的名称访问的对象。传输中涉及的两个 Git 实例有一个初始对话,其中每个 Git 在仔细阅读一组名称/ID 对后告诉对方它拥有和/或想要哪些哈希 ID。2 发送和接收 Git 实例根据需要遍历对象 DAG,以确定完成这些名称/ID 对需要哪些对象。然后发送者发送对象;3接收 Git 将这些对象添加到其对象数据库中,传输完成。

在您的情况下,这意味着某些对象只能通过标签访问,这使得推送显着更大。找到这些对象可能有点棘手——Git 有用于此的低级工具(例如git rev-parsegit branch --contains),但没有任何东西被干净地打包为面向用户的解决方案。


2新的有线协议(v2——旧的 v0 与 v1 相同)——改变了名称/ID 对的列出方式,因为事实证明,在某些存储库中,名称数据库已经增长到像 v0 一样,每次都简单地列出所有内容需要太长时间。

3发送方 Git 通常利用其对接收方 Git 对象数据库中内容的了解(由接收方必须拥有的哈希 ID 确定)来构建一个精简包,其中发送方的对象与接收方已有的对象进行 delta 压缩。请参阅下面的压缩。


关于压缩的旁白

这两个键值数据库都以多种不同的方式存储在 Git 中。对象数据库中的对象可以存储为松散的,它们是 zlib-deflate 但独立的,也可以是打包的,它们与其他对象进行增量压缩。Delta 链的行为类似于变更集,但有一个关键的区别——嗯,对实施者来说至关重要;用户根本不必关心它!——这里:至少在理论上,任何对象都可以被压缩到任何其他对象,甚至是不同类型的对象。(实际上,无论如何,Git 只针对相同类型的对象压缩对象。)即使针对 blob 压缩 blob,也不需要某些文件与以前的文件相比是 delta同一个文件的版本:它可能是同一个文件的未来版本的增量,或者是不同文件的当前版本,或者其他。

包文件通常是自包含的:在包文件中进行增量压缩的对象必须在同一个包文件内提供链中的下一个对象,一直到本身不是增量压缩的基础对象。故意违反这个假设的瘦包装git fetchgit push薄包装的接收者有义务“修复”它(git index-pack --fix-thin)或以其他方式纠正问题。但所有这些也只是内部细节。


推荐阅读