混帐 可以 确实,做对了:它可以问服务器 你有blob吗? H ? 对于一些哈希 H ,如果服务器已经拥有它,请避免再次发送。
H
不过,Git实际上并没有这么做。好吧,无论如何,通过一些措施“好”。 Git所做的是询问服务器是否具体 提交 。然后根据结果做出一些合理的,但不一定是100%准确的假设。这有时意味着不必要地发送对象。而且,并非完全偶然的是,实现推送的代码不会在您的代码之前按照您的说法执行。 (这是,我 认为 ,问题的根源,但我没有测试过。)
不过,有些事情你可以做。让我们先来看看Git在做什么。
当静态文件发生变化时,我检查分支“静态”,使用提交更改 --amend 标志,然后结帐分支“历史”和合并分支“静态”,强制更新分支“主”在远程过程结束时: git checkout static git add . git commit --amend -m 'Add static files'
当静态文件发生变化时,我检查分支“静态”,使用提交更改 --amend 标志,然后结帐分支“历史”和合并分支“静态”,强制更新分支“主”在远程过程结束时:
--amend
git checkout static git add . git commit --amend -m 'Add static files'
此时,在您自己的存储库中,您有:
R [static@{1}] / ...--o--S <-- static
(虽然实际上是 ... 部分是空的,并且 o 提交 A 下面)。
...
o
A
承诺 R 曾经是最先进的那个 static ;它已被推到一边了 S 作为新的提示 static 。这两个提交都存在于您自己的存储库中。
R
static
S
git push
的 你不是在做这一步。 强> 因此,服务器还没有提交 S 。 (查看案例代码 asset ,运行 add_static , 然后 make_master , 然后 git push --force 。该 make_master step将当前分支设置为 master 所以 git push --force 推 只要 master 。这就是为什么 git log --graph 输出没有显示 origin/static 。)如果你这样做,你需要 git push --force 这里。
asset
add_static
make_master
git push --force
master
git log --graph
origin/static
我们现在进行:
git checkout master git reset --hard history git merge -m "Merge branch 'static'" static git push --force
我们画画吧 这个 图表也是如此,包括前面的推.. master@{2} (它的 @{2} 因为我们有两个干预事件:重置,然后合并)。此图表反映了内容 您的 存储库,看起来像这样:
master@{2}
@{2}
R--------M <-- origin/master, master@{2} / / A--o--o--L <-- history, origin/history, master@{1} \ \ S--------N <-- master
(承诺 R 有 static@{1} 标签,和 S 具有 static 和 origin/static ;出于空间原因,我没有在图纸中包含这些标签。
static@{1}
同时,服务器有这样的:
R--------M <-- master / / A--o--o--L <-- history
这是事情变得有趣的地方。客户端现在必须确定要发送的对象。它通过启动与服务器的对话来实现。它始于: 我想送你 N ;你有没有 N ? 当然,服务器没有提交 N 因为你刚刚做到了。
N
由于服务器拒绝,客户说: 然后我需要你 N 的父母 L 和 S ;你有那些吗? 当然,他们确实有 L , 但不是 S 。客户现在知道要发送 N 和 S ,并且服务器具有与之关联的所有对象 L - 并且,自服务器上的历史记录 不 浅,链中的所有物体都从中到达 L 回到 A 。
L
客户端现在询问服务器是否有 S 的父母 A ,或假设它是因为 A 是...的祖先 L ;无论哪种方式,它最终意识到服务器确实有 A 。
客户端现在假设服务器具有服务器提到的所有提交中的所有对象。它使 没有 假设提交 R 存在于服务器上,因为没有提到 R 在has / want协议交换中。所以它打包了所有的对象 S ,并发送它们。服务器重新打包,发现大多数blob都是冗余的,并且有效地忽略了冗余blob。
处理此问题的一种方法是继续在服务器上设置与commit相对应的标签 R (在前一步)。也就是说,添加一个 git push --force origin static , 以便 origin 有标签 static 指向 R 。
git push --force origin static
origin
然后,当他们发送新的提交时 master ,一定要告诉他们更新 都 static 和 master :
git push --force origin static master
要么:
git push origin +static:static +master:master
(这些意思相同 - refspec上的加号设置特定refspec的强制标志,在这种情况下,我喜欢显式,但你可以使用你喜欢的任何语法)。
现在服务器将具有:
...........<-- static . R--------M <-- master / / A--o--o--L <-- history
并将宣传其事实 refs/heads/static 表示提交 R 。客户端需要此信息用于其预推钩(无论它是否实际运行任何预推钩)。因此,当客户端发送新的提交时,它将提供发送 S (用于更新 static 并且因为它在更新的历史中 master )和 N (用于更新 master ) 但 ,这次它可以告诉服务器了 R 。它 应该 只能发送一个新的blob。
refs/heads/static
(我不确定 将 这样做,但它应该很容易测试。)
请注意,同时执行这两个操作非常重要,因为只要服务器接受即可 S 就像它一样 static 和 N 就像它一样 master ,它将垃圾收集两者 M 和 R 。 (服务器通常没有启用reflog,并且所有这些对象都在包文件中,因此不受松散对象的14天宽限期的限制。)
M
另一个选择是停止重写历史记录。您可能不喜欢此选项,因为静态资产对象会随着时间的推移而累积,从而使存储库大小膨胀。但这也将完全消除问题,因为现在客户端将正确理解服务器的历史记录。
从某种意义上说,历史重写是导致问题的:客户做出了 假设 服务器没有任何static-assets-objects 因为 该分支上的每个新提交与除root提交之外的任何内容完全无关 A 。这种假设是“安全的”,因为它只会导致发送额外的对象。它节省了很多 时间 因为枚举每次提交后面的所有树和blob对象都非常慢 - 只需说: 啊啊,服务器有这个提交,所以 - 除了由浅层移植引入的复杂性,我们在这里将忽略它 - 它具有通过这个提交及其历史隐含的所有对象。 客户端几乎不必提供任何哈希ID,因为服务器很快就会响应 是的我已经有了那个 ,并终止遍历该图的那一部分。如果服务器有 L 它以前拥有一切 L 太。如果有的话 R 它以前拥有一切 R 。
好吧,我应该修改一下:它 将 节省了大量的时间,除了你重写历史记录以便客户永远不会 问 关于 R 。所有对象的完整枚举虽然很慢,但可能比从提交中重新发送大多数对象更快 R 。它肯定会节省一些带宽。但对于大多数正常情况,以及没有进行大量重写的Git历史记录,以Git枚举提交和公正的方式执行此操作会更快 假设 关于这些提交背后的树木和blob的事情。