Giải quyết xung đột merge trong Git: Hướng dẫn từ thủ công đến dùng tool

Git tutorial - IT technology blog
Git tutorial - IT technology blog

Lần đầu gặp merge conflict, mình cứ nghĩ mình đã làm hỏng cái gì đó. Dòng chữ đỏ chói CONFLICT (content): Merge conflict in app.py hiện lên, mình tắt terminal và đi pha cà phê để bình tĩnh lại.

Thực ra merge conflict hoàn toàn bình thường. Nó xảy ra khi 2 người cùng sửa cùng một đoạn code trong cùng một file, rồi cả hai đều muốn gộp (merge) vào branch chính. Git không biết phải giữ code của ai, nên nó “cầu cứu” bạn xử lý thủ công.

Merge conflict trông như thế nào?

Git đánh dấu vùng xung đột bằng ba loại marker chèn thẳng vào file — nhìn lần đầu hơi rối, nhưng hiểu rồi thì đọc được trong vài giây:

<<<<<<< HEAD
def tinh_tong(a, b):
    return a + b  # version của bạn
=======
def tinh_tong(a, b):
    result = a + b
    return result  # version của đồng nghiệp
>>>>>>> feature/refactor-math

Phần giữa <<<<<<< HEAD======= là code của bạn (branch hiện tại). Phần còn lại là code từ branch kia. Nhiệm vụ của bạn: chọn giữ cái nào, hoặc kết hợp cả hai, rồi xóa hết marker đi.

Ba cách xử lý merge conflict phổ biến

Cách 1: Sửa tay trong text editor

Cách đơn giản nhất — mở file lên, xóa các marker, giữ lại đoạn code đúng, rồi commit lại.

# Sau khi merge báo conflict
git merge feature/refactor-math

# Mở file bị conflict, sửa tay, rồi:
git add app.py
git commit -m "fix: resolve merge conflict in tinh_tong function"

Ưu điểm: Không cần cài thêm gì, kiểm soát hoàn toàn từng dòng code.
Nhược điểm: Dễ bỏ sót marker, nhất là file dài có nhiều conflict. Đã có lần mình push code lên mà quên xóa dòng >>>>>>> feature/xyz — build server báo lỗi ngay, khá xấu hổ.

Cách 2: Dùng merge tool (VS Code, vimdiff, IntelliJ)

VS Code, IntelliJ hay vimdiff đều làm một việc: hiển thị hai phiên bản song song để bạn chọn giữ cái nào — không cần nhìn vào đống marker lộn xộn nữa.

# Cấu hình VS Code làm merge tool mặc định
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# Chạy merge tool
git mergetool

Nếu đang ở trên server SSH không có GUI, dùng vimdiff:

git config --global merge.tool vimdiff
git mergetool
# Trong vimdiff:
# ]c  — nhảy đến conflict tiếp theo
# :diffg LOCAL  — giữ version của bạn
# :diffg REMOTE — giữ version từ branch kia

Ưu điểm: Nhìn rõ ràng, khó bỏ sót hơn, nhiều tool còn highlight syntax giúp đọc code dễ hơn.
Nhược điểm: Cần thiết lập ban đầu; vimdiff có learning curve nếu chưa quen vim.

Cách 3: Chiến lược ours/theirs

Đôi khi bạn chắc chắn một phiên bản luôn đúng — chẳng hạn file config chỉ cần giữ version của main, bỏ hết thay đổi từ branch kia.

# Giữ toàn bộ code của branch hiện tại
git checkout --ours package.json
git add package.json

# Ngược lại: giữ toàn bộ từ branch kia
git checkout --theirs package.json
git add package.json

Ưu điểm: Nhanh, không cần suy nghĩ nhiều.
Nhược điểm: Mất code của một bên hoàn toàn — dùng sai ngữ cảnh là mất thay đổi quan trọng không phục hồi được.

Phân tích: Khi nào dùng cách nào?

