在浅层克隆后推送到 Github 非常慢

Gui*_*uig 9 git github

我已经完成了一个大型 repo ( git clone --depth 1 git@github.com:myOrg/myRepo.git)的浅层克隆。我可以向遥控器推送新的更改,但第一次推送非常慢。后续推送就好了。该命令表示第一次推送向远程写入了大量数据:

$ git checkout -b test && \
  touch tmp.txt && \
  git add tmp.txt && \
  git commit -m tmp && \
  git push origin test

Enumerating objects: 164300, done.
Counting objects: 100% (164300/164300), done.
Delta compression using up to 12 threads
Compressing objects: 100% (93987/93987), done.
Writing objects: 100% (164300/164300), 368.72 MiB | 139.00 KiB/s, done.
Total 164300 (delta 41183), reused 164297 (delta 41182)
remote: Resolving deltas: 100% (41183/41183), done.
Run Code Online (Sandbox Code Playgroud)

这似乎没有写任何特定于本地的内容,因为 的大小.git几乎没有变化。

我很想知道发生了什么,以及是否可以在不显着增加本地克隆大小的情况下改进此过程。

笔记

这与 2012 年在这个问题中讨论的情况不同,当时推送不起作用。

tor*_*rek 10

TL;DR:使用--depth 2. 继续阅读为什么。

浅克隆(可以,但不一定必须)打败了一个重要的优化。在您的情况下,第一次推送会发生这种情况,但不会发生在后续推送中。可能会发生其他失败的情况,因此其他推动也可能很慢。

我们从这样一个事实开始,即 Git 实际上完全与提交有关,1将提交形成有向无环图。图中的顶点或节点(无论您喜欢哪个术语)都按提交哈希 ID 编号。这里相对无关紧要,但有助于具体化的事实是,节点之间的边/弧作为节点本身的一部分存储,而不是单独保存。每个节点存储其前驱节点的哈希 ID。

存储库本质上是这些提交对象的数据库。一个完整的非浅层存储库具有完整的图,从每个根到每个提示提交。甲单分支克隆可能下降图形的某些部分,但从来没有在图中的任何“间隙”。例如,给定:

           node--node--tip1
          /
root--node
          \
           node--node--tip2
Run Code Online (Sandbox Code Playgroud)

我们可以删除该行上的任何一个tip和节点,但不能删除中间行上的节点和根。在所有这些情况下,我们都可以——就像 Git 一直做的那样——从尖端开始,向后工作,最终到达根部。

现在,每个节点有两个重要的属性:

  • 号码唯一的。这是一个普遍唯一的 ID。 没有在节点的任何其他Git仓库(我们将反正满足)可以重新使用该ID。

  • 节点中的数据是严格只读的。这包括出站边缘链接。

这意味着,如果我们在发送方到接收方操作的每一侧都有一个无间隙存储库——一个完全完整的存储库,或者至少与它包含的提示提交所需的一样完整,我们可以拥有发送存储库简单地为我们枚举一些提交,通过它们的数量。如果我们,接收存储库,缺少该提交,我们要求发送方发送它,并告诉我们其父提交的编号。发件人这样做,我们看看我们是否有这些提交。对于我们缺少的任何内容,我们要求发送者发送这些内容并告诉我们他们的父提交的数量,等等。

这意味着如果我们有:

           node--node--tip1
          /
root--node
          \
           node--node--tip2
Run Code Online (Sandbox Code Playgroud)

他们有:

           node--node--tip1--new
          /
root--node
Run Code Online (Sandbox Code Playgroud)

然后他们将宣布使用 commit 的哈希 ID new。我们没有那个,所以他们应该把它添加到一堆要发送的提交中,并向我们宣布提交的哈希 ID tip1。我们tip1,所以我们只是告诉他们:我们已经有了tip1:你不需要将其发送。

这里的优化:我们只是告诉他们每一个承诺,我们有tip1一路回root 我们没有感谢,我们有一个提供给他们的答复,我可以送你tip1告诉他们关于不只是一个承诺及其文件,也是每一个前任承诺和所有的他们的文件。

他们现在知道,当他们向我们发送 commit 时new,他们只需要发送没有出现在前辈提交中的树和 blob(“文件”)对象。 此外,他们可以在任何先前提交中针对树和 blob 对象压缩这些树和 blob 对象,从tip1一直到root. 因此,与发送整个提交以及每个文件的完整快照相比,发送方可以发送的数据少得多。


树对象和 blob 对象主要通过提交找到。带注释的标签对象给图片添加了一个小皱纹,并且有一个直接指向树或 blob 的带注释的标签会添加另一个,但都不会破坏标准优化。


与发件人是浅存储库时的比较

浅库是其中一些提交(多个)标记的人工作为根的提交。提交对象实际上具有正确的父哈希 ID,但是此提交对象所在的 Git 有一个文件2,内容为:我们对 commit 的父对象一无所知tip1。不要试图寻找任何:相反,假装这tip1是一个没有父母的根提交。

这意味着发送 Git,而不是:

           node--node--tip1
          /
root--node
Run Code Online (Sandbox Code Playgroud)

刚刚:

      slightly-mangled-tip1
Run Code Online (Sandbox Code Playgroud)

在这个存储库中,我们添加新的提交:

      slightly-mangled-tip1--new
Run Code Online (Sandbox Code Playgroud)

现在我们让我们的 Git 调用他们的 Git 并提供新的提交。首先我们提供new。他们说我没有那个,你还能寄什么? 我们会提供slightly-mangled-tip1,但我们不能这样做,因为当我们阅读它时,我们会破坏它。所以我们说:对不起,这就是我们为您准备的全部内容。

他们说:那好吧,给我们发送 commit new

最后,我们看一下 commit new。它有每个文件的完整快照。我们不知道他们是否有任何这些文件。所以我们打包了整个东西并发送了所有的东西。

他们收到了所有文件,解压缩,发现我们已经复制了他们已有的 99%,忽略了额外的副本,然后将新提交放入他们的存储库中:

           node--node--tip1--new
          /
root--node
          \
           node--node--tip2
Run Code Online (Sandbox Code Playgroud)

下次我们运行时git push,我们有这个:

      slightly-mangled-tip1--new--new2
Run Code Online (Sandbox Code Playgroud)

我们为他们提供new2;他们说我没有那个,它的父母是什么,我们说new,他们说哦,我有那个,别费心送它了。这一次,我们看到他们已经拥有几乎所有文件,并且不必费心发送所有这些树和 blob 对象,并且可以根据 commit 中的内容压缩任何新的树和 blob 对象new。(我们仍然不能使用稍微管理的 tip1 提交,当然也不能使用任何丢失的先前提交,但是能够消除所有未更改的文件是巨大的。)


2或其他机制,但目前,它是一个名为.git/shallow.


你能做些什么

鉴于您打算 run git push,您将从在浅移植点和分支尖端“之间”进行一次未损坏的提交中获得很多里程。所以你git clone--depth 2. 你会得到一个稍微大一点的客户端 Git 存储库,但第一个git push会快得多。

也就是说,您将开始:

slightly-mangled-node--tip1
Run Code Online (Sandbox Code Playgroud)

在客户端上。第一个提交将导致:

slightly-mangled-node--tip1--new
Run Code Online (Sandbox Code Playgroud)

这一次你的 Git 将能够tip1在第一次推送期间提供给他们的 Git,这将触发优化。