在给定的代码库中,通常需要多少个Git SHA字符来唯一标识更改?

298
如果你想要构建一个目录结构,并将目录名称命名为Git存储库中的提交,使其既短小精悍又不容易冲突,那么通常需要多少个SHA子字符串呢?
假设我想唯一地标识此更改:https://github.com/wycats/handlebars.js/commit/e62999f9ece7d9218b9768a908f8df9c11d7e920 我可以只使用前四个字符:https://github.com/wycats/handlebars.js/commit/e629
但我觉得这样有风险。但是,假设在几年时间内,代码库可能会有大约30,000个更改,如果我使用8个字符或12个字符,发生冲突的机率是多少?有没有一个通常认为可接受的数字来解决这类问题?

相关链接:https://dev59.com/GFwY5IYBdhLWcg3wno8r - jub0bs
5个回答

324

实际上,这个问题已经在《Pro Git 书籍》的第7章中得到了回答:

通常,8到10个字符足以在一个项目中保持唯一性。最大的Git项目之一——Linux内核,开始需要使用12个字符来保证其唯一性。

7位数字是Git默认的短SHA,对于大多数项目来说这是可以接受的。内核团队已经多次增加它们的短SHA长度,正如上面提到的,因为他们有几十万次提交记录。因此,对于您的~30k提交记录,8或10位数字应该完全足够。


