以 Changeset 為核心重塑 GitHub Pull Request
原文由 Mitchell Hashimoto 于 發布,訂閱此部落格
我曾以大型開源專案(超過 1000 位貢獻者)的維護者、也曾以大型封閉原始碼企業專案的工程師身分使用 GitHub,各種規模更小的專案也都經歷過。直到今天,在 GitHub 上我幾乎所有時間都花在 pull request 上,而對我來說,那也是 GitHub 上最讓人感到挫折的部分。
雖然我希望 pull request 能有許多改進,但其中很大一部分問題,只要靠一個主要功能就能解決:changeset。這篇文章就是要說明這個提議,以及我期待看到的樣貌。
聲明:這裡的想法並非原創!我並不是要宣稱這些想法是我發明的。這裡的建議是基於已經被充分探索的 Git 工作流程,而且也已經有其他產品部分或完整地實作了,例如 Gerrit、Phabricator,或是最傳統、以電子郵件為基礎的 patch 審查。
當今的問題
現在 GitHub pull request 的生命週期,實際上就是一個巨大且可變的 changeset。這真是一團亂!
以下是現在一個典型的 PR 流程:貢獻者將一組 commit 推送到一個分支上,開啟 PR,此後這個 PR 就代表了那個分支。大家透過留言來討論 PR。當貢獻者推送新的變更時,這些變更會直接出現在同一個 PR 上,立即更新它。審查者可以留下評論,同時貢獻者也可以推送變更,而這一切都會更新到同一個 PR 上。
這帶來了許多問題:
審查者可能會針對 PR 先前某個狀態留下審查意見,結果因為在審查進行的同時貢獻者推送了變更,那份審查就立刻過時了。
更糟的是,審查意見可能只有部分過時,而其他回饋在貢獻者推送的新變更脈絡下就變得難以理解。舉例來說,一則針對程式碼行的留言可能會寫「跟上一則留言一樣的意見」,但上一則留言現在已經消失/被隱藏了,因為貢獻者推送的變更移動了那些程式碼行。1
審查本身並不包含它所針對的 commit 的中繼資料,只有送出時的時間戳記。使用者大致可以用時間戳記去對應 commit,但並不完全準確,因為如果在審查過程中剛好有新的 commit 進來,時間戳記看起來會像是針對最新的 commit,但你的審查其實是針對前一個 commit 做的。😕
為了回應審查意見而做的、還在進行中的 commit,只要一推送到分支上就會立刻被看到。這迫使貢獻者必須在單一 commit 中一次處理完所有回饋,不然就是讓審查者去面對只處理了一部分的回饋。
你很難輕易回到 PR 先前的狀態。如果你想審查較早的一組 commit 而忽略後來的 commit,就得手動建立「compare」檢視,或是在本機 checkout(我是用後者)。但不管哪種方式,你都只看得到程式碼的變更,看不到當時審查者留下的回饋!
跟上面類似,如果貢獻者一次推送了多個新的 commit,你很難輕鬆比較這一整組新 commit與舊的版本。你實際上一次只能逐一檢視單個 commit。遇到這種情況,你又得退回本機用
git手動產生 diff 來比較。還有更多……後面還會再提到一些,但我想已經足以說明問題了。
我確定上面某些觀點的細節可能有錯。一定會有人說「他只要這樣做就能解決問題 5(a)」。那當然有幫助!但我想表達的重點是,如果退一步看,造成這些問題的根本原因才是真正的癥結。也就是說,用單一、可變的 changeset 以逐一 commit 的方式去追蹤一個分支。
Changeset
解決辦法就是 changeset:讓 pull request 可以透過單調遞增的編號來進行版本控管(v1、v2……)。這些版本通常就被稱為「changeset」。
每個 changeset 都指向一個分支在某個固定時間點的狀態。這些版本是不可變的:當有新的 commit 被推送時,它們會成為新的 changeset 的一部分。如果貢獻者強制推送分支,那同樣也會形成一個新的 changeset。而先前的 changeset 會永遠被保留下來。
新的 changeset 可以在每次 commit 後立刻發布,也可以先暫緩,直到貢獻者決定要提出新版本供審查時再發布。後者讓貢獻者可以先用多個 commit 來處理先前的回饋,等到覺得準備好了再一次發布這些變更。
在 changeset 的世界裡,回饋是附加在某個 changeset 上的。如果審查者開始審查某個 changeset,而此時又有新的 changeset 發布,那也沒關係,因為整份審查作為一個原子單位,本來就是附著在先前的 changeset 上。
在後續的 changeset 中,通常會標示出哪些檔案或程式碼行在先前的 changeset 中還有未解決的留言。這能確保較早 changeset 的回饋不會遺失,並且在任何一個 changeset 被接受之前都必須被處理。
通常,每個 changeset 會由不同的 Git ref 來代表。舉例來說,現在 GitHub 的 pull request 通常是 refs/pr/1234,你可以在本機用 git 這樣來 checkout 任何一個 pull request。而 changeset 則會像是 refs/pr/1234/v2(假設性的例子),讓你也可以 checkout 個別的 changeset。
與其「核准」整個 PR 然後合併,不如讓審查者核准的是某個 changeset。這也意味著,貢獻者可以在同一個 PR 中提出採用不同做法的多個 changeset,而維護者可以選擇非最新的某個 changeset 作為最終要合併的變更。
GitHub,拜託了!
Changeset 已經是許多開源專案和公司行之有年的模式。在 Gerrit 和 Phabricator 這類現有產品中,與之相關的使用者體驗問題也已經被充分探索過了。我也相信 changeset 可以用非破壞性的方式引入(因為現在的 PR 就像是單一可變 changeset 的模式)。
Changeset 能讓 pull request 在面對更大型的專案和組織時更具可擴展性。除了可擴展性之外,它也能讓審查流程對參與 pull request 的雙方來說都更清晰、更安全。
當然,我只能就我個人和我的經驗來說,但這個單一的重大功能,將會大幅改善我在 GitHub 上的生活品質與工作能力2。
註腳
隨機一篇部落格
留言
登入後參與討論