Skip to content

6. Reflog & Bisect (Cứu hộ và điều tra)

IMPORTANT

Git không chỉ để lưu lịch sử. Trong incident thật, Git còn là hộp đen điều tralưới an toàn cứu hộ.

  • git reflog giúp bạn tìm lại nơi HEAD từng đi qua để cứu commit tưởng như đã mất.
  • git bisect giúp bạn tìm commit gây lỗi bằng tư duy chia đôi lịch sử thay vì mò từng commit.

Bài này giải quyết vấn đề gì?

Bạn sẽ gặp 2 kiểu sự cố rất thực tế:

  1. Mất dấu local work sau thao tác phá hủy

    • Ví dụ: git reset --hard, rebase nhầm, xóa branch local quá sớm.
    • Câu hỏi: "Commit mình làm chiều nay biến đâu mất rồi?"
  2. Có regression nhưng không biết commit nào gây ra

    • Ví dụ: flow thanh toán chạy tốt tuần trước, hôm nay fail; trong giữa có 30 commit.
    • Câu hỏi: "Chính xác commit nào bắt đầu làm hệ thống hỏng?"

Nếu chỉ dựa vào trí nhớ, bạn sẽ debug như đánh bạc. Reflog và bisect tồn tại để thay thế hoảng loạn bằng quy trình.


6.1 Reflog: lưới an toàn khi bạn làm mất dấu lịch sử local

Reflog giải quyết vấn đề gì?

git reflog ghi lại lịch sử di chuyển của các ref local, đặc biệt là HEAD.

Nó không phải "lịch sử commit chính thức" như git log. Nó là nhật ký kiểu:

  • HEAD từng ở SHA nào
  • bạn đã reset đi đâu
  • bạn đã checkout, rebase, merge ra sao

Nói ngắn gọn:

  • git log trả lời: "Lịch sử commit của branch là gì?"
  • git reflog trả lời: "Con trỏ local của tôi đã từng đi qua đâu?"

Mental model: vì sao reflog cứu bạn được?

text
Bạn thao tác local:

Commit A -> Commit B -> Commit C
                       ^
                     HEAD

Bạn lỡ chạy:
git reset --hard HEAD~1

Lúc này branch pointer quay về B:

Commit A -> Commit B
               ^
             HEAD

Nhưng reflog vẫn nhớ:
- HEAD từng ở C
- rồi bị reset về B

=> Bạn chưa cần nhớ SHA bằng đầu.
=> Git đã ghi "dấu chân" local cho bạn.

Kịch bản thực tế: mất commit sau git reset --hard

Giả sử bạn vừa sửa bug login suốt 2 tiếng, commit xong, rồi lỡ tay:

bash
git reset --hard HEAD~1

Bây giờ commit biến mất khỏi git log. Đừng panic.

Quy trình cứu hộ an toàn

bash
# 1) Xem HEAD từng đi qua đâu
git reflog

# 2) Xác định SHA trước khi reset
# Ví dụ thấy:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# f6e7d8c HEAD@{1}: commit: fix(auth): handle refresh token expiry

# 3) Xem lại commit để chắc chắn
git show f6e7d8c

# 4) Cách an toàn nhất: tạo branch cứu hộ từ SHA vừa tìm được
git switch -c rescue-lost-commit f6e7d8c

TIP

Trong incident thật, branch từ SHA recovered thường an toàn hơn reset thẳng. Bạn giữ nguyên trạng thái hiện tại để đối chiếu, thay vì tiếp tục thao tác phá hủy.

Nếu bạn chắc chắn muốn branch hiện tại quay lại commit đó:

bash
git reset --hard f6e7d8c

Reflog như safety net, không phải backup vĩnh viễn

Reflog là lưới an toàn local, nhưng nó có giới hạn:

  • nó chủ yếu hữu ích cho những gì đã từng tồn tại trên máy bạn
  • entry có thể bị dọn theo thời gian bởi garbage collection
  • nó không thay thế remote, backup, hay release tag

Khi nào không nên dùng reflog?

Đừng kỳ vọng reflog giải quyết mọi thứ:

  1. Không dùng để khôi phục thứ chưa từng tồn tại local

    • Bạn chưa fetch commit đó, reflog local không biết gì cả.
  2. Không dùng như hệ thống backup dài hạn

    • Nó là recovery tool, không phải nơi lưu trữ lâu dài.
  3. Không dùng để "đoán mò" rồi reset liên tục

    • Mỗi lần reset bừa chỉ làm incident rối hơn.
  4. Không dùng để sửa shared history theo kiểu liều

    • Reflog giúp tìm SHA bị mất; nó không biến force-push nguy hiểm thành an toàn.

Nếu bạn dùng reflog sai thì khôi phục thế nào?

Ví dụ bạn tìm nhầm SHA và reset sai lần nữa. Cách xử lý:

bash
git reflog

Reflog vẫn tiếp tục ghi lại cả lần reset sai mới nhất. Nghĩa là:

  • thao tác cứu hộ sai cũng để lại dấu
  • bạn có thể quay lại một mốc trước đó

Quy tắc an toàn:

  1. git reflog
  2. git show <sha>
  3. git switch -c rescue-... <sha>
  4. Chỉ reset khi bạn đã kiểm chứng đúng commit

Git bisect giải quyết vấn đề gì?

Bạn biết:

  • hiện tại hệ thống đang hỏng
  • ở một mốc cũ nào đó hệ thống từng chạy đúng

Nhưng giữa hai mốc có quá nhiều commit để kiểm từng cái.

git bisect giải bài toán này bằng binary search trên lịch sử commit:

  • đánh dấu một mốc bad
  • đánh dấu một mốc good
  • để Git checkout commit ở giữa
  • bạn test và trả lời tiếp: good hay bad

Mỗi lần như vậy, Git loại bỏ khoảng một nửa lịch sử còn nghi vấn.

Kịch bản thực tế: regression xuất hiện sau nhiều commit

Tuần trước flow thanh toán vẫn chạy. Hôm nay người dùng báo:

  • bấm thanh toán
  • spinner quay mãi
  • webhook không hoàn tất đơn hàng

Team đã merge khoảng 30 commit từ lúc còn ổn đến bây giờ. Kiểm tay từng commit là cách đốt thời gian.

Quy trình bisect cơ bản

bash
# 1) Bắt đầu phiên bisect
git bisect start

# 2) Commit hiện tại có lỗi
git bisect bad

# 3) Chọn một mốc cũ bạn TIN CHẮC là còn tốt
git bisect good v1.4.0

# 4) Git checkout commit ở giữa
# Bạn chạy test hoặc tái hiện bug

# Nếu commit hiện tại bị lỗi:
git bisect bad

# Nếu commit hiện tại chạy tốt:
git bisect good

# 5) Lặp lại cho đến khi Git kết luận
# "<sha> is the first bad commit"

# 6) RẤT QUAN TRỌNG: quay lại branch ban đầu
git bisect reset

Bisect là mindset, không chỉ là command

Điểm mạnh thật sự không nằm ở cú pháp, mà ở tư duy:

  • đừng đoán commit gây lỗi bằng cảm tính
  • đừng đọc 30 commit message rồi chọn commit "trông có vẻ đáng nghi"
  • hãy biến debugging thành chuỗi quyết định có thể kiểm chứng

Đây là khác biệt giữa:

  • mò lỗi thủ công: tốn thời gian, dễ thiên kiến
  • forensics có hệ thống: kiểm chứng, thu hẹp, kết luận

Good/Bad phải đáng tin cậy

git bisect chỉ tốt khi nhãn bạn gán là đáng tin.

Commit good đáng tin là gì?

Một commit good đáng tin thường là:

  • release tag đã QA xong
  • commit đã chạy qua CI ổn định
  • commit cũ mà team xác nhận feature còn hoạt động

Commit bad đáng tin là gì?

Một commit bad đáng tin là commit mà:

  • bạn tái hiện được lỗi rõ ràng
  • điều kiện test giống nhau
  • không bị ảnh hưởng bởi yếu tố ngoài như môi trường chập chờn

WARNING

Nếu bạn không có một mốc good đáng tinmột tiêu chí bad ổn định, bisect rất dễ cho ra kết luận sai.

Vì sao flaky tests làm bisect nguy hiểm?

Bisect giả định rằng cùng một commit sẽ cho cùng một kết quả phân loại:

  • pass => good
  • fail => bad

Với flaky test, cùng một commit có thể:

  • lần 1 pass
  • lần 2 fail

Khi đó binary search bị nhiễu. Bạn đang đưa dữ liệu không đáng tin vào thuật toán tưởng là chính xác.

Dấu hiệu bạn không nên dùng bisect ngay

  • test phụ thuộc network, third-party API, thời gian thực
  • seed dữ liệu không cố định
  • build lúc được lúc không
  • bug chỉ xuất hiện ngẫu nhiên, không có bước tái hiện rõ ràng

Trong các trường hợp này, việc nên làm trước là:

  1. ổn định môi trường test
  2. cô lập một check pass/fail đáng tin
  3. rồi mới chạy bisect

Khi nào không nên dùng bisect?

Đừng dùng bisect nếu:

  1. Bạn không có commit good đáng tin

    • Chọn bừa một commit "chắc chắc ổn" là tự phá cuộc điều tra.
  2. Tiêu chí bad không ổn định

    • Flaky tests, dữ liệu ngẫu nhiên, infra chập chờn.
  3. Lịch sử giữa good và bad có nhiều commit không build được

    • Có thể vẫn bisect với git bisect skip, nhưng nếu quá nhiều commit không test được thì kết luận sẽ yếu.
  4. Lỗi không thực sự đến từ code history

    • Ví dụ secret hết hạn, database migration ngoài luồng, config production bị chỉnh tay.

Nếu bạn dùng bisect sai thì khôi phục thế nào?

Tình huống 1: Bạn đánh dấu nhầm good/bad

Đừng cố vá tiếp giữa chừng. Cách sạch nhất:

bash
git bisect reset
git bisect start

Rồi bắt đầu lại với mốc đáng tin hơn.

Tình huống 2: Bạn quên git bisect reset

Bạn sẽ bị kẹt ở trạng thái detached HEAD trên một commit trung gian.

bash
git bisect reset

Lệnh này đưa bạn về branch ban đầu và kết thúc session.

Tình huống 3: Bạn lỡ sửa code khi đang bisect

Nếu thay đổi đó quan trọng:

bash
git switch -c rescue-bisect-work

hoặc commit/stash lại trước khi reset.

Sau đó mới:

bash
git bisect reset

Tình huống 4: Bạn quên branch gốc hoặc làm rối phiên bisect

Lúc này reflog lại cứu bạn:

bash
git reflog

Tìm mốc trước khi bắt đầu bisect, rồi branch hoặc switch về đó. Reflog và bisect thường đi cùng nhau trong debugging thật.


6.3 Quy trình incident recovery gọn để nhớ

Nếu làm mất local commit

text
Panic -> Dừng thao tác phá hủy
      -> git reflog
      -> git show <sha>
      -> git switch -c rescue-... <sha>
      -> xác minh code
      -> mới quyết định reset/cherry-pick/merge

Nếu có regression không rõ commit nào gây ra

text
Xác định BAD hiện tại
-> Chọn GOOD đáng tin
-> Viết cách test pass/fail càng ổn định càng tốt
-> git bisect start
-> good / bad cho đến khi ra first bad commit
-> git bisect reset
-> đọc commit, sửa hoặc revert

6.4 Checklist thực chiến

Trước khi dùng reflog

  • dừng reset bừa
  • ưu tiên git show trước khi reset
  • ưu tiên branch cứu hộ từ SHA recovered

Trước khi dùng bisect

  • xác nhận bug tái hiện được
  • xác nhận có một mốc good thật sự đáng tin
  • dùng test càng deterministic càng tốt
  • ghi lại cách test để tránh tự thay đổi tiêu chí giữa chừng

Sau khi điều tra xong

  • với reflog: giữ lại rescue branch cho đến khi chắc chắn đã an toàn
  • với bisect: luôn chạy git bisect reset

6.5 Tóm tắt tư duy cần nhớ

  • Reflog là safety net local khi bạn làm mất dấu commit.
  • Bisect là binary search trên lịch sử để tìm commit gây regression.
  • Cả hai đều mạnh nhất khi bạn bình tĩnh và làm theo quy trình.
  • Cả hai đều nguy hiểm nếu bạn tin vào dữ liệu không đáng tin hoặc reset bừa.

🧠 Quiz

Câu 1: git reflog giải quyết vấn đề nào tốt nhất?

  • [ ] A) Đồng bộ branch local với remote
  • [x] B) Tìm lại SHA mà HEAD local từng đi qua để cứu commit sau reset/rebase nhầm
  • [ ] C) Xóa lịch sử commit xấu khỏi remote
  • [ ] D) Tự động sửa merge conflict

💡 Giải thích: Reflog là nhật ký dịch chuyển của ref local, đặc biệt là HEAD. Nó cực hữu ích khi bạn vừa làm mất dấu commit do reset --hard, rebase nhầm, hoặc checkout linh tinh.

Câu 2: Điều kiện quan trọng nhất để git bisect đáng tin là gì?

  • [ ] A) Repository phải có dưới 100 commits
  • [x] B) Bạn có mốc good/bad đáng tin và tiêu chí test ổn định
  • [ ] C) Tất cả commit đều phải có commit message theo Conventional Commits
  • [ ] D) Chỉ dùng bisect trên branch main

💡 Giải thích: Bisect là binary search, nhưng binary search chỉ đúng khi nhãn phân loại đúng. Nếu commit good/bad bị đoán mò hoặc test bị flaky, kết luận của bisect sẽ sai lệch.

Câu 3: Sau khi tìm ra first bad commit bằng bisect, lệnh nào gần như luôn phải chạy?

  • [ ] A) git reflog expire
  • [ ] B) git reset --hard
  • [x] C) git bisect reset
  • [ ] D) git gc

💡 Giải thích: git bisect reset kết thúc session và đưa bạn về branch ban đầu. Quên lệnh này là cách rất phổ biến để tự làm mình mắc kẹt ở detached HEAD.