【问题标题】:Does git upload a changed large file entirely to remote, or could just upload the differences?git 是否将更改的大文件完全上传到远程,或者可以只上传差异?
【发布时间】:2020-06-30 05:25:02
【问题描述】:

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

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

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

谢谢

【问题讨论】:

    标签: git


    【解决方案1】:

    git 是否会在每次更改时上传 [an] 整个文件?或者可以选择仅上传差异?

    这个问题的答案实际上是“视情况而定”。

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

    作为Tim Biegeleisen answered,Git 存储 - 至少在逻辑上 - 每个文件的完整副本与每次提交(但具有重复数据删除,因此如果提交 A 和 B 都存储 某些文件的相同副本,它们共享一个存储的副本)。 Git 将这些存储的副本称为objects。但是,Git 可以在 Git 所谓的 pack files 中对这些对象进行增量压缩。

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

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

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

    Counting objects: ... done
    Compressing objects: ... done
    

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

    技术细节,适合好奇的人

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

    打包文件

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

    至少有一个对象始终是基础对象,即根本没有被压缩。我们称这个对象为 Ob。另一个对象可以针对 Ob1 进行压缩,产生一个新的压缩对象 Oc1 然后,另一个对象可以直接针对任一 Ob1 进行压缩, 反对Oc1。或者,如果下一个对象似乎没有很好地压缩 Ob1,它可以是另一个基础对象,Ob2。假设下一个对象压缩,我们称它为Oc2。如果它是针对 Oc1 压缩的,这是一个 delta 链:decompress Oc2,Git 必须读Oc2,看它链接到Oc1,读Oc1,看它链接到Ob1,并检索 Ob1。然后应用Oc1解压规则得到解压后的Oc1then为Oc2解压规则子>.

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

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

    薄包装

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

    如果你正在推送,你的 Git 是必须构建一个包文件的那个;如果你正在取货,这与交换边的效果相同。假设你在这里推。

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

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

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

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

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

    多个包文件

    增肥的背包尽可能大,可以装下他们需要装的所有物品。但它们并没有比这更大——它们不持有每个对象,只持有他们需要持有的对象。所以旧的包文件仍然存在。

    一段时间后,存储库会构建大量的包文件。此时,Git 决定是时候瘦身了,将多个包文件重新打包成一个可以容纳所有内容的包文件。这允许它完全删除多余的包文件。2默认为 50 个包文件,所以一旦你积累了 50 个单独的包——通常通过 50 次获取或推送操作——git gc --auto 将调用重新打包步骤,您将退回到一个打包文件。

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


    1浅层克隆违反了“所有祖先”的规则并使事情复杂化,但我们实际上不需要在这里详细介绍。

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

    【讨论】:

    • 根据您的回答,请您详细说明一下git在这两种情况下的行为? 1. git 是否只在推送之前打包文件? 2. 假设我们有一个大文本文件并希望将其提交推送到远程 git。 git 是否也打包单个文件或者打包过程应该有几个对象发生?
    • 关于打包文件中的增量压缩,这对传输到远程服务器的文件大小有何影响?我的意思是根据我的印象,Git 在更新本地包文件时使用增量压缩,但是在推送到远程服务器时发送整个新包文件,然后在远程服务器上,git 解压缩接收到的包文件并更新服务器上的旧对象(因为没有打包文件,因为打包文件是在服务器上的pushing 阶段创建的,我们既不从服务器拉取也不从服务器推送)。 (继续...)
    • .... 总之,每次客户端 git 向远程服务器推送一个新的提交时,它都会发送一个大小大致相同的包文件(因为它包含整个文件,而不仅仅是差异,并且增量压缩仅在本地更新包文件时发生)
    • @DummyBeginner:这……有点复杂。我将使用技术细节部分更新答案。 :-)
    • 非常感谢,我喜欢您审查流程的方式。我怎么能给这个答案加分?回到您更新的答案,这种情况是否正确? :在单个文件存储库中,当我推送到远程 Git 时,我正在发送一个瘦包(第一次推送除外),其中仅包含小尺寸的 delta 压缩对象,而接收者 Git 自动git index-pack --fix-thin 应用于收到的包,然后将其差异合并到远程 Git 上的基础对象。如果是这样的话,那我们可以说除了第一次push,Git不会把整个文件上传到其他Git。
    【解决方案2】:

    如果Git提交文件,一般会提交整个文件,并且会先压缩文件。 Git 不能通过提交对文件的差异来工作。 Git 实际上非常适合对大型文本文件进行版本控制,因为这些文件压缩得非常好,因此会留下占用空间最小的提交痕迹。

    另一方面,二进制文件在 Git 上工作得很好。原因是它们在压缩方面与文本文件相反。二进制文件不能很好地压缩,因此对 Git 存储库中的大型二进制文件进行版本控制会很快导致该存储库膨胀。

    根据下面@eftshift0 的评论/问题,我还想澄清一下 Git 与其他版本控制系统的不同之处。对于更经典的版本控制系统,例如 Perforce,所有版本的文件,包括二进制文件,都存在于一些 remote 存储系统(Perforce 称之为“仓库”)。当您在 Perforce 的本地分支中工作时,您实际上只有本地系统中每个文件的一个版本的浅表副本。从存储空间的角度来看,二进制文件是否被压缩并不重要,因为只有一个快照,此外您可能还想使用未压缩的版本。相比之下,在 Git 的模型中,当您克隆 Git 存储库时,会将 every 文件的每个 版本引入本地系统。对于文本源文件(如 Java、C 等),Git 通过 zip 压缩它们部分解决了这个问题。大多数源代码文件在很多情况下都包含非常重复的文本,而 zip 压缩在它们上效果很好,大大减少了大小。但是,在二进制文件的情况下,zip 压缩不能很好地工作。因此,如果您在 Git 历史记录中维护多个版本的二进制文件,您的存储库很容易膨胀。当您去克隆这样的存储库时,您可能最终会拉入 每个 这样的二进制文件的完整大小版本。由于显而易见的原因,这不能很好地扩展,因此通常不建议在 Git 中对大型二进制文件进行版本控制。

    【讨论】:

    • 另外,当你在git中设置压缩时,系统会打包相同路径的文件,所以即使二进制文件也有细微差别,使用空间也不会增长太快。
    • 提交整个文件的事情,与Git中的校验和概念有什么关系吗?我的意思是 Git 通过检查它们的哈希来检测更改的文件,因此要获得正确的哈希,它必须将整个文件上传到远程服务器,并且仅使用差异会破坏哈希?
    • @Tim Binary files do not compress well, and therefore versioning large binary files in your Git repository can quickly cause that repo to bloat..... 但是其他 VCS 在这方面做得更好?
    • @eftshift0 您可能忽略了 Git 和大多数其他 VCS 工具之间的重要区别(特别是不包括 Mercurial,它在许多方面类似于 Git)。请阅读我回答的最后一段以获取更多信息。
    • 哦,我明白了......所以它大约是 local 存储库/工作副本的大小......但是如果你进入服务器,其他 VCS如果你有那么多二进制文件,那么 repo 中的膨胀就会和你在 git local repo 上得到的一样多。对吗?
    猜你喜欢
    • 2019-06-25
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 1970-01-01
    相关资源
    最近更新 更多