分支的git push非常缓慢

29
我们有一个相当大的 Git 存储库(iOS 应用程序资源)。我知道使用 Git 处理它会很慢,但如果我创建一个新分支并编辑几个文件(不是二进制文件)然后推送,这需要很长时间。
感觉像整个存储库都被推送了。我原本以为 Git 只会发送差异,但是我错了吗?(我知道 Git 存储了整个文件的压缩版本,我的意思是我的分支和我从哪里分支出来之间的差异)。
如果我运行 git diff --stat --cached origin/foo,那么我会看到一个短的文件列表,看起来像我预期的样子,例如: 34 files changed, 1117 insertions(+), 72 deletions(-)。但当我推送时,它只到达 Writing objects: 21% (2317/10804) 就停止了,好像正在推送所有2.4GB的二进制数据。
我是不是漏掉了什么东西(我已经非常努力地搜索了)?这是预期的行为吗?我在使用 Git 2.2.2 在 OS X(Mavericks)上,并且通过 ssh(git@github.com)进行推送。
我在这里找到了一个类似的问题: Git - pushing a remote branch for a large project is really slow ,但没有真正的答案。

请参考 Git 邮件列表中的讨论:http://comments.gmane.org/gmane.comp.version-control.git/265716 - grahamrhay
@grahamrhay提到的帖子链接仍然有效:https://public-inbox.org/git/CAABECY1_L34sq0VPmD9UwRcwb3Fuh95OFcF26LM2eX1z-+8vkQ@mail.gmail.com/ - max630
仅针对大型仓库,在 Git For Windows 2.21 中,你现在已经可以使用 pack.sparse 配置来提高推送性能(截至2019年第一季度)。这将被推广到所有平台。详情请参见《git push is very slow for a huge repo》。 - VonC
请注意,Git 2.38(2022 年第三季度)中将出现新的设置 git -c push.useBitmaps=false push,请参考。 - VonC
3个回答

29

你正在使用“智能”传输(这是一件好事),因此您确实获得了delta(增量),或更具体地说,“delta压缩”。但这并不意味着git会推送差异。

在这里,push和fetch的工作方式相同:在智能传输中,你的git调用远程端,两端进行一次小型对话以确定哪个仓库对象属于谁,这些对象由SHA-1标识,并附加到特定标签上(通常是分支和标记名称,尽管也允许其他标签)。

例如,在这种情况下,你的git会调用他们的git并说:“我建议你将你的分支master设置为SHA-1 1234567...。我看到你的master目前是333333...,这是我认为你需要从那里到达7777777...的方法。” 他们应该回复“好的,我需要其中一些东西,但我已经有了...”。一旦你的git弄清楚了需要发送什么以及已经存在什么,它就会构建一个包含所有要发送对象的“thin pack”1。(这是“使用多达%d个线程进行增量压缩”的阶段。)

然后通过智能传输发送生成的thin pack,这是你看到“writing objects”消息的地方。(整个thin pack必须成功发送,之后接收者使用git index-pack --fix-thin将其“加厚”并将其放入仓库。)

发送的确切数据取决于thin pack中的对象。它应该只包括“他们拥有的”和“你要发送的”之间的提交集,以及为那些提交所需的任何对象(树和blob),以及您正在发送的任何注释标记和需要它们的任何对象,在他们没有的情况下。

您可以通过使用git fetch获取最新信息,然后使用git rev-list查看您要向他们发送哪些提交来查找相关提交。例如,如果你只是要在master上推送内容:

$ git fetch origin   # assuming the remote name is origin
[wait for it to finish]
$ git rev-list origin/master..master

检查这些提交记录可能会显示一个非常大的二进制文件,该文件包含在其中一个中间提交记录中,然后在以后的提交记录中被删除:

$ git log --name-status origin/master..master
如果在一个提交中有A giantfile.bin,而后续的提交(可能在git log输出中排在首位)又有D giantfile.bin,那么你可能卡在发送giantfile.bin的blob上。
如果是这种情况,你可以使用git rebase -i来消除添加巨型二进制文件的提交,这样git push就不必再发送该提交了。
(如果你的历史记录是线性的——没有合并需要推送——那么你也可以使用git format-patch创建一系列包含补丁的电子邮件消息。这些补丁适合通过电子邮件发送给其他站点的某个人——虽然 GitHub 上没有等待接收它们的人,但你可以轻松地检查补丁文件,看看它们是否太大。)

1该包是“瘦”的,因为它违反了一条通常要求任何压缩“下游”对象都必须在包本身中的规则。相反,这些“下游”对象可以(实际上必须)在接收瘦包的存储库中。


不幸的是,format-patch 的总大小为1.7MB,这并不能解释为什么推送速度很慢。 - grahamrhay
此时需要调查的是thin-pack中有什么,以及您的ssh传输是否仅仅因为一些可能与git无关的网络问题而被卡住了。像tcpdump这样的网络跟踪器对于后者可能会有所帮助。 - torek
“thin-pack” 是否等同于 “git format-patch origin/foo..foo”? - grahamrhay
不同之处在于,薄包含实际的git对象,而format-patch则生成适合通过电子邮件发送的差异。这些差异提供了重建文件所需的足够信息,但不包括完整的提交图或标签。换句话说,format-patch可以获取大部分但不是全部信息,并以不同的格式呈现。虽然可以合理地期望强烈的正相关性,但没有直接比较大小的方法。 - torek
1
@ShashankBhatt:你可能遇到了VonC在他的答案中提到的错误。获取和合并可能已触发重新打包。或者,您可能遇到了深度为1的浅克隆所发生的情况。我们没有足够的关于您的设置的信息来确定。 - torek
显示剩余2条评论

