Bài 03

Khi rối: reflog, reset, bisect, hotfix production

06 tháng 5, 2026 · 45 phút đọc

Bài 03 — Khi rối: reflog, reset, bisect, hotfix production

Lỡ delete branch chưa push. Reset hard nhầm. Bug xuất hiện 2 tuần trước, không biết commit nào gây ra. 11h đêm production sập, cần hotfix gấp. Đây là khi bạn cần bài này — Git có sẵn vũ khí cho từng case.


Reflog — vũ khí cứu commit "mất"

Reflog ghi mọi lần HEAD đổi (checkout, commit, reset, rebase). Local only — không sync remote. Giữ ~30 ngày.

git reflog

Output:

8a1b2c3 HEAD@{0}: checkout: moving from feat/x to main
9d4e5f6 HEAD@{1}: commit: Add feature X
c5d6e7f HEAD@{2}: rebase finished
...

Mỗi dòng = 1 trạng thái HEAD trong quá khứ.

Lỡ xoá branch chưa push

# Xoá branch, panic
git branch -D feat/x

# Cứu — tìm SHA cuối của branch trong reflog
git reflog | grep feat/x
# 9d4e5f6 HEAD@{1}: commit: Add feature X (last commit on feat/x)

# Tạo lại branch từ SHA đó
git checkout -b feat/x 9d4e5f6

Mọi case "mất commit" đều check reflog đầu tiên. 95% case recovery được.


Reset — undo local

3 mode, hiểu mới dùng đúng:

git reset --soft HEAD~1     # undo commit, GIỮ staged + working
git reset HEAD~1             # undo commit, GIỮ working (--mixed mặc định)
git reset --hard HEAD~1     # undo commit + XOÁ thay đổi (nguy hiểm)

Hình dung:

Trước reset:
  Working: edited
  Staged:  staged
  HEAD:    commit C

--soft:   Working: edited     ← giữ
          Staged:  staged     ← giữ
          HEAD:    commit B   (lùi 1)

--mixed:  Working: edited     ← giữ
          Staged:  CLEAR
          HEAD:    commit B

--hard:   Working: CLEAR      ← XOÁ HẾT
          Staged:  CLEAR
          HEAD:    commit B

⚠️ --hard mất thay đổi không revert dễ. Lỡ → reflog cứu:

git reflog                  # tìm SHA trước reset
git reset --hard <SHA>      # quay lại

Reset chỉ dùng cho branch local riêng. Branch shared (main, develop) → git revert (Bài 02), không reset.

Use case reset hằng ngày

# Lỡ commit message sai, chưa push — sửa lại
git reset --soft HEAD~1     # undo commit, giữ thay đổi staged
git commit -m "feat(auth): add JWT refresh (correct message)"

# Lỡ commit thiếu file
git reset --soft HEAD~1
git add forgotten-file
git commit -m "feat(auth): add JWT refresh"

# Hoặc đơn giản hơn: amend
git add forgotten-file
git commit --amend --no-edit

⚠️ --amend chỉ trên commit chưa push. Push rồi mà amend thì cần force.


Bisect — tìm commit gây bug

Bug xuất hiện. Code chạy OK 2 tuần trước. Có 200 commit ở giữa. Commit nào gây bug?

git bisect = binary search tự động qua history.

git bisect start
git bisect bad                 # commit hiện tại có bug
git bisect good v1.2.0        # commit này (2 tuần trước) OK

# Git tự checkout commit giữa
# Bạn test code → quyết định:
git bisect bad     # commit này có bug
# hoặc
git bisect good    # commit này OK

# Git checkout commit giữa tiếp theo
# Lặp lại

# Sau ~log2(N) bước:
# "abc1234 is the first bad commit"

git bisect reset               # quay về branch ban đầu

200 commit → ~8 lần test. Cực nhanh.

Tự động hoá với script

Nếu có script test cho bug:

git bisect start HEAD v1.2.0
git bisect run ./test-bug.sh
# Git tự chạy bisect dùng exit code (0 = good, non-zero = bad)

