【问题标题】:Dirty trick to keep commit hashes when rewriting Git history?重写 Git 历史记录时保持提交哈希的肮脏技巧?
【发布时间】:2020-10-05 08:13:58
【问题描述】:

注意:有一个类似的问题How to keep commit hashs not change when use git filter-repo rewrite the history,但答案集中在 Git 无法做到这一点。在这个问题中,我想探讨理论上是否可以编写一个自定义脚本来保留提交哈希。

Git filter-branchBFG Repo-Cleaner 是从回购历史中删除大文件和其他内容的两个流行工具。它们导致不同的提交 SHA / 哈希,这就是 Git 的工作方式,因为它“指纹”了提交的内容、其父级等。

但是,我们遇到了不幸的大文件提交发生在不久前的情况,并且我们有各种对较新提交的引用,例如在 GitHub 问题(“查看提交 f0ec467”)和其他外部系统中。如果我们使用 filter-branch 或 BFG,很多东西都会坏掉。

所以我来这里询问是否有一些肮脏的低级技巧如何即使对于重写的提交也保留提交 ID/SHA-1。我想对于我们想要重写的错误提交,自定义脚本将创建一个 新的 Git 对象,但“硬编码”相同/旧的 SHA-1,跳过它的计算。我认为较新的提交(它的孩子/后代)应该继续工作(?!)。

如果这不起作用,我想了解为什么。 Git 是否会定期检查哈希值是否与实际内容一致?它是否只在某些操作期间这样做,例如 gc 或 push 或 pull?

(我知道这是一块非常薄的冰,我只是在技术上探索我们的选择,然后我们才能接受我们将永远在我们的存储库中拥有一个大型二进制文件,这会带来所有影响,比如永远拥有更大的备份、完整的克隆需要更长的时间等)


更新:现在有一个公认的答案,但同时,没有答案提到git replace,这可能是解决这个问题的方法吗?我做了一些基本的实验,但还不确定。

【问题讨论】:

  • 唯一的办法就是破解SHA-1算法。见a related question
  • 也许可以hack Git 来实现你想要的,但我不推荐它。 Git 强制为重写的提交创建一个新的 SHA-1 以用于审计目的,因此很明显那里已经完成了一些工作。
  • @TimBiegeleisen 你知道它的任何细节吗?我还猜想它会在 some 情况下导致 some 问题,但我想知道 Git 何时验证 SHA-1 实际上是正确的。
  • "如果我们使用 filter-branch 或 BFG,很多东西都会坏掉。" 他们会吗?大概没有代码会破坏,只是 cmets 和问题中的引用。
  • 您试图实现的正是像 SHA-1 这样的哈希函数旨在不允许您做的事情。因此,您尝试解决的问题比重写历史时可能遇到的问题要困难得多。找到在新旧提交哈希之间构建匹配表的最佳方法,您的临时问题得到“解决”

标签: git


【解决方案1】:

我在评论中加入了link,但实际上,破坏 SHA-1 并没有多大帮助。

问题在于 Git 通过比较对象哈希 ID 来交换对象。这些目前是 SHA-1(有关未来的一些可能性,请参阅另一个问题及其答案)。如果您设法破坏 SHA-1,并生成一个生成相同哈希 ID 的新输入对象,您可以:

  • 从 Git 的对象数据库中提取旧对象,然后
  • 将新对象插入到您的 Git 数据库中

从那时起,你的 Git 将只看到新对象,而不是旧对象。但是,当您将您的 Git 连接到其他 Git,并且您的 Git 对其他 Git 说:我有对象 a123456...,您愿意吗?其他 Git 可能只会回答:不谢谢,我已经有了。 当然,他们有的。所以你让你的 Git 与他们的 Git 不兼容,但没有从中获得任何好处。

如果另一个 Git 没有有问题的对象,那么你就可以了!他们会要你的副本,你可以把它交给。

提交和标记对象在其中有一些空间来存储一些任意(并非完全任意)的用户数据。这是您放置扰动数据以破坏 SHA-1 的地方。树对象不太友好,但只要你可以对提交和标记对象做你需要做的事情,你就可以绕过这个。

至于哪来的算力,嗯,一大群树莓派电脑的价格要降了....

编辑:我忘了回答这个问题:

Git 是否定期检查哈希是否与实际内容一致?

是的。事实上,它每次通过哈希 ID 提取对象时都会执行此检查。请记住,大部分存储库是对象数据库,它是一个简单的key-value store。键是哈希 ID,存储在该键下的数据代表对象。 Git 使用密钥进行查找,然后验证存储的数据哈希到该密钥,以确保存储的数据没有被磁盘或内存错误损坏。

【讨论】:

  • 这是关于交换的一个很好的观点。我想我正在寻找的东西需要 Git 以某种方式直接支持,例如,“将大文件移动到 LFS 同时保持提交哈希以某种方式”将需要成为受支持的场景。我认为理论上可行,但现在不行,这是肯定的。
  • 关于另一点——为什么要把所有的计算都花在寻找碰撞上?难道不能只用正确的哈希创建一个 Git 对象文件吗? (我知道通过 Git 客户端是不可能的,但是对象使用记录在案的文件格式,所以我看不出无法手动创建此类对象的原因。)
  • 对象的哈希是组成对象的字节的 SHA-1 校验和。计算几个字节串的 SHA-1。看看你是否能想出一种方法来计算目标 SHA-1:你需要什么字节来提供它?如果您能为此提出快速算法,那么您已经破坏了 SHA-1。
  • 使用git replace 很好,只是要注意它的局限性:它的工作方式是当Git 将要查找散列为X 的对象(对于任何X)时,它首先检查查看 refs/replace/X 是否存在。如果是这样,Git 会查找 refs/replace/X 映射到的哈希 ID。 (使用git --no-replace-objects 来避免这种情况发生。)有两个主要缺点:(1)一个非常大的 refs/replace/* 命名空间最终会建立并使 Git 变慢。 (2) clone 不会复制这些名字,所以新的 clone 没有替换。
  • 这些缺点往往会导致长期使用替代品来解决您正在考虑的事情。解决这两个问题的方法可能会很有用。您需要进行试验并最终说服 Git 维护人员采用您认为有用的任何结果。
【解决方案2】:

提交 ID 包含其父项的提交 ID。这意味着如果两个提交具有相同的 ID,Git 不仅知道这两个提交相等,而且知道它们的整个历史记录是相等的。 这是 Git 工作方式的基础,尤其是推拉。搞砸了,后果自负。

你可以用 git-replace 做一些聪明的事情,但我没有这方面的经验。

如果这不起作用,我想了解原因。 Git 是否会定期检查哈希值是否与实际内容一致?它是否只在某些操作期间这样做,例如 gc 或 push 或 pull?

git gc 可能有问题,但git fsck 会失去理智。您永远无法修复损坏的存储库。就像torek says 一样,新旧存储库之间的推送和拉取会变得非常混乱。


我建议保留原始存储库的副本以供参考。当您找到对旧 ID 的引用时,您仍然可以查找它。如果您明智地重写它们以引用新存储库中的等效提交,最终您将不再需要旧的存储库。

您可以通过搜索十六进制字符串、检查它们是否匹配提交 ID 并将其替换为新的提交 ID 来加快此过程。可以通过在两个存储库上运行 git log --pretty='format:%H' 并一对一比较它们来获得旧到新的映射。


更新

如果您真的非常需要这些 Github 链接,您可以编写一个 http 代理,将 https://github.com/your-org/your-repo/commit/oldcommitid 重定向到 https://github.com/your-org/your-repo/commit/newcommitid

【讨论】:

  • 这些提交也可以在 GitHub 上围绕它们进行讨论,如果过去发生的时间足够远(这是我们的情况),从头开始重新创建所有内容只是一个非常具有破坏性的事件。我同意你的所有观点?。我知道我们会玩火。
猜你喜欢
  • 2020-11-17
  • 2015-04-20
  • 1970-01-01
  • 2020-05-30
  • 2010-12-02
  • 2010-09-11
  • 2017-04-14
  • 1970-01-01
  • 2015-10-19
相关资源
最近更新 更多