擊敗 Git 嚴謹疲勞與 Jujutsu

對許多開發者而言,理想的 Pull Request 是一段精心策劃的敘事:一系列邏輯性、原子性的提交,引導審閱者了解實作過程——從類型定義開始,接著是資料庫函式,最後是 UI。然而在實際開發的高壓環境中,維持這種「嚴謹」往往令人筋疲力盡。

我們大多數人會陷入「混亂提交」的模式:將功能開發、錯誤修正與重構混雜在少數幾個凌亂的變更集裡。雖然有 git rebase -ijj absorb 等工具可以清理,但它們常會帶來自己的阻礙,例如合併衝突或變更指派不精確。這種情況被稱為「git 嚴謹疲勞」——在同時解決複雜技術問題的同時,試圖保持乾淨歷史所產生的心理負擔。

「大堆洗衣」工作流程

為了對抗這種疲勞,可以採用更放鬆的方式,使用 Jujutsu(jj),這是一個將工作副本視為一等提交的版本控制系統。此工作流程不在開發 期間 追求完美歷史,而是建議先接受混亂,最後一次性整理乾淨。

流程

  1. 接受混亂:正常開發你的功能。隨意建立提交,加入暫時的除錯狀態,且不必在意具體變更落在何處。最終你可能會得到一系列凌亂且重疊的提交。
  2. 定義理想狀態:功能完成後,建立理想歷史的「骨架」。使用 jj new 建立空的提交,並給予你 希望 擁有的描述性標題(例如「定義類型」、「新增 DB 函式」、「伺服器 CRUD」)。
  3. 整合混亂:將所有實際開發的提交壓縮成一個「全部提交」。
  4. 分配變更:使用 jj squash -i 以互動方式將「全部提交」中的變更移動到理想化的骨架提交。挑選出「紅色」變更(類型)移至類型提交,接著將「藍色」變更(UI)移至 UI 提交,依此類推。

流程結束時,「全部提交」已變為空的,你的歷史則為審閱者完美結構化。

為何此方法勝過傳統方式

相較於 jj split 或標準的互動 rebase,此「洗衣」方法提供了多項優勢:

  • 降低衝突風險:因為你是從最終、穩定的狀態移動變更到新歷史,避免了在開發過程中迭代使用 jj squash -i 時常見的中間合併衝突。
  • 降低認知負荷:在編碼流程中不必決定變更屬於哪個提交。你只需在最後執行一次「排序」工作。
  • 彈性:可以先排序最簡單的區塊,而不必擔心會影響其餘歷史的順序。

權衡與反論點

和任何工作流程一樣,此方法也有其缺點。主要顧慮是 中間提交可能無法編譯。若你的團隊要求 PR 中的每個提交都必須通過 CI 且可建置,這種以邏輯類別而非時間順序排序的方式可能成為致命缺點。

社群對此議題的討論凸顯了關於「好提交」價值的更廣泛辯論。

支持「乾淨歷史」的論點

"Jujutsu 將工作副本視為提交的做法解決了 Git rebase 工作流程中許多常見的摩擦點。" — @danborn26

對於這些使用者而言,將 VCS 視為敘事工具的能力,使審閱流程顯著更有效率。

支持「Squash and Merge」的論點

相反地,有人認為若團隊在合併時僅將整個 PR 壓縮成單一提交,則為打造完美歷史所付出的努力全被浪費。

"我最終接受了 PR 的 squash,並意識到自己浪費了青春去寫好提交。" — @drdrey

其他人指出,過於乾淨的歷史實際上可能 較不 有助於除錯,因為它剝離了設計演變的脈絡。

最後的想法

無論你使用 Git 或 Jujutsu,目標都是減少編寫程式碼與記錄其歷史之間的摩擦。有些人偏好從一開始就遵守原子提交的紀律,另一些人則偏好透過 squash 完全抹除歷史,「大堆洗衣」方法則提供了折衷方案:在創作過程中允許混亂,之後再有能力精心編排敘事。

Sources