# Git Deep Dive #3: `reset`, `revert`, `rebase` và `merge` — 4 command dễ nhầm nhất khi làm việc với Git History
Series: Git Deep Dive – Từ Command đến Internal Part 3: Reset, Revert, Rebase & Merge
1. Introduction
Ở Part 1, chúng ta đã xây dựng mental model về Git Object Model:
Blob
Tree
Commit
Branch
HEAD
Reference
và biết rằng:
Branch → Commit
Commit → Tree
Tree → Blob
HEAD → Current Branch
Sang Part 2, chúng ta bổ sung thêm:
HEAD
↓
INDEX
↓
WORKING TREE
Từ đó hiểu được git add, git diff, git diff --cached và tại sao một file có thể vừa có staged changes vừa có unstaged changes.
Nhưng khi bắt đầu làm việc với Git history, chúng ta gặp bốn command thường xuyên bị nhầm lẫn:
git reset
git revert
git rebase
git merge
Chúng đều có thể khiến history "thay đổi", nhưng bản chất hoàn toàn khác nhau.
Ví dụ:
Tôi vừa commit nhầm, nên dùng
resethayrevert?
Hoặc:
Tôi muốn cập nhật feature branch với
main, nênmergehayrebase?
Hay nguy hiểm hơn:
Tôi đã push commit lên shared branch rồi. Có nên
reset --hardkhông?
Để trả lời những câu hỏi này, chúng ta sẽ không học command theo kiểu thuộc lòng.
Thay vào đó, chúng ta sẽ sử dụng hai mental model đã xây dựng từ Part 1 và Part 2:
COMMIT GRAPH
và:
HEAD
↓
INDEX
↓
WORKING TREE
2. Trước tiên: Git History thực chất là Commit Graph
Giả sử repository có:
A ─── B ─── C
↑
main
↑
HEAD
Ở đây:
main → C
HEAD → main
Commit C lại có parent là B:
C → B → A
Đây chính là commit history.
Nếu tạo feature branch:
git switch -c feature/payment
rồi commit:
A ─── B ─── C
↑
main
\
D ─── E
↑
feature/payment
↑
HEAD
Bây giờ Git có một commit graph.
Bốn command trong bài này tác động lên graph theo những cách khác nhau:
reset
→ di chuyển reference
revert
→ tạo commit mới đảo ngược thay đổi
rebase
→ replay/recreate commits trên base mới
merge
→ kết hợp histories
Đây là distinction quan trọng nhất của toàn bài.
3. git reset — Di chuyển HEAD về một state khác
Bắt đầu với:
A ─── B ─── C
↑
main
↑
HEAD
Nếu chạy:
git reset B
về mặt reference, Git sẽ đưa branch hiện tại từ:
main → C
thành:
main → B
Kết quả:
A ─── B
↑
main
↑
HEAD
C
Điều quan trọng:
resetkhông đơn giản có nghĩa là "delete commit".
Nó thay đổi vị trí mà current branch/HEAD trỏ tới.
Tùy mode, Git còn thay đổi Index và Working Tree.
Ba mode quan trọng nhất:
git reset --soft
git reset --mixed
git reset --hard
4. git reset --soft
Giả sử:
A ─── B ─── C
↑
HEAD
chạy:
git reset --soft B
Git di chuyển HEAD:
A ─── B
↑
HEAD
nhưng:
Index
Working Tree
được giữ nguyên.
Mental model:
BEFORE
HEAD → C
INDEX → C state
WORKING TREE → current state
AFTER reset --soft B
HEAD → B
INDEX → giữ nguyên
WORKING TREE → giữ nguyên
Điều gì xảy ra với changes của commit C?
Từ góc nhìn HEAD mới:
HEAD = B
INDEX = C state
nên changes từ B → C trở thành:
Changes to be committed
Đây là lý do --soft rất hữu ích khi:
Commit rồi nhưng muốn sửa lại commit.
Ví dụ:
git reset --soft HEAD~1
sau đó:
git status
changes vẫn staged.
Bạn có thể:
git commit -m "better commit message"
hoặc chỉnh sửa thêm trước khi commit lại.
5. Use case thực tế của --soft
Giả sử bạn tạo:
A ─── B ─── C
nhưng commit C có message:
fix
và bạn muốn gom thêm một số changes.
Có thể:
git reset --soft HEAD~1
sau đó:
HEAD
↓
B
INDEX
↓
changes của C vẫn staged
Modify thêm:
git add .
rồi:
git commit -m "fix: handle payment timeout"
Bạn tạo commit mới:
A ─── B ─── C'
Lưu ý:
C' ≠ C
vì đây là commit object mới.
6. git reset --mixed
Đây là default mode.
Hai command:
git reset HEAD~1
và:
git reset --mixed HEAD~1
về cơ bản tương đương trong trường hợp này.
--mixed:
HEAD
→ reset
Index
→ reset theo HEAD mới
Working Tree
→ giữ nguyên
Mental model:
BEFORE
HEAD → C
INDEX → C
WORKING TREE → C
git reset --mixed B
AFTER
HEAD → B
INDEX → B
WORKING TREE → C
Vì:
INDEX ≠ WORKING TREE
changes trở thành:
Changes not staged for commit
7. Khi nào dùng --mixed?
Giả sử bạn commit:
PaymentService.java
PaymentController.java
README.md
application.yml
nhưng nhận ra commit quá lớn.
Bạn muốn chia thành:
Commit 1
→ payment logic
Commit 2
→ config
Commit 3
→ documentation
Có thể:
git reset HEAD~1
changes quay trở lại Working Tree nhưng không staged.
Sau đó:
git add src/PaymentService.java
git add src/PaymentController.java
git commit -m "feat: add payment validation"
tiếp:
git add application.yml
git commit -m "config: configure payment provider"
và cuối cùng:
git add README.md
git commit -m "docs: update payment documentation"
Bạn vừa biến một commit lớn thành nhiều atomic commits.
8. git reset --hard
Đây là mode nguy hiểm nhất trong ba mode phổ biến.
git reset --hard B
thay đổi cả:
HEAD
Index
Working Tree
Mental model:
BEFORE
HEAD → C
INDEX → C
WORKING TREE → local changes
AFTER reset --hard B
HEAD → B
INDEX → B
WORKING TREE → B
Git đưa tracked files trong Working Tree về state của commit B.
Điều này có nghĩa local modifications trên tracked files có thể bị discard.
Ví dụ:
git reset --hard HEAD
có thể được dùng để bỏ changes trên tracked files và đưa Index/Working Tree về HEAD.
Nhưng đây cũng là command mà bạn không nên chạy theo phản xạ khi thấy repository "bị lỗi".
9. So sánh ba loại reset
Đây là bảng quan trọng nhất về reset:
| Command | HEAD | Index | Working Tree |
|---|---|---|---|
reset --soft |
Reset | Giữ | Giữ |
reset --mixed |
Reset | Reset | Giữ |
reset --hard |
Reset | Reset | Reset |
Có thể nhớ bằng progression:
--soft
HEAD
--mixed
HEAD + INDEX
--hard
HEAD + INDEX + WORKING TREE
Hoặc:
soft
↓
chạm ít nhất
mixed
↓
chạm thêm Index
hard
↓
chạm cả Working Tree
Đây chính là lý do mental model của Part 2 rất quan trọng.
Nếu không hiểu:
HEAD
INDEX
WORKING TREE
thì ba loại reset rất dễ trở thành thứ phải học thuộc lòng.
10. reset có thực sự xóa commit không?
Giả sử:
A ─── B ─── C
↑
main
sau:
git reset --hard B
bạn nhìn git log và thấy:
A ─── B
↑
main
Có vẻ C đã biến mất.
Nhưng mental model chính xác hơn là:
main
↓
B
không còn reference trực tiếp từ branch tới C.
Điều đó không đồng nghĩa object C ngay lập tức bị xóa khỏi Git object database.
Đây chính là cầu nối sang:
Part 4 – Reflog & Git Recovery
Ở Part 4 chúng ta sẽ trả lời:
Commit C đi đâu?
HEAD@{1} là gì?
Tại sao reflog có thể cứu commit?
Khi nào object thực sự có thể bị garbage collected?
11. git revert — Không rewrite history
Bây giờ xét tình huống khác.
Production branch:
A ─── B ─── C
↑
main
Commit C chứa bug.
Một cách tiếp cận là:
git reset --hard B
Nhưng nếu C đã được push và nhiều developer đã pull về thì việc rewrite shared history có thể gây vấn đề.
Thay vào đó:
git revert C
Git tạo commit mới:
A ─── B ─── C ─── R
↑
main
Trong đó:
R
=
changes đảo ngược effect của C
Điểm quan trọng:
C vẫn tồn tại
History cho thấy rõ:
C
→ feature/bug introduced
R
→ revert C
Git documentation mô tả git revert là tạo commit mới để reverse effect của commit trước đó.
12. Reset vs Revert
Đây là distinction cực kỳ quan trọng.
Reset
A ─── B ─── C
↑
main
git reset B
A ─── B
↑
main
History reference bị di chuyển.
Revert
A ─── B ─── C
↑
main
git revert C
A ─── B ─── C ─── R
↑
main
History được preserve.
13. Khi nào nên dùng Reset và Revert?
Mental model thực tế:
Local history chưa share
Có thể cân nhắc:
git reset
Ví dụ:
commit sai
chưa push
Bạn có thể rewrite local history tương đối an toàn.
Shared history
Ưu tiên:
git revert
Ví dụ:
main
release
production
shared development branch
Lý do không phải vì reset bản thân luôn nguy hiểm.
Vấn đề là:
Các developer khác có thể đã xây dựng work của họ dựa trên history cũ.
Rewrite history lúc này làm local/remote histories diverge và thường kéo theo force update remote.
Rule of thumb:
Private/local history
→ rewrite có thể acceptable
Shared history
→ preserve history
14. Ví dụ Production Incident
Giả sử:
main
A ─── B ─── C ─── D
↑
production
D gây lỗi payment.
Trong incident, mục tiêu là:
restore service quickly
+
preserve auditability
Có thể:
git revert D
tạo:
A ─── B ─── C ─── D ─── R
↑
main
Sau đó CI/CD deploy R.
History thể hiện rõ:
D
→ introduced issue
R
→ reverted D
Trong môi trường cần auditability như banking/payment, history rõ ràng như vậy thường hữu ích hơn việc rewrite shared history.
15. git merge — Kết hợp hai histories
Bây giờ chuyển sang:
git merge
Giả sử:
D ─── E feature
/
A ─── B ─── C main
Feature branch có work riêng.
Trong khi main cũng có thể tiếp tục phát triển:
D ─── E feature
/
A ─── B ─── C ─── F main
Checkout main:
git switch main
sau đó:
git merge feature
Git cần kết hợp hai histories.
Một true merge có thể tạo:
D ─── E
/ \
A ─── B ─── C ─── F ─── M
↑
main
M là merge commit.
Điểm đặc biệt:
M
có hai parents.
Mental model:
E
\
M
/
F
Khác commit bình thường:
B ← C
chỉ có một parent.
16. Merge không phải lúc nào cũng tạo Merge Commit
Đây là misconception phổ biến:
git mergeluôn tạo merge commit.
Không đúng.
Giả sử:
A ─── B ─── C
↑
main
\
D ─── E
↑
feature
main chưa có commit mới sau khi feature branch tách ra.
Checkout main:
git switch main
rồi:
git merge feature
Git có thể chỉ di chuyển pointer:
A ─── B ─── C ─── D ─── E
↑
main
feature
Đây gọi là:
Fast-forward merge
Không cần merge commit.
Git chỉ cần:
main → C
thành:
main → E
17. --ff, --no-ff và --ff-only
Ba option rất đáng biết:
Default / --ff
Nếu fast-forward được:
git merge feature
Git có thể chỉ move pointer.
--no-ff
git merge --no-ff feature
yêu cầu tạo merge commit ngay cả khi fast-forward có thể thực hiện.
Ví dụ:
A ─── B ─── C ─────── M
\ /
D ─── E
Điều này có thể hữu ích nếu team muốn preserve rõ feature boundary trong history.
--ff-only
git merge --ff-only feature
Git chỉ merge nếu có thể fast-forward.
Nếu histories diverged:
D ─── E
/
A ─── B ─── C
Git từ chối tạo merge commit.
Option này hữu ích khi muốn enforce linear movement trong một số workflow.
18. Merge Conflict thực sự xảy ra ở đâu?
Giả sử từ common ancestor:
PaymentService.java
│
common version
/ \
/ \
main feature
│ │
change line 20 change line 20
Git không thể tự quyết định version cuối cùng.
Bạn có thể thấy:
<<<<<<< HEAD
code from current branch
=======
code from feature branch
>>>>>>> feature
Sau khi resolve:
git add PaymentService.java
rồi:
git commit
merge hoàn thành.
Nếu muốn hủy merge:
git merge --abort
Một điểm thú vị từ Part 2:
Trong quá trình conflict, Index lại đóng vai trò rất quan trọng.
Git có thể giữ các stages khác nhau của conflicted path trong Index để phục vụ 3-way merge.
Có thể inspect bằng:
git ls-files -u
Đây là một ví dụ cho thấy Index không chỉ đơn giản là "nơi git add file".
19. git rebase — Replay commits trên base mới
Đây là command dễ gây nhầm lẫn nhất.
Giả sử:
D ─── E feature
/
A ─── B ─── C ─── F main
Feature được tạo từ B.
Trong lúc bạn phát triển:
D
E
main có:
C
F
Bạn muốn feature branch cập nhật trên latest main:
git switch feature
git rebase main
Git sẽ lấy những commits thuộc feature:
D
E
và replay chúng trên:
F
Kết quả:
A ─── B ─── C ─── F ─── D' ─── E'
↑
feature
Điểm cực kỳ quan trọng:
D' ≠ D
E' ≠ E
Rebase không đơn giản "di chuyển commit".
Nó tạo/replay commits trên base mới.
Git documentation mô tả rebase là việc reapply commits lên một base tip khác.
20. Tại sao Rebase tạo Commit mới?
Quay lại Part 1.
Commit chứa những thông tin như:
tree
parent
author
committer
message
Ban đầu:
D
parent → B
Sau rebase:
D'
parent → F
Parent thay đổi.
Do commit content/metadata liên quan đến identity thay đổi, object ID của commit mới cũng thay đổi.
Vì vậy:
D ≠ D'
Ngay cả khi source changes gần như giống nhau.
Đây chính là ý nghĩa của:
Rebase rewrites history.
21. Merge vs Rebase
Đây là một trong những câu hỏi Git phổ biến nhất.
Giả sử ban đầu:
D ─── E
/
A ─── B ─── C ─── F
Merge
git merge feature
có thể tạo:
D ─── E
/ \
A ─── B ─── C ─── F ─── M
History giữ lại branch topology.
Rebase
git rebase main
trên feature:
A ─── B ─── C ─── F ─── D' ─── E'
History trở nên linear hơn.
Nhưng:
D → D'
E → E'
history đã được rewritten.
22. Merge vs Rebase — Trade-off
| Merge | Rebase | |
|---|---|---|
| Rewrite commits | Không đối với existing commits | Có |
| Preserve branch topology | Có | Không theo cách linear rebase thông thường |
| Có thể tạo merge commit | Có | Không theo flow linear cơ bản |
| Linear history | Không đảm bảo | Thường có |
| Shared history | Thường an toàn hơn | Cần cẩn thận |
| Local cleanup | Không phải mục tiêu chính | Rất hữu ích |
Không có rule:
rebase = good
merge = bad
hoặc:
merge = professional
rebase = dangerous
Hai command giải quyết hai mục tiêu khác nhau.
23. Một workflow thực tế
Giả sử team có:
main
│
├── feature/payment
├── feature/topup
└── feature/billpay
Bạn đang làm:
feature/payment
Trước khi tạo Pull/Merge Request, bạn muốn cập nhật latest main.
Một workflow có thể là:
git fetch origin
git rebase origin/main
Resolve conflict nếu có:
git add <resolved-files>
git rebase --continue
Nếu muốn quay lại:
git rebase --abort
Sau khi feature branch đã clean và được review, team có thể integrate vào main theo policy của repository:
merge commit
squash merge
rebase merge
fast-forward
Việc lựa chọn strategy nên là team policy, không phải mỗi developer tự chọn tùy hứng.
24. Rebase Conflict khác Merge Conflict như thế nào?
Về mặt file conflict, bạn vẫn có thể gặp:
<<<<<<<
=======
>>>>>>>
Nhưng rebase đang replay commits từng commit một.
Ví dụ:
D
E
G
Git replay:
D'
nếu conflict → stop.
Bạn resolve:
git add .
git rebase --continue
sau đó Git tiếp tục:
E'
rồi:
G'
Vì vậy một rebase dài có thể khiến bạn phải resolve conflicts ở nhiều bước khác nhau.
Nếu thấy quá phức tạp:
git rebase --abort
đưa branch trở lại state trước khi bắt đầu rebase.
25. Interactive Rebase
Rebase còn có một khả năng rất mạnh:
git rebase -i HEAD~5
Ví dụ Git mở:
pick a123 feat: payment validation
pick b456 fix typo
pick c789 fix typo again
pick d012 add timeout
pick e345 rename variable
Bạn có thể:
pick
reword
edit
squash
fixup
drop
để chỉnh history trước khi share.
Ví dụ:
pick a123 feat: payment validation
fixup b456 fix typo
fixup c789 fix typo again
pick d012 add timeout
fixup e345 rename variable
có thể giúp biến history lộn xộn thành các logical commits rõ ràng hơn.
Interactive rebase đặc biệt hữu ích cho:
cleanup local commits
before Pull Request
Nhưng cần nhớ:
Những commits được rebase sẽ có identity/history mới.
26. Golden Rule của Rebase
Một rule rất hữu ích:
Không tùy tiện rebase commits mà người khác đã dựa vào như shared history.
Ví dụ remote:
A ─── B ─── C
↑
main
Developer khác đã:
git pull
và tạo:
A ─── B ─── C ─── X
Nếu bạn rewrite:
C → C'
thì history của hai người không còn cùng topology như trước.
Đây là nguồn gốc của nhiều vấn đề liên quan đến:
non-fast-forward
force push
duplicate-looking commits
diverged branches
Chúng ta sẽ đào sâu remote refs và force push ở:
Part 5 – Remote, Push & Git Security
27. Bốn command nhìn trên cùng một Commit Graph
Giả sử:
A ─── B ─── C
↑
HEAD
Reset
git reset B
A ─── B
↑
HEAD
→ Move reference.
Revert
git revert C
A ─── B ─── C ─── R
↑
HEAD
→ Add inverse commit.
Rebase
D ─── E
/
A ─── B ─── C
↓
A ─── B ─── C ─── D' ─── E'
→ Replay/recreate commits.
Merge
D ─── E
/ \
A ─── B ─── C ─── M
→ Join histories.
Đây là mental model quan trọng nhất của Part 3.
28. Decision Tree — Tôi nên dùng command nào?
Hãy bắt đầu bằng câu hỏi:
Tôi đang muốn làm gì?
Tôi vừa commit sai và chưa share?
reset
Nếu muốn giữ staged:
git reset --soft HEAD~1
Nếu muốn unstage nhưng giữ code:
git reset HEAD~1
Tôi muốn undo một commit đã nằm trên shared branch?
revert
Ví dụ:
git revert <commit>
Tôi muốn đưa feature commits lên latest base và giữ history linear?
rebase
Ví dụ:
git rebase main
Tôi muốn kết hợp hai development histories?
merge
Ví dụ:
git merge feature/payment
Mental shortcut:
reset
→ move
revert
→ reverse
rebase
→ replay
merge
→ combine
29. Một tình huống trong bài trắc nghiệm
Trong bộ câu hỏi ban đầu có tình huống:
Bạn có staged performance improvements chưa commit. Trong khi đó cần rollback một số commits nhưng vẫn muốn preserve staged changes.
Đây chính là nơi mental model Part 2 + Part 3 kết hợp.
Ta cần:
move HEAD
nhưng:
preserve Index
preserve Working Tree
Nhìn bảng:
| Mode | HEAD | Index | Working Tree |
|---|---|---|---|
--soft |
Reset | Giữ | Giữ |
--mixed |
Reset | Reset | Giữ |
--hard |
Reset | Reset | Reset |
Do đó:
git reset --soft <target>
là lựa chọn phù hợp nếu mục tiêu đúng là di chuyển HEAD trong local history mà vẫn giữ Index/Working Tree.
Không cần học thuộc đáp án.
Chỉ cần hiểu:
HEAD
INDEX
WORKING TREE
30. Một tình huống khác: Fast-forward
Một câu khác hỏi:
Nếu merge trong trường hợp có thể fast-forward thì sao?
Giả sử:
A ─── B ─── C
↑
main
\
D
↑
feature
Nếu:
git switch main
git merge feature
Git không bắt buộc phải tạo:
Merge Commit M
Nó chỉ cần:
main → D
Kết quả:
A ─── B ─── C ─── D
↑
main
Đây là fast-forward.
Một lần nữa:
Branch chỉ là reference.
Concept của Part 1 tiếp tục giải thích behavior của Part 3.
31. Common Mistakes
Mistake #1 — reset --hard khi panic
Developer thấy repository "messy":
git reset --hard
rồi mới nhận ra local changes chưa commit đã bị discard.
Trước khi dùng --hard, hãy biết chính xác state nào của:
HEAD
Index
Working Tree
sẽ bị thay đổi.
Mistake #2 — Reset shared branch rồi force push
Flow nguy hiểm:
git reset --hard HEAD~3
git push --force
Nếu branch được nhiều developer sử dụng, bạn có thể overwrite remote history mà người khác đang dựa vào.
Remote/force push sẽ được phân tích riêng ở Part 5.
Mistake #3 — Rebase mọi thứ vì muốn history đẹp
Linear history có lợi.
Nhưng:
clean history
không đáng để đổi lấy:
unexpected history rewrite
trên shared branches.
Mistake #4 — Merge liên tục mà không hiểu topology
Một history như:
---M---M---M---M---M---
không tự động là xấu.
Nhưng nếu mỗi developer merge main vào feature liên tục mà không có workflow thống nhất, history có thể trở nên khó đọc.
Team cần thống nhất:
merge policy
rebase policy
squash policy
branch protection
Mistake #5 — Resolve conflict rồi git add . ngay lập tức
Sau conflict:
git add .
có thể stage nhiều thứ hơn bạn nghĩ.
Tốt hơn nên kiểm tra:
git status
git diff
rồi stage các resolved files có chủ đích.
Mental model Index từ Part 2 vẫn rất quan trọng ở đây.
32. Production Perspective
Trong project thực tế, câu hỏi không phải chỉ là:
mergehayrebasecommand nào tốt hơn?
Mà là:
History strategy nào phù hợp với cách team deploy, review và rollback?
Ví dụ team có:
feature branch
↓
Pull Request
↓
CI
↓
Code Review
↓
main
↓
Release
Có thể enforce:
Protected main
No direct push
Required CI
Required review
Squash merge
Hoặc:
Feature branch
↓
Rebase before merge
↓
Fast-forward only
Cả hai đều có thể hợp lý.
Điều quan trọng là:
consistent history
+
safe collaboration
+
easy rollback
+
traceability
chứ không phải "rebase luôn tốt hơn merge".
33. Góc nhìn Backend / DevOps
Git history ảnh hưởng trực tiếp tới CI/CD.
Ví dụ pipeline:
Commit
↓
GitLab CI / GitHub Actions / Jenkins
↓
Build
↓
Docker Image
↓
Deploy Kubernetes
Docker image thường có thể được trace bằng:
commit SHA
Ví dụ:
payment-service:abc1234
Nếu shared history bị rewrite không kiểm soát, việc trace:
source commit
→ CI build
→ Docker image
→ production deployment
có thể trở nên khó reasoning hơn.
Vì vậy ở production environment, Git history không chỉ phục vụ developer.
Nó còn liên quan tới:
auditability
traceability
rollback
incident investigation
release management
34. Senior/SRE sẽ quan tâm điều gì?
Một developer mới có thể hỏi:
Làm sao undo commit này?
Senior thường hỏi thêm:
Commit đã push chưa?
Branch có shared không?
Có developer nào đã base work trên commit này chưa?
CI đã build commit này chưa?
Artifact đã deploy chưa?
Có cần preserve audit history không?
Rollback source có đồng nghĩa rollback database không?
Ví dụ:
git revert <commit>
có thể revert source code.
Nhưng nếu commit đó đã chạy database migration:
ALTER TABLE payment ...
thì Git revert không tự rollback database state.
Đây là điểm rất quan trọng trong production.
Git history và runtime state là hai thứ khác nhau:
Git
→ source history
Kubernetes
→ runtime state
Database
→ persistent state
Không nên coi git revert là một production rollback mechanism hoàn chỉnh.
35. Cheat Sheet
| Mục tiêu | Command |
|---|---|
| Undo local commit, giữ staged | git reset --soft HEAD~1 |
| Undo local commit, giữ code nhưng unstage | git reset HEAD~1 |
| Reset HEAD + Index + tracked Working Tree state | git reset --hard <commit> |
| Undo shared commit bằng history mới | git revert <commit> |
| Replay feature lên base mới | git rebase main |
| Hủy rebase | git rebase --abort |
| Tiếp tục rebase | git rebase --continue |
| Combine histories | git merge <branch> |
| Hủy merge conflict | git merge --abort |
| Chỉ cho phép fast-forward | git merge --ff-only <branch> |
| Luôn tạo merge commit khi merge thành công | git merge --no-ff <branch> |
| Cleanup local commits | git rebase -i |
36. Mental Model cần nhớ
Nếu chỉ nhớ một phần của bài này, hãy nhớ:
RESET
=====
Move reference
A ─── B ─── C
↑
HEAD
↓ reset
A ─── B
↑
HEAD
REVERT
======
Create inverse commit
A ─── B ─── C
↑
HEAD
↓ revert
A ─── B ─── C ─── R
↑
HEAD
REBASE
======
Replay commits
D ─── E
/
A ─── B ─── C
↓
A ─── B ─── C ─── D' ─── E'
MERGE
=====
Join histories
D ─── E
/ \
A ─── B ─── C ─── M
Và với reset:
HEAD INDEX WORKTREE
--soft ✓ - -
--mixed ✓ ✓ -
--hard ✓ ✓ ✓
Trong đó:
✓ = reset
- = preserve
37. Key Takeaways
Sau Part 3, bạn nên trả lời được:
reset làm gì?
Move HEAD/current branch
+
optional Index/Working Tree reset
revert làm gì?
Create new commit
→ reverse previous changes
rebase làm gì?
Replay commits
→ new base
→ new commit identities
merge làm gì?
Combine development histories
Khi nào reset?
Local/private history
Khi nào revert?
Shared history cần preserve
Khi nào rebase?
Clean/reorganize private feature history
hoặc workflow mà team đã thống nhất
Khi nào merge?
Integrate histories
và preserve topology khi cần
Mental shortcut:
reset → MOVE
revert → REVERSE
rebase → REPLAY
merge → COMBINE
38. Kết luận
Qua ba phần đầu của series, mental model của chúng ta đang dần hoàn chỉnh.
Part 1:
OBJECT DATABASE
Blob
Tree
Commit
Branch
HEAD
↓
Part 2:
LOCAL STATE
HEAD
↓
INDEX
↓
WORKING TREE
↓
Part 3:
HISTORY OPERATIONS
reset
revert
rebase
merge
Điều quan trọng nhất là chúng ta không cần học thuộc từng command một cách độc lập.
Chỉ cần hỏi:
Command này đang thay đổi cái gì?
Branch reference?
HEAD?
Index?
Working Tree?
Commit Graph?
Hay tạo Commit mới?
Khi trả lời được những câu hỏi đó, behavior của Git trở nên dễ reasoning hơn rất nhiều.
Nhưng vẫn còn một câu hỏi chưa được giải quyết:
Nếu tôi
reset --hardvà commit biến mất khỏigit log, commit đó đã thực sự bị xóa chưa?
Câu trả lời dẫn chúng ta tới một trong những cơ chế hữu ích nhất của Git.
39. Series tiếp theo
Git Deep Dive #4: Reflog — Chiếc "phao cứu sinh" khi bạn làm mất commit
Ở Part 4 chúng ta sẽ đi sâu vào:
HEAD@{0}
HEAD@{1}
HEAD@{2}
và tìm hiểu:
- Reflog thực sự lưu gì?
HEAD@{4}nghĩa là gì?- Tại sao nó có thể trỏ tới commit từ branch khác?
- Làm sao recover sau
git reset --hard? - Làm sao recover branch bị delete?
- Detached HEAD có thể recover không?
- Unreachable commit khác deleted commit thế nào?
- Khi nào Git Garbage Collection thực sự có thể khiến object biến mất?
Part 4 sẽ nối trực tiếp:
Part 1: Object Model
+
Part 3: History Manipulation
↓
Part 4: History Recovery
Và nó cũng sẽ tạo cầu nối tới Part 6 – Git Garbage Collection & Packfile, nơi chúng ta tìm hiểu khi nào những unreachable objects thực sự có thể bị Git cleanup.
References
- Git Documentation —
git reset: https://git-scm.com/docs/git-reset - Git Documentation —
git revert: https://git-scm.com/docs/git-revert - Git Documentation —
git rebase: https://git-scm.com/docs/git-rebase - Git Documentation —
git merge: https://git-scm.com/docs/git-merge
All Rights Reserved