58
还要注意,当涉及到这一点时,git非常智能。您可以将缩写设置为短的长度,比如4位,git将使用4位数字来表示尽可能多的哈希值,但是当它知道缩写不唯一时会切换到5位或更多... - twalberg
51
注意,这只适用于 Git 打印 SHA 的那一刻。如果您“保存”缩写的 SHA(例如在日志、电子邮件、即时消息等中),并且稍后使用它们来引用提交,则它们可能不再是唯一的!尽管对于常规长度(如7-12个字符)几乎不可能,但如果您将长度降至4或5,并且有几万个新对象(或提交,具体取决于上下文),则这确实可能会给您带来麻烦。 - Nevik Rehnel
8
“7位数字是短SHA的默认值”。从某种意义上说,这是正确的,但可能会让人们认为除非另有指示,否则git将始终使用7位数字缩写。实际上,git(现在)根据仓库中对象的数量动态计算短SHA(https://dev59.com/DGMl5IYBdhLWcg3wyJYe#21015031)。默认假设为7位数字(我的错)已经导致了我程序中的错误。 - LarsH

203
注意:你可以使用 git rev-parse --short 来获取最短且唯一的 SHA1。
请参见 "从常规哈希中获取 git 短哈希"
git rev-parse --short=4 921103db8259eb9de72f42db8b939895f5651489
92110

正如你在我的示例中看到的那样,即使我指定了长度为4,SHA1的长度仍为5。
对于大型仓库而言,自2010年以来7位数已经不够用了,Linus Torvalds本人在git 1.7.4.4(2010年10月)提交的commit dce9648:

默认值7是从git开发早期开始的,当时七个十六进制数字已经很多了(它涵盖了大约250多万个哈希值)。
那时候我认为65k次修订已经很多了(这是我们即将在BK中达到的),每个修订版本通常会有5-10个新对象左右,所以一百万个对象是一个很大的数字。

(BK = BitKeeper)

这些天,内核甚至不是最大的git项目,即使内核也有大约220k个修订版本(比BK树大得多),我们接近200万个对象。此时,七个十六进制数字对于其中很多对象仍然是唯一的,但当我们仅谈论对象数量和哈希大小之间的两个数量级差异时,截断哈希值将会发生冲突。它现在甚至不再接近不现实——它经常发生。
我们应该增加默认abbrev的大小,因为它过于不切实际,并在git配置文件中添加一种方法,让人们可以针对每个项目设置自己的默认值。
core.abbrev

设置对象名称缩写的长度。
如果未指定,许多命令会缩写为7个十六进制数字,这可能不足以使缩写的对象名称在足够长的时间内保持唯一。

environment.c:

int minimum_abbrev = 4, default_abbrev = 7;

注意:正如下面评论中 marco.m所指出的,core.abbrevLength在Git 1.7.4.4中已更名为core.abbrev,请参考提交a71f09f

core.abbrevlength重新命名为core.abbrev

毕竟它对应于--abbrev=$n命令行选项。


最近,Linus在Git 2.11(2016年第四季度)中添加了提交e6c587c(如Matthieu Moy答案所述):

在早期,我们决定将对象名称缩写为7个十六进制数字,但随着项目的增长,越来越有可能看到这样的短对象名称在早期制作并记录在日志消息中不再唯一。目前,Linux内核项目需要11到12个十六进制数字,而Git本身需要10个十六进制数字才能唯一地标识它们拥有的对象,而许多较小的项目可能仍然可以使用原始的7个十六进制数字默认值。一种尺寸并不能适用于所有项目。引入一种机制,在第一次请求使用默认设置缩写对象名称时估计存储库中的对象数量,并为存储库提供合理的默认值。根据预期,当使用缩短到第N位的对象名称时,我们会在具有2^(2N)个对象的存储库中看到冲突,因此使用足够数量的十六进制数字来覆盖存储库中的对象数量。我们添加到缩短名称中的每个十六进制数字(4位)使我们能够在存储库中拥有四倍(2位)的对象。

请参见提交记录e6c587c(2016年10月1日),作者为Linus Torvalds(torvalds
请参见提交记录7b5b772提交记录65acfea(2016年10月1日),作者为Junio C Hamano(gitster
(在提交记录bb188d0中由Junio C Hamano -- gitster --合并,于2016年10月3日)

那个新属性(猜测SHA1缩写值的合理默认值)直接影响Git计算其发布版本号的方式。

7
这个答案提供了一种检查单个代码库中最长的“缩短”哈希值的方法:https://dev59.com/GFwY5IYBdhLWcg3wno8r#32406103。 - Kyle Strand
1
请注意,core.abbrevLength 已更名为 core.abbrev - marco.m
1
我想补充一下,你可以运行 git rev-parse --short=10 --verify HEAD 来生成 10 个字符。我们之前使用的是 git log -1 --format=%h,但那只生成了 7 个字符,导致了冲突。 - grayaii
感谢您的解释,文档(https://git-scm.com/docs/git-rev-parse)已经过时。 - André Werlang
@AndréWerlang 你的意思是链接不再有效吗?还是它的内容已经过时了? - VonC
显示剩余3条评论

61

这被称为生日问题。

对于概率小于1/2的情况,碰撞的概率可以近似表示为

p ~= (n2)/(2m)

其中n是项目数量,m是每个项目的可能性数量。

十六进制字符串的可能性数量为16c,其中c是字符数。

因此,对于8个字符和30K次提交

30K ~= 215

p ~= (n2)/(2m) ~= ((215)2)/(2*168) = 230/233 = ⅛

将其增加到12个字符

p ~= (n2)/(2m) ~= ((215)2)/(2*1612) = 230/249 = 2-19


1
正是我想要解决的问题,谢谢!@Messa回答中链接的概率表也很有帮助。 - Kyle Chadha
2
太棒了,我们需要的不仅仅是这样的内容,还需要更多类似的。请不仅解释它是什么,还要解释它是如何实现的。 - http8086

16

这个问题已经得到了解答。但是,对于任何寻找背后数学原理的人,它被称为“生日悖论”(维基百科)。

它涉及到从N个人中选出的两个(或更多)人在同一天过生日的概率,与具有总共N个提交的代码库中,两个(或更多)git提交具有相同长度为X的哈希前缀的概率是类似的。

查看概率表。例如,在哈希十六进制字符串长度为8时,当代码库只有大约9300个项(git提交)时,发生冲突的概率达到1%。对于110,000次提交,概率为75%。但是,如果您使用哈希十六进制字符串长度为12,则在100,000个提交中发生冲突的概率低于0.1%。


2

2
这种方法的问题在于大型项目通常是从小型项目开始的。 - plugwash

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