git 是将更改后的大文件完全上传到远程,还是可以上传差异?

Dum*_*ner 4 git

假设我有一个大文本文件,它会定期更改某些部分。我想让它与 git 服务器上的远程版本保持同步,最好只上传其更改的部分。

git 的默认行为是什么?git 每次更改时都会上传整个文件吗?或者可以选择只上传差异?

非文本(二进制)文件呢?

谢谢

tor*_*rek 5

git 每次更改时都会上传 [an] 整个文件吗?或者可以选择只上传差异?

对此的答案实际上是“视情况而定”。

您所描述的系统——我们说“给定现有文件 F,使用 F 的第一部分,然后插入或删除这一位,然后使用 F 的另一部分”等等——称为增量压缩增量编码

正如Tim Biegeleisen 回答的那样,Git存储——至少在逻辑上——每个提交的每个文件的完整副本(但使用重复数据删除,所以如果提交 A 和 B 都存储某个文件的相同副本,它们共享一个存储副本)。Git 将这些存储副本称为对象。但是,Git可以在 Git 称为pack files 的内容中对这些对象进行增量压缩。

当一个 Git 需要将内部对象发送到另一个 Git 以提供提交及其文件时,它可以:

  • 一个一个地发送单个对象,或
  • 发送包含对象的打包版本的包文件。

如果您使用发送包文件的 Git 协议,Git 只能在此处使用增量压缩。您可以轻松判断您是否正在使用包文件,因为之后git push您将看到:

Counting objects: ... done
Compressing objects: ... done
Run Code Online (Sandbox Code Playgroud)

这个压缩阶段发生在构建包文件时。不能保证当 Git 压缩对象时,它确实对另一个 Git 已经拥有的对象的某个版本使用了增量压缩。但这就是目标,并且通常会如此(除了在 Git 2.26 中引入并在 Git 2.27 中修复的错误)。

技术细节,好奇的

有关于包文件一般规则git fetchgit push明确的违反。然而,要真正理解这一切是如何运作的,我们应该首先描述这个一般规则。

打包文件

Git 有一个程序(以及各种内部函数,如果/根据需要可以更直接地使用),它仅使用一组原始对象或一些现有的包文件或两者来构建新的包文件。无论如何,这里使用的规则是新的包文件应该是完全独立的。也就是说,包文件PF内的任何对象只能针对也在PF内的其他对象进行增量压缩。因此,给定一组对象 O 1 , O 2 , ..., O n,唯一允许的增量压缩是针对出现在同一包文件中的某些 O j压缩某些 O i

至少一个对象始终是基础对象,即根本未压缩。我们称这个对象为 O b。另一个对象可以针对 O b1压缩,产生一个新的压缩对象 O c1 然后,另一个对象可以直接针对 O b1针对 O c1压缩。或者,如果下一个对象毕竟似乎不能很好地压缩 O b1,则它可能是另一个基础对象 O b2。假设下一个对象压缩,我们称它为 O c2。如果它反对Ø压缩C1,这是一个三角链:解压缩O c2,Git 将必须读取 O c2,看到它链接到 O c1,读取 O c1,看到它链接到 O b1,并检索 O b1。然后应用O c1解压规则得到解压后的O c1然后再应用O c2的解压规则。

由于所有这些对象都在一个单独的包文件中,Git 只需要打开一个文件。然而,解压缩一个很长的链可能需要在文件中进行大量跳转,以找到各种对象并应用它们的增量。因此,δ链长度是有限的。Git 还尝试将对象物理放置在包文件中,以一种使读取(单个)包文件有效的方式,即使隐含的跳转也是如此。

为了遵守所有这些规则,Git 有时会为存储库中的每个对象构建一个全新的包文件,但只是偶尔。在构建这个新的包文件时,Git 使用以前的包文件作为指导,指示哪些先前打包的对象与其他先前打包的对象压缩得很好。然后它只需要花费大量的 CPU 时间来查看新的(从以前的打包文件开始)对象,看看哪些对象压缩得很好,因此在构建链时应该使用什么顺序等等。您可以关闭此功能并完全从头开始构建包文件,如果某些先前的包文件(无论如何)构造不佳,并且git gc --aggressive执行此操作。您还可以调整各种大小:请参阅git repack.

薄包装

对于git fetchgit push,包构建代码关闭“所有对象必须出现在包中”选项。相反,增量压缩机被告知,它应该承担的一些组对象的存在。因此,它可以将这些对象中的任何一个用作基础或链对象。当然,假定存在的对象必须可以在某处找到。因此,当您的 Git 与其他 Git 交谈时,他们会通过哈希 ID 谈论提交。

如果你正在推送,你的 Git 是一个必须构建打包文件的人;如果你正在取物,这与交换边的工作方式相同。让我们假设你在这里推动。

你的 Git 告诉他们:我已经提交 X。他们的 Git 告诉你:我也有 X或者我没有 X。如果他们确实X,您的 Git 会立即知道两件事:

  1. 他们也拥有X的所有祖先。1
  2. 因此,它们拥有X的所有tree 和 blob 对象,以及其所有祖先的tree 和 blob 对象。

显然,如果他们确实提交了X,则您的 Git 不需要发送它。你的 Git 只会发送X 的后代(可能会提交YZ)。但是通过上面的第 2 项,您的 Git 现在可以构建一个包文件,您的 Git 只是假设他们的 Git 拥有导致并包括提交X 的所有历史记录中的每个文件。

所以这就是“假设对象存在”代码真正发挥作用的地方:如果您修改了提交YZ 中的文件F1F2,但没有触及任何其他内容,则它们不需要任何其他文件——以及您的新文件F1F2文件可以针对commit X中的任何对象或其任何祖先进行增量压缩。

生成的包文件称为瘦包。构建瘦包后,您的推送(或他们对您的获取的响应者)通过网络发送瘦包。他们(为了你的推送,或者你为了你的获取)现在必须“修复”这个薄包,使用git index-pack --fix-thin. 修复瘦包只需打开它,找到所有增量链及其对象 ID,然后在存储库中找到这些对象——记住,我们保证它们可以在某处找到——并将这些对象放入包中,让它不再薄。

多个包文件

肥大的包尽可能大,以容纳他们需要容纳的所有物体。但它们并没有比这更大——它们不会容纳所有物体,只会容纳它们需要容纳的物体。所以旧的包文件仍然存在。

过了一会儿,一个存储库建立了大量的包文件。在这一点上,Git 决定是时候精简一些东西了,将多个包文件重新打包成一个可以容纳所有内容的包文件。这允许它完全删除多余的包文件。2 默认为 50 个包文件,因此一旦您累积了 50 个单独的包(通常通过 50 次获取或推送操作),git gc --auto将调用重新打包步骤,您将退回到一个包文件。

请注意,这种重新打包对瘦包没有影响:那些只依赖于感兴趣对象的存在,并且这种存在隐含在 Git 有提交的事实中。提交意味着拥有它的所有祖先(尽管再次参见脚注 1),所以一旦我们看到另一个 Git 提交了X,我们就完成了这部分计算,并且可以相应地构建我们的瘦包。


1浅层克隆违反了这个“所有祖先”的规则并使事情复杂化,但我们真的不需要在这里讨论细节。

2在某些情况下,需要保留旧包装;为此,您只需创建一个包名称以.keep. 这主要用于您共享--reference存储库的那些设置。