返回 登录
0

Git 2.11发布

Git
阅读4143

原文Git 2.11 has been released
作者:peff 翻译赖信涛 责编:仲培艺

开源项目Git发布Git 2.11.0,带来了很多新特性和bugfixs,有70多位贡献者参与。下面是一些闪亮的新特性。

SHA-1名字的缩写

Git 2.11打印的SHA-1名字更长了,还提供了处理模糊SHA-1短名更好的工具。

你可能已经注意到,Git是用16进制的字符串作为Git Object的id,像66c22ba6fbe0724ecce3d82611ff0ec5c2b0255f这样。他们是由SHA-1哈希函数产生的,一共160位,40个字符。两个SHA-1名字相同的概率和你在未来八年之内被闪电击中的概率一样,所以一点也不用担心会碰到两个对象id相同的情况。

你也可能已经注意到,这么长的id不方便使用、键入甚至复制粘贴。为了克服这个缺点,Git提供一种缩写形式,只需要7位就可以了(例如665cas3)。你可以在Git命令中使用id的缩写形式。但不幸的是,这么短的名字有很大的几率重复,尤其是在大项目中,动辄上万个对象。

这个问题的解决方案是,当使用缩写名字时,Git去检测重复,默认从7位开始,每次加一位,直到这个id能确定仓库中唯一一个对象。同样,当你指定一个SHA-1缩写名字时,Git会确认这个id能确认唯一一个对象。

好了,万事大吉。Git已经这样用了很多年了,那么还有什么问题呢?

还存在的问题是,仓库随着开发会不断增长,也会增加越来越多的对象。某个id今天能指定唯一的对象,明天就不一定了。如果你在某个bug report或commit信息中引用了SHA-1短名,随着项目增长,可能会变得有歧义。尤其是像Linux Kernal这样的仓库,已经有了超过五百万个对象,这意味着在12位之内出现冲突的几率很高。一些旧的引用已经出现歧义了,比如这里,不能正确使用像git show这样的命令。

为了解决这个问题,Git 2.11带来了系列优化。

首先,id缩写的最小长度会和仓库的对象数量成比例。这也并不是万无一失的,随着项目成长,对象的数量可能迅速增多。如果你用Git管理过中型的项目,通过git log --oneline这样的命令会发现id在不断增长。source

但是这依然没有解决遇到有歧义的SHA-1简写的问题。为此,Git 2.11提供了两个特性。第一,Git不会简单地提示错误信息,而是打印出可能的对象列表,附带一些细节信息。这些信息足够你选择想要的那个对象了。source

当然,如果Git能自动识别那个你想要的对象就更棒了。之前,Git能学习通过内容推断你想要的对象是哪个。比如,git log可以查看一个commit(或者指向commit的tag)。但是其他命令,比如git show,对象不仅有commit;这就没有内容来猜测对象了。现在,你可以通过core.disambiguate来设置一个你偏好的类型。source

性能优化

Git的一个目标就是速度。有很多优化来自于重新设计,代码中也有很多要优化的地方。几乎所有Git的版本更新都带来新的优化选项,2.11也不例外。下面就来看一下几个优化的地方。

递增链

Git 2.11在数据库中读取递增链(Delta chains)更快了,这次在操作速度上有了很大提升。为了了解其中的原理,首先我们先来看一下什么是递增链。

你可能已经知道,Git的原则是避免重复存储一份相同的文件,因为所有的对象都是用SHA-1基于内容命名的。但是在版本控制系统中,文件一般都是不同的(比如,即使文件只修改了一小部分,也是不同的)。Git将这些相关的对象存储为“deltas”:选择其中的一个对象作为基础(base),其余对象用链的形式存储修改的指令,比如“删除50-100行”,“在50的位置添加新内容”等。这样做的结果是,deltas只占据了一小部分空间,Git所占空间随着changes的增加线性增加,和所有版本的大小无关。

版本控制中,文件总是在不断变化。最优的base一般都是相邻的版本。如果base本身也是一个delta,那么就构成一个delta chain(递增链):版本2是基于版本1的delta,版本3是基于版本2的delta,以此类推。问题是,使用这些递增链取出对象的代价很大,例如,如果要得到版本3就要重构版本1和2.随着链的深入,重构中间的一个版本代价非常高。

为此,Git通常将链的深度限定为50个对象。然而,当使用git gc --aggressive重新打包时,限制会变成250,生成体积更小的包。但是这个数字挑得有点随意,权衡CPU的速度和节省空间,50是最好的选择,于是在Git 2.11中这个数字被定为默认值。source

即使是只有50个deltas,也是很大的开销。为了降低影响,Git缓存最近构建的对象。这个方案效果很好,因为deltas及其bases一般都离得很近,像git log这样的命令经常用到最近用过的对象。缓存的大小可以自定义,也随着近几年计算机的内存不断增长而增加。但是由于缓存使用一种简单的数据结构,Git保存的内容比它实际的保存能力要少,会及时清除过期缓存。

在Git 2.11中,delta base缓存经过彻底的大修。不仅性能更好(大型仓库10%的性能提升),而且随着对core.deltaBaseCacheLimit容量(默认96M)设置更大,性能也会明显提升。在极端情况下,将容量设置为1G,Linux内核仓库的性能可以提升32%。[sourcesource]