Sau vài năm làm việc với Git trong team, mình tổng kết lại như này:

  • File config (package.json, requirements.txt, .env.example): Dùng ours/theirs — ít logic, chỉ cần quyết định version nào đúng.
  • File code có logic phức tạp: Bắt buộc sửa tay hoặc dùng merge tool — cần hiểu cả hai thay đổi trước khi quyết định giữ cái nào.
  • Nhiều conflict trong 1 file: Dùng merge tool như VS Code — visualize rõ hơn, ít bỏ sót marker hơn.
  • Trên server SSH: Sửa tay với nano/vim hoặc dùng vimdiff.

Workflow thực tế: Xử lý conflict từng bước

Workflow mình dùng mỗi khi gặp conflict — từng bước, không bỏ sót:

# Bước 1: Xem danh sách file bị conflict
git status
# File conflict hiện với trạng thái "both modified"

# Bước 2: Xem chi tiết conflict trong từng file
git diff

# Bước 3: Mở VS Code để giải quyết (hoặc sửa tay)
code .

# Bước 4: Sau khi sửa xong, đánh dấu đã resolve
git add app.py

# Bước 5: Kiểm tra xem còn conflict không
git status

# Bước 6: Hoàn tất merge
git commit
# Hoặc nếu đang rebase:
git rebase --continue

Trước khi push, chạy thêm lệnh này để chắc không còn sót marker — đã cứu mình ít nhất vài lần khỏi build fail xấu hổ:

grep -r "<<<<<<< " --include="*.py" --include="*.js" .
# Không có output = an toàn để push

Cách phòng tránh để giảm conflict

Mình học bài này theo kiểu cực chẳng đã — dọn conflict mệt quá mới chịu thay đổi workflow. Team 8 người áp dụng 4 quy tắc dưới đây, số conflict giảm từ 3–4 lần/ngày xuống còn 1–2 lần/tuần:

  1. Pull trước khi bắt đầu làm: Luôn sync code mới nhất về trước khi viết code mới.
  2. Branch nhỏ, sống ngắn: Branch tồn tại càng lâu, càng diverge xa main, conflict càng nhiều và càng khó xử lý. Mình đặt quy tắc: branch không quá 3 ngày.
  3. Tách nhiệm vụ rõ ràng: Hai người không cùng sửa một file trong cùng sprint nếu có thể tránh được.
  4. Merge main vào branch thường xuyên: Mỗi sáng sync main vào branch đang làm để conflict nhỏ và dễ xử lý ngay, thay vì để tích lũy.
# Workflow buổi sáng
git checkout main
git pull origin main
git checkout feature/my-task
git rebase main
# Giải quyết conflict nhỏ ngay từ đây — dễ hơn nhiều so với để đến cuối sprint

Merge vs Rebase — Chọn cái nào?

Câu hỏi mình hay được hỏi nhất. Ngắn gọn:

  • Merge: Tạo merge commit, giữ nguyên lịch sử thật. Dùng khi gộp feature branch vào main.
  • Rebase: Viết lại lịch sử, commit nối tiếp nhau gọn hơn. Dùng để cập nhật feature branch với main trước khi merge.

Quy tắc đơn giản nhất: không bao giờ rebase branch đang được nhiều người dùng chung. Chỉ rebase branch mà một mình bạn đang làm.

Tổng kết

Nhớ lại cái lần đầu tắt terminal đi pha cà phê đó — thực ra mình chỉ cần mở file, xóa mấy dòng marker, rồi commit. Đơn giản vậy thôi. Nắm rõ 3 cách trong bài, bạn sẽ không cần cà phê bình tĩnh mỗi khi thấy dòng đỏ chói nữa.

Về dài hạn, thói quen team mới là thứ thực sự giảm conflict. Branch nhỏ, merge sớm, sync mỗi sáng — nghe đơn giản nhưng mình cần gần 6 tháng mới làm đều. Bắt đầu từ một thứ thôi: chạy git rebase main trước khi code mỗi sáng. Chỉ vậy đã thấy khác rồi.

Share: