主題: 學習筆記

Git Merge vs Rebase:兩條時間線的故事

一篇有趣又專業的 git merge 與 git rebase 完整指南——何時使用、如何影響你的專案歷史。

動態迷因(展開/收合)
main 又往前了:現在該 merge,還是 rebase? · 來源:GIPHY

想像你正在和朋友們一起寫一本小說。每個人同時在寫不同的章節。當要把所有人的作品合併時,你有兩個選擇:用便條紙把所有草稿釘在一起,標註誰在什麼時候寫了什麼(merge);或者把每個人的章節重新謄寫到乾淨的紙上,讓它看起來像是按照完美順序寫成的(rebase)。

歡迎來到 Git 的永恆辯論:merge vs rebase

1. 基礎:我們在解決什麼問題?

在 Git 中,當你在功能分支上工作時,主分支可能已經往前推進了。你需要整合這些變更。git mergegit rebase 都能達成這個目標,但它們的方式截然不同。

# 你的功能分支從 main 分叉出去
main:    A --- B --- C --- D
                \
feature:         E --- F --- G

問題是:我們如何把 D 的變更帶入功能分支(或反過來)?

2. Git Merge:「全家福照片」方法 📸

git merge 透過創建一個新的「合併提交」(merge commit) 來結合兩個分支的歷史。

運作方式

git checkout feature
git merge main

結果看起來像這樣:

main:    A --- B --- C --- D --------
                \                    \
feature:         E --- F --- G --- M (合併提交)

Merge 的優點

  • 保留完整歷史:每個提交、每個分支、每個決策都被記錄下來
  • 非破壞性:原始提交保持不變
  • 容易理解:合併提交清楚地顯示分支何時被合併
  • 對共享分支安全:永遠不會重寫公開歷史

Merge 的缺點

  • 混亂的歷史:你的 git log 會變成一張纏繞的合併提交網
  • 雜訊:頻繁的合併會產生很多「Merge branch ‘main’ into feature」提交

何時使用 Merge

✅ 將功能分支合併回 main ✅ 在多人協作的共享分支上工作 ✅ 當你想保留開發的確切歷史時 ✅ 如果你不確定——merge 永遠是更安全的選擇!

3. Git Rebase:「時光機」方法 ⏰

git rebase 把你的提交取出來,然後在另一個分支的頂端重新播放,就像你從那裡開始工作一樣。

運作方式

git checkout feature
git rebase main

結果看起來像這樣:

main:    A --- B --- C --- D
                            \
feature:                     E' --- F' --- G'

注意提交現在是 E'F'G'——它們是新的提交,有相同的變更但不同的提交雜湊值!

Rebase 背後的魔法

  1. Git 找到兩個分支的共同祖先(B)
  2. Git「分離」你的提交(E、F、G)
  3. Git 將功能分支指標移到 main 的頂端(D)
  4. Git 在 D 上面一個一個重新播放你的提交

Rebase 的優點

  • 線性歷史:乾淨、直線的提交序列
  • 容易閱讀git log 講述一個清晰的故事
  • 沒有合併提交:歷史中更少雜亂
  • 更容易二分搜尋:用 git bisect 找 bug 更簡單

Rebase 的缺點

  • 重寫歷史:創建新的提交雜湊值
  • ⚠️ 對共享分支危險:永遠不要 rebase 別人正在使用的提交!
  • 衝突可能重複:你可能需要多次解決相同的衝突

何時使用 Rebase

✅ 在推送前整理本地提交 ✅ 讓功能分支與 main 保持同步(本地) ✅ 壓縮混亂的進行中提交 ✅ 當你想要乾淨、線性的專案歷史時

4. Interactive Rebase:秘密武器 🗡️

git rebase -i(互動式 rebase)是 Git 最強大的功能之一。它讓你可以編輯、合併、重新排序或刪除提交。

git rebase -i HEAD~3  # Rebase 最後 3 個提交

這會開啟一個編輯器:

pick e3a1b35 新增使用者認證
pick 7c82d14 修正認證的錯字
pick 9f4e821 新增密碼驗證

# 指令:
# p, pick = 使用提交
# r, reword = 使用提交,但編輯提交訊息
# e, edit = 使用提交,但停下來修改
# s, squash = 使用提交,但合併到前一個提交
# f, fixup = 像 "squash",但丟棄這個提交的日誌訊息
# d, drop = 移除提交

常見使用情境

壓縮混亂的提交:

pick e3a1b35 新增使用者認證
squash 7c82d14 修正認證的錯字
squash 9f4e821 新增密碼驗證

這會把三個提交合併成一個乾淨的提交!

重新排序提交: 只要改變行的順序即可。

編輯提交訊息:pick 改成 reword

5. 黃金法則 ⚠️

永遠不要 rebase 已經推送到共享儲存庫的提交。

當你 rebase 時,你正在創建具有新雜湊值的新提交。如果其他人已經基於原始提交進行工作,你會製造出重複提交和衝突的噩夢。

# Rebase 之前(你的隊友看到的)
main:    A --- B --- C
                \
feature:         D --- E  ← 隊友正在這裡工作

# 你 rebase 之後
main:    A --- B --- C
                      \
feature:               D' --- E'  ← 不同的提交!

# 當隊友試著推送時... 大混亂! 💥
動態迷因(展開/收合)
在共享分支改寫歷史後,團隊聊天室的即時畫面。 · 來源:GIPHY

6. 實用工作流程

這是一個讓你兩全其美的工作流程:

開發功能

# 1. 創建功能分支
git checkout -b feature/awesome-thing main

# 2. 工作時進行提交
git commit -m "新增初始實作"
git commit -m "修正 bug"
git commit -m "WIP: 嘗試一些東西"
git commit -m "真正修正那個 bug"

# 3. 推送前,用互動式 rebase 整理
git rebase -i main
# 把混亂的提交壓縮成邏輯單元

# 4. 推送你乾淨的分支
git push origin feature/awesome-thing

整合 Main 的變更

# 選項 A:Rebase(如果你還沒推送)
git fetch origin
git rebase origin/main

# 選項 B:Merge(如果你已經推送了)
git fetch origin
git merge origin/main

最終整合

# 功能完成時,合併到 main
git checkout main
git merge --no-ff feature/awesome-thing
# --no-ff 即使可以快進也會創建合併提交
# 這會在歷史中保留功能分支的痕跡

7. 解決衝突

Merge 和 rebase 都可能導致衝突。以下是處理方式:

Merge 期間

git merge main
# 衝突!編輯檔案來解決
git add resolved-file.js
git commit  # 完成合併

Rebase 期間

git rebase main
# 衝突!編輯檔案來解決
git add resolved-file.js
git rebase --continue  # 繼續下一個提交

# 如果事情出錯了:
git rebase --abort  # 回到 rebase 之前的狀態

8. 快速參考卡

面向 Merge Rebase
歷史 非線性,保留全部 線性,重寫提交
合併提交 創建一個
原始提交 保留 替換為新的
衝突解決 一次 每個提交各一次
對共享分支安全 ✅ 是 ❌ 否
可讀性 可能混亂 乾淨且線性

9. 懶人包

  • 使用 merge 用於公開/共享分支,以及當保留歷史很重要時
  • 使用 rebase 用於本地整理和保持功能分支更新
  • 永遠不要 rebase 已經推送並共享的提交
  • 如果不確定,merge 永遠是安全的

外部參考連結

官方文件

教學與指南

視覺化學習