您可能想要重试相同的
git repack
命令,这次使用Git 2.32(2021年第二季度,几乎晚了8年),以及它的新选项
--geometric=<n>
:
"git repack
"(man)到目前为止只能将所有内容打包成一个单独的包(或按大小拆分)。
引入了一种更聪明的策略来降低重新打包存储库的成本。
查看
提交 14e7b83(2021年3月19日),
提交 2a15964,
提交 13d746a,
提交 dab3247,
提交 f25e33c(2021年3月5日),以及
提交 0fabafd,
提交 339bce2,
提交 c9fff00,
提交 f62312e(2021年2月22日)由
Taylor Blau (ttaylorr
)。
查看
提交 ccae01c(2021年3月5日)由
Junio C Hamano (gitster
)。
查看
提交 20b031f,
提交 6325da1,
提交 fbf20ae,
提交 60bb5f2(2021年2月22日)由
Jeff King (peff
)。
(由Junio C Hamano -- gitster
--合并于提交 2744383,2021年3月24日)
首先:
find_kept_pack_entry()
packfile
: 引入 'find_kept_pack_entry()
'
共同作者:Jeff King
签署者:Jeff King
签署者:Taylor Blau
评审者:Jeff King
未来的调用者将需要一个功能,为给定对象ID填充结构体
pack_entry
,但仅限于从任何保留的包中获取其位置。
特别是,一种新的“
git repack
”模式(
man)将确保生成的包按对象计数形成几何级数,它将标记不想重新打包的包为“保留在内存中”,并且它希望在遍历可达性时尽快停止访问任何一个保留的包中的对象。但是,它不想在非保留或.keep包处停止遍历。
显然的替代方案是使用“
find_pack_entry()
”,但这并不完全足够,因为它只返回它找到的第一个包,这个包可能被保留也可能没有(如果有选项,mru缓存会使其不可预测)。
除此之外,您可以遍历所有包,查找每个包中的对象,但这会随着包的数量而扩展,这可能是禁止的。
引入“
find_kept_pack_entry()
”函数,它类似于“
find_pack_entry()
”,但仅填充保留包中的对象。
然后:
git pack-objects --stdin-packs
建议者:Jeff King
签名作者:Taylor Blau
评审者:Jeff King
在即将提交的代码中,'
git repack
'
(man) 将创建一个打包文件,其中包含一些打包文件(被包含的打包文件)中的所有对象
排除其他一些打包文件中的对象(被排除的打包文件)。
调用者可以自己迭代这些打包文件,并将其找到的对象直接通过stdin传递给'
git pack-objects
'
(man),但是这种方法有一些缺点:
- 每个想要以这种方式驱动'
git pack-objects
'的调用者都需要自己实现打包迭代。这迫使调用者考虑细节,例如将对象提供给pack-objects的顺序,而调用者可能不愿意这样做。
- 如果包含的打包文件中的对象集很大,则需要通过管道发送大量数据,这是低效的。
- 调用者也必须跟踪被排除的对象,并确保不会发送出现在包含和被排除的打包文件中的任何对象。
但最大的缺点是缺少可达性遍历。
因为调用者直接传递了对象列表,这些对象没有被分配名称哈希,这可能会对增量选择过程产生负面影响,导致' git pack-objects
'无法找到好的增量,即使它们存在。
调用者可以自己制定可达性遍历,但以这种方式驱动' git pack-objects
'的唯一方法是执行完整遍历,然后在遍历完成后删除被排除的对象。
这可能会对关心性能的调用者产生不利影响,特别是在具有许多对象的存储库中。
引入 'git pack-objects --stdin-packs
'(man)来解决这四个问题。
'git pack-objects --stdin-packs
' 期望在stdin上接收一个打包文件名称列表,其中 'pack-xyz.pack
' 表示该打包文件已被包含,而 '^pack-xyz.pack
' 表示它被排除。结果的打包文件包括至少出现在一个包含的打包文件中的所有对象,并且不出现在任何被排除的打包文件中。
git pack-objects
现在在其手册页面中包含以下内容:
--stdin-packs
从标准输入读取打包文件的基本名称(例如,pack-1234abcd.pack
),而不是对象名称或修订参数。
生成的包含所有列在包中的对象(那些不以^
开头的对象),但排除在排除包中列出的任何对象(以^
开头)。
与--revs
或暗示--revs
的选项(如--all
)不兼容,但--unpacked
是兼容的。
最终:
git repack --geometric=<n>
建议者:Derrick Stolee
签署者:Taylor Blau
审核者:Jeff King
Often it is useful to both:
- have relatively few packfiles in a repository, and
- avoid having so few packfiles in a repository that we repack its entire contents regularly
This patch implements a '--geometric=' option in 'git repack
'(man).
This allows the caller to specify that they would like each pack to be at least a factor times as large as the previous largest pack (by object count).
Concretely, say that a repository has 'n' packfiles, labeled P1, P2, ..., up to Pn.
Each packfile has an object count equal to 'objects(Pn)
'.
With a geometric factor of 'r
', it should be that:
objects(Pi) > r*objects(P(i-1))
for all i in [1, n]
, where the packs are sorted by
objects(P1) <= objects(P2) <= ... <= objects(Pn).
Since finding a true optimal repacking is NP-hard, we approximate it along two directions:
We assume that there is a cutoff of packs before starting the repack where everything to the right of that cut-off already forms a geometric progression (or no cutoff exists and everything must be repacked).
We assume that everything smaller than the cutoff count must be repacked.
This forms our base assumption, but it can also cause even the "heavy" packs to get repacked, for e.g., if we have 6 packs containing the following number of objects:
1, 1, 1, 2, 4, 32
then we would place the cutoff between '1, 1' and '1, 2, 4, 32', rolling up the first two packs into a pack with 2 objects.
That breaks our progression and leaves us:
2, 1, 2, 4, 32
^
(where the '^
' indicates the position of our split).
To restore a progression, we move the split forward (towards larger packs)
joining each pack into our new pack until a geometric progression
is restored.
Here, that looks like:
2, 1, 2, 4, 32 ~> 3, 2, 4, 32 ~> 5, 4, 32 ~> ... ~> 9, 32
^ ^ ^ ^
This has the advantage of not repacking the heavy-side of packs too often while also only creating one new pack at a time.
Another wrinkle is that we assume that loose, indexed, and reflog'd objects are insignificant, and lump them into any new pack that we create.
This can lead to non-idempotent results.
现在,
git repack
在其
手册页面中包含了以下内容:
-g=<因子>
--geometric=<因子>
排列结果包结构,以便每个连续的包含至少 <因子>
倍于下一个最大包的对象数。
git repack
通过确定需要重打包成一个包以确保几何级数的“割”来实现这一点。它选择最小的一组包文件,以便尽可能多地保留较大的包文件(按包含在该包中的对象计数)。
与其他重新打包模式不同,要打包的对象集合是由正在“卷起”的包的集合唯一确定的;换句话说,需要组合的包已确定以恢复几何级数。
当指定了 --unpacked
时,松散的对象隐式地包含在此“卷起”中,而不考虑它们的可达性。这可能会在未来发生变化。这个选项(暗示着一个截然不同的重新打包模式)不能保证与 git repack
的所有其他选项组合一起使用。
结果:
Test HEAD^ HEAD
5303.5: repack (1) 57.34(54.66+10.88) 56.98(54.36+10.98) -0.6%
5303.6: repack with kept (1) 57.38(54.83+10.49) 57.17(54.97+10.26) -0.4%
5303.11: repack (50) 71.70(88.99+4.74) 71.62(88.48+5.08) -0.1%
5303.12: repack with kept (50) 72.58(89.61+4.78) 71.56(88.80+4.59) -1.4%
5303.17: repack (1000) 217.19(491.72+14.25) 217.31(490.82+14.53) +0.1%
5303.18: repack with kept (1000) 246.12(520.07+14.93) 217.08(490.37+15.10) -11.8%
而且在“--stdin-packs”情况下,它的扩展性要好一些(尽管即使在1,000个包的情况下也没有太大提升):
5303.7: repack with --stdin-packs (1) 0.00(0.00+0.00) 0.00(0.00+0.00) =
5303.13: repack with --stdin-packs (50) 3.43(11.75+0.24) 3.43(11.69+0.30) +0.0%
5303.19: repack with --stdin-packs (1000) 130.50(307.15+7.66) 125.13(301.36+8.04) -4.1%
从
Taylor Blau那里查看“
Git 2.33:几何再打包”。
历史上,
git repack
有两种操作方式:一种是将所有松散的对象重新打包到一个新的包中(可选删除每个对象的松散副本),另一种是将所有包合并到一个新的包中(可选删除冗余的包)。通常情况下,当存储库中的包数量较少时,Git 的性能更好,因为许多操作与存储库中的包数量成比例。因此,将所有内容打包到一个单独的包中通常是一个好主意。但从历史上来看,繁忙的存储库通常要求将其所有内容打包到一个单独的巨大包中。这是因为可达性位图是服务器端 Git 性能的关键优化,只能描述单个包中的对象。因此,如果您想使用位图有效地覆盖存储库中的许多对象,则这些对象必须存储在同一个包中。我们正在努力消除这个限制(您可以
阅读更多有关我们如何做到这一点的信息),但实现一个新的打包方案是实现这一目标的重要步骤,它在相对较少的包和打包最近添加的对象之间进行权衡(换句话说,近似于自上次打包以来添加的新对象)。
为了实现这一点,Git 学习了一种新的“几何”重新打包策略。
引用:
这个想法是确定一组(相对较小的)可以合并在一起的 pack,以便剩余的 pack 基于对象大小形成一个几何级数。换句话说,如果最小的 pack 有 N 个对象,则下一个最大的 pack 至少会有 2N 个对象,以此类推,在每个步骤中加倍(或按任意常数增长)。
“
git pack-objects --stdin-packs
” 的输入验证已经在 Git 2.34(2021年第四季度)中得到了修正。
(man)
请参考以下提交记录:
-
commit 561fa03 (于2021年7月9日)
-
commit fb20d4b (于2021年6月21日)
这些提交记录由
Ævar Arnfjörð Bjarmason (avar
)进行。
(于2021年8月24日),
Junio C Hamano -- gitster
--已将其合并在了
commit 5c933f0中。
签名作者:Ævar Arnfjörð Bjarmason
在
339bce2 中添加了
--stdin-packs
选项,修复了一个段错误(segfault)("
builtin/pack-objects.c
: add '--stdin-packs' option",2021-02-22,Git v2.32.0-rc0 --
merge listed in
batch #4)。
read_packs_list_from_stdin()
函数没有检查它正在读取的行是否是有效的包,因此在使用
pack_mtime_cmp()
进行 QSORT() 时,我们将有一个空的 "util" 字段。
"util" 字段用于将包含/排除的包的名称与它们对应的
packed_git
结构相关联。
逻辑错误在于假设我们可以迭代所有包并注释掉我们得到的排除和包含的包,而不是检查我们从 stdin 得到的行。
有一个对于被排除的包的检查,但是对于包含的包只是简单地假定为有效的。
正如我们在测试中所指出的,我们将不会报告第一个坏行,而是根据
string-list.c
API 排序最先的行。
在这种情况下,我认为这是可以接受的。
还有
其他替代方法的讨论。
即使我们有点懒,让我们断言我们在测试中期望得到的行,因为将来更改此代码的人可能会忽略这种情况,并希望更新测试和注释。
加速重新打包的另一种方法是使用
--quiet
,这实际上是在Git 2.35(2022年第1季度)中运行的。请参见
提交47ca93d,
提交e4d0c11(2021年12月20日),作者为
Derrick Stolee(derrickstolee
)。
(由Junio C Hamano -- gitster
--合并于提交88a516a,2022年1月5日)
“repack”:使“--quiet”禁用进度条
帮助者:Jeff King
签名者:Derrick Stolee
在尝试一些 '
git repack
'
(man) 的想法时,我使用 '
--quiet
' 运行它,发现仍然会显示某些进度输出。
具体来说,写入多包索引的输出显示了进度。
cmd_repack()
中的 '
show_progress
' 变量是用
isatty(2)
初始化的,并且不受 '
--quiet
' 标志的影响。
'--quiet' 标志修改了
po_args
.quiet 选项,该选项被转换为 '
git pack-objects
'
(man) 子进程的 '--quiet' 标志。
但是,'
show_progress
' 用于直接将进度信息发送到编写多包索引的逻辑中,该逻辑不使用子进程。
修复方法是如果
po_opts.quiet
为 true,则将 '
show_progress
' 修改为 false;否则修改为
isatty(2)
。
这个新期望简化了稍后检查两者的条件。
更新文档以明确 '-q' 将禁用所有进度,并确保 '
git pack-objects
' 子进程将收到该标志。
git repack
现在包括在其手册页中:
--quiet
不要在标准错误流中显示任何进度,并将-q
选项传递给'git pack-objects'。请参见git pack-objects
。
在 Git 2.37(2022 年第三季度)中,教会 "
git repack --geometric
"
(man) 更好地使用
--keep-pack
,避免在使用
packsize
限制时破坏存储库。
查看
提交 66731ff,
提交 aab7bea(2022年5月20日),作者为
Taylor Blau (ttaylorr
)。
查看
提交 4b5a808(2022年5月20日),作者为
Victoria Dye (vdye
)。
(由Junio C Hamano -- gitster
--于提交 16a0e92中合并,2022年6月3日)
repack
: 尊重 -- 保持包装,使用几何重新包装
共同作者:Taylor Blau
签署者:Victoria Dye
签署者:Taylor Blau
将“repack”更新为使用“--keep-pack”选项忽略在命令行上命名的包。
具体来说,修改“init_pack_geometry()”以使命令行保留的包与具有磁盘上“.keep”文件的包相同(即跳过该包并不将其包含在“geometry”结构中)。
如果没有这个处理,“--keep-pack”包将被包含在“geometry”结构中。
如果该包位于几何分割线之前(至少存在一个其他包和/或松散对象),则“repack”假定该包的内容通过“pack-objects”“卷起”到另一个包中。
然而,由于内部调用的“pack-objects”正确地排除了“--keep-pack”对象,因此它创建的任何新包都不会包含保留的对象。
最后,“repack”将“--keep-pack”删除为“冗余”(因为它假设“pack-objects”创建了一个包含其内容的新包),从而导致可能丢失对象和存储库损坏。
在Git 2.39之前(2022年第四季度),当几何重打包功能与
--pack-kept-objects
选项一起使用时,我们会丢失标记有
.keep
文件的包。请参见
提交197443e(2022年10月17日)由
Taylor Blau(ttaylorr
)提交。
(由Junio C Hamano -- gitster
--合并于提交f62c546,2022年10月27日)
“
repack
:不要使用
--pack-kept-objects
命令移除带有
.keep
后缀的 pack 文件。”
“共同撰写者:Victoria Dye”
“签名者:Taylor Blau”
git repack
(man) supports a --pack-kept-objects
flag which more or less translates to whether or not we pass --honor-pack-keep
down to git pack-objects
(man) when assembling a new pack.
This behavior has existed since ee34a2b ("repack
: add repack.packKeptObjects
config var", 2014-03-03, Git v2.0.0-rc0 -- merge).
In that commit, the documentation was extended to say:
[...] Note that we still do not delete `.keep` packs after
`pack-objects` finishes.
Unfortunately, this is not the case when --pack-kept-objects
is combined with a --geometric
repack.
When doing a geometric repack, we include .keep
packs when enumerating available packs only when pack_kept_objects
is set.
So this all works fine when --no-pack-kept-objects
(or similar) is given.
Kept packs are excluded from the geometric roll-up, so when we go to delete redundant packs (with -d
), no .keep
packs appear "below the split" in our geometric progression.
But when --pack-kept-objects
is given, things can go awry.
Namely, when a kept pack is included in the list of packs tracked by the pack_geometry
struct and part of the pack roll-up, we will delete the .keep
pack when we shouldn't.
Note that this doesn't result in object corruption, since the .keep
pack's objects are still present in the new pack.
But the .keep
pack itself is removed, which violates our promise from back in ee34a2b.
But there's more.
Because repack
computes the geometric roll-up independently from selecting which packs belong in a MIDX (with --write-midx
), this can lead to odd behavior.
Consider when a .keep
pack appears below the geometric split (ie., its objects will be part of the new pack we generate).
We'll write a MIDX containing the new pack along with the existing .keep
pack.
But because the .keep
pack appears below the geometric split line, we'll (incorrectly) try to remove it.
While this doesn't corrupt the repository, it does cause us to remove the MIDX we just wrote, since removing that pack would invalidate the new MIDX.
Funny enough, this behavior became far less noticeable after e4d0c11 ("repack
: respect kept objects with '--write-midx -b'", 2021-12-20, Git v2.35.0-rc0 -- merge listed in batch #7), which made pack_kept_objects
be enabled by default only when we were writing a non-MIDX bitmap.
But e4d0c11 didn't resolve this bug, it just made it harder to notice unless callers explicitly passed --pack-kept-objects
.
The solution is to avoid trying to remove .keep
packs during --geometric
repacks, even when they appear below the geometric split line, which is the approach this patch implements.
使用 Git 2.39(2022年第四季度),创建多包位图时,无条件删除每个包的位图文件,因为它们永远不会被查询。
参见
提交55d902c(2022年10月17日),由
Taylor Blau (ttaylorr
)撰写。
(由Junio C Hamano -- gitster
--在提交c5dd777中合并,2022年10月28日)
签署者:Taylor Blau
当我们在重打包后写入MIDX位图时,仓库可能会处于同时存在基于打包和多打包可达性位图的状态。
例如,如果保留了一个打包(通过具有.keep
文件或在几何重打包期间未被卷起),并且该打包具有位图文件,则重打包将编写多包索引和位图。
在加载仓库的可达性位图时,始终优先使用多包位图,因此基于打包的位图是多余的。
即使没有传递'-d
',让我们无条件地删除它,因为没有实际理由保留两者。
以下补丁就是这样做的。
-f
而不是-F
? - clacke