Linux 內核 Git 歷史記錄中,最大最奇怪的提交信息是這樣的

Linux愛好者2018-08-08 22:10:08

(點擊上方公眾號,可快速關注)


編譯:伯樂在線/可樂



我們通常認為 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技能

閱讀原文

TAGS: