首页 > 解决方案 > 编辑 git 历史的影响

问题描述

我们的一个存储库的状态非常糟糕。有人不小心将 4 GB 的二进制文件提交到 repo 并将其推送到远程 master。然后,他们说:“哎呀!” 并恢复了该提交。

不幸的是,git 只存储 diff,并且因为它不能真正存储二进制文件的 diff,所以它将整个文件存储在历史记录中。而且因为它在历史记录中包含了两次(一次是添加时,一次是删除时),所以现在 repo 的大小为 8 GB。这给我们带来了巨大的问题,并使我们的构建时间比他们需要的时间长了大约一个小时。

我知道我可以使用 rebase 和 filter-branch 之类的工具来删除这些提交或从 git 历史记录中删除这些文件。然而,关于这些工具的每一篇文章或文档都说“如果你想要编辑的提交已经被推送到远程,那么不要这样做!重写历史是一个坏主意!!!”

但它并没有真正解释改写历史的影响。我们真的别无选择——我们必须从历史记录中删除这些文件。但是,由于所有关于重写 git 历史的危险的可怕警告,我们非常害怕实际尝试删除这些文件。

因此,我希望有帮助的 StackOverflow 用户能够解释使用过滤器分支删除这些大文件可能产生的影响,或者是否有一些我们不知道的更好的解决方案。

标签: gitgit-rebasegit-filter-branchgit-history

解决方案


git 存储差异是一个常见的误解。它实际上存储了每个版本的全部内容*。事实上,整个 git 模型都是围绕源代码的保证位完美检索而构建的,这是基于差异的 VCS 无法实现的。

您可能已经对二进制文件进行了两次提交,或者您正在计算数据库中的副本和工作目录中的副本。

不过要回答你的核心问题。

Git 将数据存储为相互引用的对象集合。(请参阅Merkle 树)因为树和历史都是由引用其他对象的对象构建的,所以很难从 git 存储库中真正消除共享数据。

“重写历史”甚至有点用词不当,因为 git 从不重写历史,它只是返回并创建一个新的历史,然后指向那个新的历史。在垃圾收集之前,旧的东西可能会挂起几个月。一旦你开始分享它,在 git 的逻辑模型中,你重写的历史只是另一个 repo 实例上的另一个分支。

通常,分支会向前移动代码库,并且可以合并以将历史记录在一起。如果您有一个功能分支被调用feature1并将其合并到您的master分支中,那么不仅仅是代码成为 master 的一部分,所有提交也feature1成为 master 的一部分。当每个分支都是一段离散的代码时,这不是问题。

当您尝试重写历史时,它确实会成为一个问题。假设您按照您的建议进行操作,并使用 filter-branch 从历史记录中删除代码(尽管变基会更容易并且可能更安全,因为它是相当新的)。团队中的每个成员都会删除该分支的本地副本并签出新的副本。一切都很好,除了你正在处理 featureX,并且在错误发生后已经将 master 分支合并到其中,所以旧的 master 是你的 featureX 分支的一部分。featureX在and之间进行差异master将显示与旧 master 之间的差异相同的结果featureX,但所有这些提交仍然是featureX. 在 git 的大脑中,featureX在添加大文件时分支,当你将它合并到 master 时,featureX把一切都带回来。

所以这就是危险,即使一个人,在他们的任何分支的某个地方,仍然拥有历史中旧提交的副本,你最终不仅仍然拥有你想要摆脱的文件,而且还要处理完整的第二个历史版本。

如果您必须删除它,可以这样做,但您必须非常仔细地协调该过程以确保已清理存储库的每个实例。对于一个非常小的团队,这并不可怕,但是你的团队越大越分散,就越难。

*当它打包对象进行存储时,它确实做了一些巧妙的增量压缩工作,但总是以一种保证位完美重建的方式。Git 甚至会将整个历史记录中的一点不合适的地方检测为损坏的存储库。


推荐阅读