如何列出数据库中的所有git对象?

66

有没有更好的方法获取存储库中所有对象的原始SHA1列表,而不是使用以下命令:
ls .git/objects/??/\*

cat .git/objects/pack/*.idx | git show-index

我知道 git rev-list --all 可以列出由 .git/refs 引用的提交对象,但我要找的是所有,包括由 git-hash-objectgit-mktree 等创建的未引用对象。


昨天我在一个测试仓库上刚刚这样做了 - 使用 ls -alR .git/objects,但我同意这不是理想的方法... +1 - johnny
2
另一方面,如果您特别寻找未引用的对象,则可能会对 git-fsck [--unreachable] 感兴趣... - johnny
2
...而ls命令只会列出松散的对象,而不是打包的对象,这些对象也可能没有被引用--因此在我的问题中使用了git show-index。 - kbro
3
使用 Git 2.19(Q3 2018)版本,git cat-file --batch-check --batch-all-objects --unordered 命令速度相当快。请参见 我的答案 - VonC
8个回答

48

尝试

 git rev-list --objects --all

编辑 Josh提出了一个很好的观点:

 git rev-list --objects -g --no-walk --all

列出可以从ref-log中访问的所有列表对象。

如果想要查看不可访问提交中的所有对象:

 git rev-list --objects --no-walk \
      $(git fsck --unreachable |
        grep '^unreachable commit' |
        cut -d' ' -f3)

将所有内容结合起来,为了以rev-list --objects的输出格式真正获得所有对象,您需要类似于以下的方法:

{
    git rev-list --objects --all
    git rev-list --objects -g --no-walk --all
    git rev-list --objects --no-walk \
        $(git fsck --unreachable |
          grep '^unreachable commit' |
          cut -d' ' -f3)
} | sort | uniq

为了使输出结果更有用(对于tree / blobs按路径排序,先列出commits),可以使用额外的| sort -k2,它将为相同路径分组所有不同的blobs(修订)。


1
仅列出从 refs/ 可达的对象,不包括 reflogs 和不可达对象。 - Josh Lee
@sehe:我在我的git脚本中包含了一个(稍作修改的)版本,网址为https://github.com/johnbartholomew/gitvoodoo。我想以GPLv3发布这些脚本--你能告诉我这是否可行吗?(代码的来源在README和脚本顶部附近的注释中有说明)。感谢你提供的有用扩展! - John Bartholomew
@sehe:谢谢。我最初使用了Mark的答案,但你的答案有一个优点,就是可以使用rev-list来遍历提取每个blob的树路径(当然,并不是唯一的,但有一个有效的路径是有益的)。 - John Bartholomew
@sehe:FYI,GNU声称CC-SA不与GPL兼容,因此在GPLv3作品中包含此处发布的代码的许可并非自动。 - me_and
这显示了每个SHA对应的blob路径,非常棒。是否有一种简单的方法来帮助识别提交和树?例如,通过路径查找树,通过提交消息的第一行查找提交。 - Ciro Santilli OurBigBook.com
显示剩余6条评论

46

我不知道这个选项是从什么时候开始存在的,但你可以

git cat-file --batch-check --batch-all-objects

根据 man 手册,这将为您提供存储库中的所有对象和任何备用对象存储 (不仅是可达对象)。

(强调是我的)。

示例输出:

$ git cat-file --batch-check --batch-all-objects
64a77169fe44d06b082cbe52478b3539cb333d45 tree 34
6692c9c6e231b1dfd5594dd59b32001b70060f19 commit 237
740481b1d3ce7de99ed26f7db6687f83ee221d67 blob 50
0e5814c4da88c647652df8b1bd91578f7538c65f tag 200

如上所示,此操作将返回对象类型及其大小以及每个哈希值,但您可以轻松删除此信息,例如使用

git cat-file --batch-check --batch-all-objects | cut -d' ' -f1

或者通过对--batch-check设置自定义格式来实现。

编辑:如果你不关心顺序,你可以(从Git 2.19开始)添加--unordered标志以加快速度。有关更多详细信息,请参见VonC的答案


6
我知道在StackOverflow上感谢评论是被 frowned upon 的,但你的回答让我节省了一周的工作时间。我的安装出了问题,reflog和ref-list都无法正常工作。请注意,此处的“FUBAR”表示安装遭遇了严重的问题。 - Ainar-G
2
这个答案比其他答案更好,因为输出还显示了对象的类型大小。(更新了答案,并提供了一个示例输出,以便清楚地说明,因为我已经寻找这个命令有一段时间了。) - toraritte

19
Erki Der Loonyanswer中建议使用git cat-file --batch-check --batch-all-objects命令,新版Git 2.19(2018年第三季度)增加了选项--unordered,可以使其更快。

API用于迭代所有对象,可选择按打包文件中出现的顺序列出对象,如果调用者在枚举对象时访问这些对象,则有助于访问的本地性。 请查看commit 0889aae, commit 79ed0a5, commit 54d2f0d, commit ced9fff (2018年8月14日)以及commit 0750bb5, commit b1adb38, commit aa2f5ef, commit 736eb88, commit 8b36155, commit a7ff6f5, commit 202e7f1 (2018年8月10日),作者为Jeff King (peff)。此更改已于2018年8月20日合并,合并者为Junio C Hamano -- gitster --,合并提交为commit 0c54cda

cat-file:支持--batch-all-objects的"unordered"输出

If you're going to access the contents of every object in a packfile, it's generally much more efficient to do so in pack order, rather than in hash order. That increases the locality of access within the packfile, which in turn is friendlier to the delta base cache, since the packfile puts related deltas next to each other. By contrast, hash order is effectively random, since the sha1 has no discernible relationship to the content.

This patch introduces an "--unordered" option to cat-file which iterates over packs in pack-order under the hood. You can see the results when dumping all of the file content:

$ time ./git cat-file --batch-all-objects --buffer --batch | wc -c
6883195596

real 0m44.491s
user 0m42.902s
sys  0m5.230s

$ time ./git cat-file --unordered \
                      --batch-all-objects --buffer --batch | wc -c
  6883195596

real 0m6.075s
user 0m4.774s
sys  0m3.548s

Same output, different order, way faster. The same speed-up applies even if you end up accessing the object content in a different process, like:

git cat-file --batch-all-objects --buffer --batch-check |
grep blob |
git cat-file --batch='%(objectname) %(rest)' |
wc -c

Adding "--unordered" to the first command drops the runtime in git.git from 24s to 3.5s.

Side note: there are actually further speedups available for doing it all in-process now. Since we are outputting the object content during the actual pack iteration, we know where to find the object and could skip the extra lookup done by oid_object_info(). This patch stops short of that optimization since the underlying API isn't ready for us to make those sorts of direct requests.

So if --unordered is so much better, why not make it the default? Two reasons:

  1. We've promised in the documentation that --batch-all-objects outputs in hash order. Since cat-file is plumbing, people may be relying on that default, and we can't change it.

  2. It's actually slower for some cases. We have to compute the pack revindex to walk in pack order. And our de-duplication step uses an oidset, rather than a sort-and-dedup, which can end up being more expensive.

If we're just accessing the type and size of each object, for example, like:

git cat-file --batch-all-objects --buffer --batch-check

my best-of-five warm cache timings go from 900ms to 1100ms using --unordered. Though it's possible in a cold-cache or under memory pressure that we could do better, since we'd have better locality within the packfile.

And one final question: why is it "--unordered" and not "--pack-order"? The answer is again two-fold:

  1. "pack order" isn't a well-defined thing across the whole set of objects. We're hitting loose objects, as well as objects in multiple packs, and the only ordering we're promising is within a single pack. The rest is apparently random.

  2. The point here is optimization. So we don't want to promise any particular ordering, but only to say that we will choose an ordering which is likely to be efficient for accessing the object content. That leaves the door open for further changes in the future without having to add another compatibility option


在Git 2.20(2018年第四季度)中,它甚至更快了,具体表现为:

请查看commit 8c84ae6commit 8b2f8cbcommit 9249ca2commit 22a1646commit bf73282(于2018年10月4日由René Scharfe (rscharfe)提交)。
(于2018年10月19日由Junio C Hamano -- gitster合并到commit 82d0a8c中)

oidset: 使用khash

Reimplement oidset using khash.h in order to reduce its memory footprint and make it faster.

Performance of a command that mainly checks for duplicate objects using an oidset, with master and Clang 6.0.1:

$ cmd="./git-cat-file --batch-all-objects --unordered --buffer --batch-check='%(objectname)'"

$ /usr/bin/time $cmd >/dev/null
0.22user 0.03system 0:00.25elapsed 99%CPU (0avgtext+0avgdata 48484maxresident)k
0inputs+0outputs (0major+11204minor)pagefaults 0swaps

$ hyperfine "$cmd"
Benchmark #1: ./git-cat-file --batch-all-objects --unordered --buffer --batch-check='%(objectname)'

Time (mean ± σ):     250.0 ms ±   6.0 ms    [User: 225.9 ms, System: 23.6 ms]

Range (min … max):   242.0 ms … 261.1 ms

随着这个补丁:

$ /usr/bin/time $cmd >/dev/null
0.14user 0.00system 0:00.15elapsed 100%CPU (0avgtext+0avgdata 41396maxresident)k
0inputs+0outputs (0major+8318minor)pagefaults 0swaps

$ hyperfine "$cmd"
Benchmark #1: ./git-cat-file --batch-all-objects --unordered --buffer --batch-check='%(objectname)'

Time (mean ± σ):     151.9 ms ±   4.9 ms    [User: 130.5 ms, System: 21.2 ms]

Range (min … max):   148.2 ms … 170.4 ms

Git 2.21 (2019年第一季度) 进一步优化了写出提交图的代码路径,按照常规方式访问打包顺序中的对象。
请参见commit d7574c9 (2019年1月19日),作者为Ævar Arnfjörð Bjarmason (avar)
(由Junio C Hamano -- gitster --commit 04d67b6合并,2019年2月5日)
使用FOR_EACH_OBJECT_PACK_ORDERfor_each_object_in_pack()略微优化“提交图写入”步骤。Derrick Stolee在Windows上进行了自己的测试,显示精度高,提高了2%。
Git 2.23 (2019年第三季度)改进了“git rev-list --objects”,该命令学会了使用"--no-object-names"选项来消除作为pack-objects分组提示的对象路径。
参见commit 42357b4(由Emily Shaffer (nasamuffin)于2019年6月19日提交)。 (由Junio C Hamano -- gitster --合并于commit f4f7e75,2019年7月9日)

rev-list:教授--no-object-names以启用管道传输

Allow easier parsing by cat-file by giving rev-list an option to print only the OID of a non-commit object without any additional information.
This is a short-term shim; later on, rev-list should be taught how to print the types of objects it finds in a format similar to cat-file's.

Before this commit, the output from rev-list needed to be massaged before being piped to cat-file, like so:

git rev-list --objects HEAD | cut -f 1 -d ' ' |
    git cat-file --batch-check

This was especially unexpected when dealing with root trees, as an invisible whitespace exists at the end of the OID:

git rev-list --objects --filter=tree:1 --max-count=1 HEAD |
    xargs -I% echo "AA%AA"

Now, it can be piped directly, as in the added test case:

git rev-list --objects --no-object-names HEAD | git cat-file --batch-check

那么这就是两者之间的区别:

vonc@vonvb:~/gits/src/git$ git rev-list --objects HEAD~1..
9d418600f4d10dcbbfb0b5fdbc71d509e03ba719
590f2375e0f944e3b76a055acd2cb036823d4b44 
55d368920b2bba16689cb6d4aef2a09e8cfac8ef Documentation
9903384d43ab88f5a124bc667f8d6d3a8bce7dff Documentation/RelNotes
a63204ffe8a040479654c3e44db6c170feca2a58 Documentation/RelNotes/2.23.0.txt

而且,使用--no-object-name选项:

vonc@vonvb:~/gits/src/git$ git rev-list --objects --no-object-names HEAD~1..
9d418600f4d10dcbbfb0b5fdbc71d509e03ba719
590f2375e0f944e3b76a055acd2cb036823d4b44
55d368920b2bba16689cb6d4aef2a09e8cfac8ef
9903384d43ab88f5a124bc667f8d6d3a8bce7dff
a63204ffe8a040479654c3e44db6c170feca2a58

在Git 2.31(2021年第一季度)中,对内核revindex的抽象访问进行了改进,允许按照包文件中出现的顺序枚举存储在其中的对象,为引入磁盘上预计算的revindex做准备,这应该可以加快这些操作的速度。

请查看commit e5dcd78, commit d5bc7c6, commit 8389855, commit 1c3855f, commit 2891b43, commit b130aef, commit 0a7e364, commit fc150ca, commit 3a3f54d, commit 45bef5c, commit 78232bf, commit 011f3fd, commit a78a903, commit cf98f2e, commit 5766508, commit eb3fd99, commit 6a5c10c, commit 66cbd3e, commit 952fc68, commit f33fb6e (2021年1月13日) 由 Taylor Blau (ttaylorr) 提交。
请查看commit 779412b (2021年1月14日) 由 Jeff King (peff) 提交。
(合并于 commit bcaaf97,2021年1月25日,由 Junio C Hamano -- gitster --)

pack-revindex: 引入新的API

签署者:Taylor Blau

在接下来的几个补丁中,我们将准备好在内存中加载反向索引(将.idx内容的反向映射到内核中),或直接从尚未介绍的磁盘格式中加载。
为此,我们将引入一个API,避免调用者明确索引packed_git结构中的revindex指针。
与反向索引交互有四种方式。
因此,在现有API被移除时,将从pack-revindex.h导出四个函数。
调用者可以:
1.加载包的反向索引。这涉及到打开索引、生成数组,然后对其进行排序。
由于打开索引可能失败,因此该函数('load_pack_revindex()')返回一个整数。
因此,它只需要一个参数:调用者想要为其构建反向索引的'struct packed_git``'。
这个函数非常适合当前和新的API。
调用者将不得不继续明确打开反向索引,但是如果存在磁盘上的反向索引,这个函数最终将学会如何检测和加载反向索引。
否则,它将回退到从头开始在内存中生成一个。
2.将包位置转换为偏移量。
这个操作现在称为pack_pos_to_offset()
它需要一个包和一个位置,并返回相应的off_t
任何错误都会调用BUG(),因为调用者不适合处理失败并继续进行。
3.将包位置转换为索引位置。与上面相同;这需要一个包和一个位置,返回一个uint32_t
这个操作被称为pack_pos_to_index()
这里也适用于关于错误条件的相同思考。
4.查找给定偏移量的包位置。
这个操作现在被称为offset_to_pack_pos()
它需要一个包、一个偏移量和一个指向uint32_t的指针,如果该偏移量处存在对象,则将位置写入其中。
否则,返回-1表示失败。
与直接访问不同,与此调用相关的错误检查要更加健壮一些。
这很重要,因为调用者应始终传递指向两个对象边界的偏移量。
与直接访问不同,API强制执行这种情况。
这将在随后的一个补丁中变得重要,其中一个调用者没有但可以检查返回值,将从find_revindex_position()中的有符号-1作为对'revindex'数组的索引。
两个设计缺陷被带入新的API中:
1.请求越界对象的索引位置将导致BUG()(因为不存在这样的对象),但是请求包末尾不存在的对象的偏移量将返回包的总大小。
这使得对于始终想要获取两个相邻对象的偏移量差(以计算磁盘上的大小)但不想担心包末尾边界的调用者来说非常方便。
2.offset_to_pack_pos()懒惰地加载反向索引,但pack_pos_to_index()不会(前者的调用者适合处理错误,但后者的调用者不适合)。
使用Git 2.32(2021年第二季度),"git (branch|tag) --format=..."已经进行了微小的优化。
参见commit 844c3f0(2021年4月20日)和commit 22f69a8(2021年4月19日)由ZheNing Hu(adlternative提交。
(由Junio C Hamano -- gitster --commit c108c8c中合并,2021年5月7日)

ref-filter: 重用输出缓冲区

协助者:Junio C Hamano
协助者:Jeff King
协助者:René Scharfe
签名:ZheNing Hu

当我们使用 git for-each-ref(man) 时,每个引用都会分配自己的输出 strbuf 和错误 strbuf。
但是我们可以为每个步骤的 ref 的输出重复使用最终的 strbuf。
尽管 git 在 format_ref_array_item() 返回非零值并输出错误缓冲区的内容时将退出程序,但错误缓冲区也将被重复使用。
使用性能测试工具 hyperfine 在 Git 存储库本身上对 git for-each-ref 进行性能测试,其性能从 23.7 ms ± 0.9 ms 改变为 22.2 ms ± 1.0 ms。
优化相对较小。
同时,我们将此优化应用于 git tag -l(man)git branch -l(man)
这种方法类似于 79ed0a5("cat-file: use a single strbuf for all output",2018-08-14,Git v2.19.0-rc0 -- merge)用于加速 cat-file 内置命令的方法。

请参见 https://dev59.com/pknSa4cB1Zd3GeqPR-Nq#70162248 - VonC

16
这是一个更加正确、简单和快速的脚本版本,参考了Markwillkill的答案。

  • 它使用rev-parse --git-path在更复杂的Git存储库设置中查找objects目录(例如,在多工作树情况下或其他情况下)。

  • 它避免了所有不必要的findgrepperlsed的使用。

  • 即使您没有松散对象或包(或两者都没有...如果您倾向于在新存储库上运行此操作),它也能优雅地运行。

  • 但是,它需要来自本千年的Bash(2.02或更新版本,具体而言是extglob部分)。

分享并享受。

#!/bin/bash
set -e
shopt -s nullglob extglob

cd "`git rev-parse --git-path objects`"

# packed objects
for p in pack/pack-*([0-9a-f]).idx ; do
    git show-index < $p | cut -f 2 -d ' '
done

# loose objects
for o in [0-9a-f][0-9a-f]/*([0-9a-f]) ; do
    echo ${o/\/}
done

1
不错。我喜欢“这个千年的Bash”警告。+1 - VonC
如果对 extglob 的依赖是个问题,那它可能不是必需的:for i in pack/.idx; do git show-index < $i | cut -f 2 -d' '; done; for i in ??/; do echo ${i//}; done - Waxrat
1
@Waxrat:这仅适用于松散对象的“extglob”模式。此外,我的代码中的$p未引用,因为使用extglob语法意味着变量不可能包含需要引用的文件名。如果您切换到可以捕获IFS字符的Glob,则还应该在使用值时添加引号。 — 但是,“extglob”会有什么问题呢? - Aristotle Pagaltzis

10

编辑:Aristotle 发布了一个更好的答案,应该标记为正确答案。

编辑:脚本包含语法错误,在 grep -v 行末缺少反斜杠。

Mark 的回答对我有用,经过一些修改:

  • 使用 --git-dir 而非 --show-cdup 来支持裸仓库
  • 在没有 pack 时避免出错
  • 使用 perl,因为 OS X Mountain Lion 的 BSD 风格的 sed 不支持 -r

#!/bin/sh

set -e

cd "$(git rev-parse --git-dir)"

# Find all the objects that are in packs:

find objects/pack -name 'pack-*.idx' | while read p ; do
    git show-index < $p | cut -f 2 -d ' '
done

# And now find all loose objects:

find objects/ \
    | egrep '[0-9a-f]{38}' \
    | grep -v /pack/ \
    | perl -pe 's:^.*([0-9a-f][0-9a-f])/([0-9a-f]{38}):\1\2:' \
;

1
谢谢。我在我的回答中进行了改进。 - Aristotle Pagaltzis
1
FYI,这个脚本在后半部分也错误地包括了打包文件本身的条目,例如 objects/pack/pack-23fd2d5638f6cd3bf2a433de4ea3cc26ebe7bdfa.idx - Ethan T
@EthanT 谢谢。我加了 grep -v,现在对我来说已经修复了。看起来 Aristotle 的答案没有这个错误,我还是推荐使用他的答案。 - willkil

7

我不知道有什么比查看所有松散的对象文件和所有打包文件的索引更好的方法。Git存储库的格式非常稳定,使用这种方法,您不必依赖于完全正确的git fsck选项,后者被归类为瓷器。我认为这种方法更快。以下脚本显示存储库中的所有对象:

#!/bin/sh

set -e

cd "$(git rev-parse --show-cdup)"

# Find all the objects that are in packs:

for p in .git/objects/pack/pack-*.idx
do
    git show-index < $p | cut -f 2 -d ' '
done

# And now find all loose objects:

find .git/objects/ | egrep '[0-9a-f]{38}' | \
  sed -r 's,^.*([0-9a-f][0-9a-f])/([0-9a-f]{38}),\1\2,'

我的原始版本是基于查找包文件中最大对象的实用脚本,但我改用git show-index,正如你在问题中建议的那样。

我将此脚本制作成了GitHub gist


谢谢。我在我的回答中进行了改进。 - Aristotle Pagaltzis

4

另一个有用的选项是使用git verify-pack -v <packfile>

verify-pack -v 列出数据库中所有对象以及它们的对象类型。


1
在某些奇怪的情况下,“git verify-pack -v .git/objects/pack/pack-….idx”是唯一一个能够确认某个大文件仍然被某个地方引用的命令,除了“git show Some-SHA1”之外。结果发现我没有传递正确的参数给“git reflog expire”...(而且,reflog 显示完全为空!) - marcus

1

结合 Erki der Loonysehe 的解决方案,我们几乎可以获取所有 blob 对象文件名(除了在 git 中被排除的文件):

git cat-file --batch-check --batch-all-objects | grep blob | cut -d" " -f1 | xargs -n1 git rev-list --objects -g --no-walk --all > .recovered/allblobobject.txt
cat .recovered/allblobobject.txt | sort | uniq > .recovered/allblobuniqobject.txt

这个blob到文件名的映射可以用于通过以下方式恢复消失的文件:
git fsck --full --no-reflogs --unreachable --lost-found | grep blob | cut -d" " -f3 > .recovered/bloblist.txt
for /F "tokens=*" %A in (.recovered/bloblist.txt) do (git cat-file -p %A > .recovered/%A)

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