Giao diện
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 tra và lưới an toàn cứu hộ.
git refloggiúp bạn tìm lại nơi HEAD từng đi qua để cứu commit tưởng như đã mất.git bisectgiú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ế:
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?"
- Ví dụ:
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 logtrả lời: "Lịch sử commit của branch là gì?"git reflogtrả 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~1Bâ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 f6e7d8cTIP
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 f6e7d8cReflog 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ứ:
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ả.
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.
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.
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 reflogReflog 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:
git refloggit show <sha>git switch -c rescue-... <sha>- Chỉ reset khi bạn đã kiểm chứng đúng commit
6.2 Git Bisect: tư duy điều tra regression bằng binary search
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 resetBisect 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 tin và mộ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à:
- ổn định môi trường test
- cô lập một check pass/fail đáng tin
- rồi mới chạy bisect
Khi nào không nên dùng bisect?
Đừng dùng bisect nếu:
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.
Tiêu chí bad không ổn định
- Flaky tests, dữ liệu ngẫu nhiên, infra chập chờn.
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.
- Có thể vẫn bisect với
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 startRồ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 resetLệ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-workhoặc commit/stash lại trước khi reset.
Sau đó mới:
bash
git bisect resetTì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 reflogTì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/mergeNế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 revert6.4 Checklist thực chiến
Trước khi dùng reflog
- dừng reset bừa
- ưu tiên
git showtrướ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 resetkế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.