对象查找

delta base的优化让我们得到独立对象的速度更快了,但是在拿到对象之前,要做的是“找到对象”。Git 2.11优化了在多个packfiles的情况下找到对象的效率。

当你有很多对象时,Git会将它们打包成一个“packfiles”:一个包含多个对象的文件,带有索引,优化查询。通过packfiles,仓库进行fetching和pushing能提高速度。随着一天天的使用,packfiles的数量可能变大,直到下一次重新打包的时候,将他们放进一个单独的pack。虽然在单个的packfile中查找对象的效率很高,但是当有很多packfiles时,Git必须做线性搜索,检查每个packfile来寻找所要的对象。

之前,Git的做法是将最后找到目标对象的包放到缓存中,来降低线性查找的代价。这样做有一定的作用,因为大多数要查找的对象都是跟时间相关的,packfiles根据时间来选择性缓存,在下次查找时,相同的地方再次找到的概率很大,就不必再去检查其他的pack了。

在Git 2.10中,使用一种存储最近使用(most recently used,MRU)的数据结构替换“最后找到的pack”缓存,来替换之前的“缓存最后找到的pack”。加快了对象的读取速度,尤其在packs数量很大时,提升非常明显。

在Git 2.11中,MRU策略被应用于重新打包操作。之前,重新打包甚至不支持缓存“最后找到的pack”。MRU策略在重新打包方面对速度的提升更明显;重新打包1000多个pack的Linux Kernel仓库,速度提升了70%。source source

Patch IDs

Git 2.11对“patch IDs”的计算进行了优化,git rebase非常依赖patch IDs的计算。

patch IDs是一个commit所做的更改的“指纹”。使用patch ID,可以找到重复的commits(两个commit在历史的不同位置,但是带来的更改是相同的。)rebase命令使用patch IDs来找预警合并到upstream的commits。

Patch IDE计算现在忽略merge和rename,运行时间在某些情况下可以提高50倍。[source source]

高级过滤处理

Git有一个“过滤”中间件,可以在文件内容和文件系统存储之间转换。这也是Git的行结束符转换的实现方式,但是它也可以运行外部程序。Git LFS系统可以通过注册自己的过滤程序来hook Git。

Git用来和过滤程序通讯的协议非常简单。它对每一个单独的文件执行过滤器,将内容传给过滤器,从过滤器拿回结果。如果你有大量的文件需要过滤,启动进程的开销可能会很大,而且期间要一直占据资源(例如HTTP链接),不和其他程序共享。

Git 2.11新增了一种过滤器,复杂度高了一些,但是拥有在单一进程内过滤大量文件的能力。最多可以在checkout大量Git LFS对象时提速80%。

旧的协议依然可以正常使用,新的协议也具有可扩展性。经过讨论之后,决定将它设为异步操作。[source]

杂项

  • 在我们关于Git 2.9的文章中,我们提到了diff算法的一些改进,使结果更容易阅读(–compaction-heuristic选项)。该算法没有被设置为默认,因为一些特殊情况下,它不能很好地处理。但是经过一些非常彻底的分析,Git 2.11有了一个改进算法,它的行为和之前类似,但涵盖更多情况,没有任何回归。新选项以–indent-heuristic(和diff.indentHeuristic)命名,并且可能会成为未来版本Git的默认值。[suorce]

  • 想要知道通过merge commit引入的commit?Git现在能理解负数父选择器,它可以排除父分支的提交(正数是选择),这项操作可能花费一分钟的时间,但这意味着git log 1234abcd^-1这样的命令能排除原分支已有的commit,显示出通过merge带来的commit了。你也可以通过^-(排除·)作为^-1的简写。[source。]

  • 新增了一个凭据助手,contrib/,可以用GNOME libsecret存储Git密码。[source]

  • git diff命令现在理解--submodule=diff参数了(使diff应用diff.submodule),可以将changes作为一个实际的patch在两个子模块间显示。[source]

  • git status有一个新的机器可读的输出格式,更容易解析和包含更多的信息。如果你对Git的脚本有兴趣,可以试试看。[source]

  • 继续进行将Git的shell脚本改写成C程序的工作,这可以极大地提高跨平台执行(例如Windows)的性能(特别是在可能调用循环子程序的程序中)。[sourcesource]

总结

这只是Git 2.11的一部分,此版本一共包含650次commits。完整列表可参考release notes

讨论

mixedmath:这是一份很好的部分发布说明集。我想去看完整的发行说明,我认为这些版本发布说明都写得很好。让人印象深刻,特别还考虑到是来自Git这样的巨大贡献者。

godson_drafty说:纠正一下:被闪电击中的概率是~ 1/1,000,000每年,被杀死的概率是1/10,000,000,或者说大约1/2^23.25。根据这些数据,sha1hash遇到冲突的概率应该是和6.8年遭遇雷击的概率相同。


130+位讲师,16大分论坛,中国科学院院士陈润生、滴滴出行高级副总裁章文嵩、联想集团高级副总裁兼CTO芮勇、上交所前总工程师白硕等专家将亲临2016中国大数据技术大会,票价折扣即将结束,预购从速

图片描述

评论