</>Học Dev
Bài học

Tuần 2 - Ngày 3: Conflict Resolution Nâng Cao

Tuần 2 – Ngày 3

Tuần 2 - Ngày 3: Conflict Resolution Nâng Cao

Mục tiêu học tập

  • Xử lý conflict trong large merge
  • Hiểu semantic conflicts (nguy hiểm nhất)
  • Sử dụng git mergetool (kdiff3, VS Code)
  • Cấu hình và dùng git rerere để tái dùng conflict resolution
  • Chiến lược --no-ff trong team workflows

1. Loại Conflicts

Textual conflict — Git phát hiện được

Hai người sửa cùng dòng hoặc vùng gần nhau trong file.

<<<<<<< HEAD
const MAX_RETRY = 3;
=======
const MAX_RETRY = 5;
>>>>>>> feature/retry-policy

Git có thể phát hiện và báo cáo — bạn chỉ cần resolve thủ công.

Semantic conflict — Nguy hiểm nhất

Merge "clean" (không có textual conflict) nhưng logic bị sai.

// Developer A (main): đổi tên hàm
function processPayment(amount, currency) { ... }

// Developer B (feature/multi-currency): gọi hàm theo tên cũ
const result = processOrder(amount);    // TypeError at runtime!

Merge thành công, CI pass nếu test coverage thấp — nhưng production crash.

Cách phòng ngừa:

# Test toàn diện sau mỗi merge
npm test && npm run e2e

# TypeScript/static analysis phát hiện tên hàm sai
# Luôn chạy type-check trong CI:
tsc --noEmit

# Pair review: reviewer đọc kỹ logic, không chỉ diff

2. Xử lý Conflict trong Large Merge

Xem trước khi merge

# Xem files nào sẽ conflict trước khi merge
git merge --no-commit --no-ff feature/large-refactor
# nếu có conflict → git merge --abort để hủy
# nếu OK → git commit để hoàn thành

# Hoặc dùng diff3 strategy:
git merge --strategy-option diff3 feature/large-refactor

Chiến lược xử lý conflict lớn

# 1. Bắt đầu merge, git liệt kê files conflict
git merge feature/big-feature
# CONFLICT (content): Merge conflict in src/api/routes.ts
# CONFLICT (content): Merge conflict in src/models/user.ts
# CONFLICT (add/add): Merge conflict in src/utils/validators.ts

# 2. Xem danh sách tất cả files conflict
git diff --name-only --diff-filter=U

# 3. Resolve từng file một, bắt đầu từ file ít phụ thuộc nhất
git add src/utils/validators.ts    # đã resolve
git add src/models/user.ts         # đã resolve
# ... tiếp tục ...

# 4. Kiểm tra còn file nào chưa resolve
git status | grep "both modified"

# 5. Hoàn thành merge
git commit

# 6. QUAN TRỌNG: chạy tests ngay sau merge
npm test

Conflict markers 3-way (diff3 style)

# Bật conflict3 style — thấy được base chung
git config --global merge.conflictstyle diff3

# Output với diff3:
<<<<<<< HEAD
const timeout = 30000;
||||||| base     ← nội dung trước cả 2 branch thay đổi
const timeout = 20000;
=======
const timeout = 60000;
>>>>>>> feature/auth

# Hiểu context: base=20000, HEAD tăng lên 30000, feature tăng lên 60000
# Quyết định: có thể cần cả hai → 90000? hay chỉ lấy feature = 60000?

3. git mergetool — Visual Conflict Resolution

# Chạy mergetool (mở editor được cấu hình)
git mergetool

# Hoặc cho một file cụ thể
git mergetool src/api/routes.ts

Cấu hình VS Code làm mergetool

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# Hoặc dùng VS Code built-in merge editor (Git 2.34+)
# VS Code tự động detect conflict và show 3-way merge editor

Cấu hình vimdiff

git config --global merge.tool vimdiff
git config --global mergetool.vimdiff.layout "LOCAL,BASE,REMOTE/MERGED"
# LOCAL = HEAD (bên trái)
# BASE = ancestor chung
# REMOTE = branch đang merge
# MERGED = file output (bên dưới)

Các tool phổ biến khác

# Kdiff3 (GUI, cross-platform)
git config --global merge.tool kdiff3

# Meld (Linux, GUI)
git config --global merge.tool meld

# Beyond Compare (commercial, rất mạnh)
git config --global merge.tool bc

# IntelliJ/WebStorm tích hợp built-in merge tool

4. git rerere — Reuse Recorded Resolution

rerere = Reuse Recorded Resolution. Git ghi nhớ cách bạn resolve một conflict cụ thể và tự động áp dụng lại nếu conflict đó xuất hiện lần sau.

Khi nào rerere hữu ích?

Tình huống: Long-running feature branch, thường xuyên rebase với main.
Mỗi lần rebase lại gặp cùng conflict ở cùng file.

Không có rerere: Resolve thủ công mỗi lần → mất thời gian
Có rerere: Lần đầu resolve → Git ghi nhớ → lần sau tự apply

Cấu hình và sử dụng

# Bật rerere (resolutions được lưu tại .git/rr-cache/ trong mỗi repo)
git config --global rerere.enabled true

# Hoặc chỉ cho repo hiện tại
git config rerere.enabled true
# Workflow với rerere

# 1. Lần đầu gặp conflict
git rebase main
# CONFLICT: src/config.ts

# Resolve thủ công
vim src/config.ts   # edit file

# Stage file
git add src/config.ts
git rebase --continue
# → Git ghi lại: "conflict này" → "resolution này"

# 2. Lần sau rebase lại (sau khi main có thêm commits)
git rebase main
# CONFLICT: src/config.ts
# Recorded preimage for 'src/config.ts'
# → Git tự động apply resolution đã ghi nhớ!
# Rerere may have been used to resolve this conflict.

# Kiểm tra rerere đã apply đúng chưa
git diff src/config.ts    # xem kết quả
git add src/config.ts
git rebase --continue
# Xem các resolution đã ghi nhớ
git rerere status

# Xem chi tiết một resolution
ls .git/rr-cache/

# Xoá bỏ tất cả rerere records (nếu cần reset)
git rerere forget src/config.ts

# Bật tự động stage sau khi rerere apply (cẩn thận — kiểm tra trước)
git config --global rerere.autoUpdate true

5. Chiến lược --no-ff trong Team Workflows

Tại sao --no-ff quan trọng trong Git Flow

# Không có --no-ff (fast-forward khi có thể):
# git log --graph:
# * abc1234 feat: add feature C (commit của feature branch)
# * def5678 feat: add feature B
# * 9ab1234 chore: init
# → Không thể biết "feature C" kết thúc ở commit nào, bắt đầu ở đâu

# Với --no-ff:
# *   merge-hash Merge branch 'feature/C' into main
# |\
# | * abc1234 feat: complete feature C
# | * xyz1234 feat: WIP feature C
# |/
# * def5678 feat: add feature B
# → Rõ ràng: feature C gồm 2 commits, merge tại điểm nào

Khi nào dùng --no-ff

# Git Flow: luôn --no-ff khi merge feature vào develop
git switch develop
git merge --no-ff feature/user-search
git branch -d feature/user-search

# GitHub Flow: thường squash merge (GitHub UI) hoặc merge commit
# → PR merge button: "Merge commit" vs "Squash and merge" vs "Rebase and merge"

# Squash merge: tất cả commits của feature thành 1 commit
# → Lịch sử main rất clean, nhưng mất detail của feature
# → Phù hợp cho team muốn main history = list of features

# Merge commit (--no-ff): giữ structure
# → Phù hợp cho audit trail, debugging chi tiết

6. Conflict Resolution Checklist

Trước khi bắt đầu merge lớn:
□ git status clean (no uncommitted changes)
□ git pull/rebase main mới nhất
□ Thông báo team: "Đang merge X, hold off on merging to main"

Trong khi resolve:
□ Hiểu context của cả hai thay đổi (đọc commit message)
□ Dùng diff3 style để thấy base
□ Test sau mỗi file resolve (không đợi hết tất cả)
□ Ghi chú lý do resolve theo hướng nào

Sau khi merge:
□ git status sạch (không còn conflict markers)
□ Build pass: npm run build
□ Test pass: npm test
□ Kiểm tra có semantic conflict không (logic review)
□ Push với PR để reviewer check lại

