Bối cảnh & Tại sao bạn cần zdiff3
Dân dev chắc không lạ gì cảm giác toát mồ hôi khi resolve một merge conflict hơn 100 dòng: “Đoạn này ông A xóa hay ông B viết thêm? Giá trị ban đầu vốn là bao nhiêu?”. Khổ nỗi, Git mặc định chỉ ném vào màn hình hai khối code đối đầu nhau: nhánh hiện tại (HEAD) và nhánh cần gộp. Bạn không hề có dữ liệu về trạng thái gốc của file trước khi hai bên bắt đầu chỉnh sửa.
Mất ngữ cảnh đồng nghĩa với việc bạn phải đoán mò. Team mình từng dính một cú phốt nhớ đời: dev giải quyết xung đột bằng cách giữ code nhánh mình và xóa đè nhánh kia. Kết quả là toàn bộ logic validate coupon mới ra mắt bốc hơi khỏi production ngay đêm mở bán. Cả đội phải thức trắng đến 3h sáng để trace commit và rollback hotfix khẩn cấp.
Đây là kiểu xung đột 2 chiều (2-way diff) mà Git hiển thị theo mặc định:
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 3000;
const retries = 3;
>>>>>>> feature/retry-logic
Nhìn vào đây, đố bạn biết giá trị timeout ban đầu là bao nhiêu? Nhánh HEAD tăng từ 1000 lên 5000, hay nhánh feature hạ từ 5000 xuống 3000? Chịu chết. Muốn biết, bạn buộc phải gõ git log hoặc git blame để tra lại lịch sử. Rất mất thời gian.
Để giải bài toán này, Git từ lâu đã có chế độ diff3. Cơ chế này bổ sung một khối ||||||| base ở giữa, đại diện cho tổ tiên chung (Common Ancestor) trước khi rẽ nhánh.
Tiếc là diff3 đời cũ xử lý còn khá thô. Nó thường kéo theo cả những dòng code giống hệt nhau ở hai biên vào vùng conflict. Khối xung đột vì thế dài ngoằng và lẫn nhiều dòng code thừa.
Từ bản 2.35 (phát hành đầu năm 2022), Git ra mắt zdiff3 (Zealous diff3) để khắc phục nhược điểm trên. Từ “Zealous” thể hiện sự quyết liệt: Git chủ động cắt tỉa (trim) sạch sẽ các dòng trùng nhau ở đầu và cuối. Khối hiển thị chỉ giữ lại đúng những dòng thực sự mâu thuẫn kèm phần base tương ứng. Sau khi áp dụng zdiff3 cho team 12 dev tại công ty mình, các sự cố merge nhầm logic khi release giảm tới hơn 80%.
Kiểm tra phiên bản Git
Tính năng này đã có sẵn từ Git 2.35. Bạn hãy kiểm tra phiên bản trên máy:
git --version
Nếu phiên bản thấp hơn 2.35 (ví dụ Ubuntu 20.04 LTS mặc định dùng Git 2.25), bạn cần cập nhật Git:
# Ubuntu / Debian
sudo add-apt-repository ppa:git-core/ppa -y
sudo apt update
sudo apt install git -y
# macOS (Homebrew)
brew install git
# Arch Linux
sudo pacman -S git
Chỉ cần Git từ 2.35 trở lên là dùng được ngay. Bạn không cần cài thêm bất kỳ plugin bên ngoài nào.
Cách bật zdiff3 trong Git
Tùy theo nhu cầu làm việc, bạn có thể thiết lập cho toàn bộ máy hoặc cài riêng cho từng repo.
1. Thiết lập toàn cục (Khuyên dùng)
Chạy lệnh này một lần duy nhất trên terminal để áp dụng cho mọi project trên máy:
git config --global merge.conflictstyle zdiff3
2. Cấu hình trực tiếp qua file .gitconfig
Nếu quen mở file cấu hình người dùng tại ~/.gitconfig, bạn chỉ việc thêm hai dòng này:
[merge]
conflictstyle = zdiff3
3. Áp dụng cho một repository cụ thể
Khi làm việc trong repo công ty có yêu cầu riêng, bạn chỉ cần bỏ flag --global:
cd /path/to/your-project
git config merge.conflictstyle zdiff3
4. So sánh thực tế: default vs zdiff3
Giả sử file gốc của chúng ta có hàm tính tiền:
// common ancestor
function calculateTotal(price, tax) {
const subtotal = price * 1.1;
return subtotal + tax;
}
Nhánh main đổi thuế từ 10% lên 15%:
function calculateTotal(price, tax) {
const subtotal = price * 1.15;
return subtotal + tax;
}
Nhánh feature đổi tên biến từ subtotal sang amount và nâng thuế lên 20%:
function calculateTotal(price, tax) {
const amount = price * 1.2;
return amount + tax;
}
Khi merge hai nhánh lại, zdiff3 gom gọn vùng xung đột như sau:
function calculateTotal(price, tax) {
<<<<<<< HEAD
const subtotal = price * 1.15;
||||||| base
const subtotal = price * 1.1;
=======
const amount = price * 1.2;
>>>>>>> feature
return subtotal + tax; // Cần chỉnh lại theo tên biến mới sau khi merge
}
Nhờ có khối ||||||| base, mọi thứ sáng tỏ ngay:
- Dòng gốc ban đầu là
subtotal = price * 1.1. - Nhánh HEAD chỉ tăng hệ số lên 1.15.
- Nhánh feature vừa đổi hệ số lên 1.2, vừa đổi tên biến thành
amount.
Bạn biết chính xác nguồn cơn thay đổi từ hai phía. Việc đưa ra quyết định merge chuẩn chỉ tốn chưa đầy 10 giây.
Kiểm tra và trải nghiệm thực tế
Thiết lập xong, bạn gõ lệnh sau để đảm bảo Git đã ghi nhận cấu hình:
git config --get merge.conflictstyle
Kết quả in ra đúng chữ zdiff3 là xong.
Tạo kịch bản test nhanh trong 30 giây
Bạn có thể chạy thử kịch bản sau trong thư mục /tmp để tận mắt thấy cách zdiff3 hoạt động:
# 1. Tạo repo thử nghiệm
mkdir /tmp/test-zdiff3 && cd /tmp/test-zdiff3
git init
git config merge.conflictstyle zdiff3
# 2. Tạo commit gốc (Ancestor)
echo -e "alpha\nbravo\ncharlie" > test.txt
git add test.txt
git commit -m "Commit base"
# 3. Tạo nhánh A và sửa dòng bravo
git checkout -b branch-a
echo -e "alpha\nbravo-modified-A\ncharlie" > test.txt
git commit -am "Branch A update"
# 4. Quay về main và tạo nhánh B, sửa cùng vị trí
git checkout main
git checkout -b branch-b
echo -e "alpha\nbravo-modified-B\ncharlie" > test.txt
git commit -am "Branch B update"
# 5. Kích hoạt merge conflict
git merge branch-a
Mở file test.txt lên, bạn sẽ thấy kết quả bóc tách gãy gọn:
alpha
<<<<<<< HEAD
bravo-modified-B
||||||| base
bravo
=======
bravo-modified-A
>>>>>>> branch-a
charlie
Hai dòng alpha và charlie nằm nguyên vẹn bên ngoài. Bạn thấy rõ từ chữ bravo ban đầu, hai nhánh đã rẽ hướng như thế nào.
Tương thích mượt mà với VS Code và JetBrains
Các IDE phổ biến như VS Code, Cursor hay bộ JetBrains (IntelliJ, WebStorm) đều nhận diện rất tốt cấu trúc 3 phần này. Trên VS Code, khi mở file conflict có zdiff3, editor sẽ tự bật giao diện 3-Way Merge Editor. Khung Common Ancestor nằm ngay chính giữa, hai bên là Current và Incoming. Bạn chỉ việc tick chọn dòng code cần giữ mà không lo xóa nhầm công sức của đồng đội.
