第四章:合併與解決衝突 – 團隊協作的核心技能

在前一章中,我們學會了如何建立和管理分支。但分支的真正價值在於能夠將不同的開發成果整合在一起。今天我們要學習合併分支以及處理過程中可能出現的衝突。作為一位經驗豐富的工程師,我可以告訴你,掌握衝突解決是從初學者進階到專業開發者的重要里程碑。

什麼是合併?

合併就像是將兩條河流匯聚成一條。在 Git 中,我們將不同分支上的變更整合到一個分支中。想像你和同事分別在寫一本書的不同章節,最終你們需要將所有章節合併成完整的書籍。

Git 提供了兩種主要的整合方式:mergerebase。它們的目標相同,但採用的策略不同,產生的歷史記錄也不一樣。

Fast-forward 合併 vs 三方合併

Fast-forward 合併

這是最簡單的合併情況,發生在目標分支沒有新的提交時:

# 假設的情況
# main:    A---B
# feature:     B---C---D

git switch main
git merge feature

# 結果:main 直接指向 D
# main:    A---B---C---D

實際操作範例:

# 建立並切換到功能分支
git switch -c feature/add-footer
echo "版權所有 2024" > footer.html
git add footer.html
git commit -m "新增網站頁尾"

# 回到 main 分支(沒有新的提交)
git switch main
git merge feature/add-footer

輸出會顯示:

Updating a1b2c3d..e4f5g6h
Fast-forward
 footer.html | 1 +
 1 file changed, 1 insertion(+)

三方合併

當兩個分支都有新的提交時,Git 會進行三方合併:

# 情況:
# main:    A---B---E
# feature:     B---C---D

git switch main
git merge feature

# 結果:建立新的合併提交 M
# main:    A---B---E---M
#              \     /
# feature:      C---D

實際操作範例:

# 在 main 分支上進行修改
git switch main
echo "主分支的變更" >> README.md
git add README.md
git commit -m "更新 README"

# 切換到功能分支並修改
git switch feature/user-login
echo "登入功能完成" >> login.js
git add login.js
git commit -m "完成使用者登入功能"

# 回到 main 並合併
git switch main
git merge feature/user-login

系統會提示你輸入合併提交的訊息,預設通常是:

Merge branch 'feature/user-login'

Merge vs Rebase 的差異

Merge:保留歷史脈絡

git switch main
git merge feature/payment

優點:

  • 保留完整的歷史記錄
  • 可以看出什麼時候進行了合併
  • 較安全,不會改變已存在的提交

缺點:

  • 歷史記錄可能變得複雜
  • 圖形化歷史會有很多分叉

Rebase:線性歷史

git switch feature/payment
git rebase main
git switch main
git merge feature/payment  # 這會是 fast-forward

優點:

  • 產生線性、乾淨的歷史記錄
  • 更容易理解專案的發展過程
  • 沒有額外的合併提交

缺點:

  • 改寫了提交歷史
  • 如果使用不當可能造成問題
  • 對已推送的分支使用 rebase 需要特別小心

實際範例比較:

# 使用 merge
git log --oneline --graph
* d4e5f6g (main) Merge branch 'feature/payment'
|\
| * b2c3d4e (feature/payment) 實作支付功能
| * a1b2c3d 新增支付表單
* | c5d6e7f 修復主頁 bug
|/
* 9f8e7d6 初始提交

# 使用 rebase
git log --oneline --graph
* b2c3d4e (main) 實作支付功能
* a1b2c3d 新增支付表單
* c5d6e7f 修復主頁 bug
* 9f8e7d6 初始提交

什麼是衝突?

衝突發生在 Git 無法自動決定如何合併兩個分支的變更時。最常見的情況是兩個分支修改了同一個檔案的同一部分。

想像兩個編輯同時修改一篇文章的同一段落,系統無法決定要採用哪個版本,就需要人工介入決定。

衝突產生的常見情況

  1. 修改同一行內容
  2. 一方修改,另一方刪除
  3. 二進位檔案衝突
  4. 檔案名稱衝突

