Py学习  »  Git

Linux 内核 Git 历史记录中,最大最奇怪的提交信息是这样的

Linux爱好者 • 5 年前 • 491 次点击  

(点击上方公众号,可快速关注)


编译:伯乐在线/可乐



我们通常认为 git merges 有两个父节点。例如,由我写的最新的 Linux 内核合并操作是提交2c5d955,这是 4.10-rc6 版本发行前准备工作的一部分。它有两个父节点:


2c5d955 Merge branch 'parisc-4.10-3' of ...

|

*- 2ad5d52 parisc: Don't use BITS_PER_LONG in use ...

*- 53cd1ad Merge branch 'i2c/for-current' of ...


Git 还支持章鱼式的合并,这意味着可以有超过两个父节点的合并。这对于我们那些从事小型项目开发的人来说,这似乎很奇怪:与三四个父节点合并会不会令人感到困惑?这得看情况而定。有时候,一个内核的维护者需要一次同时合并几十个单独的历史记录。一个接着一个的30个合并提交比起单独的一个30路(30个父节点)合并更加令人困惑,特别是当30路合并没有冲突的时候。


章鱼式合并可能比你想象地更常见。在内核的提交历史记录中有649,306个提交。其中 46,930 (7.2%) 个提交是合并提交。在合并提交中,有 1,549 (3.3%) 是章鱼式合并。(截止到我当前的 git HEAD 指向的提交 566cf87 。)


$ git log --oneline | wc -l

   649306

$ git log --oneline --merges | wc -l

   46930

$ git log --oneline --min-parents=3 | wc -l

    1549


作为比较,Rails 的所有提交中的 20% 是合并提交 (12,401/63,111),但没有一个章鱼式合并。Rails 大概更能代表一般的工程; 我认为大多数的 git 用户甚至都不知道章鱼式合并。


现在,显而易见的问题是: 这些章鱼式合并的规模有多大? 在这里每行开头的 “>” 是续行符;这个命令写了总共5行。这篇文章中的所有命令都是我在做实验的时候输入到终端里面的,所以它们未必容易看懂。我对于结果更感兴趣,贴出代码只是为了满足那些好奇的人。


$ (git log --min-parents=2 --pretty='format:%h %P' |

>  ruby -ne '/^(w+) (.*)$/ =~ $_; puts "#{$2.split.count} #{$1}"' |

>  sort -n |

>  tail -1)

66 2cde51f


66 个父节点!这么多的父节点,这个提交到底发生了什么?



这让许多历史记录可视化工具都无法正常运行,引出了 Linus Torvalds 的一个回应:


我刚刚从 Takashi 那收到了一些消息,因此我看到了你的合并提交 2cde51fbd0f3 。这个提交有 66 个父节点。


[…]


它被拉取(pulled)了,并且状况良好,但显然它在 “章鱼式合并很好” 和 “上帝” 之间做到了平衡,这不是一个章鱼式合并,这是一个克苏鲁(一个章鱼头人神的巨人)式的合并。


正如我所看到的,这个有66个父节点的不同寻常的提交在某种程度上只是对于ASoc代码修改的正常合并。ASoc 代表了芯片上的ALSA系统。ALSA系统是音频子系统;“单片系统是集成在单片硅芯片上计算机的术语。综上所述,ASoc 是对嵌入式设备的声音支持系统。


那么这样的合并多久会发生一次呢?永远都不会发生。规模排第二的合并是 fa623d1 ,它仅仅有 30 个父节点。然而,因为足够的背景知识,从 30 到 66 之间的巨大差距并不会令人感到惊讶。


一次 git 提交的父节点的数量大概是一种单侧分布(通常非正式的说法是幂律分布,因为这里对这个不感兴趣,所以不必严格正确)。软件系统的许多属性都属于单侧分布。等一下;我将会生成一个图表来确定…(大量严格的图表确定了)。是的,它确实是单侧分布:



简单地说来, “单侧分布”意味着小事件比大事件多得多,而且大事件的最大规模是没有界限的。内核的提交历史中包含了 45,381 个两个父节点的合并,但仅仅有一个 66个父节点的合并。假如考虑足够多的开发历史记录的话,我们可能会看到多于66个父节点的合并。


单个函数或是单个模块的代码行数也是单侧分布(大多数函数和模块都很小,但是其中一些很大;想想 web app 中的 “User” 类)。同样,对于模块的变化率(大多数模块都不会经常变动,只有其中一些会不断改动;再想想 “User” 类)。这些分布在软件开发中无处不在,而且经常在下面的双对数坐标图中呈直线分布。


对于父节点的数量最大的提交,我们就讨论到这。那么在差异方面的合并又怎样呢?在差异方面,我的意思是被合并的两个分支之间的差异。我们可以通过简单地比较合并节点的父节点,然后统计它们差异的行数来衡量这一点。


例如,如果一个分支一年前从 master 分支分离出去,改变了一行代码,之后被合并回 master 分支,在这段时间内对于 master 分支的所有修改都会被统计到,同样分离出去的分支上的改变也会被统计到。我们可以引出更直观的差异概念,但因为 git 不会保留分支的元数据,所以它们很难或者说是几乎不可能计算出来。


在任何情况下,作为计算差异的起点,下面是最近内核合并的差异:


$ git diff $(git log --merges -1 --pretty='format:%P') | wc -l

     173


在英语中,这个命令的含义是这样的:“比较最近合并的两个父节点,然后统计差异的行数。”为了找出差异最多的合并,我们可以遍历每个合并提交,用类似的方法统计差异的行数。然后,作为一个测试,我们将搜索所有合并中恰好有 2,000 行差异的分支。


$ (git log --merges --pretty="%h" |

   while read x; do

     echo "$(git diff $(git log --pretty=%P $x -1) | wc -l)" $x

   done > merges.txt)

$ sort -n merges .txt | grep 'b2000b'

    2000 3d6ce33

    2000 7fedd7e

    2000 f33f6f0


(这个命令需要花费很长的时间运行: 我想大约需要12小时,尽管我已经减少了许多。)


我认为合并的差异大小遵循单侧分布,就像父节点数量的统计一样。所以它应该在双对数坐标图表中显示为一条直线。让我检查一下….对了:



我把差异的大小定在1000行左右(注:前面用2000行,得到的数据太少),否则没有足够的样本来生成有效的曲线。


右下角难看的原因部分是因为量化问题,另一部分是由于缺乏大量的提交导致样本数量较小,与之前的图表情况一样。


现在,显而易见的问题是: 提交历史中差异最大的合并是哪个?


$ sort -n merges.txt | tail -1

22445760 f44dd18


22,445,760 行差异!这看起来根本不可能这么大-因为差异的行数比整个内核源代码的行数都大。


Greg Kroah-Hartman 在2016年9月19做了这一提交,当时正处在 4.8-rc6 版本的开发期间。Greg 是 Linus Torvalds 的 “副官” 之一 – 他(Linus)最亲近,最值得信赖的开发者。简单地说,副官们构成了内核 pull request 树中的第一层。Greg 负责维护内核的稳定分支,驱动程序内核,USB 子系统和其他几个子系统。


在更加仔细的研究这个合并之前,我们需要一点背景知识。通常我们把合并作为菱形分支模式(先分支,然后合并,见下图)的一部分:



在2014年,Greg 开始在一个新的仓库开发 Greybus (移动设备总线),这就好像是他创建了一个全新的项目一样。最后,Greybus 的开发工作完成时,它就被合并到了内核中。但因为它是从一个崭新的仓库开始的,所以它和内核的中的其他源代码没有共同的历史记录。所以除了2005年我们公认的初始提交之外,这个合并为内核又添加了一个 “初始提交”。这个仓库现在有两条独立的初始提交,而不是通常的菱形分支模式:



通过查看合并提交的两个父节点中分别存在多少文件,我们可以看到一些蛛丝马迹:


$ git log -1 f44dd18 | grep 'Merge:'

Merge: 9395452 7398a66

$ git ls-tree -r 9395452 | wc -l

   55499

$ git ls-tree -r 7398a66 | wc -l

     148


一条分支存在大量的文件,因为它包含了整个内核的源文件。而另一条仅仅包含了很少的文件,因为它包含的只是 Greybus 的历史记录。


像章鱼式合并一样,这会让一些 git 用户感到奇怪。但是内核开发人员是专家级的 git 用户,并倾向于放弃使用这个功能,但绝对不是盲目的放弃


最后一个问题:这种情况到底发生了多少次?内核中有多少独立的 “初始化” 提交?事实上是四次:



如果我们要画出这些提交,为了清楚起见,我们忽略所有其他的历史记录,如下图:



这四个提交中的每一个都是离当前内核版本库 HEAD 节点很遥远的祖先节点,并且都没有父节点。从 git 的角度来看,内核历史“开始”了不同的四次,所有的这些提交最终都被合并在一起。


这四个提交中的第一个(在我们输出的底部)是2005年的初始化提交,也就是我们通常认为的初始化提交。第二个是文件系统 btrfs 的开发,它是独立仓库完成的。第三个是 Greybus,同样也是独立仓库完成的,我们之前已经说过。


第四个初始化提交,a101ad9,很奇怪,正如下面看到的:


$ git show --oneline --stat a101ad9

a101ad9 Share upstreaming patches

README.md | 2 ++

1 file changed, 2 insertions(+)


它刚创建了一个 README.md 文件。但随后,它就立即被合并到正常的内核历史的提交 e5451c8 中了!


$ git show e5451c8

commit e5451c8f8330e03ad3cfa16048b4daf961af434f

Merge: a101ad9 3cf42ef

Author: Laxman Dewangan <ldewangan@nvidia.com>

Date:   Tue Feb 23 19 :37:08 2016 +0530


为什么有人会创建一个只包含两行文本的 README 文件的初始化提交,然后立即合并到主线的历史记录中呢?我想不出任何理由,所以我怀疑这是一个意外!但它没有造成任何危害;它只是很奇怪。(更新:这是个意外,Linus用他一贯的方式回应了。)


顺便提一句,这也是历史记录中差异数量排第二的提交,仅仅因为它是一个不相关提交的合并,就像我们仔细研究过的 Greybus 的合并一样。


现在你知道了:Linux 内核的 git 历史记录一些最奇怪的事情。一共有 1,549 个章鱼式合并,其中有一个是拥有 66 个父节点的提交。差异最多的合并有 22,445,760 行差异,尽管它有点技术性因为它和仓库的其他部分没有公共的历史记录。内核拥有四个独立的初始化提交,其中一个是失误导致的。尽管上面的这些都不会出现在绝大多数的 git 仓库中,但是所有的这些功能都是在 git 的设计范围之内的。



【关于译者】


可乐:本科在读,对 python、linux、安全很感兴趣,希望能够阅读国外最新的技术新闻,也希望能够翻译一些文章帮助到别人




【关于投稿】


如果大家有原创好文投稿,请直接给公号发送留言。


① 留言格式:
【投稿】+《 文章标题》+ 文章链接

② 示例:
【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/

③ 最后请附上您的个人简介哈~



看完本文有收获?请分享给更多人

关注「Linux 爱好者」,提升Linux技能


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/OxPT54pjUR
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/22171
 
491 次点击