Tìm Bug siêu tốc với git bisect: Dùng tìm kiếm nhị phân truy tìm commit gây lỗi
Hướng dẫn git bisect từ cơ bản đến thực tế: chọn good/bad commit, tự động hóa với bisect run, xử lý flaky test, skip, merge và xác minh root cause.
ShanverseUpdated 12 min read— viewsLoading views
Git
Developer Tools
Nếu có 1.024 commit khả nghi, kiểm tra tuần tự có thể cần tới 1.024 lượt. Với điều kiện mỗi commit có thể được phân loại rõ là good hoặc bad, bisect chỉ cần khoảng 10 lượt.
git bisect giải quyết bài toán gì?
Bạn biết:
Một commit cũ vẫn hoạt động đúng: good.
Một commit mới đã có lỗi: bad.
Git chọn một commit ở giữa để bạn test. Nếu commit đó good, nửa cũ bị loại; nếu bad, nửa mới hơn bị loại. Quy trình lặp lại tới khi còn commit đầu tiên làm hành vi chuyển từ good sang bad.
Comments
Mermaid
Loading diagram…
Điều kiện để bisect hiệu quả
Bạn cần một phép kiểm tra:
Lặp lại được.
Cho kết quả rõ ràng good/bad.
Áp dụng được cho phần lớn commit trong phạm vi.
Không phụ thuộc ngẫu nhiên vào dữ liệu, mạng hoặc môi trường.
Ví dụ tốt:
Một unit test bắt đầu fail.
API trả sai status code.
Build không còn thành công.
Thời gian xử lý vượt ngưỡng xác định.
UI mất một phần tử có thể kiểm tra bằng test tự động.
Ví dụ khó:
Bug chập chờn.
Test phụ thuộc dịch vụ bên ngoài.
Schema/database thay đổi nhưng môi trường test không được khôi phục đúng.
Nhiều commit giữa phạm vi không build được.
Quy trình thủ công cơ bản
Giả sử HEAD đang lỗi, còn tag v2.3.0 hoạt động đúng.
1. Chuẩn bị working tree sạch
Commit, stash hoặc chuyển thay đổi đang làm dở sang branch khác. Bisect sẽ checkout nhiều commit và có thể bị chặn bởi thay đổi local.
Nếu cần stash:
2. Bắt đầu phiên bisect
Đánh dấu commit hiện tại là bad:
Đánh dấu mốc cũ là good:
Có thể viết gọn:
Trong cú pháp này, commit đầu là bad và commit sau là good.
3. Test commit Git vừa chọn
Git checkout một commit giữa phạm vi ở trạng thái detached HEAD.
Nếu không có lỗi:
Nếu lỗi xuất hiện:
Git tiếp tục chọn commit giữa của phạm vi còn lại.
4. Lặp tới commit gây lỗi đầu tiên
Kết thúc, Git báo dạng:
Xem commit:
5. Thoát và quay lại branch ban đầu
Nếu đã stash:
Ví dụ thực tế: Tìm commit làm hỏng thanh toán
Xác định test tối thiểu
Giả sử bug là API chấp nhận số tiền âm. Tạo hoặc dùng test:
Kiểm tra hai đầu trước khi bisect:
Nếu hai mốc không tái hiện đúng, phạm vi hoặc test chưa hợp lệ.
Chạy bisect
Ở mỗi commit:
Đánh dấu theo kết quả đến khi Git tìm được ứng viên.
Tự động hóa với git bisect run
Nếu test trả exit code đáng tin cậy, Git có thể tự chạy toàn bộ quá trình.
Tạo script:
Cấp quyền:
Chạy:
Quy ước exit code:
0: good.
1 đến 127, trừ 125: bad.
125: commit không thể test, Git sẽ skip.
Viết script bisect đáng tin cậy
Một script thực tế cần phân biệt “bug xuất hiện” và “commit không test được”.
Ở đây:
Không cài/build được → 125, skip.
Test pass → test runner trả 0, good.
Test fail → mã khác 0 hợp lệ, bad.
Nếu test runner có thể trả mã lớn hoặc có nhiều loại lỗi, hãy ánh xạ rõ:
Khi test chưa tồn tại ở commit cũ
Đây là tình huống phổ biến. Một test mới có thể không chạy trên lịch sử cũ.
Các cách xử lý:
Dùng script ngoài repository
Đặt script tại đường dẫn ngoài repo để checkout commit cũ không làm nó biến mất:
Kiểm tra hành vi bằng command ổn định
Ví dụ gọi binary hoặc API local và kiểm tra output:
Tạm thời áp dụng test vào mỗi commit
Có thể dùng script cherry-pick hoặc copy test fixture, nhưng phải tránh biến chính test thành nguồn lỗi. Phương pháp này cần kiểm soát chặt dependency và cleanup.
Commit không build được: dùng skip
Thủ công:
Chỉ định commit/range:
Trong automation, trả exit code 125.
Nếu quá nhiều commit liên tiếp bị skip, Git có thể chỉ trả về một nhóm ứng viên thay vì duy nhất một commit. Khi đó cần:
Sửa môi trường để test được nhiều commit hơn.
Thu hẹp phạm vi.
Dùng một predicate đơn giản hơn.
Kiểm tra thủ công các ứng viên còn lại.
Xem tiến trình bisect
Xem danh sách commit còn lại trong công cụ log:
Dạng text:
Xem các quyết định đã đánh dấu:
Lưu log để tái hiện:
Phát lại:
Nếu đánh dấu nhầm, lưu log, sửa dòng good/bad không đúng, reset và replay file đã chỉnh.
Thu hẹp phạm vi đúng cách
Chọn good commit gần nhất có thể
Phạm vi càng nhỏ, số lượt test càng ít:
Tag release, deployment SHA hoặc CI artifact thường là mốc tốt.
Dùng nhiều good commit
Một lịch sử có nhiều nhánh có thể cần nhiều mốc known-good:
Giới hạn theo path
Git tập trung vào commit chạm path đó. Tuy nhiên, bug trong src/payment/ có thể do config, dependency hoặc shared utility bên ngoài path. Chỉ giới hạn khi bạn có bằng chứng tốt.
Lịch sử có nhiều merge
Có thể khảo sát first-parent history:
Cách này hữu ích để tìm Pull Request/merge nào đưa bug vào branch chính, nhưng có thể xác định merge commit thay vì commit chi tiết bên trong feature branch. Sau đó bisect tiếp trong branch/PR tương ứng nếu cần.
Bug hiệu năng với custom predicate
Bisect không chỉ dành cho pass/fail chức năng. Bạn có thể định nghĩa ngưỡng hiệu năng.
Chạy benchmark nhiều lần và dùng median để giảm nhiễu. Giữ CPU load, cache, data và cấu hình ổn định.
Flaky test: kẻ thù của bisect
Tìm kiếm nhị phân giả định mỗi commit thuộc một phía rõ ràng. Một lần đánh dấu sai có thể đưa toàn bộ quá trình sang nửa lịch sử sai.
Giảm flaky:
Chạy test nhiều lần.
Cố định seed ngẫu nhiên.
Tắt network dependency.
Dùng database fixture độc lập.
Giữ timezone, locale và environment variables ổn định.
Dùng median/majority vote cho benchmark.
Ví dụ yêu cầu lỗi xuất hiện 3/5 lần:
Sau khi tìm được “first bad commit”
Đừng sửa ngay khi chưa xác minh.
1. Đọc commit và context
2. So sánh hai phía của boundary
Với merge commit, parent cần kiểm tra có thể không đơn giản là ^; xem graph và parent thực tế.
3. Kiểm tra dependency và môi trường
Commit đầu tiên tái hiện lỗi có thể chỉ kích hoạt bug cũ, cập nhật dependency hoặc thay đổi dữ liệu/config. Root cause có thể nằm sâu hơn.
4. Tạo branch sửa lỗi từ branch đúng
Không phát triển fix dài hạn trên detached commit mà bisect đang checkout.
5. Viết regression test
Regression test phải fail trước fix và pass sau fix. Đây là bằng chứng tốt nhất rằng bạn đã xử lý đúng lỗi.
Bisect trong repository lớn
Dùng worktree riêng
Để không làm gián đoạn branch đang làm việc:
Kết thúc:
Cache dependency có kiểm soát
Cache package manager giúp nhanh hơn, nhưng build artifacts cũ có thể gây kết quả sai. Xóa output nhạy cảm giữa các lượt:
Chỉ chạy test liên quan
Một test nhỏ và quyết định thường tốt hơn full test suite kéo dài hàng giờ. Sau khi tìm được commit, vẫn chạy bộ test đầy đủ để xác minh fix.
Sai lầm thường gặp
Chọn “good” nhưng chưa kiểm tra
Một tag cũ được cho là ổn không có nghĩa test hiện tại sẽ pass. Luôn checkout và xác nhận hai đầu.
Đánh dấu theo build thay vì bug
Nếu mục tiêu là bug thanh toán, commit build fail không tự động là bad. Hãy skip nếu không thể đánh giá hành vi mục tiêu.
Quên dữ liệu hoặc migration
Mỗi commit có thể cần schema/config khác. Script phải dựng môi trường phù hợp hoặc skip có chủ đích.
Bisect trên test flaky
Một quyết định sai làm mất nửa phạm vi đúng. Ổn định predicate trước.
Sửa code trong lúc bisect mà không kiểm soát
Thay đổi local có thể ảnh hưởng mọi lượt. Đưa harness ra ngoài repo hoặc giữ script cố định, sau đó reset sạch giữa các lần.
Coi first bad commit là kết luận cuối cùng
Bisect xác định ranh giới hành vi; review code và regression test mới xác nhận root cause.
Checklist git bisect
Working Tree sạch hoặc đang dùng worktree riêng.
Có một commit bad được xác minh.
Có một commit good là ancestor phù hợp.
Test/predicate lặp lại được.
Good thực sự pass và bad thực sự fail.
Build/environment failure trả 125, không bị coi là bug.
Flaky test được ổn định hoặc chạy nhiều lần.
Đã lưu git bisect log nếu điều tra quan trọng.
Đã đọc diff của first bad commit.
Đã test cả hai phía boundary.
Đã chạy git bisect reset.
Fix có regression test.
Cheat sheet
Tổng kết
Quy trình tìm bug bằng bisect:
Xác định và kiểm tra một mốc good và một mốc bad.
Chuẩn hóa test thành predicate ổn định.
Để Git chia đôi lịch sử và đánh dấu từng commit.
Tự động hóa bằng git bisect run khi có thể.
Xác minh boundary, đọc diff và viết regression test.
1gitswitch--detachv2.3.02npmci3npmtest----runInBandpayment-negative-amount4# phải pass56gitswitch-7npmtest----runInBandpayment-negative-amount8# phải fail
1# Thủ công2gitbisectstart3gitbisectbadHEAD4gitbisectgood<known-good>5# chạy test6gitbisectgood# hoặc bad / skip7gitbisectreset89# Gọn10gitbisectstart<bad><good>1112# Tự động13gitbisectstart<bad><good>14gitbisectrun./scripts/reproduce-bug.sh15gitbisectreset1617# Theo dõi18gitbisectlog19gitbisectvisualize--oneline
Related posts
More articles with related topics and technologies.
Hướng dẫn Git Aliases và .gitconfig thực chiến: alias an toàn, log đẹp, cấu hình pull/push/rebase, includeIf cho tài khoản work/personal, bảo mật và chia sẻ dotfiles.
Phân tích Gitflow vs GitHub Flow vs Trunk-Based Development: cấu trúc branch, ưu nhược điểm, điều kiện áp dụng, ma trận quyết định và lộ trình chuyển đổi cho từng loại dự án.
Hướng dẫn git worktree từ cơ bản đến thực tế: tạo nhiều working tree, làm feature và hotfix song song, review PR, quản lý list/remove/prune/lock/repair và tránh lỗi phổ biến.