3
请注意,Git 2.25 修复了在拥有超过 1023 个包时 pack-objects 的极度减速问题。下面是具体数字。
另一个选项是 Git 2.38 (2022 年第三季度) 提出了新设置 git -c push.useBitmaps=false push,以禁用 git push 的打包。
但是对于 Git 2.25 的修复:
这可能会对你的情况产生积极影响,特别是当你拥有大量的包文件时。
查看 提交 f66e040(2019年11月11日)由 Jeff King (peff) 提交。
(合并于 提交 8faff38,由 Junio C Hamano -- gitster --,2019年12月1日)pack-objects: 避免无意义的 oe_map_new_pack() 调用
签署者:Jeff King 审核者:Derrick Stolee”
自从43fa44fa3b(pack-objects:将in_pack移出结构体object_entry,2018-04-14)以来,我们使用了一个复杂的系统来保存一些每个对象的内存。每个object_entry结构都有一个10位字段用于存储它所在的包的索引。我们使用packing_data->in_pack_by_idx将这些索引映射到指针上,在程序开始时对其进行初始化。如果我们有2^10或更多的包,则创建一个包指针数组,每个对象一个。这是packing_data->in_pack。如果在我们初始化in_pack_by_idx之后有新的包到达,它还没有索引。我们通过调用oe_map_new_pack()解决这个问题,它会动态切换到不太优化的in_pack机制,分配数组并为已经看到的对象回填它。但是,即使我们已经切换到这种机制(因为我们确实看到了一个新包,或者因为我们一开始就有太多的包),这个逻辑也会触发。结果不会产生错误的结果,但非常慢。发生的情况是:假设你有一个包含500k个对象和2000个包的仓库,你想重新打包。在查看任何对象之前,我们调用prepare_in_pack_by_idx()。它开始为每个包分配索引。在第1024个包之前,它发现有太多的包,于是退出,将in_pack_by_idx作为NULL。在实际添加对象到打包列表时,我们调用oe_set_in_pack(),它检查包是否已经有了索引。如果是第1023个后的包,则没有索引,我们将调用oe_map_new_pack()。但是这个函数没有什么有用的工作要做。我们已经使用in_pack,所以它只是毫无意义地遍历完整个对象列表,尝试回填in_pack。我们最终会为近1000个包(每个包可能被一个以上的对象触发)执行此操作。每次触发时,我们可能会迭代超过500k个对象。因此,在绝对最坏的情况下,这是对象数量的平方级别。解决方案很简单:如果我们已经转换为使用in_pack,则不需要再检查包是否具有索引,因为根据定义,我们不会使用它。因此,我们可以将“包是否具有有效索引”的检查推入条件语句的一半中,我们知道我们将使用它的地方。当前的p5303测试不幸地没有注意到这个问题,因为它最多只能达到1000个包。如果我们在2000个包时添加一个新测试,它会显示改进:
Test                      HEAD^               HEAD
>     ----------------------------------------------------------------------
5303.12: repack (2000)    26.72(39.68+0.67)   15.70(28.70+0.66) -41.2%

However, these many-pack test cases are rather expensive to run, so adding larger and larger numbers isn't appealing. Instead, we can show it off more easily by using GIT_TEST_FULL_IN_PACK_ARRAY, which forces us into the absolute worst case: no pack has an index, so we'll trigger oe_map_new_pack() pointlessly for every single object, making it truly quadratic.

Here are the numbers (on git.git) with the included change to p5303:

Test                      HEAD^               HEAD
>     ----------------------------------------------------------------------
5303.3: rev-list (1)      2.05(1.98+0.06)     2.06(1.99+0.06) +0.5%
5303.4: repack (1)        33.45(33.46+0.19)   2.75(2.73+0.22) -91.8%
5303.6: rev-list (50)     2.07(2.01+0.06)     2.06(2.01+0.05) -0.5%
5303.7: repack (50)       34.21(35.18+0.16)   3.49(4.50+0.12) -89.8%
5303.9: rev-list (1000)   2.87(2.78+0.08)     2.88(2.80+0.07) +0.3%
5303.10: repack (1000)    41.26(51.30+0.47)   10.75(20.75+0.44) -73.9%

Again, those improvements aren't realistic for the 1-pack case (because in the real world, the full-array solution doesn't kick in), but it's more useful to be testing the more-complicated code path.

While we're looking at this issue, we'll tweak one more thing: in oe_map_new_pack(), we call REALLOC_ARRAY(pack->in_pack). But we'd never expect to get here unless we're back-filling it for the first time, in which case it would be NULL.
So let's switch that to ALLOC_ARRAY() for clarity, and add a BUG() to document the expectation. Unfortunately this code isn't well-covered in the test suite because it's inherently racy (it only kicks in if somebody else adds a new pack while we're in the middle of repacking).


1
在我的情况下,我无意中将一个非常大的文件添加到了提交中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接