Git的半秘密空树对象可靠吗?为什么没有一个符号名称来表示它?

150

Git有一个众所周知(至少是比较出名的)的空树,其SHA1为:

4b825dc642cb6eb9a060e54bf8d69288fbee4904

您可以在任何存储库中看到这个,甚至是新创建的存储库,使用git cat-file -tgit cat-file -p命令。[2020年进行了编辑:SHA-256空树哈希ID为:

6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321

正如VonC在他的回答中提到的那样,我的问题显然比较早,大约提前了8年。

如果您努力工作并非常小心,可以使用此空树来存储没有文件的目录(请参见如何向git存储库添加空目录的答案),但这不是一个好主意。

它更有用作为git diff-tree的一个参数,其中一个示例钩子执行此操作。

我想知道的是:

  1. 这个方法有多可靠-即将来的git版本是否会没有编号为4b825dc642cb6eb9a060e54bf8d69288fbee4904的git对象?
  2. 为什么没有空树的符号名称(或者有吗)?

(创建符号名称的一种快速而简单的方法是将SHA1放在.git / Nulltree中。不幸的是,您必须为每个存储库都这样做。似乎最好只是将魔术数字放入脚本等中。我只是普遍厌恶魔术数字。)


5
记得哈希值;-) 使用SHA1("tree 0\0") = 4b825dc642cb6eb9a060e54bf8d69288fbee4904 (\0 是NUL字符) - Thomas
8
@Thomas:从下面VonC的答案中提到的git hash-object -t tree /dev/null方法具有不硬编码SHA-1的优点,以防未来某个版本的Git切换到SHA-2等其他哈希算法。(我不会预测何时会发生这种情况。 :-) 这将更容易地将Mercurial切换到SHA-2,因为他们为此留出了余地。) - torek
当然,你是正确的,但这是一段有用的“无用知识”,而且对其他人来说可能会有帮助! - Thomas
2
@Thomas:看起来哈希算法的更改可能会比预期的时间早发生。 :-) - torek
说到“Git的某个未来版本”,我认为你会对我最新(2017年12月)对我2012年回答的修改感兴趣:https://stackoverflow.com/revisions/9766506/7 - VonC
3
注意:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 将很快成为新的 4b825dc642cb6eb9a060e54bf8d69288fbee4904 空树哈希。请参见下面我编辑过的答案 - VonC
3个回答

128

这个帖子提到:

如果您不记得空树的SHA1值,您可以通过以下方式推导出它:

git hash-object -t tree /dev/null

Or, as Ciro Santilli proposes in the comments:

printf '' | git hash-object --stdin -t tree

或者,如这里所示,来自Colin Schimmelfing

git hash-object -t tree --stdin < /dev/null

所以我想最好是用该命令的结果定义一个变量作为您的空sha1树(而不是依赖于“众所周知的值”)。

注意:Git 2.25.1(2020年2月)在commit 9c8a294中提出:

empty_tree=$(git mktree </dev/null)

# Windows (Command Prompt):
git mktree <NUL

# Windows (PowerShell):
$null | git mktree

并补充道:

作为历史记录,现在被称为repo_read_object_file()的函数在346245a1bb(“hard-code the empty tree object”,2008-02-13,Git v1.5.5-rc0 -- merge)中学习了空树。现在被称为oid_object_info()的函数在c4d9986f5f("sha1_object_info: examine cached_object store too",2011-02-07,Git v1.7.4.1)中学习了空树。


注意,当作者想要其第一次提交为空时(请参见博客文章“如何初始化我的Git存储库”),您将在某些GitHub仓库中看到SHA1弹出。
$ GIT_AUTHOR_DATE="Thu, 01 Jan 1970 00:00:00 +0000" GIT_COMMITTER_DATE="Thu, 01 Jan 1970 00:00:00 +0000" git commit --allow-empty -m 'Initial commit'

将会给你:

Empty tree SHA1

