哪些git命令执行完整性检查?

21

尝试确定在git-1.7.4.1中用户收到有关对象数据库损坏警告的快速程度,我进行了一次一位比特的切换:

$ git init repo
在 /tmp/repo/.git/ 中初始化 Git 空仓库。
$ cd repo
$ echo '非常重要的信息' >critical
$ git add critical
$ git commit -m "非常重要"
[master(根提交)c4d6d90] 非常重要
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 critical
$ git ls-tree HEAD
100644 blob 82d423c32c4bb2c52938088e0234db041bf4eaaf    critical
$ git show 82d423c32c4bb2c52938088e0234db041bf4eaaf
非常重要的信息
$ echo '非常重要的信息' | git hash-object --stdin -w
81a3797afe76d339db25c0f9c705a6caa47279c2
$ mv .git/objects/81/a3797afe76d339db25c0f9c705a6caa47279c2 \
     .git/objects/82/d423c32c4bb2c52938088e0234db041bf4eaaf

当然,git-fsck 注意到了:

$ git fsck
error: sha1 mismatch 82d423c32c4bb2c52938088e0234db041bf4eaaf
error: 82d423c32c4bb2c52938088e0234db041bf4eaaf: object corrupt or missing missing blob 82d423c32c4bb2c52938088e0234db041bf4eaaf

但是,git-log 对更改感到满意:

$ git log -p
commit c4d6d90467af9ffa94772795d5c5d191228933c1
Author: Greg Bacon <gbacon@dbresearch.net>
Date:   Thu Apr 7 12:20:53 2011 -0500
非常重要
diff --git a/critical b/critical 新文件模式100644 索引0000000..82d423c --- /dev/null +++ b/critical @@ -0,0 +1 @@ +非常重要的信息

git-checkout 一样。

$ rm critical 
$ git checkout .
$ cat critical 
非常重要的信息

git-show 的特定调用会显示损坏信息:

$ git show 82d423c32c4bb2c52938088e0234db041bf4eaaf
error: sha1不匹配 82d423c32c4bb2c52938088e0234db041bf4eaaf
fatal: 错误对象82d423c32c4bb2c52938088e0234db041bf4eaaf

但是广泛应用没有注意到.

$ git show
提交c4d6d90467af9ffa94772795d5c5d191228933c1
作者: Greg Bacon <gbacon@dbresearch.net>
日期:2011年4月7日星期四12:20:53 -0500
非常重要的信息
diff --git a/critical b/critical 新文件模式100644 索引0000000..82d423c --- /dev/null +++ b/critical @@ -0,0 +1 @@ +非常重要的信息

甚至git-clone也没有注意到!

$ cd ..
$ git clone repo clone
克隆到“clone”……
完成。
$ cat clone/critical 
非常重要的信息

请问有哪些特定的git命令模式(例如,应该存在git show $sha1但不存在git showgit show HEAD),它们执行完整性检查?


我很好奇 - 这只是一般的好奇心,还是你试图实现某些具体的东西?(不幸的是,如果没有深入挖掘源代码,我无法得知实际答案。) - Cascabel
1
@Jefromi 这个想法源于 r/programming 上有关备份的讨论。有人对每个 git clone 都是一个恢复备份提出了反对意见,并表示“当然,这是依赖于 Git 没有任何可能破坏文件所有版本的缺陷。” 我错误地假设普通使用会很快警告有关损坏的情况,即使是这种病态情况。 - Greg Bacon
我认为这个教训是:如果你把git用作备份系统,而你的主要仓库失败了,那么在从备份中恢复后,你应该运行git fsck。此外,最好发送一封电子邮件,说明“我们已经恢复了中央仓库。'master'位于<SHA1>,'release'位于<SHA1>”,以确保每个人的克隆都是一致的。 - Tyler
Git会立即告诉你关于常见的损坏模式:在实际的磁盘松散对象文件或打包文件中翻转位(或其他更改)。你所展示的是,Git不会自动通知完全替换一个有效的松散对象与另一个有效的松散对象。你的情况很有趣,但似乎是一个不太可能的故障模式(尽管如果你正在使用复制/克隆存储库的某些东西中存在系统性错误,则肯定是可能的)。你可能想在Git邮件列表上提出这个问题。 - Chris Johnsen
3个回答

6
作为回应Mark Longair的答案,我启动了cscope并发现:
(请注意cscope有一个curses界面,并且很好地集成到Vim中,以防您对此感兴趣)
Functions calling this function: parse_object

  File              Function                       Line
0 bundle.c          verify_bundle                   110 struct object *o = parse_object(e->sha1);
1 bundle.c          create_bundle                   242 struct object *object = parse_object(sha1);
2 bundle.c          create_bundle                   247 struct object *object = parse_object(sha1);
3 bundle.c          create_bundle                   323 obj = parse_object(sha1);
4 commit.c          lookup_commit_reference_gently   30 struct object *obj = deref_tag(parse_object(sha1), NULL, 0);
5 http-backend.c    show_text_ref                   372 struct object *o = parse_object(sha1);
6 http-push.c       one_remote_object               742 obj = parse_object(sha1);
7 http-push.c       add_remote_info_ref            1530 o = parse_object(ref->old_sha1);
8 log-tree.c        add_ref_decoration               93 struct object *obj = parse_object(sha1);
9 merge-recursive.c get_ref                        1664 object = deref_tag(parse_object(sha1), name, strlen(name));
a pack-refs.c       handle_one_ref                   43 struct object *o = parse_object(sha1);
b pretty.c          format_commit_one               835 parse_object(commit->object.sha1);
c reachable.c       add_one_reflog_ent              122 object = parse_object(osha1);
d reachable.c       add_one_reflog_ent              125 object = parse_object(nsha1);
e reachable.c       add_one_ref                     133 struct object *object = parse_object(sha1);
f reflog-walk.c     fake_reflog_parent              234 commit_info->commit = (struct commit *)parse_object(reflog->osha1);
g refs.c            peel_ref                        647 o = parse_object(base);
h refs.c            write_ref_sha1                 1452 o = parse_object(sha1);
i remote.c          ref_newer                      1482 o = deref_tag(parse_object(old_sha1), NULL, 0);
j remote.c          ref_newer                      1487 o = deref_tag(parse_object(new_sha1), NULL, 0);
k revision.c        add_head_to_pending             166 obj = parse_object(sha1);
l revision.c        get_reference                   176 object = parse_object(sha1);
m revision.c        handle_commit                   196 object = parse_object(tag->tagged->sha1);
n revision.c        handle_one_reflog_commit        855 struct object *o = parse_object(sha1);
o server-info.c     add_info_ref                     12 struct object *o = parse_object(sha1);
p sha1_name.c       peel_to_type                    508 o = parse_object(sha1);
q sha1_name.c       peel_to_type                    511 if (!o || (!o->parsed && !parse_object(o->sha1)))
r sha1_name.c       peel_onion                      573 o = parse_object(outer);
s sha1_name.c       peel_onion                      578 if (!o || (!o->parsed && !parse_object(o->sha1)))
t sha1_name.c       handle_one_ref                  698 struct object *object = parse_object(sha1);
u sha1_name.c       get_sha1_oneline                740 if (!parse_object(commit->object.sha1))
v tag.c             deref_tag                        16 o = parse_object(((struct tag *)o)->tagged->sha1);
w tree.c            parse_tree_indirect             271 struct object *obj = parse_object(sha1);
x tree.c            parse_tree_indirect             284 parse_object(obj->sha1);
y upload-pack.c     got_sha1                        342 o = parse_object(sha1);
z upload-pack.c     reachable                       382 parse_object(commit->object.sha1);
A upload-pack.c     receive_needs                   526 object = parse_object(sha1);
B upload-pack.c     send_ref                        644 struct object *o = parse_object(sha1);
C upload-pack.c     mark_our_ref                    670 struct object *o = parse_object(sha1);
D walker.c          loop                            182 parse_object(obj->sha1);

3
很酷,尽管很难猜出一些较低级别的东西最终被用在哪里。你需要做更多的追踪才能真正弄清楚。 - Cascabel

6

以下是我如何查找此内容的方法,尽管我不会去检查每个源文件以确定执行检查的条件。:)

克隆git的源代码:

git clone git://git.kernel.org/pub/scm/git/git.git

查看您关心的版本:

cd git
git checkout v1.7.1

寻找错误信息:

git grep 'sha1 mismatch'

这会带你进入object.cparse_object函数。现在找到该函数:
git grep parse_object

...并且需要检查38个文件中调用该函数的条件。


0

Git 2.38 (Q3 2022) 在 parse_object() 上进行了更多的添加。

响应 "git fetch"(man)git clone(man) 请求的服务器端已经进行了优化,允许它在不重新计算和验证对象名称的情况下发送其对象存储中的对象。

请查看提交 945ed00(2022年9月7日),以及提交 9a8c3c4提交 0bc2557提交 c868d8e(2022年9月6日),作者为Jeff King (peff)
(由Junio C Hamano -- gitster --于2022年9月13日合并至提交 8b2f027

parse_object():允许跳过哈希检查

签名作者:Jeff King

parse_object()函数会检查它解析的任何对象的对象哈希值。
这是一个很好的功能,因为它意味着我们可以在正常使用过程中捕获位损坏,而不是等待特定的fsck操作。

但它也可能会变慢。
对于blob来说尤其明显,除了哈希检查之外,我们完全可以返回而不加载对象内容。

现在有人可能会想知道首先在blob上调用parse_object()的意义是什么,但通常这并不是故意的:我们从某个地方得到了一个oid,不知道类型,并且想要一个对象结构。
对于提交和树,解析通常是有帮助的;我们即将查看内容。
但对于blob来说,情况就不太一样了,我们可能正在收集它们作为可达性遍历的一部分,等等,实际上并不关心其中的内容。
当然,blob往往比较大。

我们不想只是抛弃blob的哈希检查。
在某些情况下,我们确实依赖它们(例如,rev-list(man) --verify-objects使用parse_object()来检查它们)。
只有调用者知道他们将如何使用结果。
因此,我们可以通过提供一个特殊的标志来帮助他们跳过哈希检查。

我们可以只应用于blob,因为它们将是性能改进的主要来源。
但如果调用者不关心检查哈希值,我们也可以为其他对象类型跳过它。
即使我们无法避免读取对象内容,我们仍然可以跳过实际的哈希计算。

如果这似乎让Git对损坏的安全性稍微降低了一点,那可能是这样的。
但这是我们已经在做出的一系列权衡之一。
例如,“rev-list --objects”不会打开它打印的blob的内容。
当提交图存在时,我们会完全跳过大多数提交。
重要的是在安全跳过检查的情况下使用此标志。
例如,在为获取而服务的包中,我们知道客户端将完全索引对象并执行连接性检查。
从服务器端重新哈希一个blob本身没有太多好处。
事实上,大多数时间我们都不会这样做!修订机制不会打开由遍历到达的blob,而只会打开由“want”行直接请求的一个。
因此,如果正确应用,这个新功能在实践中不应该使任何事情变得不安全。


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