Đây là use case khiến atomic commit (Bài 01) cực giá trị. Commit gộp 5 thứ → bisect ra "commit này có bug" — 5 thứ trong commit, không biết thứ nào.


Hotfix production — workflow

Production sập 2h sáng. Quy trình an toàn:

# 1. Branch hotfix từ MAIN (production), KHÔNG từ develop
git switch main
git pull
git switch -c hotfix/payment-500

# 2. Fix tối thiểu — chỉ sửa cái cần để stop bleeding
# KHÔNG refactor, KHÔNG cleanup, KHÔNG add test mới (nếu lo thời gian)
# Chỉ FIX. Atomic commit.
git commit -am "fix(payment): handle null response from gateway"

# 3. Push, mở emergency PR
git push -u origin hotfix/payment-500

# 4. Approve fast-track (1 reviewer thay 2, skip non-critical CI)
# Merge vào main

# 5. Tag release patch
git switch main
git pull
git tag -a v1.2.1 -m "Hotfix: payment null response"
git push origin v1.2.1

# 6. Deploy v1.2.1 lên production
# 7. Verify production OK
# 8. Sáng hôm sau: viết test, postmortem, share team

Khi fix forward khó / lâu: rollback

Friday 4h chiều, bug nghiêm trọng — đừng cố fix. Rollback bằng git revert:

git switch main
git pull
git revert <SHA-of-bad-commit>
git push
# Deploy commit revert

5 phút deploy revert > 2 tiếng debug + fix forward + verify. Fix thật vào thứ 2.


Pre-commit hook — chặn lỗi tự động

Hook = script chạy trước commit/push. Chặn rác trước khi vào history.

Setup với pre-commit framework (Python):

pip install pre-commit

Tạo .pre-commit-config.yaml ở root repo:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-merge-conflict        # check <<<<<<< marker
      - id: check-added-large-files     # cảnh báo file > 500KB
      - id: check-yaml
      - id: check-json

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks                    # detect AWS key, GitHub token, etc.

Cài hook vào repo:

pre-commit install

Mỗi git commit, hook chạy. Phát hiện secret / conflict marker / file lớn → block commit.

Đưa vào template repo của team — mọi developer onboard có sẵn.


Câu chuyện thực tế (capstone)

Tháng 11/2024, deploy v3.2.0 lên production lúc 3h chiều. Service hoạt động bình thường.

22h đêm, alert: order service báo 500 với rate ~5%. Bug data-dependent — chỉ 1 số order fail.

# Step 1: xem v3.2.0 có gì mới
git log v3.1.0..v3.2.0 --oneline
# 50 commit. Quá nhiều để đọc tay.

Rollback toàn bộ v3.2.0? Không — có 1 số tính năng customer đã dùng, rollback gây regression khác.

Bisect:

git bisect start
git bisect bad v3.2.0
git bisect good v3.1.0

# Git checkout giữa, deploy lên staging với data sample → test
git bisect bad     # bug có ở đây

# Git checkout giữa tiếp
git bisect good    # bug không có

# 6 vòng sau (50 commit / 2^6 ~ 1):
# "abc1234 is the first bad commit"
# Title: "refactor(order): simplify validation logic"
git show abc1234

Diff cho thấy validation logic miss check order.coupon == null. Edge case.

Hotfix:

git switch main
git switch -c hotfix/order-coupon-null
# Sửa validator, thêm null check
git commit -am "fix(order): handle null coupon in validation"
git push -u origin hotfix/order-coupon-null

Fast-track PR → merge → tag v3.2.1 → deploy. 23h45 — production OK.

Hôm sau: viết unit test cho edge case, postmortem chia sẻ team.

Bài học:

  1. Bisect tiết kiệm hàng giờ — đừng đọc 50 commit tay.
  2. Atomic commit (Bài 01) làm bisect chính xác. Commit gộp = bisect mơ hồ.
  3. Hotfix branch từ main, tag patch version, không từ develop.
  4. Pre-commit hook catch sớm — nhưng không thay được test thật.