Câu hỏi ôn tập

  1. Tại sao semantic conflict nguy hiểm hơn textual conflict?

    Xem đáp án

    Textual conflict Git phát hiện và báo trực tiếp — bạn biết cần resolve. Semantic conflict là khi merge clean (không có conflict markers) nhưng logic bị sai: ví dụ Dev A đổi tên function, Dev B vẫn gọi tên cũ. Merge thành công, CI pass nếu test coverage thấp, nhưng production crash. Không có tool tự động phát hiện — chỉ code review kỹ lưỡng, TypeScript/static analysis, và integration tests mới bắt được.

  2. diff3 conflict style bổ sung thêm gì so với default?

    Xem đáp án

    diff3 (bật bằng git config --global merge.conflictstyle diff3) thêm một section thứ ba giữa ======= markers: ||||||| base — hiển thị nội dung trước khi cả hai branches thay đổi (ancestor chung). Điều này giúp hiểu context: bạn thấy giá trị gốc là gì, HEAD thay đổi thế nào, và feature branch thay đổi thế nào — dễ quyết định resolve hơn chỉ thấy 2 versions.

  3. git rerere giải quyết vấn đề gì trong long-running feature branches?

    Xem đáp án

    Long-running feature branches phải rebase thường xuyên với main. Nếu cùng một conflict xuất hiện nhiều lần (ví dụ file config bị cả hai branches sửa), bạn phải resolve thủ công mỗi lần rebase. rerere (Reuse Recorded Resolution) ghi nhớ cách bạn resolve lần đầu và tự động áp dụng lại những lần sau — tiết kiệm thời gian và tránh sai nhất quán.

  4. Sự khác biệt giữa merge commit (--no-ff) và squash merge?

    Xem đáp án

    Merge commit (--no-ff): giữ tất cả commits của feature branch trong lịch sử, tạo một merge commit với 2 parents — lịch sử có nhánh rẽ rõ ràng, audit trail đầy đủ. Squash merge: gộp tất cả commits của feature thành 1 commit duy nhất trên main — lịch sử main rất clean (mỗi entry = một feature), nhưng mất detail của quá trình phát triển feature. GitHub PR squash merge dùng cách này.

  5. Lệnh nào liệt kê tất cả files có conflict chưa resolve?

    Xem đáp án

    git diff --name-only --diff-filter=U (U = Unmerged). Hoặc git status | grep "both modified". Trong khi merge đang in-progress, git status cũng hiện "Unmerged paths" rõ ràng. Sau khi resolve từng file phải git add <file> để đánh dấu resolved trước khi git commit hoặc git rebase --continue.

Bài tập thực hành

mkdir conflict-advanced-lab && cd conflict-advanced-lab
git init
git config user.name "Test" && git config user.email "test@test.com"

# Bật rerere
git config rerere.enabled true
git config merge.conflictstyle diff3

# 1. Tạo conflict tái lặp (để test rerere)
echo "timeout: 30" > config.yml
git add . && git commit -m "chore: init config"

git switch -c feature/retry
echo "timeout: 60" > config.yml
git add . && git commit -m "feat: increase timeout"

git switch main
echo "timeout: 45" > config.yml
git add . && git commit -m "feat: medium timeout for main"

# 2. Lần 1: resolve thủ công
git merge feature/retry
# → CONFLICT in config.yml với diff3 markers
# Resolve: timeout: 60
echo "timeout: 60" > config.yml
git add config.yml && git commit
# Git ghi nhận resolution

# 3. Tạo lại conflict scenario
git switch feature/retry
echo "timeout: 60" >> config.yml  # thêm dòng mới để có commit mới
git add . && git commit -m "feat: additional config"
git switch main
echo "timeout: 45" >> config.yml
git add . && git commit -m "feat: more main changes"

# 4. Merge lần 2 — rerere tự apply
git merge feature/retry
# Git thông báo: "Resolved 'config.yml' using previous resolution."
git status   # kiểm tra auto-resolved
cat config.yml   # xem kết quả

# 5. Test --no-ff
git switch -c feature/no-ff-test
echo "new feature" >> config.yml
git add . && git commit -m "feat: no-ff test"

git switch main
git merge --no-ff feature/no-ff-test -m "feat: merge no-ff-test"
git log --oneline --graph
# Thấy merge commit với 2 parents

Tài liệu tham khảo chính thức


Tiếp theo: Ngày 4 — Release Management