Lập trình · 21/09/2026

Git bisect: Tìm commit gây lỗi mà không dò từng phiên bản

Tính năng giảm giá vẫn đúng ở bản phát hành trước nhưng hôm nay lại tính sai, trong khi nhánh chính đã có hàng chục commit mới. Đọc lần lượt từng thay đổi sẽ mất thời gian. Git bisect giúp thu hẹp khoảng lịch sử cần kiểm tra dựa trên một phiên bản đã xác nhận tốt và một phiên bản đã xác nhận lỗi.

Git bisect: Tìm commit gây lỗi mà không dò từng phiên bản

Tính năng giảm giá vẫn đúng ở bản phát hành trước nhưng hôm nay lại tính sai, trong khi nhánh chính đã có hàng chục commit mới. Đọc lần lượt từng thay đổi sẽ mất thời gian. Git bisect giúp thu hẹp khoảng lịch sử cần kiểm tra dựa trên một phiên bản đã xác nhận tốt và một phiên bản đã xác nhận lỗi.

1. Bisect giải quyết bài toán nào?

Git chọn các commit trung gian để bạn kiểm thử rồi đánh dấu kết quả. Cách tìm kiếm này giảm đáng kể số lần thử so với đi tuần tự; trên lịch sử tuyến tính có 64 ứng viên và một ranh giới tốt–lỗi rõ ràng, thường chỉ cần khoảng sáu lần phân loại để thu hẹp đến commit cần xem. Số bước thực tế còn phụ thuộc đồ thị lịch sử và các commit bị bỏ qua. Xem Pro Git: Debugging with Git.

Điều quan trọng nhất là định nghĩa một lỗi cụ thể. Trong ví dụ này, tiêu chí là: hàm apply_discount(100, 20) phải trả về 80. Kết quả khác là lỗi đang điều tra; lỗi kết nối mạng của bộ kiểm thử không phải bằng chứng cho cùng vấn đề.

2. Chuẩn bị mốc và môi trường kiểm tra

Hãy tự chạy cùng một phép thử tại cả hai mốc trước khi bắt đầu. Đừng đánh dấu bản phát hành cũ là good chỉ vì chưa có ai báo lỗi. Nếu lỗi lúc có lúc không, cần làm cho phép thử ổn định trước: cố định dữ liệu đầu vào, phiên bản runtime và các phụ thuộc cần thiết.

Để giữ riêng công việc đang làm, có thể tạo worktree phục vụ điều tra. Các tên main, v1.8.0 và thư mục dưới đây là ví dụ; thay bằng mốc đã xác minh và đường dẫn chưa tồn tại trong dự án của bạn:

git worktree add --detach ../bisect-lab main
cd ../bisect-lab
git status --short

Worktree riêng có thư mục làm việc và index riêng nhưng vẫn chia sẻ kho Git. Kiểm tra trạng thái sạch trước khi chuyển các phiên bản; môi trường build, database thử nghiệm và dịch vụ phụ thuộc vẫn cần được thiết lập riêng. Xem tài liệu git worktree.

3. Tìm lỗi thủ công

Giả sử HEAD trong worktree là bản lỗi, còn tag v1.8.0 đã vượt qua phép thử:

git bisect start
git bisect bad HEAD
git bisect good v1.8.0

Git chuyển đến một commit cần kiểm tra. Chạy phép thử giảm giá rồi nhập đúng một lệnh phù hợp với kết quả:

git bisect good
# Hoặc, nếu tái hiện đúng lỗi đang điều tra:
git bisect bad

Lặp lại đến khi Git báo commit lỗi đầu tiên. Trước khi kết thúc phiên điều tra, lưu nhật ký và kết quả ra ngoài worktree:

git bisect log > ../bisect-discount.log
git rev-parse refs/bisect/bad > ../bisect-discount-commit.txt
git bisect reset

git bisect reset kết thúc phiên bisect và quay lại vị trí ban đầu; nó không có nghĩa là hoàn tác commit lỗi trên nhánh chính. Quy trình cơ bản được mô tả trong tài liệu git bisect.

4. Tự động hóa với một phép thử nhỏ

Nếu mỗi lần thử đều làm cùng một việc, hãy viết script thay vì tự đánh dấu. Ví dụ này giả định dự án có file pricing.py ở thư mục gốc, chứa hàm thuần apply_discount(price, percent). Nó kiểm tra đúng một trường hợp giảm giá; không phải bộ kiểm thử hoàn chỉnh của ứng dụng.

Lưu script sau tại ../check_discount.py, bên ngoài worktree để việc đổi commit không làm mất script:

from pathlib import Path
import importlib.util
import sys

path = Path("pricing.py")
if not path.is_file():
    sys.exit(125)

try:
    spec = importlib.util.spec_from_file_location("pricing", path)
    pricing = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(pricing)
    if not hasattr(pricing, "apply_discount"):
        sys.exit(125)
    result = pricing.apply_discount(100, 20)
except Exception as error:
    print(f"Cannot classify this revision: {error}", file=sys.stderr)
    sys.exit(128)

sys.exit(0 if result == 80 else 1)

Script nạp module từ file bằng importlib của Python. Với lỗi cần tìm là kết quả tính sai, một exception bất ngờ sẽ dừng điều tra để kiểm tra môi trường, thay vì bị coi ngay là bằng chứng bad.

Từ worktree, sau khi xác nhận script trả 0 ở mốc tốt và 1 ở mốc lỗi, bắt đầu một phiên mới:

git bisect start HEAD v1.8.0
git bisect run python3 -B ../check_discount.py
git bisect log > ../bisect-discount.log
git rev-parse refs/bisect/bad > ../bisect-discount-commit.txt
git bisect reset

Lệnh giả định Python 3 có tên python3; trên Windows có thể thay bằng py -3 nếu máy đã cài Python Launcher. Tham số -B tránh tạo bytecode cache trong ví dụ. Chỉ coi kết quả là xác định được commit khi Git thông báo tìm thấy; script bị dừng hoặc nhiều commit bị skip chưa phải một kết luận hoàn chỉnh.

5. Hiểu mã thoát trước khi chạy tự động

Mã thoátGit bisect run hiểu là
0Good: phép thử đạt.
1–127, trừ 125Bad: phép thử thất bại.
125Skip: phiên bản này không thể phân loại.
128 trở lênDừng quá trình tự động.

Đặc biệt chú ý 126 và 127: lỗi không chạy được lệnh có thể bị tính là bad. Kiểm tra interpreter và đường dẫn script trước. Khi làm thủ công, dùng git bisect skip cho commit không thể kiểm thử, nhưng skip quá gần ranh giới có thể khiến Git chỉ đưa ra một nhóm ứng viên. Chi tiết tại phần tự động hóa và bỏ qua commit trong tài liệu Git.

6. Xác minh nguyên nhân trước khi sửa

Commit tìm được là đầu mối cho phép thử đã chọn, chưa thay thế việc phân tích nguyên nhân. Đọc diff bằng git show COMMIT_SHA, kiểm tra lại commit đó và phiên bản trước nó trong môi trường thử nghiệm, rồi thêm regression test vào dự án. Thay COMMIT_SHA bằng mã đã lưu; git show giúp xem thay đổi đi kèm commit.

Nếu lịch sử có giai đoạn lỗi được sửa rồi tái xuất hiện, kết quả có thể không đại diện cho lần xuất hiện đầu tiên trong toàn bộ lịch sử. Hãy thu hẹp về một khoảng có chuyển trạng thái rõ ràng. Với merge commit, cần xem các nhánh và parent liên quan thay vì mặc định một diff đơn lẻ đã giải thích toàn bộ lỗi.

7. Checklist áp dụng cho nhóm phát triển

  • Một tiêu chí lỗi cụ thể, có thể lặp lại.
  • Hai mốc good/bad đã chạy cùng phép thử.
  • Worktree sạch, môi trường thử nghiệm tách khỏi production.
  • Script nằm ngoài khoảng mã bị checkout và có mã thoát rõ ràng.
  • Lưu nhật ký, commit tìm được và điều kiện tái hiện.
  • Xác minh nguyên nhân, bổ sung test rồi mới đưa bản sửa qua quy trình review.

Giá trị của Git bisect nằm ở việc biến câu hỏi “thay đổi nào làm hỏng tính năng?” thành một quá trình kiểm chứng có hệ thống. Một phép thử nhỏ nhưng đáng tin cậy thường giúp khoanh vùng nhanh hơn nhiều so với đọc hàng chục commit theo cảm tính.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.