衝突解決的完整流程

讓我們模擬一個真實的衝突情況:

步驟 1:建立衝突情況

# 建立測試檔案
git switch main
echo "歡迎來到我們的網站" > welcome.txt
git add welcome.txt
git commit -m "新增歡迎訊息"

# 在 main 分支修改
echo "歡迎來到我們的官方網站" > welcome.txt
git add welcome.txt
git commit -m "更新歡迎訊息為官方網站"

# 建立功能分支並修改同一檔案
git reset --hard HEAD~1  # 回到分歧點
git switch -c feature/welcome-update
echo "歡迎來到我們的專業網站" > welcome.txt
git add welcome.txt
git commit -m "更新歡迎訊息為專業網站"

步驟 2:嘗試合併並遇到衝突

git switch main
git merge feature/welcome-update

你會看到衝突訊息:

Auto-merging welcome.txt
CONFLICT (content): Merge conflict in welcome.txt
Automatic merge failed; fix conflicts and then commit the result.

步驟 3:檢查衝突狀態

git status

輸出會顯示:

On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   welcome.txt

no changes added to commit (use "git add" or "git commit -a")

步驟 4:查看衝突內容

cat welcome.txt

你會看到 Git 插入的衝突標記:

<<<<<<< HEAD
歡迎來到我們的官方網站
=======
歡迎來到我們的專業網站
>>>>>>> feature/welcome-update

衝突標記說明:

  • <<<<<<< HEAD:目前分支(main)的內容開始
  • =======:分隔線
  • >>>>>>> feature/welcome-update:合併分支的內容結束

步驟 5:解決衝突

你有三個選擇:

選擇 1:保留目前分支的版本

echo "歡迎來到我們的官方網站" > welcome.txt

選擇 2:採用合併分支的版本

echo "歡迎來到我們的專業網站" > welcome.txt

選擇 3:手動合併兩個版本

echo "歡迎來到我們的官方專業網站" > welcome.txt

步驟 6:標記衝突已解決

git add welcome.txt

步驟 7:完成合併

git commit

Git 會開啟編輯器讓你輸入合併提交訊息,預設訊息通常已經很合適:

Merge branch 'feature/welcome-update'

# Conflicts:
#	welcome.txt

使用合併工具解決衝突

設定合併工具

# 使用 VS Code 作為合併工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# 使用其他常見工具
git config --global merge.tool vimdiff    # Vim
git config --global merge.tool meld       # Meld(Linux)
git config --global merge.tool opendiff   # FileMerge(macOS)

啟動合併工具

git mergetool

這會開啟視覺化的三方比較介面,讓你更容易理解和解決衝突。

複雜衝突的處理策略

多檔案衝突

# 查看所有有衝突的檔案
git diff --name-only --diff-filter=U

# 逐一解決每個檔案
git mergetool file1.js
git mergetool file2.css
git mergetool file3.html

# 或批量處理
git mergetool

二進位檔案衝突

# 對於圖片等二進位檔案,需要手動選擇版本
git checkout --ours image.png      # 使用目前分支的版本
git checkout --theirs image.png    # 使用合併分支的版本
git add image.png

檔案重新命名衝突

# 當一個分支重新命名檔案,另一個分支修改內容時
git status  # 會顯示 "deleted by us" 或 "deleted by them"

# 手動處理
git add renamed-file.js    # 加入重新命名的檔案
git rm old-file.js         # 移除舊檔案

合併策略選擇

何時使用 Merge

# 適合以下情況:
# 1. 公開的、已推送的分支
# 2. 想保留完整的歷史脈絡
# 3. 團隊偏好看到明確的合併點

git switch main
git merge feature/user-dashboard

何時使用 Rebase

# 適合以下情況:
# 1. 私人的、未推送的分支
# 2. 希望保持線性歷史
# 3. 整理提交歷史

git switch feature/user-profile
git rebase main
git switch main
git merge feature/user-profile  # Fast-forward merge

何時使用 Squash and Merge

# 將功能分支的所有提交壓縮成一個
git switch main
git merge --squash feature/small-fix
git commit -m "修復小 bug:登入按鈕樣式問題"

預防衝突的最佳實踐

1. 頻繁同步主分支

# 每天開始工作前
git switch main
git pull origin main

# 切換到功能分支並同步
git switch feature/my-feature
git merge main    # 或使用 git rebase main

2. 保持功能分支短命

# 好的做法:小而頻繁的功能分支
git switch -c feature/add-login-button
# 1-2 天完成
git switch -c feature/validate-login-form
# 1-2 天完成

# 避免:長期的大功能分支
git switch -c feature/complete-user-system
# 2-3 週才完成,衝突機率大增

3. 團隊溝通協調

# 當多人修改同一區域時,提前溝通
# 例如:都要修改 header.css 時,先討論分工

進階合併技巧

使用 cherry-pick 選擇性合併

# 只合併特定的提交
git cherry-pick a1b2c3d

# 合併多個提交
git cherry-pick a1b2c3d e4f5g6h

# 合併提交但不自動提交(可以修改)
git cherry-pick --no-commit a1b2c3d

部分檔案合併

# 只合併特定檔案
git checkout feature/payment -- payment.js
git add payment.js
git commit -m "合併支付功能檔案"

互動式 rebase 清理歷史

# 在合併前清理功能分支的提交歷史
git switch feature/user-profile
git rebase -i HEAD~5

# 在編輯器中可以:
# pick -> 保留提交
# squash -> 合併到前一個提交
# reword -> 修改提交訊息
# drop -> 刪除提交

衝突解決工具比較

命令列工具

# 使用 git diff 查看衝突
git diff

# 使用 git show 查看特定提交
git show HEAD

# 使用 git log 查看合併歷史
git log --merge

圖形化工具

# 設定不同的合併工具
git config --global merge.tool kdiff3      # KDiff3
git config --global merge.tool p4merge     # Perforce P4Merge
git config --global merge.tool tortoisemerge  # TortoiseMerge
git config --global merge.tool beyondcompare  # Beyond Compare

推薦工具特色:

  • VS Code:免費,整合度高,適合日常使用
  • Beyond Compare:功能強大,但需付費
  • KDiff3:免費,跨平台,功能完整
  • P4Merge:免費,介面直觀

實際專案中的合併工作流程

功能開發完整流程

# 1. 建立功能分支
git switch main
git pull origin main
git switch -c feature/shopping-cart

# 2. 開發過程中定期同步
git fetch origin
git merge origin/main  # 或 git rebase origin/main

# 3. 功能完成,準備合併
git switch main
git pull origin main
git merge feature/shopping-cart

# 4. 如果有衝突,解決後繼續
# 如果沒有衝突,直接完成

# 5. 推送並清理
git push origin main
git branch -d feature/shopping-cart
git push origin --delete feature/shopping-cart

緊急修復流程

# 1. 從生產版本建立修復分支
git switch main
git pull origin main
git switch -c hotfix/critical-security-fix

# 2. 快速修復
echo "fixed security issue" > security.patch
git add security.patch
git commit -m "修復關鍵安全漏洞"

# 3. 合併到主分支
git switch main
git merge hotfix/critical-security-fix

# 4. 如果有開發分支,也要合併
git switch develop
git merge hotfix/critical-security-fix

# 5. 立即部署
git push origin main
git push origin develop

常見合併錯誤與解決

錯誤 1:合併錯誤的分支

# 如果合併還沒推送,可以重置
git reset --hard HEAD~1

# 如果已經推送,使用 revert
git revert -m 1 HEAD

錯誤 2:衝突解決不完全

# 檢查是否還有未解決的衝突
git diff --check

# 查看合併狀態
git status

# 如果發現問題,重新開始
git merge --abort

錯誤 3:錯誤的衝突解決

# 如果發現解決錯誤,可以重新解決
git reset --mixed HEAD~1
git merge feature/branch-name
# 重新解決衝突

團隊合併規範建議

Code Review 流程

# 1. 推送功能分支
git push -u origin feature/new-feature

# 2. 建立 Pull Request / Merge Request
# 3. 團隊 Review
# 4. 修改意見後更新
git add .
git commit -m "根據 review 意見修改"
git push

# 5. 核准後合併

合併訊息規範

# 好的合併訊息
git commit -m "Merge branch 'feature/user-authentication'

新增使用者驗證功能,包括:
- 登入/登出功能
- 密碼加密儲存
- 會話管理
- 單元測試覆蓋率 95%

Closes #123"

# 避免無意義的訊息
git commit -m "merge"
git commit -m "fix conflicts"

自動化合併檢查

使用 Git Hooks

# 在 .git/hooks/pre-merge-commit 中加入檢查
#!/bin/sh
# 檢查是否有未解決的衝突標記
if grep -r "<<<<<<< HEAD" .; then
    echo "錯誤:發現未解決的衝突標記"
    exit 1
fi

# 檢查測試是否通過
npm test
if [ $? -ne 0 ]; then
    echo "錯誤:測試未通過"
    exit 1
fi

CI/CD 整合

# .github/workflows/merge-check.yml
name: Merge Check
on:
  pull_request:
    branches: [ main ]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Run tests
      run: npm test
    - name: Check for conflict markers
      run: |
        if grep -r "<<<<<<< HEAD" .; then
          echo "Found unresolved conflict markers"
          exit 1
        fi

我的合併經驗談

作為一位資深工程師,我想分享一些實戰心得:

1. 衝突不可怕,可怕的是逃避

剛開始遇到衝突時,很多人會感到恐慌。其實衝突是正常的,代表團隊在積極開發。關鍵是要:

  • 冷靜分析衝突的原因
  • 理解兩個版本的差異
  • 做出合理的整合決策

2. 溝通比技術更重要

# 遇到複雜衝突時,不要自己猜測
# 找相關的開發者討論:
git log --oneline file-with-conflict.js  # 查看誰修改過
git blame file-with-conflict.js          # 查看每行的作者

然後找該作者討論最佳的合併方式。

3. 工具是助手,理解是關鍵

雖然有很多強大的合併工具,但理解衝突的本質更重要:

  • 為什麼會產生衝突?
  • 兩個版本想要達成什麼目標?
  • 如何整合才能保持功能完整?

衝突解決檢查清單

每次解決衝突後,我都會進行以下檢查:

# 1. 確認沒有衝突標記
grep -r "<<<<<<< HEAD" .
grep -r "=======" .
grep -r ">>>>>>> " .

# 2. 執行測試
npm test  # 或其他測試指令

# 3. 檢查語法
npm run lint  # 或其他程式碼檢查工具

# 4. 本地運行確認功能
npm start  # 啟動應用程式測試

# 5. 檢查合併後的程式碼邏輯
git diff HEAD~1  # 查看這次合併的所有變更

小結

合併和衝突解決是 Git 使用中最具挑戰性的部分,但也是最重要的技能之一:

核心概念回顧:

  • Fast-forward vs 三方合併:理解不同的合併情況
  • Merge vs Rebase:選擇適合的整合策略
  • 衝突標記:學會讀懂 Git 的衝突提示
  • 解決策略:保留、替換、或整合

重要指令:

git merge branch-name       # 合併分支
git rebase branch-name      # 變基操作
git mergetool              # 啟動合併工具
git merge --abort          # 取消合併
git status                 # 查看合併狀態

最佳實踐:

  1. 頻繁同步主分支
  2. 保持功能分支簡短
  3. 使用合適的合併工具
  4. 團隊溝通協調
  5. 建立合併檢查流程

在下一篇文章中,我們將學習遠端倉庫的操作,包括如何與 GitHub、GitLab 等平台協作,以及如何設定和管理多個遠端倉庫。


記住,成為合併衝突解決的專家需要時間和練習。每次遇到衝突都是一次學習機會,不要害怕犯錯,在安全的環境中多加練習,你很快就能掌握這項重要技能。

404NOTE
404NOTE
文章: 40

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *