我今天想知道 git 如何确保其元数据的完整性,我对遇到的情况感到有点惊讶。我使用以下简单设置进行测试:
- 两个工作存储库,分别称为
x
和y
- 一个裸存储库,称为
xy.git
因此,最初x
和y
正在推送和拉取x.git
,一切正常。现在,假设其中一个元数据对象(.git/objects/...
)x.git
因某种原因(选择您最喜欢的随机事件)损坏。
我原本以为下次推送或拉取时会出现问题,但令我惊讶的是,一切似乎都运行正常。更多提交,更多推送和拉取,都没有问题。第一次报告损坏是当我尝试从裸存储库克隆另一个工作存储库时,我的克隆处于不可用状态。
现在我觉得情况还不算太糟,因为得益于 git 的架构,在最坏的情况下,我只需转储裸存储库,然后使用我的一个工作集的所有历史记录重新创建它。但事实并非如此。没有任何通知,损坏的文件已经通过拉取进入工作存储库,因此也无法从中克隆新的裸存储库。
这种情况不仅当我从裸存储库中的损坏文件开始时会发生,还可以通过这种方式在裸存储库中引入工作存储库中的损坏文件。
当然,也许可以通过其他方式修复此问题,但我仍然感到惊讶(也有点担心),似乎很容易就弄乱了存储库,让使用它的每个人都能发现。尤其是因为错误可能在下次有人尝试克隆之前不会被注意到。难道不应该在某个地方、以某种方式对此进行检查吗?
这里有谁愿意尝试一下是否可以重现?我尝试了 git 版本 2.7.4。
非常欢迎任何关于如何防止此类腐败的建议。
答案1
我原本以为下次推拉时会出问题,但出乎意料的是,一切似乎都运行正常。更多提交,更多推拉,没有问题。
每个对象(文件、提交等)都以其内容的 SHA1 哈希值(加上一个小标题)命名。每当将单个对象读入内存以供使用时,都会对数据进行哈希处理并将其与对象的名称进行比较;任何不匹配都会导致显示错误。
然而,大多数操作不需要将整个存储库读入内存。一般来说,所有命令都只读取所需的最少内容——当然,你会如果您尝试签出损坏的提交或与之不同的提交,就会注意到这个问题,但创建提交等操作根本不关心以前的对象。即使推送也只需要选择一小部分对象(作为“精简”包的增量基础),因为双方都知道对方已经拥有什么。
(此优化是基于快照的布局的直接结果。例如,git 添加不需要与旧文件进行差异化,因为它只是在运行过程中构建新的快照。然后git 提交将此快照在不知情的情况下转换为提交/树对象任何事物除了 ID 之外,还有关于前一次提交的所有信息。)
这种情况不仅当我从裸存储库中的损坏文件开始时会发生,还可以通过这种方式在裸存储库中引入工作存储库中的损坏文件。
首先,请记住,同一台计算机、同一文件系统的克隆不会打包和传输对象 - 它只是硬链接文件,以节省空间和时间。您必须明确选择退出file://
通过从URL 而不是简单路径进行克隆来实现这一点。
然而,通过 SSH 或 HTTPS(或前面提到的 file:// URL)进行的克隆实际上会读取和写入对象数据以构建传输包,因此任何本应是传输一部分的损坏对象将要中止该进程。
如果你设法将损坏的对象推送到偏僻的服务器——它同时通过了本地打包和远程解包——这有点不寻常(特别是在2013 年 git.kde.org 故事) 并且我会在 Git 邮件列表中提出这一担忧。
(不要担心文档中提到transfer.fsckObjects
默认情况下禁用 - 它仅禁用验证对象结构和语法,而不是哈希验证。)
难道不应该在某个地方、以某种方式对此进行检查吗?
可以使用以下方式手动进行全面检查:git fsck
命令。最好在您的“中央”存储库上执行 cronjob。完整检查不是自动的,因为除了最小的 Git 存储库外,在每次提交/推送/拉取/任何操作时重新检查完整存储库会花费不合理的时间。
A部分的每当 git 决定运行git gc --auto
后台维护进程时,check 都会隐式发生。此维护会读取所有最近创建的“松散”对象并将它们归档到 .pack 文件中,因此这些对象的验证是免费的。(但是,它不是按照预设的时间表运行,而是只要您拥有的松散对象数量超过设定的限制,它就会运行。)