Reorient GitHub Pull Requests Around Changesets

Mitchell Hashimoto

以變更集為核心重新設計 GitHub Pull Request

我曾以超大型開源專案(超過 1000 位貢獻者)的維護者、超大型封閉原始碼企業專案的工程師,以及各種規模更小的專案等身分使用 GitHub。直到今日,在 GitHub 上我幾乎所有時間都花在 GitHub Pull Request 上,而對我來說,那不幸地也是 GitHub 上最令人挫折的部分。

我很希望看到 Pull Request 有許多改進,但只要透過一項主要功能,就能解決我大部分的問題:changesets(變更集)。這篇部落格文章將說明這項建議以及我期望看到的樣貌。

聲明:這裡的想法並非原創!我並不聲稱這些想法是由我提出的。我在此提出的建議是基於已被充分探索的 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)」。這很有幫助!但是,我想表達的重點是,如果你退一步看,造成這些問題的根本原因才是真正的問題所在。也就是說,以逐一 commit 為單位追蹤分支的單一、可變 changeset。


Changesets

解決方案就是 changesets:一個 Pull Request 可以透過遞增的單調數字(v1、v2……)來進行版本化。這些版本通常被稱為「changesets」。

每個 changeset 都指向分支在固定時間點的狀態。這些版本是不可變的:當新的 commit 被推送時,它們會成為新的 changeset 的一部分。如果貢獻者強制推送分支,那也會成為新的 changeset 的一部分。前一個 changeset 會被永久保存。

新的 changeset 可以立即發布(逐一 commit),也可以延後到貢獻者決定提出新版本以供審查時再發布。後者讓貢獻者可以透過多個 commit 來處理先前的回饋,並在準備好時才發布這些變更。

在 changesets 的世界裡,回饋是依附在某個 changeset 上的。如果審查者開始審查某個 changeset,而此時發布了新的 changeset,這也沒關係,因為審查作為一個原子單位是依附在先前的 changeset 上的。

在後續的 changesets 中,通常會標示某個檔案或程式碼行在先前的 changesets 中仍有未解決的留言。這可確保較早的 changesets 上的回饋不會遺失,且在任何 changeset 被接受之前都必須被處理。

通常,每個 changeset 都由不同的 Git ref 來表示。舉例來說,如今的 GitHub Pull Request 通常是 refs/pr/1234,你可以在本地端使用 git 來 checkout 任何 Pull Request。changeset 則會像是 refs/pr/1234/v2(假設性的例子),讓你也可以 checkout 個別的 changesets。

審查者不再是「核准」整個 PR 並進行合併,而是核准某個changeset。這意味著貢獻者也可以在單一 PR 中發布多個採用不同解法的 changesets,而維護者則有可能選擇非最新的 changeset 作為他們想要合併的變更集合。


GitHub,拜託了!

Changesets 是在許多開源專案和公司中已確立的模式。在 Gerrit 和 Phabricator 等現有產品中,使用者體驗問題也已經被充分探索。我也相信 changesets 可以以非破壞性的方式引入(因為現有的 PR 就像是單一可變 changeset 模式)。

Changesets 將使 Pull Request 對於更大型的專案和組織而言更具可擴展性。除了可擴展性之外,它們還能讓 Pull Request 中雙方的審查流程更清晰、更安全。

當然,我只能就我個人及我的經驗發言,但這項單一的重大功能將大幅改善我在使用 GitHub 時的生活品質與工作能力2

註腳

  1. 這是一個微小但令人困擾的問題,但這個問題擴大後會變成嚴重的問題。

  2. 「那就別用 GitHub 啊!」我以前聽過這種回饋。我如今使用 GitHub 還有很多其他原因,所以對我個人而言,目前這並不是可行的選項。如果你可以不用 GitHub,那麼是的,你可以在其他產品中找到對 changeset 的支援。

原文由 Mitchell Hashimoto 發布

本文章由 muse-spark-1.2-contributor 進行翻譯