(看到树的SHA1了吗?)
你甚至可以将现有的历史记录重新基于那个空提交(参见“git:如何将提交插入为第一个,移动所有其他提交?”)。
在这两种情况下,您不依赖于该空树的确切SHA1值。您只需遵循最佳实践,使用第一个空提交初始化您的存储库即可。

要做到这一点:

git init my_new_repo
cd my_new_repo
git config user.name username
git config user.email email@com

git commit --allow-empty -m "initial empty commit"

这将生成一个与您的repo、用户名、电子邮件和创建日期特定的SHA1提交(这意味着每次提交本身的SHA1将不同)。但是,由该提交引用的树将是4b825dc642cb6eb9a060e54bf8d69288fbee4904,即空树SHA1。
git log --pretty=raw

commit 9ed4ff9ac204f20f826ddacc3f85ef7186d6cc14
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904      <====
author VonC <vonc@laposte.net> 1381232247 +0200
committer VonC <vonc@laposte.net> 1381232247 +0200

    initial empty commit

要显示单个提交的树形结构(显示提交树SHA1):

git show --pretty=format:%T 9ed4ff9ac204f20f826ddacc3f85ef7186d6cc14
4b825dc642cb6eb9a060e54bf8d69288fbee4904

如果那个提交是你的第一个提交,并参考了一个空树,你可以用以下命令显示该空树的SHA1值:
git log --pretty=format:%h --reverse | head -1 | xargs git show --pretty=format:%T
4b825dc642cb6eb9a060e54bf8d69288fbee4904

甚至在Windows上也可以使用Gnu On Windows命令


如下文所述,使用git diff <commit> HEAD命令,将显示当前分支HEAD中所有文件的差异:

git diff --name-only 4b825dc642cb6eb9a060e54bf8d69288fbee4904 HEAD

注意:空树值在cache.h中有正式定义。
#define EMPTY_TREE_SHA1_HEX \
    "4b825dc642cb6eb9a060e54bf8d69288fbee4904"

自 Git 2.16 (2018 年第一季度) 起,它被用于一个不再仅仅与 SHA1 相关的结构中,如 commit eb0ccfd 所示:

切换空树和 blob 查找以使用哈希抽象

empty_tree_oidempty_blob_oid 的使用切换为使用 current_hash 抽象,该抽象表示当前正在使用的哈希算法。

查看更多信息,请参见 "Why doesn't Git use more modern SHA?":自 Git 2.19 (2018 年第三季度) 起,它是 SHA-2

随着 Git 2.25(2020年第一季度)的到来,测试正在为 SHA-2 过渡 做准备,并涉及空树。

请查看commit fa26d5ecommit cf02be8commit 38ee26bcommit 37ab8ebcommit 0370b35commit 0253e12commit 45e2ef2commit 79b0edccommit 840624fcommit 32a6707commit 440bf91commit 0b408cacommit 2eabd38(2019年10月28日),以及commit 1bcef51commit ecde49b(2019年10月5日)由brian m. carlson (bk2204)提交。
(由Junio C Hamano -- gitster --commit 28014c1中合并,2019年11月10日)

t/oid-info: 添加空树和空blob值

Signed-off-by: brian m. carlson

测试套件最终将学会如何使用除SHA-1之外的算法运行。为此,需要教授test_oid函数族如何查找空blob和空树值,以便可以使用它们。

因此,t/oid-info/hash-info现在包括:

rawsz sha1:20
rawsz sha256:32

hexsz sha1:40
hexsz sha256:64

zero sha1:0000000000000000000000000000000000000000
zero sha256:0000000000000000000000000000000000000000000000000000000000000000

algo sha1:sha1
algo sha256:sha256

empty_blob sha1:e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
empty_blob sha256:473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813

empty_tree sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904
empty_tree sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321

SHA2 "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321" 是新的 SHA1 "4b825dc642cb6eb9a060e54bf8d69288fbee4904" 空树。

@torek:我已经添加了一些示例来阐述首个空提交的最佳实践,以说明空树SHA1。 - VonC
1
如果你在 git hash-object 命令中传递 -w 参数,它会在当前的代码库中创建对象。如果将来这个空树丢失了,那么在你运行的代码库中就会重新创建一个空树。 - javawizard
1
或者,如果您喜欢使用管道而不是/dev/null的魔法:printf '' | git hash-object --stdin -t tree :) - Ciro Santilli OurBigBook.com
我并没有为你的答案添加太多内容,只是提供一个FYI,另一种推导它的方法是 git mktree < /dev/nullgit hash-object 和这个命令产生相同的结果。 - nonsensickle
@Ogen(和@VonC):git diff <options> <hash>比较给定的哈希值(可以是空树)与当前工作树,而不是与HEAD提交进行比较。对于后者,您需要使用git diff <options> <hash> HEAD - torek
显示剩余7条评论

5
这是如何在存储库不为空的情况下创建空树提交的答案。 https://dev59.com/WWUq5IYBdhLWcg3wXvMC#14623458 但我更喜欢将“empty”作为标签,而不是分支。简单的方法是:
git tag empty $(git hash-object -t tree /dev/null)

因为标签可以直接指向树形结构,而不需要提交。现在获取工作树中的所有文件:
git diff --name-only empty

或者使用stat:

git diff --stat empty

所有文件的差异:

git diff empty

检查所有文件中的空格:
git diff --check empty

...但是在标签创建中使用魔数只是掩盖了问题的本质(使用魔数SHA-1)。 - Romain Valeri
不是这样的。我使用标签来指向树状对象。现在,这个树状对象由SHA-1定义,在未来它可以被更改,例如更改为SHA-256等(通过存储库迁移)。但标签将保持不变。:) 标签的主要特点是指向对象。标签可以在内部使用SHA-1或其他内容,这只涉及Git内部的问题。 - Olleg
我理解你的意思。但是,如果你(或任何阅读此内容的人)(或者一个脚本,更糟的是)在以后尝试应用它(你的第一行),它可能会在新的哈希算法上失败,而将你的第一行替换为一个已执行的表达式(生成此哈希)将继续成功。 - Romain Valeri
如果将此与自动生成空树哈希的方法之一相结合,您可以使其具有未来兼容性(如@RomainValeri所建议的)。但是,如果由我决定,git rev-parse 将拥有新的标志或关键字等内容,以生成(a)空树哈希和(b)null-commit哈希。这两者都对脚本非常有用,并可防止提议中的SHA-256更改。 - torek
好的,已更改。但这不会是“最简单的方法”。 :) - Olleg
显示剩余3条评论

4
我写了一篇博客文章,介绍了两种不同的方法来查找哈希值:http://colinschimmelfing.com/blog/gits-empty-tree/。如果由于某种原因发生更改,您可以使用下面的两种方法来查找它。然而,我相信在.bashrc别名等中使用哈希值是非常可靠的,我认为它不会很快改变。至少它可能会是Git的一个重大发布。
这两种方法是:
1.上面的答案:git hash-object -t tree --stdin < /dev/null 2.只需初始化一个空仓库,然后在该新仓库中运行git write-tree - git write-tree将输出哈希值。

使用 --stdin 命令运行时,git 2.7.2 给出 fatal: Cannot open '--stdin': No such file or directory 错误。 然而,像 VonC 的回答中那样不带 --stdin 运行可以得到哈希值。 - sigy
这个回答现在并不是很有用,因为博客文章已经失效了。这就是为什么我们通常不赞成在SO上使用这些答案的原因。 - Philip Whitehouse
2
@PhilipWhitehouse,博客文章并没有死亡,但无论如何我在我的回答中包括了两种方法 - 我同意如果不包括这两种方法,那将不是一个好的答案。 - schimmy
1
参考资料:git的空树 @@ https://web.archive.org/web/20220718025749/http://colinschimmelfing.com/blog/gits-empty-tree - rivy

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