Sau Bài 7 học cách cứu commit tưởng đã mất, bài này giải quyết một bài toán khác: commit nào trong 200 commit vừa qua đã làm hỏng tính năng X? Dò từng commit một (build lại, test lại) là O(n) — chậm và nản. git bisect biến việc đó thành một bài toán tìm kiếm nhị phân quen thuộc, giảm xuống còn O(log n) bước.

1. Bài Toán: Lỗi Nằm Ở Đâu Trong Hàng Trăm Commit?

Giả sử bạn biết commit hiện tại (HEAD) có lỗi, và commit từ 1 tuần trước (hoặc 1 tag release cũ) thì chắc chắn ổn. Ở giữa 2 điểm đó là hàng chục, hàng trăm commit. Dò tuần tự từ đầu tới cuối tốn tối đa n lần build+test. Nhưng nếu giả định lỗi chỉ xuất hiện đúng 1 lần và không biến mất rồi quay lại (tính chất đơn điệu — monotonic), bạn có thể áp dụng đúng thuật toán tìm kiếm nhị phân trên mảng đã sắp xếp: luôn kiểm tra điểm giữa, rồi thu hẹp một nửa phạm vi mỗi lần.

2. git bisect start/good/bad: Thu Hẹp Phạm Vi

Quy trình thủ công gồm 4 loại lệnh:

Lệnh Ý nghĩa
git bisect start Bắt đầu phiên bisect, chưa biết ranh giới good/bad nào.
git bisect bad [ref] Đánh dấu 1 commit là lỗi (mặc định là HEAD hiện tại nếu không truyền ref).
git bisect good <ref> Đánh dấu 1 commit cũ hơn là ổn — thiết lập ranh giới dưới của phạm vi tìm kiếm.
git bisect reset Kết thúc phiên, quay lại đúng branch/HEAD ban đầu trước khi bisect.

Ngay khi có cả 1 mốc good và 1 mốc bad, Git tự động checkout commit ở chính giữa phạm vi đó — luôn dưới dạng detached HEAD (Bài 3), vì bạn chỉ đang "ghé thăm" để test chứ không làm việc trên đó. Bạn build/test commit đó, rồi báo lại good hoặc bad, Git tiếp tục thu hẹp phạm vi còn một nửa. Lặp lại tới khi phạm vi chỉ còn đúng 1 commit — đó chính là "thủ phạm" đầu tiên gây lỗi.

ℹ️ Không build được thì skip, đừng đoán
Có những commit không thể test được (không build được, thiếu dependency tại thời điểm đó). git bisect skip báo cho Git biết "bỏ qua commit này, không tính là good hay bad" — Git sẽ chọn 1 điểm giữa khác thay vì buộc bạn đoán mò, tránh làm sai lệch kết quả tìm kiếm.

3. Tự Động Hoá Với git bisect run

Thay vì tự tay test rồi gõ good/bad hàng chục lần, git bisect run <lệnh> giao toàn bộ vòng lặp cho 1 script — thường là lệnh chạy test. Git đọc mã thoát (exit code) của lệnh đó để tự quyết định:

Exit code Git hiểu là
0 good — commit này ổn.
1124, 126127 bad — commit này lỗi.
125 skip — không thể test commit này (dành riêng, không phải "bad").

Toàn bộ vòng lặp checkout → chạy script → đọc exit code → thu hẹp phạm vi diễn ra tự động, không cần tương tác — cực kỳ hữu ích khi phạm vi tìm kiếm lên tới hàng trăm commit.

💡 Mẹo: script cho bisect run nên tự lo phần build
Một lệnh điển hình: git bisect run npm test hoặc 1 script bash tự build rồi chạy test cụ thể. Script nên exit 125 ngay khi build thất bại vì lý do không liên quan tới bug đang tìm (ví dụ thiếu file tại thời điểm đó) — để Git bỏ qua commit đó thay vì tính nhầm là bad.

4. So Sánh Độ Phức Tạp: O(log n) vs O(n)

Số commit cần dò Dò tuần tự (tối đa) git bisect (tối đa)
16 16 lần ~4 lần
256 256 lần ~8 lần
1.000.000 1.000.000 lần ~20 lần
🕳️ Cạm bẫy thường gặp: lỗi "không đơn điệu" phá vỡ giả định của bisect
Tìm kiếm nhị phân chỉ đúng khi tính chất good/bad đổi đúng 1 lần dọc lịch sử. Nếu bug được sửa ở 1 commit rồi vô tình tái xuất hiện ở commit sau đó (không đơn điệu), hoặc test bị flaky (lúc pass lúc fail trên cùng 1 commit), git bisect có thể hội tụ sai chỗ. Luôn xác nhận thủ công commit mà Git chỉ ra thực sự là nguyên nhân trước khi kết luận.
🔬 Đào sâu: bisect trên lịch sử có merge commit
Với lịch sử tuyến tính, "điểm giữa" chỉ đơn giản là trung vị theo thứ tự commit. Khi có merge commit (nhiều cha), Git dùng thứ tự tô-pô (topological order) của toàn bộ DAG để chọn điểm giữa, và có thể phải checkout qua nhiều nhánh phụ trước khi hội tụ. Cờ git bisect start --first-parent giới hạn việc tìm kiếm chỉ theo nhánh chính, bỏ qua nội dung bên trong các merge — hữu ích khi bug chắc chắn nằm ở nhánh chính chứ không phải trong 1 feature branch đã merge vào.

Sân chơi tương tác: Demo Bisect Trên 24 Commit Giấu 1 Thủ Phạm

Bên dưới là 24 commit liên tiếp — đúng 1 commit trong số đó âm thầm gây lỗi (nội dung không hé lộ trước, giống thực tế). Hãy tự tay bisect: đánh dấu 1 mốc cũ là good, HEAD hiện tại là bad, rồi lặp lại good/bad theo commit Git tự checkout tới khi tìm ra thủ phạm. Thử cả git bisect run để xem toàn bộ vòng lặp tự động hoá chỉ trong vài bước.

🔍 Sân chơi tương tác: Git Bisect Simulator

Terminal giả lập

  • git bisect start
  • git bisect bad [C<n>]
  • git bisect good C<n>
  • git bisect skip
  • git bisect run (tự động hoá)
  • git bisect log
  • git bisect reset

Nhật ký

git-bisect-live.js

Trắc nghiệm ôn tập

Câu 1: Với 1000 commit giữa 1 mốc good và 1 mốc bad, git bisect cần tối đa khoảng bao nhiêu lần checkout+test để tìm ra thủ phạm, so với dò tuần tự?

Trắc nghiệm ôn tập

Câu 2: Trong git bisect run <script>, mã thoát (exit code) 125 có ý nghĩa gì?

Trắc nghiệm ôn tập

Câu 3: Điều gì khiến kết quả của git bisect có thể SAI (chỉ ra nhầm thủ phạm)?

📖 Tài liệu tham khảo / References

Bài viết liên quan trong series

Bài 9: Remote & Collaboration Bài 7: Undo & Phục Hồi Quay lại Lộ trình Series Git