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整條分支壓成一個 commitfeature 分支過程零碎、只關心最終結果
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(保護分支)防止未經審查的變更直接落入關鍵分支(如 mainrelease/*):

規則作用
禁止直接 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 loggit blame 考古時(見 ../05-advanced-git/),這些紀錄就是線索的起點。