# Git Deep Dive #4: Reflog & Git Recovery — Commit biến mất khỏi `git log` đã thực sự bị xóa chưa?
Series: Git Deep Dive – Từ Command đến Internal
Part 4: Reflog & Git Recovery
1. Introduction
Ở cuối Part 3, chúng ta để lại một câu hỏi.
Giả sử repository đang có:
A ─── B ─── C ─── D
↑
main
↑
HEAD
Sau đó vì một lý do nào đó bạn chạy:
git reset --hard HEAD~2
History trở thành:
A ─── B
↑
main
↑
HEAD
Chạy:
git log
và bạn không còn thấy:
C
D
Lúc này phản ứng quen thuộc thường là:
"Hai commit của mình bị xóa rồi?"
Nếu chưa push lên remote và cũng không nhớ commit hash thì sao?
Code mất thật rồi?
Phải viết lại?
Không hẳn.
Thử:
git reflog
Bạn có thể thấy thứ gì đó tương tự:
a82f38c HEAD@{0}: reset: moving to HEAD~2
d71a932 HEAD@{1}: commit: implement payment timeout
c921af1 HEAD@{2}: commit: add payment validation
Commit tưởng như đã "biến mất" vẫn có thể được tìm thấy.
Đây là lúc chúng ta cần hiểu một trong những cơ chế recovery hữu ích nhất của Git:
REFLOG
Trong bài này, chúng ta sẽ tìm hiểu:
- Reflog thực sự là gì?
HEAD@{0},HEAD@{1},HEAD@{2}nghĩa là gì?- Tại sao
git logkhông thấy commit nhưnggit refloglại thấy? - Reachable và unreachable commit là gì?
- Recover sau
reset --hardnhư thế nào? - Recover branch bị delete như thế nào?
- Recover sau một rebase sai như thế nào?
- Detached HEAD có làm mất commit không?
- Khi nào
git fsckhữu ích? - Reflog có phải backup vĩnh viễn không?
- Khi nào commit thực sự có nguy cơ biến mất?
Mental model của Part 4:
REF UPDATE
↓
REFLOG
↓
OLD COMMIT
↓
RECOVERY
2. Trước khi hiểu Reflog, hãy quay lại Part 1
Ở Part 1 chúng ta đã biết:
Branch
không phải một container chứa commits.
Branch chỉ là một reference.
Ví dụ:
main
↓
D
với commit graph:
A ─── B ─── C ─── D
↑
main
Nếu chạy:
git reset --hard B
điều quan trọng xảy ra là:
Before:
main → D
After:
main → B
Commit D không nhất thiết bị Git xóa ngay lập tức.
Thứ vừa thay đổi trước hết là:
reference
Từ đây xuất hiện một câu hỏi:
Git có nhớ
maintrước đó từng trỏ tớiDkhông?
Có.
Đó chính là một trong những vai trò của Reflog.
3. Reflog là gì?
reflog là viết tắt của:
reference log
Có thể hiểu đơn giản:
Reflog ghi lại lịch sử thay đổi của references trong local repository.
Ví dụ:
main → A
sau commit:
main → B
sau commit tiếp:
main → C
sau reset:
main → A
Commit graph hiện tại có thể chỉ cho bạn:
A
↑
main
nhưng reflog có thể lưu dấu vết kiểu:
main từng → A
main từng → B
main từng → C
main hiện → A
Có thể hình dung:
Commit Graph
============
A ─── B ─── C
↑
main
Reflog
======
main:
C → A reset
B → C commit
A → B commit
Một bên cho bạn biết:
Repository hiện tại reachable như thế nào?
Một bên cho bạn biết:
Reference này đã di chuyển như thế nào trong local repository?
Đây là hai khái niệm khác nhau.
4. git log và git reflog không trả lời cùng một câu hỏi
Đây là distinction quan trọng nhất của Part 4.
git log
Thông thường bạn dùng:
git log
để xem commit history reachable từ HEAD.
Ví dụ:
A ─── B ─── C
↑
HEAD
Git có thể walk:
C → B → A
git reflog
git reflog
lại tập trung vào:
HEAD/reference đã từng trỏ tới đâu?
Ví dụ:
HEAD@{0} → B
HEAD@{1} → D
HEAD@{2} → C
HEAD@{3} → B
Do đó hoàn toàn có thể xảy ra:
git log
không còn thấy commit D, nhưng:
git reflog
vẫn cho bạn tìm lại D.
Mental model:
git log
↓
Commit Graph
git reflog
↓
Reference Movement History
5. HEAD@{0} nghĩa là gì?
Giả sử:
git reflog
trả về:
7ac91f2 HEAD@{0}: reset: moving to HEAD~2
9fd712a HEAD@{1}: commit: implement payment
31ad820 HEAD@{2}: commit: validate customer
4ce1021 HEAD@{3}: checkout: moving from main to feature/payment
Các notation:
HEAD@{0}
HEAD@{1}
HEAD@{2}
HEAD@{3}
không phải commit number.
Nó mô tả các giá trị trước đây của reference.
Có thể hiểu:
HEAD@{0}
→ HEAD hiện tại
HEAD@{1}
→ vị trí trước đó của HEAD
HEAD@{2}
→ vị trí trước nữa
HEAD@{3}
→ ...
Git còn hỗ trợ time-based notation.
Ví dụ:
git show 'HEAD@{yesterday}'
hoặc:
git show 'main@{1 week ago}'
Ý nghĩa ở đây là:
Reference local đó từng trỏ tới đâu tại thời điểm tương ứng?
Nó không có nghĩa:
Hãy tìm commit được tạo đúng một tuần trước.
Đây là distinction rất dễ nhầm.
6. Reflog là local
Đây là một đặc tính cực kỳ quan trọng.
Reflog mô tả history của references trong:
local repository
Ví dụ bạn có:
Laptop A
Laptop B
GitLab
Reflog trên Laptop A:
HEAD@{0}
HEAD@{1}
HEAD@{2}
không có nghĩa Laptop B sẽ có cùng reflog.
Có thể hình dung:
Developer A
.git/logs/...
│
└── reflog A
Developer B
.git/logs/...
│
└── reflog B
Reflog không phải một shared history giống commit graph được push/fetch giữa repositories.
Đây là lý do:
Reflog rất mạnh cho local recovery, nhưng không nên được coi là remote backup.
7. Reflog được lưu ở đâu?
Trong repository thông thường, bạn có thể thấy:
.git/
├── HEAD
├── refs/
├── objects/
└── logs/
Reflog thường liên quan tới:
.git/logs/
Ví dụ:
.git/logs/HEAD
và reflog của branch có thể nằm dưới:
.git/logs/refs/heads/main
Bạn có thể thử:
cat .git/logs/HEAD
Một entry về concept có dạng:
old-object-id new-object-id author timestamp action
Điểm quan trọng ở đây không phải thuộc format.
Mà là nhận ra:
Reflog thực sự lưu old/new object IDs khi refs được cập nhật.
Đó là lý do Git có thể biết:
HEAD trước reset từng ở commit nào.
8. Scenario #1 — Recover sau git reset --hard
Đây là use case kinh điển.
Ban đầu:
A ─── B ─── C ─── D
↑
main
Bạn chạy:
git reset --hard B
Bây giờ:
A ─── B
↑
main
Bạn nhận ra:
"Oops. Tôi vẫn cần C và D."
Đầu tiên:
git reflog
Ví dụ:
a123456 HEAD@{0}: reset: moving to B
d789abc HEAD@{1}: commit: implement timeout
c456def HEAD@{2}: commit: add payment validation
HEAD@{1} đang trỏ tới D.
Trước khi làm bất kỳ thao tác destructive nào khác, có thể inspect:
git show HEAD@{1}
hoặc:
git log --oneline HEAD@{1}
Nếu đúng commit cần recover, cách an toàn là tạo branch:
git branch recovery/payment HEAD@{1}
Graph lại có reference tới D:
A ─── B ─── C ─── D
↑ ↑
main recovery/payment
Commit không còn "lost".
9. Tại sao nên tạo recovery branch trước?
Giả sử tìm thấy:
HEAD@{4}
có commit cần cứu.
Bạn có thể nghĩ:
git reset --hard HEAD@{4}
Nhưng trong recovery, cách an toàn hơn thường là trước tiên tạo một reference mới:
git branch recovery HEAD@{4}
Tại sao?
Bởi vì mục tiêu đầu tiên của recovery nên là:
FIND
↓
PROTECT
↓
INSPECT
↓
RESTORE
thay vì:
FIND
↓
immediately reset everything
Sau khi tạo:
recovery → lost commit
commit đó lại có một branch reference rõ ràng.
Sau đó bạn có thể:
git switch recovery
inspect code, diff và quyết định merge/cherry-pick/reset tùy tình huống.
Mental model:
Find SHA
↓
Create reference
↓
Protect commit
↓
Decide recovery strategy
10. Scenario #2 — Xóa nhầm branch
Giả sử:
main
A ─── B ─── C
\
D ─── E
↑
feature/payment
Bạn chạy:
git branch -D feature/payment
Branch biến mất.
git branch
không còn:
feature/payment
Có phải D và E lập tức bị delete?
Không nhất thiết.
Bạn có thể kiểm tra:
git reflog
và tìm commit E nếu hoạt động liên quan còn được reflog ghi lại.
Ví dụ tìm được:
9fa82bc
Recover:
git branch feature/payment-recovered 9fa82bc
Kết quả:
A ─── B ─── C
\
D ─── E
↑
feature/payment-recovered
Bạn vừa tạo lại một reference tới commit cũ.
11. Branch bị delete không đồng nghĩa commits bị delete
Đây là nơi Part 1 trở nên cực kỳ quan trọng.
Branch:
feature/payment
chỉ là reference:
feature/payment → E
Delete branch:
git branch -D feature/payment
về concept chủ yếu làm mất:
reference → E
chứ không có nghĩa Git ngay lập tức đi vào object database và xóa:
E
D
tree
blob
Ngay sau đó, các objects có thể vẫn tồn tại.
Vấn đề thay đổi thành:
Có còn cách nào tìm được object ID của commit đó không?
Reflog chính là một trong những answer quan trọng nhất.
12. Reachable vs Unreachable — Concept quan trọng nhất sau Reflog
Giả sử:
A ─── B ─── C
↑
main
Từ main, Git có thể đi:
C
↓
B
↓
A
Các commit đó là:
reachable
Có thể hình dung:
main
↓
C
↓
B
↓
A
All reachable
Bây giờ:
git reset --hard B
Branch trở thành:
main
↓
B
↓
A
C không còn reachable từ main.
C
A ─── B
↑
main
Nếu không còn ref/reflog hay root phù hợp nào dẫn tới C, object đó có thể được xem là unreachable trong context tương ứng.
Điểm quan trọng:
UNREACHABLE
≠
IMMEDIATELY DELETED
Đây là distinction quan trọng nhất để hiểu Git recovery.
13. Reachability giống Garbage Collection Root
Nếu bạn từng làm Java, concept này khá quen thuộc.
Trong JVM:
GC Roots
↓
Object A
↓
Object B
Object reachable từ GC Root được giữ lại.
Một object không còn reachable có thể trở thành candidate cho Garbage Collection.
Git cũng có một mental model tương tự.
Refs
│
├── branches
├── tags
├── remote-tracking refs
└── ...
↓
Commits
↓
Trees
↓
Blobs
Reflogs cũng có vai trò trong việc giữ lịch sử object có thể được tìm thấy/protected trong các maintenance operations.
Có thể nghĩ:
REFERENCE ROOTS
↓
REACHABLE OBJECTS
và:
no useful reference path
↓
UNREACHABLE OBJECTS
↓
eventually eligible for cleanup
So sánh với Java GC không hoàn toàn giống implementation của Git, nhưng đây là một mental model khá hữu ích.
14. Scenario #3 — Recover sau Rebase sai
Ở Part 3 chúng ta đã biết:
rebase
=
replay commits
Giả sử ban đầu:
D ─── E
/
A ─── B ─── C
Bạn thực hiện rebase và kết quả không như mong muốn:
A ─── B ─── C ─── D' ─── E'
Các commit cũ:
D
E
không còn nằm trên current branch history.
Nhưng HEAD reflog có thể ghi lại vị trí trước khi rebase.
git reflog
Ví dụ:
e91ac12 HEAD@{0}: rebase (finish): returning to refs/heads/feature
4ac781e HEAD@{1}: rebase (pick): implement payment
...
8bc129a HEAD@{5}: rebase (start): checkout main
71de221 HEAD@{6}: commit: original feature tip
Nếu:
71de221
là state trước rebase, trước tiên có thể bảo vệ nó:
git branch before-rebase 71de221
Sau đó compare:
git log --oneline --graph --all
hoặc:
git diff before-rebase feature
rồi mới quyết định cách restore.
15. ORIG_HEAD — Một recovery hint khác
Ngoài reflog, Git còn có một ref đặc biệt:
ORIG_HEAD
Một số command làm thay đổi HEAD theo cách đáng kể như:
merge
rebase
reset
có thể ghi lại vị trí trước operation vào ORIG_HEAD.
Ví dụ sau một reset, bạn có thể kiểm tra:
git show ORIG_HEAD
hoặc:
git log -1 ORIG_HEAD
Mental model:
Before dangerous-ish operation
HEAD → C
↓ reset
HEAD → A
ORIG_HEAD → C
Nhưng không nên xây toàn bộ recovery strategy chỉ dựa vào ORIG_HEAD.
Reflog thường cho bạn lịch sử phong phú hơn:
HEAD@{0}
HEAD@{1}
HEAD@{2}
...
trong khi ORIG_HEAD chủ yếu hữu ích như một pointer tới state trước một số operations gần đây.
16. Scenario #4 — Commit trong Detached HEAD
Ở Part 1 chúng ta đã nhắc tới:
Detached HEAD
Ví dụ:
git switch --detach <commit>
HEAD trỏ trực tiếp tới commit:
HEAD
↓
C
thay vì:
HEAD
↓
main
↓
C
Sau đó bạn commit:
A ─── B ─── C ─── D
↑
HEAD
nhưng không có branch:
branch → D
Sau đó bạn switch về main:
git switch main
bây giờ:
A ─── B ─── C
↑
main
D
Commit D có thể không còn reachable từ branch.
Nhưng reflog của HEAD có thể giúp tìm lại vị trí đó.
git reflog
Tìm commit D, rồi:
git branch recovered-detached-work <D_SHA>
Kết quả:
A ─── B ─── C ─── D
↑ ↑
main recovered-detached-work
Đây là lý do detached HEAD không tự động có nghĩa:
"Code của bạn chắc chắn sẽ mất."
Nhưng nếu tạo commit trong detached HEAD, hãy tạo branch nếu muốn giữ work lâu dài.
17. HEAD@{n} không phải HEAD~n
Hai syntax nhìn khá giống nhau nhưng hoàn toàn khác.
HEAD~2
Commit ancestry
Ví dụ:
A ─── B ─── C ─── D
↑
HEAD
HEAD~1 = C
HEAD~2 = B
HEAD~3 = A
Nó đi theo parent relationship trong commit graph.
HEAD@{2}
Reflog history
Nó có nghĩa gần như:
HEAD từng có value nào ở hai reflog entries trước?
Ví dụ bạn switch branch:
main → feature → main → hotfix
Reflog có thể ghi:
HEAD@{0} → hotfix
HEAD@{1} → main
HEAD@{2} → feature
Do đó:
HEAD~2
và:
HEAD@{2}
có thể trỏ tới hai commit hoàn toàn khác nhau.
Mental shortcut:
~
=
commit ancestry
@{}
=
ref history
18. @{-1} — Quay lại branch trước
Có một notation rất tiện:
git switch -
tương tự concept của:
@{-1}
@{-1} đại diện cho branch/commit đã checkout trước current one.
Ví dụ:
main
↓
feature/payment
↓
hotfix
Bạn đang ở:
hotfix
thì previous checkout có thể được tham chiếu qua:
@{-1}
Đây không hoàn toàn là cùng loại notation với:
HEAD@{1}
nên đừng nhầm:
HEAD@{1}
→ previous HEAD reflog value
@{-1}
→ previous checkout branch/commit
19. git fsck — Khi reflog không đủ
Có trường hợp bạn không dễ tìm commit qua:
git reflog
nhưng object vẫn còn tồn tại trong object database.
Git cung cấp:
git fsck
để kiểm tra connectivity và validity của objects.
Một command hữu ích trong recovery investigation:
git fsck --unreachable
hoặc trong một số trường hợp:
git fsck --no-reflogs --unreachable
Git có thể report:
unreachable commit <object-id>
Bạn có thể inspect:
git show <object-id>
Nếu đó là commit cần tìm:
git branch recovery/<name> <object-id>
Một lần nữa:
find object
↓
inspect
↓
create reference
20. dangling và unreachable có giống nhau không?
Không nên coi hai từ này hoàn toàn đồng nghĩa.
Git có thể report:
unreachable commit ...
hoặc:
dangling commit ...
unreachable nói về object không reachable từ các reference nodes đang được xét.
dangling là object tồn tại nhưng không được trực tiếp sử dụng theo connectivity mà git fsck đang phân tích.
Đối với recovery, điều quan trọng hơn là:
Object còn tồn tại trong database không?
Nếu còn object ID:
git show <sha>
vẫn có thể inspect được.
Sau đó tạo reference:
git branch recovery <sha>
để giữ lại nó.
21. git fsck --lost-found
Git còn có:
git fsck --lost-found
Command này có thể ghi dangling objects vào:
.git/lost-found/
Ví dụ commit objects:
.git/lost-found/commit/
Đây có thể là fallback khi recovery phức tạp hơn.
Tuy nhiên trong các tình huống thông thường như:
reset nhầm
rebase nhầm
detached HEAD
workflow nên bắt đầu đơn giản:
git reflog
↓
git show <candidate>
↓
git branch recovery <candidate>
Chỉ khi đó không đủ mới đi sâu hơn vào object database với git fsck.
22. Reflog có phải backup không?
Không.
Đây là misconception nguy hiểm.
Reflog là:
local reference history
không phải:
permanent backup system
Reflog entries có thể expire.
Theo Git configuration mặc định, reachable reflog entries thường có expiration window dài hơn, trong khi entries liên quan tới commits không còn reachable từ current tip có thể expire sớm hơn.
Các default phổ biến của Git là:
gc.reflogExpire
→ 90 days
gc.reflogExpireUnreachable
→ 30 days
Các giá trị này có thể được config lại.
Vì vậy:
"I can always recover with reflog"
là một assumption sai.
Đúng hơn nên nghĩ:
Reflog cung cấp một recovery window rất hữu ích, nhưng không phải guarantee vĩnh viễn.
23. Vậy commit khi nào thực sự biến mất?
Giả sử:
A ─── B ─── C
↑
main
sau reset:
A ─── B
↑
main
C
Nếu C vẫn được reflog/reference bảo vệ, Git có lý do để giữ nó.
Theo thời gian:
reference removed
↓
reflog entry expires
↓
object becomes unprotected/unreachable
↓
repository maintenance / GC
↓
object may eventually be pruned
Đây là lý do chúng ta không nên nói:
reset = delete commit
Mental model chính xác hơn:
RESET
↓
move ref
↓
commit may become unreachable
↓
reflog may still reference it
↓
later expiration / GC may remove it
Phần cuối của lifecycle này sẽ được đào sâu trong:
Part 6 — Git Garbage Collection, Packfile & Repository Maintenance
24. Đừng chạy git gc --prune=now khi đang recovery
Nếu bạn vừa làm mất commit và đang cố recover, đây không phải lúc để "dọn repository".
Đặc biệt tránh chạy theo hướng:
git gc --prune=now
mà không hiểu consequences.
--prune=now có thể loại bỏ loose unreachable objects không dựa trên grace period thông thường, và Git documentation cũng cảnh báo mode này làm tăng risk trong một số tình huống concurrent writes.
Trong recovery, mindset nên là:
STOP destructive operations
↓
inspect reflog
↓
protect candidate commits
↓
then repair history
Không phải:
repository đang rối
↓
clean everything
25. Recovery Playbook
Khi nghi ngờ mất commit, có thể sử dụng playbook sau.
Step 1 — Đừng panic và đừng tiếp tục rewrite history
Tránh ngay lập tức chạy thêm:
git reset --hard
git rebase
git gc
Step 2 — Kiểm tra current state
git status
git log --oneline --graph --decorate --all
Step 3 — Kiểm tra Reflog
git reflog
Hoặc:
git reflog show HEAD
Step 4 — Inspect candidate
git show <sha>
hoặc:
git log --oneline <sha>
Step 5 — Protect commit
git branch recovery/<name> <sha>
Ví dụ:
git branch recovery/payment-before-reset 9fd712a
Step 6 — Compare
git diff main recovery/payment-before-reset
hoặc:
git log --graph --oneline --decorate --all
Step 7 — Chọn recovery strategy
Có thể:
merge
cherry-pick
reset
restore branch
tùy mục tiêu.
Step 8 — Nếu reflog không tìm thấy
Đi sâu hơn:
git fsck --unreachable
rồi inspect candidate objects.
Mental model:
STOP
↓
FIND
↓
INSPECT
↓
PROTECT
↓
RECOVER
26. Một ví dụ thực hành từ đầu đến cuối
Tạo repository thử nghiệm:
mkdir git-recovery-lab
cd git-recovery-lab
git init
Tạo commit A:
echo "A" > demo.txt
git add demo.txt
git commit -m "commit A"
Commit B:
echo "B" >> demo.txt
git add demo.txt
git commit -m "commit B"
Commit C:
echo "C" >> demo.txt
git add demo.txt
git commit -m "commit C"
Kiểm tra:
git log --oneline
Bạn có:
C
B
A
Bây giờ cố tình:
git reset --hard HEAD~2
git log:
A
B và C "biến mất".
Bây giờ:
git reflog
Tìm state trước reset.
Ví dụ:
abc1234 HEAD@{1}: commit: commit C
Inspect:
git show abc1234
Recover:
git branch recovered-history abc1234
Kiểm tra:
git log --oneline --graph --decorate --all
Bạn sẽ thấy:
B ─── C
/ ↑
A ─── recovered-history
↑
main
Bạn vừa tự recover history mà không cần remote backup.
27. Java Developer Mental Model
Nếu bạn là Java Backend Developer, có thể liên hệ Git recovery với object reachability trong JVM.
Java
GC Root
↓
Object A
↓
Object B
Nếu không còn path:
GC Root Object C
↑
unreachable
C trở thành candidate cho GC.
Git
refs/heads/main
↓
Commit C
↓
Commit B
↓
Commit A
Nếu branch bị move:
refs/heads/main
↓
Commit B
↓
Commit A
Commit C
C có thể không còn reachable từ branch.
Nhưng Git còn có:
reflog
có thể giữ dấu vết tới previous ref values trong một khoảng thời gian.
Do đó mental model gần đúng:
Java
Root → reachable object → retained
Git
Ref/Reflog → reachable/protected object → retained
Đây không phải cùng một GC implementation, nhưng analogy này giúp reasoning về lifecycle của Git objects.
28. Production Perspective
Reflog rất hữu ích, nhưng trong production workflow bạn không nên xây safety strategy dựa hoàn toàn vào:
"nếu có gì thì reflog cứu"
Một repository quan trọng vẫn nên có:
Remote repository
+
Protected branches
+
Pull/Merge Request
+
Code review
+
CI checks
+
Backup / retention strategy
Ví dụ:
Developer
↓
feature/payment
↓
Merge Request
↓
CI
↓
main
↓
release tag
↓
artifact
Nếu developer reset nhầm local feature:
reflog
rất hữu ích.
Nhưng nếu production artifact, source repository hoặc critical history bị mất ở infrastructure level, reflog trên laptop của một developer không phải disaster recovery strategy.
29. Reflog và CI/CD
Giả sử production đang chạy:
payment-service:9fd712a
với:
9fd712a
là commit SHA.
Nếu developer rewrite local branch:
A ─── B ─── C
thành:
A ─── B ─── C'
commit:
C
có thể không còn trong latest branch history.
Nhưng production artifact vẫn có thể đang được trace tới:
C
Đây là lý do trong CI/CD, nên lưu metadata như:
commit SHA
build number
artifact version
image digest
deployment version
Git recovery và production rollback là hai vấn đề liên quan nhưng không giống nhau.
Git Recovery
→ recover source history
Deployment Rollback
→ recover runtime version
Database Recovery
→ recover persistent state
Một Senior Backend/SRE cần phân biệt rõ ba layer này.
30. Common Mistakes
Mistake #1 — Nghĩ git log không thấy nghĩa là commit đã bị xóa
Không đúng.
not visible from current history
không đồng nghĩa:
object deleted from database
Hãy kiểm tra:
git reflog
Mistake #2 — Nghĩ Reflog nằm trên GitLab/GitHub
Local reflog của bạn là local state.
Không nên assume:
push
→ reflog được upload
Mistake #3 — Dùng reset --hard liên tục khi đang recover
Bạn có thể làm tình huống khó reasoning hơn.
Recovery nên ưu tiên:
inspect
before
mutate
Mistake #4 — Tìm thấy SHA nhưng không tạo reference
Nếu tìm thấy lost commit:
abc1234
hãy cân nhắc bảo vệ ngay:
git branch recovery abc1234
thay vì chỉ ghi SHA ra đâu đó rồi tiếp tục thao tác.
Mistake #5 — Coi Reflog là permanent backup
Reflog entries có expiration.
Unreachable objects có thể cuối cùng bị garbage collected.
Mistake #6 — Chạy cleanup khi đang recovery
Các command cleanup/prune không phải công cụ đầu tiên khi đang cố cứu unreachable work.
31. Reflog Cheat Sheet
| Mục tiêu | Command |
|---|---|
| Xem HEAD reflog | git reflog |
| Xem reflog cụ thể | git reflog show <ref> |
| Xem commit tại reflog entry | git show HEAD@{n} |
| Xem previous value của branch | git show main@{1} |
| Xem state theo thời gian | git show 'main@{yesterday}' |
Inspect ORIG_HEAD |
git show ORIG_HEAD |
| Tạo recovery branch | git branch recovery <sha> |
| Xem toàn bộ graph | git log --graph --oneline --decorate --all |
| Tìm unreachable objects | git fsck --unreachable |
| Tìm objects bỏ qua reflogs khi tính reachability | git fsck --no-reflogs --unreachable |
| Ghi dangling objects vào lost-found | git fsck --lost-found |
32. Recovery Decision Tree
COMMIT "MẤT"
│
▼
git log --all
│
┌──────┴──────┐
│ │
Found Not Found
│ │
▼ ▼
create ref git reflog
│
┌──────┴──────┐
│ │
Found Not Found
│ │
▼ ▼
git show SHA git fsck
│ │
▼ ▼
create branch inspect objects
│ │
└──────┬──────┘
▼
RECOVER
Điều cần nhớ:
DON'T PANIC
↓
DON'T CLEAN
↓
FIND
↓
PROTECT
↓
RECOVER
33. Mental Model cần nhớ
Nếu chỉ nhớ một diagram của Part 4, hãy nhớ:
Branch
│
▼
Commit D
│
▼
Commit C
│
▼
Commit B
│
▼
Commit A
Sau reset:
Branch
│
▼
Commit B
│
▼
Commit A
Commit D
│
▼
Commit C
Nhưng:
Reflog
│
└──────────────► Commit D
nên recovery vẫn có thể thực hiện.
Sau khi tạo branch:
recovery
│
▼
Commit D
│
▼
Commit C
Commit lại được bảo vệ bằng một reference rõ ràng.
Mental shortcut:
REFLOG
=
WHERE THE REF USED TO POINT
và:
UNREACHABLE
≠
DELETED
34. Senior Developer Mindset
Junior mindset thường là:
"Command nào cứu commit?"
Senior mindset nên là:
Commit còn tồn tại không?
Nó còn reachable từ ref nào không?
Reflog còn giữ previous ref value không?
Tôi có object ID không?
Tôi có nên tạo recovery branch trước không?
History này local hay shared?
Remote có copy của commit không?
CI/CD có artifact được build từ commit đó không?
Có database/runtime state liên quan không?
Điều này thay đổi Git từ:
collection of commands
thành:
state + references + object graph
Và đó chính là mục tiêu của series này.
35. Key Takeaways
Sau Part 4, bạn nên nhớ:
Reflog là gì?
Local history of reference updates
git log cho biết gì?
Commit history reachable từ revision đang xét
git reflog cho biết gì?
Reference trước đây từng trỏ tới đâu
HEAD~2 là gì?
Đi hai parent generations trong commit graph
HEAD@{2} là gì?
Prior value trong HEAD reflog
Branch bị delete có xóa commits ngay không?
Không nhất thiết.
Reset có xóa commit ngay không?
Không.
Nó có thể làm commit không còn reachable từ branch.
Unreachable có nghĩa là deleted?
Không.
Reflog có phải backup vĩnh viễn?
Không.
Recovery workflow?
FIND
↓
INSPECT
↓
PROTECT
↓
RECOVER
36. Kết luận
Bốn phần đầu của series bây giờ bắt đầu nối với nhau thành một mental model hoàn chỉnh.
Part 1 — Git Object Model
Blob
Tree
Commit
Branch
HEAD
↓
Part 2 — Git Index
HEAD
↓
INDEX
↓
WORKING TREE
↓
Part 3 — History Manipulation
reset → MOVE
revert → REVERSE
rebase → REPLAY
merge → COMBINE
↓
Part 4 — Recovery
REF MOVE
↓
UNREACHABLE COMMIT
↓
REFLOG
↓
RECOVERY
Đến đây chúng ta đã trả lời được câu hỏi từ Part 3:
Sau
git reset --hard, commit biến mất khỏigit logđã thực sự bị xóa chưa?
Câu trả lời là:
Không nhất thiết.
Branch có thể không còn trỏ tới commit đó, nhưng reflog có thể vẫn giữ dấu vết giúp chúng ta tìm lại object ID và tạo reference mới.
Nhưng điều này lại tạo ra một câu hỏi khác:
Nếu local history có thể bị rewrite, vậy chuyện gì xảy ra khi history đó đã được push lên remote và developer khác cũng đang sử dụng nó?
Đó là lúc Git không còn là bài toán của một developer.
Nó trở thành bài toán distributed collaboration.
Git Deep Dive #5: Remote, Push & Git Security
Ở Part 5 chúng ta sẽ đi từ:
Local Repository
│
│ push / fetch
▼
Remote Repository
và tìm hiểu:
originthực sự là gì?- Local branch và remote-tracking branch khác nhau thế nào?
git fetchthực sự thay đổi cái gì?git pushthực sự gửi gì?git-receive-packđóng vai trò gì?- Fast-forward push là gì?
- Tại sao Git reject non-fast-forward push?
git push --forcenguy hiểm ở đâu?--force-with-leasebảo vệ chúng ta như thế nào?- Signed commits/tags giải quyết vấn đề gì?
git-shelldùng để làm gì?- Branch protection liên quan thế nào tới Git security?
- Git authentication và commit identity có phải cùng một thứ không?
Mental model của Part 5 sẽ là:
LOCAL REFS
│
│ fetch / push
▼
REMOTE REFS
│
▼
DISTRIBUTED HISTORY
Sau Part 5, chúng ta sẽ quay lại nơi series bắt đầu:
.git/objects
để trả lời câu hỏi cuối cùng:
Git có hàng nghìn, hàng triệu objects như vậy thì tại sao repository không tăng kích thước vô hạn?
Đó sẽ là:
Part 6 — Git Garbage Collection, Packfile & Repository Maintenance.
References
-
Git Documentation — Reflog
https://git-scm.com/docs/git-reflog -
Git Documentation — Specifying Revisions (
HEAD@{n},HEAD~n,ORIG_HEAD)
https://git-scm.com/docs/gitrevisions -
Git Documentation —
git fsck
https://git-scm.com/docs/git-fsck -
Git Documentation —
git gc
https://git-scm.com/docs/git-gc
All rights reserved