+1

# 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 reset hay revert?

Hoặc:

Tôi muốn cập nhật feature branch với main, nên merge hay rebase?

Hay nguy hiểm hơn:

Tôi đã push commit lên shared branch rồi. Có nên reset --hard khô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:

reset khô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 IndexWorking 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

Mmerge 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 merge luô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--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
Preserve branch topology Không theo cách linear rebase thông thường
Có thể tạo merge commit 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à:

merge hay rebase command 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 --hard và commit biến mất khỏi git 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


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.