我们通常认为 git merges 有两个父节点。例如,由我写的最新的 Linux 内核合并操作是提交2c5d955,这是 4.10-rc6 版本发行前准备工作的一部分。它有两个父节点:
2c5d955Merge branch'parisc-4.10-3'of...
|
*- 2ad5d52parisc: 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)
662cde51f
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" |
whilereadx;do
echo"$(git diff $(git log --pretty=%P $x -1) | wc -l)"$x
done > merges.txt)
$sort -nmerges.txt | grep'b2000b'
20003d6ce33
20007fedd7e
2000f33f6f0
(这个命令需要花费很长的时间运行: 我想大约需要12小时,尽管我已经减少了许多。)
我认为合并的差异大小遵循单侧分布,就像父节点数量的统计一样。所以它应该在双对数坐标图表中显示为一条直线。让我检查一下….对了:
我把差异的大小定在1000行左右(注:前面用2000行,得到的数据太少),否则没有足够的样本来生成有效的曲线。
右下角难看的原因部分是因为量化问题,另一部分是由于缺乏大量的提交导致样本数量较小,与之前的图表情况一样。
现在,显而易见的问题是: 提交历史中差异最大的合并是哪个?
$sort -nmerges.txt | tail -1
22445760f44dd18
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 -1f44dd18 | grep'Merge:'
Merge: 93954527398a66
$git ls-tree -r9395452 | wc -l
55499
$git ls-tree -r7398a66 | wc -l
148
一条分支存在大量的文件,因为它包含了整个内核的源文件。而另一条仅仅包含了很少的文件,因为它包含的只是 Greybus 的历史记录。
像章鱼式合并一样,这会让一些 git 用户感到奇怪。但是内核开发人员是专家级的 git 用户,并倾向于放弃使用这个功能,但绝对不是盲目的放弃
最后一个问题:这种情况到底发生了多少次?内核中有多少独立的 “初始化” 提交?事实上是四次:
如果我们要画出这些提交,为了清楚起见,我们忽略所有其他的历史记录,如下图:
这四个提交中的每一个都是离当前内核版本库 HEAD 节点很遥远的祖先节点,并且都没有父节点。从 git 的角度来看,内核历史“开始”了不同的四次,所有的这些提交最终都被合并在一起。
这四个提交中的第一个(在我们输出的底部)是2005年的初始化提交,也就是我们通常认为的初始化提交。第二个是文件系统 btrfs 的开发,它是独立仓库完成的。第三个是 Greybus,同样也是独立仓库完成的,我们之前已经说过。
第四个初始化提交,a101ad9,很奇怪,正如下面看到的:
$git show --oneline --stat a101ad9
a101ad9 Share upstreaming patches
README.md | 2 ++
1file changed,2insertions(+)
它刚创建了一个 README.md 文件。但随后,它就立即被合并到正常的内核历史的提交 e5451c8 中了!
$git show e5451c8
commit e5451c8f8330e03ad3cfa16048b4daf961af434f
Merge: a101ad93cf42ef
Author: Laxman Dewangan
Date: Tue Feb2319:37:082016 +0530
为什么有人会创建一个只包含两行文本的 README 文件的初始化提交,然后立即合并到主线的历史记录中呢?我想不出任何理由,所以我怀疑这是一个意外!但它没有造成任何危害;它只是很奇怪。(更新:这是个意外,Linus用他一贯的方式回应了。)
顺便提一句,这也是历史记录中差异数量排第二的提交,仅仅因为它是一个不相关提交的合并,就像我们仔细研究过的 Greybus 的合并一样。
现在你知道了:Linux 内核的 git 历史记录一些最奇怪的事情。一共有 1,549 个章鱼式合并,其中有一个是拥有 66 个父节点的提交。差异最多的合并有 22,445,760 行差异,尽管它有点技术性因为它和仓库的其他部分没有公共的历史记录。内核拥有四个独立的初始化提交,其中一个是失误导致的。尽管上面的这些都不会出现在绝大多数的 git 仓库中,但是所有的这些功能都是在 git 的设计范围之内的。
-
芯片
+关注
关注
455文章
50806浏览量
423515 -
Linux
+关注
关注
87文章
11304浏览量
209467 -
嵌入式设备
+关注
关注
0文章
110浏览量
16961
原文标题:Linux 内核 Git 历史记录中,最大最奇怪的提交信息是这样的
文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论