我们有一个相当大的git仓库(ios app资源)。我很欣赏git在使用它时会很慢,但如果我创建一个新分支并编辑几个文件(不是二进制文件)并推送,则需要永远。
感觉整个回购都被推了。我的印象是git只会发送差异,是不是错了? (我知道git存储整个文件的压缩版本,我的意思是我的分支和我从哪里分支的差异。)
如果我运行git diff --stat --cached origin/foo
,那么我会看到一个简短的文件列表,看起来像我期望的那样,例如34 files changed, 1117 insertions(+), 72 deletions(-)
。但当我推动它进入Writing objects: 21% (2317/10804)
并停止时,好像它正在推动所有2.4GB的二进制数据。
我错过了什么(我用Google搜索了很多)?这是预期的行为吗?我在OS X(Mavericks)和ssh(git@github.com)上使用git 2.2.2。
我在这里找到了一个类似的问题:Git - pushing a remote branch for a large project is really slow但没有真正的答案。
答案 0 :(得分:21)
您正在使用“智能”传输(这是一件好事),因此您可以获得增量,或者更具体地说,“增量压缩”。但这并不是说git会推动差异。
push和fetch在这里工作方式相同:在智能传输上,你的git调用远程,并且两端都有一个迷你对话,以确定谁拥有哪些存储库对象,由SHA-1标识并附加到特定标签(通常也是分支和标签名称,但也允许使用其他标签)。
例如,在这种情况下,你的git会打电话给他们并说:“我建议你将你的分支master
设置为SHA-1 1234567...
。我看到你的{{1} }目前是master
,这是我认为您需要从333333...
获得的内容。“他们应回答“好吧,我需要其中一些,但我已经......”。一旦你的git找到了需要发送的内容以及已经存在的内容,你的git会构建一个包含所有待发送对象的“瘦包” 1 。 (这是“使用最多%d个线程进行增量压缩”阶段。)
然后通过智能传输发送生成的薄包;这是您看到“编写对象”消息的位置。 (必须成功发送整个瘦包,然后使用7777777...
再次将接收器“加长”并将其放入存储库。)
究竟发送什么数据取决于瘦包中的对象。那个应该只是“他们拥有的东西”和“你发送的内容”之间的提交集,以及这些提交所需的任何对象(树和blob),以及任何带注释的标签你是发送和那些他们尚未拥有的对象。
您可以使用git index-pack --fix-thin
获取相关提交,以获取最新信息,然后使用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
,那么你可能会挂起发送{{1的blob }}
如果是这种情况,您可以使用D giantfile.bin
来消除添加巨型二进制文件的提交,以便giantfile.bin
不必发送该提交。
(如果您的历史记录是线性的 - 没有合并要推送 - 那么您也可以使用git rebase -i
创建一系列包含补丁的电子邮件。这些适用于通过电子邮件发送给某人。其他网站 - 不是有人在github上等待接收它们,但你可以轻松检查补丁文件,看看它们中是否有巨大的数据。)
1 包是“瘦”的,因为它违反了普通的包文件规则,该规则要求任何增量压缩“下游”对象都在包本身中。相反,“下游”对象(实际上必须)可以在接收瘦包的存储库中。
答案 1 :(得分:0)
请注意,当您拥有超过1023个装箱时,Git 2.25修复了装箱对象中的极端减速问题。参见下面的数字。
这可能会对您的案件产生积极影响,因为您有大量的打包文件。
请参见commit f66e040的Jeff King (peff
)(2019年11月11日)。
(由Junio C Hamano -- gitster
--在commit 8faff38中合并,2019年12月1日)
pack-objects
:避免毫无意义的oe_map_new_pack()
通话签名人:杰夫·金
作者:Derrick Stolee自43fa44fa3b(打包对象:将
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次包装(每个包装可能由一个以上的物体触发)。每次触发时,我们最多可以迭代50万个对象。因此,在绝对最坏的情况下,对象数量是二次方。
解决方案很简单:如果已经转换为使用
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%
但是,这些多包测试用例运行起来相当昂贵,因此添加越来越多的用例并不吸引人。相反,我们可以通过使用
GIT_TEST_FULL_IN_PACK_ARRAY,
来更轻松地展示它,这将迫使我们进入绝对最坏的情况:没有包具有索引,因此我们将为每个对象无意义地触发oe_map_new_pack()
,使其真正二次方。以下是git.git上的数字,其中包括对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%
同样,对于1件装的包装盒来说,这些改进是不现实的(因为在现实世界中,无法使用全阵列解决方案),但是测试更复杂的代码路径更为有用。
我们正在研究此问题时,我们将进一步调整一件事:在
oe_map_new_pack()
中,我们称为REALLOC_ARRAY(pack->in_pack)
。但是除非我们第一次重新填充它,否则我们永远不会期望到达这里,在这种情况下,它将是NULL
。
因此,为清楚起见,我们将其切换为ALLOC_ARRAY()
,并添加一个BUG()来记录期望值。不幸的是,该代码在测试套件中并未得到很好的覆盖,因为它本质上是不友好的(只有当其他人在重新包装过程中添加了新包装时,它才会生效)。