主題: 學習筆記
Git Merge vs Rebase:兩條時間線的故事
一篇有趣又專業的 git merge 與 git rebase 完整指南——何時使用、如何影響你的專案歷史。
動態迷因(展開/收合)
想像你正在和朋友們一起寫一本小說。每個人同時在寫不同的章節。當要把所有人的作品合併時,你有兩個選擇:用便條紙把所有草稿釘在一起,標註誰在什麼時候寫了什麼(merge);或者把每個人的章節重新謄寫到乾淨的紙上,讓它看起來像是按照完美順序寫成的(rebase)。
歡迎來到 Git 的永恆辯論:merge vs rebase!
1. 基礎:我們在解決什麼問題?
在 Git 中,當你在功能分支上工作時,主分支可能已經往前推進了。你需要整合這些變更。git merge 和 git 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 背後的魔法
- Git 找到兩個分支的共同祖先(B)
- Git「分離」你的提交(E、F、G)
- Git 將功能分支指標移到 main 的頂端(D)
- 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' ← 不同的提交!
# 當隊友試著推送時... 大混亂! 💥
動態迷因(展開/收合)
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 永遠是安全的
外部參考連結
官方文件
教學與指南
視覺化學習
- Learn Git Branching(互動式教學——強烈推薦!)