Bạn mở diff giữa main và feature rồi thấy một file chỉ được thêm trên main lại hiện như bị xóa. Nhánh feature có thể chưa hề xóa file ấy: bạn đang so hai snapshot khác nhau. Dùng ba dấu chấm chuyển điểm đầu so sánh về tổ tiên chung để xem phần nhánh thứ hai mang vào.
Bài đối chiếu tài liệu git-diff ngày 07/11/2026 và chạy bằng Git 2.54.0. Mọi commit và nhánh đều được tạo trong repository tạm. Không đổi repository công việc, không fetch hoặc push; demo không có merge phức tạp hay nhiều merge base.
Chốt hai đầu của phép so sánh
git diff A..B tương đương so snapshot A với snapshot B. git diff A...B so merge base của A và B với B. Với cú pháp ba chấm, vị trí nhánh thứ hai có ý nghĩa: đảo A và B có thể trả phần thay đổi khác.
Đây là ngữ nghĩa của git diff. Không chuyển nguyên cách hiểu này sang mọi lệnh Git chỉ vì chúng cũng dùng dấu chấm; mỗi lệnh có thể diễn giải revision notation theo mục đích khác.
1. Tạo hai nhánh có thay đổi riêng
Chạy nguyên khối Bash sau. Dấu ngoặc tròn giữ việc cd trong subshell:
(
set -eu
demo_dir=$(mktemp -d)
printf '%s\n' "$demo_dir"
cd "$demo_dir"
git init -q -b main
git config user.name 'AeDev Demo'
git config user.email '[email protected]'
printf 'shared\n' > common.txt
git add -- common.txt
git -c commit.gpgsign=false commit -qm 'Base'
git switch -q -c feature
printf 'feature work\n' > feature-only.txt
git add -- feature-only.txt
git -c commit.gpgsign=false commit -qm 'Feature work'
git switch -q main
printf 'main work\n' > main-only.txt
git add -- main-only.txt
git -c commit.gpgsign=false commit -qm 'Main work'
git diff --name-status main..feature
git diff --name-status main...feature
)
Giữ đường dẫn thư mục tạm ở dòng đầu để kiểm tra tiếp. Phép hai chấm phải có A cho feature-only.txt và D cho main-only.txt. Phép ba chấm chỉ có A cho feature-only.txt trong fixture này.
Chữ D của phép so hai snapshot nói rằng file có ở đầu main nhưng không có ở đầu feature. Nó không tự chứng minh đã có một commit trên feature xóa file main-only.txt. Bảng lịch sử trong ví dụ giúp bạn tách hai cách giải thích.
2. Đảo chiều và đối chiếu merge base
Mở shell ở đúng repository tạm vừa in, chạy các lệnh sau:
git diff --name-status feature...main
base=$(git merge-base main feature)
git diff --name-status "$base" feature
git log --oneline --graph --all
git status --short
Khi đảo chiều ba chấm, phần được chọn là thay đổi của main từ tổ tiên chung: main-only.txt. Khi ghi rõ merge base rồi so với feature, kết quả phải khớp main…feature. Working tree vẫn sạch vì các lệnh diff và log này chỉ đọc.
Biến base ở đây chỉ là hash commit được Git trả về. Nó không đổi branch, không merge và không reset nội dung. Không cần sửa lịch sử để hiểu vì sao hai cách diff có kết quả khác nhau.
Kiểm tra trước khi dùng cho code review
Xác định nhánh đích và nhánh muốn review, rồi chọn chúng theo đúng thứ tự. Nếu dùng remote-tracking branch, nhớ tên ấy phản ánh lần cập nhật local gần nhất; bài không chạy fetch nên không chứng minh main đang khớp server của dự án.
Sau khi hiểu danh sách đường dẫn, đọc patch đầy đủ để xem nội dung thay đổi. –name-status chỉ giúp nhìn bản đồ file, không chứng minh thay đổi an toàn hoặc chương trình vẫn build.
Lỗi thường gặp và giới hạn
Đừng thay hai chấm bằng ba chấm chỉ để làm một dòng D biến mất. Trước hết xác định bạn cần so snapshot cuối cùng hay xem công việc của nhánh từ điểm tách. Cả hai câu hỏi đều hữu ích trong các tình huống khác nhau.
Demo có một tổ tiên chung đơn giản. Lịch sử đã rebase, squash hoặc có nhiều merge phức tạp cần được đọc lại theo commit thực tế; bài chưa thử những trường hợp đó. Khi báo cáo kết quả review, ghi rõ hai ref và phép so sánh đang dùng để đồng đội tái hiện đúng cùng một patch.

