Git 還沒完全會(huì)用呢,就給它貢獻(xiàn)了代碼!
以下文章來源于ByteDance Web Infra ,作者韓欣
關(guān)于 Git
Git 是目前世界上最為廣泛使用的軟件版本控制系統(tǒng)(Version Control System),同時(shí)也是一個(gè)成熟及活躍的開源項(xiàng)目。
Git 最初是由 Linux 之父 Linus Torvalds 在 2005 年創(chuàng)建,至今已經(jīng)迭代了17年。但任何程序都會(huì)有Bug,對(duì)于 Git 這樣一個(gè)成熟的開源項(xiàng)目也不例外。引用 Linus 的話來說:
“Bugs will happen, if they don’t happen in hardware, they will happen in software and if they don’t happen in your software and they will happen in somebody else’s software.” Torvalds said.
“錯(cuò)誤總會(huì)發(fā)生,如果它們不發(fā)生在硬件中,它們將發(fā)生在軟件中;如果它們不發(fā)生在你的軟件中,也會(huì)發(fā)生在其他人的軟件中?!?br>
接下來,我們將從一個(gè)由 Git 引發(fā)的故障,一起來看看如何排查問題,并逐步進(jìn)行 Git 社區(qū)貢獻(xiàn)。
Git 問題排查
現(xiàn)場(chǎng)還原
這個(gè)問題最初在 2022.6.8 被發(fā)現(xiàn),代碼平臺(tái)服務(wù)收到報(bào)警,收到了大量的 Git 的代碼下載請(qǐng)求。在一番定位之后,發(fā)現(xiàn)在部分代碼下載的服務(wù)器上,發(fā)現(xiàn)了下圖的一連串 Git 進(jìn)程:
從上圖我們可以看到一個(gè)關(guān)鍵點(diǎn),PID 與 PPID 首尾相連,這是一個(gè)典型的嵌套循環(huán)問題。
在這個(gè)循環(huán)當(dāng)中,產(chǎn)生了大量的 Git 進(jìn)程,不但對(duì)代碼平臺(tái)產(chǎn)生了壓力,也對(duì)用戶本地產(chǎn)生了極大的困擾(本地的進(jìn)程資源被大量占用)。
所幸這個(gè)問題只要滿足觸發(fā)條件,就可以穩(wěn)定復(fù)現(xiàn)。在獲取了異常的倉庫副本之后,我們就開始了問題剖析之旅。
問題剖析
首先,讓我們一起分析下異常倉庫的特點(diǎn):
.git/objects/info 目錄下,存在 commit-graph 圖文件。
倉庫使用了 .git/objects/info/alternates 來引入一些外部的倉庫。
使用了局部克隆(Partial Clone):在 .git/config 當(dāng)中發(fā)現(xiàn)了一些痕跡。
[remote "origin"]
promisor = true
partialclonefilter = blob:none
為了不對(duì)代碼平臺(tái)產(chǎn)生異常調(diào)用,我們嘗試在本地電腦上還原這個(gè)問題:
我們將異常倉庫副本,解壓到 work 目錄下。
通過 git clone --bare {url} remote.git 我們下載了一個(gè)遠(yuǎn)程倉庫。
通過 git -C work remote set-url origin "$(pwd)remote.git" 將 work 的遠(yuǎn)程倉庫設(shè)置成本地。
接下來,我們就可以通過執(zhí)行異常命令來復(fù)現(xiàn)問題。
為了更好地跟蹤,我們可以通過 GIT_TRACE=1 或者 GIT_TRACE2_EVENT="$(pwd)/event.trace" 來開啟過程追蹤。
為了更好地定位問題,我們也可以考慮修改 Git 源碼,通過 trace_print 增加一些額外的調(diào)用日志。此外,我們還可以借助 LLDB / GDB 幫助我們進(jìn)行本地觀察與調(diào)試。
最終,在一番努力之后,一個(gè)可怕的死循環(huán)調(diào)用鏈逐漸浮出水面:
git fetch
-> do_fetch_pack_v2()
-> deref_without_lazy_fetch()
-> lookup_commit_in_graph()
-> repo_has_object_file()
-> promisor_remote_get_direct()
-> fetch_objects()
-> git fetch (??,我們又回到了最初相遇的地點(diǎn),開始新的輪回)
原因分析
讓我們就著調(diào)用鏈路,再來回顧一下原因:
由于本地是一個(gè)的局部克隆的倉庫,存在一些對(duì)象缺失的情況,而訪問這些缺失的對(duì)象,就可能產(chǎn)生懶加載拉取。
而在懶加載的過程中,引用查找 deref_without_lazy_fetch() 方法會(huì)嘗試先去從 commit-graph 提交圖當(dāng)中尋找對(duì)象來進(jìn)行一些加速。
而 lookup_commit_in_graph() 當(dāng)中,使用了 repo_has_object_file() 來判斷對(duì)象是否存在于 Git 倉庫當(dāng)中,這個(gè)方法,可能引發(fā)懶加載拉取本地缺失的對(duì)象,開啟一輪新的git fetch。
而新一輪的 git fetch ,由于本地存在 commit-graph,在引用查找中,再次進(jìn)入了步驟2~3的循環(huán)當(dāng)中。
這里有一個(gè)關(guān)鍵點(diǎn),問題是由于提交記錄存在于 commit-graph,而在 Git 對(duì)象倉庫中缺失導(dǎo)致的。這種情況通常并不會(huì)發(fā)生。結(jié)合倉庫使用了 objects/info/alternates 來引入外部倉庫,我們來猜想一下問題是如何產(chǎn)生的。
猜想驗(yàn)證
如下圖當(dāng)中,通過 objects/info/alternates ,我們引入了一個(gè)外部的共享倉庫,來減少當(dāng)前倉庫的對(duì)象存儲(chǔ)。同時(shí),我們創(chuàng)建了一個(gè) commit-graph,來加速本地的提交記錄訪問。
由于共享的倉庫與本地倉庫維護(hù)的引用列表(分支、標(biāo)簽等統(tǒng)稱為引用)并不相同,在觸發(fā) git gc或者自動(dòng) gc 之后,由于提交記錄 B 是一個(gè)不可達(dá)的對(duì)象(不存在于任何引用當(dāng)中),導(dǎo)致了存在于 commit-graph 當(dāng)中的共享提交記錄 B 被清理了。
這時(shí)候,只要在當(dāng)前倉庫觸發(fā)了懶加載,就會(huì)引發(fā)前面的死循環(huán)調(diào)用鏈,產(chǎn)生大量的 Git 進(jìn)程,最終拖垮用戶本地及遠(yuǎn)端服務(wù)。
雖然從原因來看,這個(gè)問題的產(chǎn)生,某種意義上是由于不合理使用 alternates 及 commit-graph 導(dǎo)致的,但我們的確很難限制用戶該如何使用。于是,我們決定尋求 Git 社區(qū)的幫助。
尋求 Git 社區(qū)幫助
我們決定,派你出發(fā),開始這次的 Git 社區(qū)之旅。
芝麻開門
提到開源社區(qū),大家都會(huì)不約而同想到Github[1],比較這可是全球最大的“程序員交友社區(qū)”。
可當(dāng)你興沖沖地跑到 https://github.com/git/git 準(zhǔn)備提交一個(gè) Issue (問題)時(shí),嗯?說好的Issues 入口去哪了?
再看右邊的描述:
雖然,你可以通過 Github 上的 GitGitGadget 機(jī)器人幫助我們進(jìn)行補(bǔ)丁提交,可是你現(xiàn)在只是想請(qǐng)教社區(qū)一個(gè)問題,該怎么辦呢?
曲徑通幽
在 git-scm.com 上,通過閱讀 《MyFirstContribution》[2],你找到了尋求幫助的三個(gè)路徑:
1、git@vger.kernel.org
這是主要的 Git 項(xiàng)目郵件列表,用于進(jìn)行代碼審查、版本公告、設(shè)計(jì)討論等。
這也是目前 Git 社區(qū)最主要的交流方式,Github 上的 GitGitGadget 也是幫助你做郵件轉(zhuǎn)換的工作。
任何有興趣貢獻(xiàn)的人,都在這里發(fā)布問題。
但需要注意的是,Git 列表需要純文本電子郵件,并且在回復(fù)郵件時(shí)更喜歡內(nèi)聯(lián)和底部發(fā)布。
2、git-mentoring@googlegroups.com
此郵件列表面向新的貢獻(xiàn)者,并被創(chuàng)建為在主列表的公眾視線之外發(fā)布問題和接收答案的地方。對(duì)幫助指導(dǎo)新人特別感興趣的資深貢獻(xiàn)者出現(xiàn)在列表中。為了避免搜索索引器,查看消息需要組成員身份;任何人都可以加入,無需批準(zhǔn)。
3、#git-devel[3] 在 Libera Chat[4] 上
這個(gè) IRC 頻道用于 Git 貢獻(xiàn)者之間的對(duì)話。
如果有人當(dāng)前在線并且知道你的問題的答案,你可以實(shí)時(shí)獲得幫助;或者你也可以閱讀 scrollback[5] 以查看是否有人回答了你。但I(xiàn)RC 不允許離線私信,因此如果你嘗試私信某人然后退出 IRC,他們將無法回復(fù)你。
你隨意翻開如下鏈接中看到了其中一天的討論:
https://colabti.org/irclogger/irclogger_log/git-devel?date=2022-06-20
新手上路
接下來你選擇使用郵件的方式,請(qǐng)求社區(qū)的幫助,于是,你奮筆疾書,寫了一封郵件。
當(dāng)你通過 git@vger.kernel.org 發(fā)送郵件到社區(qū)之后,就可以通過 https://lore.kernel.org/git 這個(gè)公共的收件箱列表,找到剛才發(fā)送的郵件。
除此之外,你驚喜地發(fā)現(xiàn),新世界的大門正式向你敞開:
由于“Git 列表需要純文本電子郵件,并且在回復(fù)郵件時(shí)更喜歡內(nèi)聯(lián)和底部發(fā)布”,因此在使用郵件服務(wù)時(shí)請(qǐng)?zhí)貏e注意:
例如使用Gmail時(shí),請(qǐng)選擇“Plain text mode”:
你也可以選擇使用git send-email來發(fā)送/回復(fù)郵件:
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
你可以通過點(diǎn)擊 permalink 找到如下的命令行:
git send-email \
--in-reply-to={Message-ID} \
--to=someone@email.com
--cc=git@vger.kernel.org \
--subject='Re: [PATCH v2 0/1] scalar: move to the top-level, test, CI and "install" support' \
/path/to/YOUR_REPLY
請(qǐng)?zhí)貏e注意攜帶 in-reply-to,只有這樣你的郵件才會(huì)被很好地組織在一起,避免丟失一些上下文。
小試牛刀
在與社區(qū)交流的過程中,你不斷深入,找到了可行的修復(fù)方案。我想,此刻的你一定躍躍欲試,想盡快將自己的修復(fù)方案發(fā)給社區(qū)。但如果你不想石沉大海,或是迎來一些不必要的批評(píng),請(qǐng)你先保持冷靜,除了前面提到的《MyFirstContribution》,你還需要再做一些準(zhǔn)備工作。
1、?? 閱讀 Git 的《補(bǔ)丁提交》[6]
https://github.com/git/git/blob/master/Documentation/SubmittingPatches
《補(bǔ)丁提交》將指導(dǎo)你如何建立自己的工作,比如該基于 maint(穩(wěn)定版本)還是該基于master ,又或者是指導(dǎo)你該如何清晰地表達(dá)這個(gè)補(bǔ)丁的價(jià)值。
在這個(gè)文檔當(dāng)中,你將學(xué)到關(guān)于補(bǔ)丁制作與提交的方方面面。
2、閱讀 Git 的《編碼指南》[7]
https://github.com/git/git/blob/master/Documentation/CodingGuidelines
《編碼指南》將幫助你了解一些Git非常較真的編碼規(guī)范,從下面的一個(gè)例子你就可以看出他們對(duì)空格有多么的較真:
(incorrect)
cat hello > world < universe
echo hello >$world
(correct)
cat hello >world <universe
echo hello >"$world"
3、學(xué)習(xí)如何編寫 Git 的測(cè)試用例
https://github.com/git/git/blob/master/t/README
Git 當(dāng)中,使用一套以 shell 腳本為主,名為Sharness的測(cè)試框架。這套測(cè)試框架在2011年創(chuàng)建,并從 Git 的 test-lib.sh 當(dāng)中派生出來,成為了一個(gè)獨(dú)立的項(xiàng)目:https://github.com/chriscool/sharness
4、勇敢發(fā)送你的補(bǔ)丁
當(dāng)你一切準(zhǔn)備就緒,確認(rèn)Github CI也亮起了綠燈,勇敢地發(fā)送你的補(bǔ)丁吧,開始屬于你的Git貢獻(xiàn)之旅。
小結(jié)
我們從一個(gè) Git 引發(fā)的故障開始,了解了 Git 問題排查的過程,在找到并復(fù)現(xiàn)問題之后,嘗試尋求 Git 社區(qū)的幫助,并小試牛刀,向 Git 社區(qū)提交了我們的補(bǔ)丁。
Git 自2005年開始發(fā)展至今,正是由于其廣泛的使用,以及一大批優(yōu)秀的社區(qū)貢獻(xiàn)者,讓 Git 變得越來越好。使用 Git,發(fā)現(xiàn)問題并改進(jìn)它,在這個(gè)過程中你一定可以得到別樣的收獲。
附錄
Github:https://github.com
《MyFirstContribution》:https://git-scm.com/docs/MyFirstContribution / https://github.com/git/git/blob/main/Documentation/MyFirstContribution.txt
#git-devel:https://web.libera.chat/#git-devel
Libera Chat:https://libera.chat
scrollback:https://colabti.org/irclogger/irclogger_logs/git-devel
CodingGuidelines:https://github.com/git/git/blob/master/Documentation/CodingGuidelines
SubmmitingPatches:https://github.com/git/git/blob/master/Documentation/SubmittingPatches
作者:韓欣
歡迎關(guān)注微信公眾號(hào) :前端印象