git:致命错误:无法找到包'pack-xxxxxxx.pack'

3

遇到一个奇怪的问题。不确定我是如何引起的。

$ git gc
Enumerating objects: 625644, done.
Counting objects: 100% (625644/625644), done.
Delta compression using up to 16 threads
Compressing objects: 100% (126399/126399), done.
Writing objects: 100% (625644/625644), done.
Total 625644 (delta 497529), reused 622563 (delta 494488), pack-reused 0
fatal: could not find pack 'pack-6f30656f301f5f88438a5216f1df773bafcdf6d3.pack'
fatal: failed to run repack

我能看到它所指的文件,不确定它是否已损坏。全都是二进制。

$ ls -lsah .git/objects/pack/pack-6f30656f301f5f88438a5216f1df773bafcdf6d3.pack
1168 -rw-------  1 javier  staff   584K Oct  8  2021 .git/objects/pack/pack-6f30656f301f5f88438a5216f1df773bafcdf6d3.pack

仔细观察一下,这个哈希值没有像目录中的其他.pack文件那样附带一个.rev/.idx。这是问题所在吗?

1个回答

3
似乎在Git 2.42(2023年第三季度)中得到了解决,现在避免了“git pack-objects --cruft"(man)”的破坏,这是由于代码在枚举存储库中的pack文件时存在不一致性所导致的。
查看提交 73320e4(2023年6月7日)由Taylor Blau (ttaylorr)完成。
(由Junio C Hamano -- gitster --提交 e224f26中合并,2023年6月26日)

builtin/repack.c:仅收集完整的包

报告者:Michael Haggerty
签名者:Taylor Blau

