git log --oneline 的輸出像一行行簡短的航海記錄。一個好的歷史,讀起來像一篇結構清楚的文章:每一段只做一件事,標題能獨立成立,順序合理,讀者不需要反覆猜測為什麼。

這和“提交得漂亮”無關。它關乎別人能否安全地進入你留下的上下文。

一個提交只解釋一個決定

如果一個提交同時修復 bug、升級依賴、重命名文件、調整縮進,reviewer 很難判斷哪一行導致了什麼。把變化拆成獨立的決定,歷史就會自然變得可讀。

拆分的標準不是行數,而是問題:

  • 如果標題裡出現兩個“和”,可能應該拆開;
  • 如果回滾其中一個變化時會連帶影響另一個,說明它們耦合;
  • 如果格式化改動無法和邏輯改動分開 review,先單獨提交。

這不是要求每個提交都小到微不足道,而是讓每個提交都能被單獨理解。

標題要能獨立成立

把提交標題當成變更日誌裡的第一行。它應該說明“做了什麼”,而不是“改了一些東西”。

feat(archive): add year grouping to writing index
fix(search): keep focus inside the dialog
docs: explain how bilingual routes work

feat、fix、docs 不是儀式,它們幫助讀者快速判斷影響範圍。項目如果沒有約定,就選一套一致的規則,並堅持使用。

正文可以回答“為什麼”,但不要把它寫成會議紀要。把背景、權衡和驗證方式留在需要的地方。

用 rebase 整理,而不是掩蓋

我經常在功能分支裡留下一些草稿式提交:

wip
fix typo
actually fix it
tests

直接合並這些提交沒有錯,但歷史會變得嘈雜。如果項目允許,我會在提 PR 前做一次交互式 rebase:

git rebase -i origin/main

把 WIP 合併進有意義的提交,調整順序,重新寫 message。注意,這一步只應該修改還沒有共享的提交。已經推到公共分支並被別人拉取的歷史,不要隨意重寫。

描述限制,而不只是結果

一個真正有用的提交會說明邊界。“修復了搜索”不如“關閉對話框後恢復觸發按鈕的焦點,並避免方向鍵滾動頁面”來得可靠。

如果這次改動有已知的未覆蓋場景,把它寫進 PR 或提交正文,而不是留給下一個人猜。誠實地記錄限制,本身就是工程判斷的一部分。

歷史的複利

提交歷史很少在當下產生回報。它真正的價值出現在幾個月後:你需要定位一次迴歸,新同事需要理解一個奇怪的分支,或者你需要在另一個項目裡複用同樣的設計。

可讀歷史不是額外工作。它只是把未來的溝通成本,提前壓縮進了一條條小小的記錄裡。Git 保存了時間,而好的提交信息讓時間可以被重新閱讀。