Lab

Reflog cứu commit

mkdir /tmp/recovery && cd /tmp/recovery
git init

# Tạo 5 commit
for i in 1 2 3 4 5; do
  echo "line $i" >> file.txt
  git add . && git commit -m "commit $i"
done

# Lỡ reset hard mất 3 commit
git reset --hard HEAD~3
git log --oneline    # chỉ còn 2 commit

# Reflog
git reflog
# 8a1b2c3 HEAD@{0}: reset: moving to HEAD~3
# 9d4e5f6 HEAD@{1}: commit: commit 5
# ...

# Quay lại
git reset --hard HEAD@{1}
git log --oneline    # 5 commit đủ

Bisect tự động

mkdir /tmp/bisect-lab && cd /tmp/bisect-lab
git init
echo 'echo OK; exit 0' > test.sh
chmod +x test.sh
git add . && git commit -m "init"

# 5 commit OK
for i in 1 2 3 4 5; do
  echo "# v$i" >> test.sh
  git commit -am "v$i"
done

# Commit thứ 6 break
echo 'exit 1' >> test.sh
git commit -am "BAD: introduce bug"

# 4 commit OK tiếp
for i in 7 8 9 10; do
  echo "# v$i" >> test.sh
  git commit -am "v$i"
done

# Bisect tự động
git bisect start HEAD HEAD~10
git bisect run bash test.sh
# Output: "abc1234 is the first bad commit"
git bisect reset

Pitfalls

  1. git reset --hard không kiểm tra reflog trước → tưởng mất. 95% case reflog cứu.
  2. Bisect mà commit gộp → kết quả mơ hồ ("commit này gộp 5 thứ, thứ nào gây bug?"). Atomic commit từ đầu.
  3. Hotfix từ develop, không phải main → mang theo feature chưa release. Luôn từ main / production tag.
  4. Friday afternoon fix forward → tệ hơn rollback. Revert trước, fix thứ 2.
  5. Skip pre-commit hook bằng --no-verify → defeat purpose. Chỉ skip khi hook giả mạo (rất hiếm).
  6. Reset trên branch shared → đồng đội loạn. Luôn revert.

Tóm tắt

  • Reflog = log local của HEAD. Cứu commit "mất" trong 30 ngày. Mọi case panic — check đây đầu tiên.
  • Reset 3 mode: --soft giữ thay đổi + staged, --mixed giữ working, --hard XOÁ. Branch shared → revert thay reset.
  • Bisect = binary search tìm commit gây bug. Auto với git bisect run.
  • Hotfix: branch từ main, fix tối thiểu, tag patch, deploy. Friday 4pm = rollback, không fix forward.
  • Pre-commit hook chặn secret + lỗi sớm. gitleaks + pre-commit framework.

🎓 Tổng kết — 3 bài Git

Bạn đã đi qua:

  • Bài 01: Workflow hằng ngày — clone, commit, branch, gitignore.
  • Bài 02: Làm việc team — merge/rebase, conflict, PR, GitFlow vs Trunk-based.
  • Bài 03: Khi rối — reflog, reset, bisect, hotfix.

20 lệnh Git phổ biến nhất. Đủ cho 95% công việc DevOps.

Lúc chưa rõ:

  • Lý thuyết Git internal (blob, tree, commit object) — Pro Git book chương 10.
  • Submodule, subtree — khi project monorepo / có dependency Git khác.
  • Worktree — khi cần checkout 2 branch cùng lúc.
  • Custom hook nâng cao, server-side hook.

Những thứ trên hiếm dùng, đọc khi gặp use case cụ thể. Đừng học trước — quên ngay.

Lời khuyên cuối:

  1. Lab xong 3 bài này trên repo sandbox.
  2. Setup pre-commit hook + branch protection cho repo team — defense in depth.
  3. Khi rối — đọc message Git, đừng chạy command random Stack Overflow. Git đa phần nói rõ.
  4. git reflog là người bạn đêm khuya.
← Bài trước
Bài 02
Zalo tư vấn