为了根据哪些包是“保留的”(它们有一个.keep文件,或者通过--keep-pack选项标记),以及“非保留的”包(其他任何包),git repack使用其collect_pack_filenames()函数。通常情况下,我们会依赖于一个方便的函数,比如get_all_packs()来枚举和分区包集合。但是collect_pack_filenames()直接使用readdir()来读取"$GIT_DIR/objects/pack"目录的内容,并将以".pack"结尾的每个条目添加到适当的列表中(即保留的或非保留的)。这是微妙的竞争条件,因为collect_pack_filenames()可能会看到一个未完全暂存的包(即缺少".idx"文件)。通常情况下,这不会引起问题。但是在生成无用的包时可能会出现问题。这是因为git repack将现有的保留包列表传递给git pack-objects --cruft等命令,以指示不会从存储库中删除任何保留的包(以便无用的包机制可以避免将这些包中出现的对象作为无用的对象进行打包)。但是read_cruft_objects()通过调用get_all_packs()列出包文件。因此,如果存在一个".pack"文件(使得该包能够通过collect_pack_filenames()显示),但是没有相应的".idx"文件(使得该包能够通过get_all_packs()显示),我们将会报错。通过教collect_pack_filenames()只收集具有相应的*.idx文件的包,以指示这些包已经完全暂存,可以修复上述问题。值得注意的是有几点:由于extra_keep列表中的每个条目(其中包含--keep-pack名称)都有一个.pack后缀,我们需要将后缀从".pack"更改为".idx",然后进行比较。由于我们使用fname_kept_list来确定要删除的包(使用git repack -d命令),我们以前可能会删除没有索引的*.pack文件(因为存在".pack"文件足以将该包包括在现有非保留包列表中)。现在我们将不再删除它(因为该包不会出现在列表中)。这是更正确的行为,因为我们不希望与正在暂存的包竞争。但是删除部分暂存的包是不太可能的,因为将包的.idx文件移动到适当位置的时间窗口非常短暂。请注意,此窗口不包括接收和索引包所需的时间,因为传入的数据进入"$GIT_DIR/objects/tmp_pack_XXXXXX",该目录不以".pack"结尾,因此collect_pack_filenames()会忽略它。将来,这个函数可能应该重写为for_each_file_in_pack_dir()的回调函数,但这是我们在短期内能做的最简单的更改。
而且,仍然在Git 2.42(2023年第三季度)中,我们创建了.pack然后是.idx,我们只考虑那些可用的带有.idx的pack文件(那些只有.pack的文件还没有准备好),所以我们应该在删除.pack之前先删除.idx以保持一致性。
请参阅commit 0dd1324(2023年6月20日)由Derrick Stolee(derrickstolee提交。
(由Junio C Hamano -- gitster --合并于commit b2166b0,2023年6月29日) packfile:在删除.pack文件之前,删除.idx文件。

签名:Derrick Stolee

安装packfile时,我们将.pack文件放在.idx文件之前。 这样做的目的是让Git在pack目录中扫描.idx文件,然后从该列表加载.pack文件。
然而,当我们删除packfile时,我们并没有按照应该的顺序进行操作。 unlink_pack_path()方法会先删除.pack文件,然后再删除.idx文件。
这就产生了一个窗口,在.pack文件被删除和.idx文件被删除之间可能会中断进程,导致仓库处于一种看起来奇怪但实际上并不太严重的状态,前提是我们假设pack是安全可删除的。 没有.pack的.idx文件会造成一些开销,但不会中断其他Git进程。
这个顺序是通过“git repack”(man)内置命令引入的,由a1bbc6c(“repack:将shell脚本重写为C”,2013-09-15,Git v1.8.5-rc0 - merge)引入,尽管我们必须小心地跟踪代码移动中的历史,8434e85(“repack:为将来使用重构pack删除”,2019-06-10,Git v2.23.0-rc0 - merge列在batch #6中)。
在73320e4(“builtin/repack.c:仅收集完整形式的packs”,2023-06-07,Git v2.42.0 - merge列在batch #5中)之后,这变得更加重要,该更改改变了“git repack”在垃圾pack过程中扫描packfile的方式。 以前它会寻找.pack文件,但由于pack的安装顺序存在问题:在创建.pack文件和创建其.idx文件之间进行repack将导致严重错误。
关于在“git repack”场景中出现没有.pack的.idx文件时应该怎么做,有一个独立的建议,但这个更改专注于更安全地删除.pack文件。
修改顺序,在删除.pack之前删除.idx。 对于.pack上的其他修饰符仍然应该在.pack之后,这样我们就可以知道packfile的所有假定属性,只要它在文件系统中存在,以防我们希望通过重新索引.pack文件来恢复它。
最近对 "git repack"(man) 的更改使得当存储库中的剩余 .idx 文件不再具有相应的 .pack 文件时,其反应变得不太友好:这个问题已经在 Git 2.42 (Q3 2023) 中得到了修复。

查看 提交 def390d(2023年7月11日)由 Taylor Blau(ttaylorr
查看 提交 0af0672(2023年7月11日)由 Derrick Stolee(derrickstolee
(由 Junio C Hamano -- gitster --提交 c6a5e1a 中合并,2023年7月18日)

builtin/repack.c:在collect_pack_filenames()中避免目录遍历 签名:Taylor Blau

When repacking, the function collect_pack_filenames() is responsible for collecting the set of existing packs in the repository, and partitioning them into "kept" (if the pack has a ".keep" file or was given via --keep-pack) and "nonkept" (otherwise) lists.

This function comes from the original C port of git-repack.sh from back in a1bbc6c ("repack: rewrite the shell script in C", 2013-09-15, Git v1.8.5-rc0 -- merge), where it first appears as get_non_kept_pack_filenames().
At the time, the implementation was a fairly direct translation from the relevant portion of git-repack.sh, which looped over the results of

find "$PACKDIR" -type f -name '*.pack'

either ignoring the pack as kept, or adding it to the list of existing packs.

So the choice to directly translate this function in terms of readdir() in a1bbc6c made sense.
At the time, it was possible to refine the C version in terms of packed_git structs, but was never done.

However, manually enumerating a repository's packs via readdir() is confusing and error-prone.
It leads to frustrating inconsistencies between which packs Git considers to be part of a repository (i.e., could be found in the list of packs from get_all_packs()), and which packs collect_pack_filenames() considers to meet the same criteria.

This bit us in 73320e4 ("builtin/repack.c: only collect fully-formed packs", 2023-06-07, Git v2.42.0 -- merge listed in batch #5), and again in the previous commit.

Prevent these issues from biting us in the future by implementing the collect_pack_filenames() function by looping over an array of pointers to packed_git structs, ensuring that we use the same criteria to determine the set of available packs.

One gotcha here is that we have to ignore non-local packs, since the original version of collect_pack_filenames() only looks at the local pack directory to collect existing packs.


有没有临时解决方案可以在2.42版本发布之前使用? - Jiri Tousek
@JiriTousek 我到目前为止还没有看到任何一个。 - VonC
@JiriTousek 我到目前为止还没有看到任何一个。 - undefined
@JiriTousek 我找到的解决办法是删除本地仓库,然后重新克隆。 - Arjun
@Arjun 那应该是有效的。话虽如此,Git 2.42 应该会在本月底之前发布。(8月21日) - VonC
1
Git 2.42现已发布,我可以确认它已经解决了我的问题。 - undefined

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