Bỏ qua để đến nội dung
Knowledge Hub

Playbook cứu sự cố Git: reset, revert, reflog và bisect Trung cấp

Thời gian đọc ước tính: 7 phút

Trang này trả lời: gặp sự cố với Git thì dùng lệnh nào, và cách tự động tìm commit gây bug. Hai demo có output thật, chạy lại được.

Đang tải diagram…

Tình huống Lệnh Ghi chú
Commit cuối sai, chưa push git reset --soft HEAD~1 Bỏ commit, giữ thay đổi ở trạng thái staged. Chỉ sửa message: git commit --amend
Bỏ thay đổi chưa commit của một file git restore <file> Lấy lại từ index; mất vĩnh viễn phần chưa commit
Lỡ git add git restore --staged <file> Chỉ bỏ khỏi index, file trong worktree giữ nguyên
Commit đã push lên main gây bug git revert <sha> Tạo commit mới đảo ngược; không rewrite history. Revert merge: git revert -m 1 <merge>
Mất commit sau reset --hard, rebase hỏng, xoá branch git reflog → git branch rescue <sha> Reflog chỉ có ở repo local
Tìm commit gây bug git bisect start <bad> <good> + git bisect run <cmd> Tìm nhị phân; kết thúc bằng git bisect reset
Đang code dở, cần sửa gấp chỗ khác git stash push -m "..." → sau đó git stash pop Thêm -u để stash cả file untracked
Code chưa commit nhưng đang ở nhầm branch git switch -c feature/x Thay đổi chưa commit đi theo sang branch mới
Lỡ commit vào main local (chưa push) git branch feature/x → git reset --hard origin/main → git switch feature/x Kiểm tra git status sạch trước khi reset --hard
Cần một commit từ branch khác git cherry-pick <sha> Tạo commit mới với hash mới; dùng -x để ghi nguồn
Rebase/merge rối tung git rebase --abort / git merge --abort Quay về trạng thái trước khi bắt đầu

Đã push lên branch chung thì luôn revert, không reset. reset + force push buộc mọi người đồng bộ lại, và dễ làm mất commit của người khác (xem Lỗi hay gặp).

Demo 1: cứu commit bị reset --hard bằng reflog

Phần tiêu đề “Demo 1: cứu commit bị reset --hard bằng reflog”

Reflog ghi lại mỗi lần tip của branch và HEAD thay đổi trong repo local. Thời hạn mặc định:

Loại entry Giữ Config
Còn reachable 90 ngày gc.reflogExpire
Không còn reachable 30 ngày gc.reflogExpireUnreachable

Commit “mất” vẫn nằm trong object store tới khi hết hạn và bị gc dọn.

Output thật từ examples/src/git-workflow/demo.sh (git 2.54.0):

$ git log --oneline
e4486e9 feat: thêm API thanh toán
dd91a76 feat: thêm API đơn hàng
2edc7b5 feat: khung app
$ git reset --hard HEAD~2 # lỡ tay: 2 commit biến mất khỏi branch
HEAD is now at 2edc7b5 feat: khung app
$ git reflog -n 4
2edc7b5 HEAD@{0}: reset: moving to HEAD~2
e4486e9 HEAD@{1}: commit: feat: thêm API thanh toán
dd91a76 HEAD@{2}: commit: feat: thêm API đơn hàng
2edc7b5 HEAD@{3}: commit (initial): feat: khung app
$ git reset --hard 'HEAD@{1}' # quay về vị trí ngay trước lệnh reset
HEAD is now at e4486e9 feat: thêm API thanh toán
$ git log --oneline
e4486e9 feat: thêm API thanh toán
dd91a76 feat: thêm API đơn hàng
2edc7b5 feat: khung app
  • Đọc reflog từ trên xuống: HEAD@{0} là hiện tại, HEAD@{1} là ngay trước đó.
  • An toàn hơn reset --hard: tạo branch tại hash đó (git branch rescue e4486e9), xem lại rồi mới quyết.
  • Giới hạn: reflog không cứu được thay đổi chưa từng commit (hoặc chưa từng git add). git restore và reset --hard xoá chúng thật.

Kịch bản: price.sh tính giá sau giảm 10% cho đơn 200, kỳ vọng 180. Tag v1.0 đúng; HEAD sai; ở giữa có 7 commit. Script kiểm tra nằm ngoài repo để mọi commit dùng cùng một bài test:

#!/bin/sh
# exit 0 = good, 1..127 (trừ 125) = bad, 125 = skip
[ "$(sh ./price.sh)" = "180" ]

Output thật:

$ git log --oneline
6e3c8ed docs: changelog 6
ff94529 docs: changelog 5
b824065 docs: changelog 4
375576b refactor: tách bước làm tròn
a203b7a docs: changelog 3
ccca1c0 docs: changelog 2
93a4098 docs: changelog 1
961be8e feat: tính giá sau giảm 10%
$ git bisect start HEAD v1.0
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[a203b7a5538e8e6c4a0b22e25ec0ead9bb96a012] docs: changelog 3
$ git bisect run sh ../check.sh
running 'sh' '../check.sh'
Bisecting: 1 revision left to test after this (roughly 1 step)
[b824065e9f27c18b15c4f035723cba5fac9fe1b5] docs: changelog 4
running 'sh' '../check.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[375576bfcca8253835e23ee6f30ab0ff26feea0d] refactor: tách bước làm tròn
running 'sh' '../check.sh'
375576bfcca8253835e23ee6f30ab0ff26feea0d is the first bad commit
commit 375576bfcca8253835e23ee6f30ab0ff26feea0d
Author: demo <demo@example.com>
Date: Sun Oct 18 09:08:00 2026 +0700
refactor: tách bước làm tròn
price.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
$ git bisect reset
Previous HEAD position was 375576b refactor: tách bước làm tròn
Switched to branch 'main'
  • Ba lần chạy test cho 7 commit ứng viên. Bisect là tìm nhị phân, nên 1.000 commit cũng chỉ cần khoảng 10 lần.
  • Bài học kèm theo: commit gây bug tên refactor — đúng loại commit đáng lẽ không được đổi hành vi.

Đang tải diagram…

Exit code Ý nghĩa
0 good
1–127, trừ 125 bad
125 skip (commit này không test được, ví dụ không build)
Khác (ví dụ 128–255) dừng bisect

Bẫy hay gặp: script trả 127 (“command not found”) bị hiểu là bad, bisect chỉ sai commit. Với project TypeScript:

  • exit 125 khi npm ci hoặc build thất bại.
  • Chỉ trả bad khi đúng test tái hiện bug fail.
  • Test flaky làm bisect chỉ sai commit (xem flaky tests).

Demo cách bẫy này chỉ sai commit: Lỗi hay gặp.

Script tự tạo repo trong thư mục tạm, không đụng repo hiện tại, tự dọn khi xong:

Terminal window
bash examples/src/git-workflow/demo.sh

Script cố định tên tác giả và ngày commit, nên hash giống output trên nếu cùng phiên bản git.

Tiếp theo: Secret, PR và checklist.