Git:如何处理具有共享历史记录的文件副本?

Git: How are copies of a file with a shared history handled?

我将 CSS 用户样式备份到 git 存储库,如下所示:

❯ fd                                                                                            
stylus-2021-05-18.json
stylus-2021-05-20.json

这些备份文件显然大部分是相同的,即stylus-2021-05-18.jsonstylus-2021-05-20.json的过去历史。 git 是如何处理的?

显然,我可以将文件重命名为 stylus.json 并让 git 完全处理版本控制,但我想知道 git 是否足够聪明,可以与自动生成这些文件。

纯粹从技术角度来看很容易:如果 git 历史记录中的两个文件曾经具有完全(逐字节)相同的内容,那么它们将引用 the same blob object*,实际内容只会存储一次。因此,如果您当前的 fileA 版本与 2 次提交前的 fileB 相同,那么它们仍然只会在 .git 子目录中存储一次。无论文件是否具有不同的名称,是否在同一提交或其他提交或不同路径上,这都有效:只要内容相同,blob 将被重用。

另一方面:如果这种情况发生得太频繁,则表明您正在以一种并非真正打算使用的方式使用版本控制:给定的提交不应包含任何“历史数据”或“存档”:这就是其他 commits/tags/branches 的用途。任何给定分支的 HEAD 应该完全(且仅)包含当前与该分支相关的内容。但这部分在技术上不是必需的:它只是关于通常如何使用 git 的约定。

* 请注意,这种重用甚至会进入目录级别,即如果两个目录包含相同的子目录和文件,它们将引用相同的树对象。这使得存储“非常相似”的提交非常有效:实际上只有差异需要额外存储。请注意,提交仍然是 snapshots and not diffs.

TL;DR

提交始终创建为完整文件快照,但垃圾收集会创建提交包,它使用差异压缩有效地存储相似的 blob,无论它们是否来自同一文件。

简介

我对 Git 存储“差异”而不是完整文件的理解完全错误。在完成一些阅读和一些实验之后,我发现修改文件或创建文件副本并不重要,当您提交更改或新文件时,Git 会创建一个全新的 blob , 每一次.

但是,这是非常低效的,因为您最终会得到同一文本的许多不同副本,并且 blob 之间的差异很小。当 Git 创建包时,该问题得到解决。我不完全理解 Git 如何搜索要打包的东西,但在一个包中,它将一些 blob 存储为整个 blob,而另一些存储为与其他 blob 的差异。

实验

# create a big file and commit it
seq 1 1000000 | shuf > bigfile
git add bigfile
git commit -m'bigfile'

此时,find .git -ls 显示了一个大 blob (3.5MB) 存储了这个 6.9MB 的文件。

# modify the big file and commit the change
echo change >> bigfile
git commit -m'modify bigfile' bigfile

此时,find .git -ls 向我展示了 两个 大块,每个大约 3.5MB。对我来说似乎效率很低,但请继续阅读...

# Add another big file, similar to the first one, and commit it
cp bigfile bigfile2
echo some trivial change >> bigfile2
git add bigfile2
git commit -m'bigfile2'

事情没有好转:find .git -ls 向我展示了 三个 个大块,每个大约 3.5MB!

现在,在您推送时的某个时刻,Git 可能会打包您的沙箱,但我们现在可以强制执行此操作:运行 git gc。正如我错误地认为的那样,这不仅仅是垃圾收集,它还在创建提交包。在 运行ning git gc 之后,find .git -ls 现在报告单个包大约 3.2MB。因此,我的三个大 blob 被识别为相似、压缩更好且存储有效。我认为这叫做“差异压缩”。

参考资料

我刚刚阅读的回答这个问题的在线帖子: