Merge Request(GitLab)與 Pull Request(GitHub)是同一件事的兩個名稱:把一條分支的變更提案合併進目標分支,並在合併前進行審查與自動化驗證。它是現代團隊協作的核心交匯點,也是知識沉澱的場所。
本章統一以 MR 指稱 Merge Request 與 Pull Request,兩者概念一致,差異僅在平台用語。
一、MR 生命週期#
stateDiagram-v2
[*] --> Draft: 開啟草稿 MR
Draft --> Open: 標記為 ready
Open --> Review: 指派審查者
Review --> ChangesRequested: 要求修改
ChangesRequested --> Review: 推送修正
Review --> Approved: 通過審查 + CI 綠燈
Approved --> Merged: 合併
Merged --> [*]
Open --> Closed: 放棄
Closed --> [*]- Draft:尚未準備好審查,先開出來讓 CI 跑、讓他人看見進度。
- Open / Review:正式請求審查,審查者逐輪回饋。
- Approved:審查通過且所有必要檢查(CI、覆蓋率、protected branch 規則)綠燈。
- Merged / Closed:合併進目標分支,或放棄關閉。
盡早開 Draft MR。它讓 CI 提早跑、讓隊友提早看到方向,避免在錯誤方向上累積大量工作後才被打回。
二、Review Checklist#
審查不是「找錯字」,而是把關正確性、可維護性與團隊一致性。
Code Review 檢查清單
正確性
- 變更確實解決了 MR 描述聲明的問題
- 邊界條件與錯誤路徑有處理
- 沒有引入明顯的安全問題(注入、機密外洩)
測試
- 新增/修改的邏輯有對應測試
- CI 全綠(建置、測試、靜態檢查)
可維護性
- 命名清楚,意圖可從程式碼讀出
- 沒有無謂的複雜化或過度設計
- 公開介面/行為變更有更新文件
範圍
- MR 聚焦單一目的,沒有夾帶無關變更
- commit 訊息與 MR 描述能說明「為什麼」
小 MR 是好審查的前提。一個 MR 越大,審查者越容易草草放行(rubber stamp)。控制在數百行以內、聚焦單一目的,是讓審查真正發揮作用的關鍵。
三、squash / rebase / merge 決策表#
合併 MR 時平台通常提供三種方式,差別在於對歷史的處理:
| 方式 | 結果歷史 | 適用場景 |
|---|---|---|
| Merge commit | 保留分支所有 commit + 合併點 | 想保留完整開發脈絡、分支本身已是乾淨的 commit |
| Squash and merge | 整條分支壓成一個 commit | feature 分支過程零碎、只關心最終結果 |
| Rebase and merge | 分支 commit 線性接到主線 | 想要線性歷史、且分支 commit 本身就有意義 |
flowchart TD
A[要合併 MR] --> B{分支內 commit<br/>是否各自有意義?}
B -->|否, 過程零碎| C[Squash and merge]
B -->|是, 每個 commit 乾淨| D{團隊偏好<br/>線性歷史?}
D -->|是| E[Rebase and merge]
D -->|否| F[Merge commit]多數團隊的好預設是 Squash and merge:開發過程的 “fix typo”、“wip” 等雜訊被壓掉,主線歷史每個 commit 對應一個完整、可回滾的變更單元。
四、Protected Branches#
protected branch(保護分支)防止未經審查的變更直接落入關鍵分支(如 main、release/*):
| 規則 | 作用 |
|---|---|
| 禁止直接 push | 強制所有變更走 MR |
| 要求 N 個核可 | 合併前需指定數量的審查通過 |
| 要求 CI 通過 | pipeline 綠燈才能合併 |
| 禁止 force push | 防止歷史被改寫 |
| 限制合併者角色 | 僅特定角色可執行合併 |
保護分支之所以禁止 force push,正是因為改寫共用分支歷史會牽連所有協作者(見 ../01-git-fundamentals/)。這是把「不要改寫已推送歷史」的紀律用平台規則強制落地。
五、Conventional Commits#
Conventional Commits 是一套 commit 訊息規範,讓訊息可被人與機器一致解讀:
<type>(<scope>): <subject>
<body>
<footer>| type | 用途 |
|---|---|
feat | 新功能 |
fix | 修復 bug |
refactor | 不改變行為的重構 |
docs | 文件 |
test | 測試 |
chore | 雜項(建置、依賴更新) |
# 範例
git commit -m "feat(auth): support OAuth login"
git commit -m "fix(cart): correct total when quantity is zero"規範化的 commit 訊息能自動產生 changelog、自動推導 Semantic Versioning 的版本號(
feat➡️ minor、fix➡️ patch、破壞性變更 ➡️ major),讓發布流程少一道手工。
六、Commit 與 MR 作為活文件#
commit 訊息與 MR 描述不只是流程儀式,它們是團隊最持久的知識庫:程式碼會被改寫,但「為什麼這樣改」往往只留在這裡。
好的 commit 訊息回答的是「為什麼」而非「做了什麼」。「做了什麼」看 diff 就知道;「為什麼這樣做、權衡了什麼、放棄了哪些方案」只有作者知道——若不寫下,半年後連作者自己都會忘。
對比範例
# 糟糕:只描述了 diff 已經顯示的事
fix bug
# 良好:說明問題、原因與權衡
fix(payment): retry on transient gateway timeout
支付閘道在高峰期偶發 504,導致使用者重複下單。
改為對逾時做最多 3 次指數退避重試,並以冪等鍵
避免重複扣款。未採用拉長 timeout 的方案,因會
惡化使用者等待體驗。MR 描述作為決策紀錄(ADR)#
當一個 MR 涉及架構層級的取捨,它的描述就是一份輕量的 Architecture Decision Record:
MR 描述範本(含決策紀錄)
## 背景
為什麼需要這個變更?要解決什麼問題?
## 方案
採用了什麼做法?
## 取捨
考慮過哪些替代方案?為什麼沒選?
## 影響
誰會受影響?是否有破壞性變更、遷移步驟?
## 驗證
如何確認這個變更有效?(測試、手動步驟)把分支名(含 issue 編號)、Conventional Commit 訊息、與結構化的 MR 描述串起來,整條協作軌跡就成為可搜尋、可追溯的活文件。日後用
git log、git blame考古時(見 ../05-advanced-git/),這些紀錄就是線索的起點。