Skip to content

05. Reset, Revert & Cherry-pick

IMPORTANT

Ba lệnh này đều là selective history tools: chúng giúp bạn can thiệp vào lịch sử Git theo cách có chủ đích, nhưng mức độ an toàn hoàn toàn khác nhau.

  • git reset: phẫu thuật lịch sử local. Nhanh, mạnh, nhưng dễ gây tai nạn nếu đụng vào lịch sử đã chia sẻ.
  • git revert: undo an toàn cho shared history. Không xóa lịch sử, chỉ thêm một commit đảo ngược.
  • git cherry-pick: cấy ghép chọn lọc. Lấy đúng commit bạn cần sang branch hiện tại, nhưng không phải chiến lược thay thế merge/rebase.

Bài này giải quyết vấn đề gì?

Trong công việc thật, bạn thường gặp 3 tình huống:

  1. Lịch sử local đang bẩn trước khi mở PR → cần bỏ vài commit WIP, nhưng vẫn giữ code để dọn dẹp lại.
  2. Một commit xấu đã lên production hoặc main → cần hoàn tác an toàn mà không phá lịch sử cả team.
  3. Một hotfix nằm trên branch khác → cần mang đúng commit fix đó sang branch hiện tại mà không kéo theo cả đống thay đổi khác.

Ba tình huống này nghe giống nhau ở bề mặt: “undo” hoặc “lấy một phần lịch sử”.
Nhưng nếu dùng sai công cụ, bạn có thể:

  • làm lệch lịch sử branch shared,
  • tạo duplicate commit khó theo dõi,
  • hoặc tự xóa mất local work.

Mental model: chọn đúng dao mổ

text
Bạn muốn làm gì?

1) "Tôi muốn quay lại lịch sử LOCAL của chính mình"
   -> git reset

2) "Tôi muốn hủy tác dụng của một commit đã chia sẻ với team"
   -> git revert

3) "Tôi chỉ muốn lấy MỘT VÀI commit từ branch khác"
   -> git cherry-pick

Reset vs Revert: khác nhau ở triết lý

LệnhGiải quyết vấn đề gì?Tác động lên lịch sửKhi nào phù hợp?Hồ sơ an toàn
git resetDọn lịch sử local, bỏ commit local, unstage/uncommitViết lại vị trí HEAD, có thể làm commit trở nên unreachableLocal branch riêng của bạn, chưa push hoặc chưa chia sẻNguy hiểm nếu đụng shared history
git revertHoàn tác một commit đã tồn tại trong lịch sử sharedThêm commit mới đảo ngược thay đổimain, production, branch đã push cho teamAn toàn cho collaboration
git cherry-pickLấy đúng commit cần thiết từ branch khácTạo commit mới với nội dung tương tự trên branch hiện tạiHotfix, backport, chọn lọc fixAn toàn vừa phải, nhưng dễ bị lạm dụng

1) git reset: local-history surgery

git reset giải quyết vấn đề gì?

git reset dùng khi bạn muốn thay đổi lại lịch sử local của chính branch hiện tại:

  • bỏ commit WIP trước khi mở PR,
  • unstage file đã add nhầm,
  • quay HEAD về commit cũ hơn,
  • dọn lại lịch sử local cho sạch trước khi chia sẻ.

Nói ngắn gọn: reset không phải “undo cho team”, mà là dao mổ cho lịch sử local của riêng bạn.

Ba chế độ cần nhớ

bash
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
Chế độCommit historyStaging areaWorking directoryDùng khi nào
--softLùi HEADGiữ nguyênGiữ nguyênMuốn gộp/sửa lại commit message hoặc commit lại cho đẹp
--mixed (mặc định)Lùi HEADBỏ stageGiữ nguyênMuốn uncommit + chọn stage lại từ đầu
--hardLùi HEADXóaXóaMuốn bỏ sạch local changes và commit local

Ví dụ thực tế: reset local WIP trước khi dọn PR

Bạn đang ở branch feature/payment-timeout và có 3 commit local:

text
A - commit sạch cuối cùng
B - wip: thử timeout approach 1
C - wip: thêm debug logs
D - wip: sửa tạm test

Bạn nhận ra trước khi mở PR, mình cần dọn lại thành 1 commit tử tế.

bash
git reset --mixed HEAD~3

Kết quả:

  • branch quay lại commit A,
  • toàn bộ thay đổi từ B, C, D vẫn nằm trong working directory,
  • bạn có thể stage lại theo từng phần và tạo commit sạch hơn.

Đây là use case rất chuẩn của reset: cleanup local WIP trước khi chia sẻ.

Khi nào KHÔNG nên dùng git reset?

Không dùng reset khi:

  • commit đã push lên main hoặc branch shared,
  • branch đang được nhiều người cùng làm,
  • bạn không chắc commit đó đã được chia sẻ hay chưa,
  • bạn định “undo production bug” bằng cách rewrite history.

CAUTION

Không reset shared history rồi force-push như một thói quen.
Với team workflow nghiêm túc, đó là cách tạo incident collaboration nhanh nhất.

Nếu dùng sai git reset, recovery thế nào?

Tình huống điển hình: bạn lỡ git reset --hard và mất local commit.

Bước 1: xem lại dấu vết bằng reflog

bash
git reflog

Ví dụ:

text
8c2f1ab HEAD@{0}: reset: moving to HEAD~2
41d9e77 HEAD@{1}: commit: fix(payment): handle timeout retry

Bước 2: cứu lại commit bằng reset hoặc branch tạm

bash
git reset --hard 41d9e77

Hoặc an toàn hơn:

bash
git switch -c rescue-reset-accident 41d9e77

Checklist trước khi reset

  • Branch này có phải branch local riêng của mình không?
  • Commit này đã push chưa?
  • Mình muốn giữ code hay xóa sạch code?
  • Nếu lỡ tay, mình có biết dùng git reflog để quay lại không?

2) git revert: shared-history-safe undo

git revert giải quyết vấn đề gì?

git revert dùng khi một commit đã đi vào lịch sử chia sẻ và bạn cần hủy tác dụng của nó mà không rewrite history.

Đây là công cụ đúng trong các tình huống như:

  • commit lỗi đã lên main,
  • production đang cháy và bạn cần rollback an toàn,
  • team đã pull commit đó rồi nên không thể giả vờ “nó chưa từng tồn tại”.

Cách hoạt động

bash
git revert <bad-commit-sha>

Git sẽ tạo ra một commit mới có thay đổi ngược với commit xấu.

text
A - B - C - D
        ^
        commit xấu

git revert C

A - B - C - D - E
                ^
                E đảo ngược tác dụng của C

Lịch sử vẫn minh bạch:

  • commit xấu vẫn còn để audit,
  • commit revert cho thấy team đã rollback như thế nào,
  • không ai bị lệch lịch sử.

Ví dụ thực tế: revert bad production commit

Giả sử commit 9f31abc đã được deploy lên production và gây lỗi login.

bash
git switch main
git pull origin main
git revert 9f31abc
git push origin main

Đây là lựa chọn đúng vì:

  • production cần rollback nhanh,
  • main là shared branch,
  • mọi người khác có thể pull bình thường,
  • CI/CD và audit trail vẫn rõ ràng.

Khi nào KHÔNG nên dùng git revert?

Không nên dùng revert khi:

  • commit đó chỉ mới ở local của bạn,
  • bạn chỉ muốn dọn WIP trước khi mở PR,
  • bạn đang cố “lau sạch” lịch sử local riêng của mình,
  • bạn dùng revert liên tục để vá một workflow branch quá lộn xộn thay vì sửa chiến lược branch.

Nói cách khác:

  • local cleanup → nghĩ tới reset
  • shared undo → nghĩ tới revert

Nếu dùng sai git revert, recovery thế nào?

Tình huống điển hình: bạn revert nhầm commit.

Giải pháp thường là revert chính commit revert đó:

bash
git log --oneline
git revert <sha-cua-commit-revert>

Điều này tạo thêm một commit mới để khôi phục lại thay đổi trước đó.

TIP

Nếu bạn đã revert một commit trên main rồi sau đó phát hiện rollback là sai, đừng reset main để xóa revert.
Hãy revert chính commit revert đó để lịch sử vẫn an toàn và minh bạch.


3) git cherry-pick: selective transplant, không phải branch strategy

git cherry-pick giải quyết vấn đề gì?

git cherry-pick dùng khi bạn cần đem đúng commit đang cần từ branch khác sang branch hiện tại.

Use case chuẩn:

  • lấy một hotfix từ branch hotfix/... sang main,
  • backport một fix từ main xuống release branch cũ,
  • chọn đúng một commit sửa bug mà không merge cả branch nguồn.

Đây là hành động cấy ghép chọn lọc, không phải cách thay thế merge/rebase trong workflow hằng ngày.

Cách dùng cơ bản

bash
git cherry-pick <commit-sha>

Git sẽ tạo một commit mới trên branch hiện tại với nội dung gần tương đương commit gốc.

Ví dụ thực tế: cherry-pick hotfix

Giả sử team fix lỗi timeout đăng nhập trên branch hotfix/login-timeout, và commit fix là a1b2c3d.

Bạn cần đưa đúng fix đó vào main ngay, nhưng không muốn merge toàn bộ hotfix branch vì branch còn thêm log tạm và vài thay đổi chưa review.

bash
git switch main
git pull origin main
git cherry-pick a1b2c3d
git push origin main

Đây là use case đẹp của cherry-pick: lấy đúng một commit cứu hỏa.

Khi nào KHÔNG nên dùng git cherry-pick?

Không nên dùng cherry-pick khi:

  • bạn định dùng nó thay cho merge/rebase thường xuyên,
  • bạn muốn đồng bộ cả một branch dài hạn,
  • bạn không hiểu commit phụ thuộc vào context nào,
  • commit bạn chọn phụ thuộc vào nhiều commit trước đó nhưng bạn chỉ pick một mình nó.

WARNING

cherry-pick không phải branch strategy.
Nếu lạm dụng, bạn sẽ tạo ra nhiều commit “na ná nhau nhưng SHA khác nhau”, khiến lịch sử khó đọc, khó trace, và dễ conflict về sau.

Nếu cherry-pick bị conflict hoặc dùng sai, recovery thế nào?

Nếu conflict xảy ra:

bash
git cherry-pick --abort

Điều này đưa bạn về trạng thái trước khi bắt đầu cherry-pick.

Nếu bạn đã cherry-pick xong nhưng nhận ra pick nhầm:

  • nếu commit vẫn local, có thể git reset --hard HEAD~1 chỉ khi chưa push và bạn chắc chắn branch là private/local;
  • nếu đã push lên shared branch, hãy git revert <sha-cua-commit-cherry-pick>.

Nguyên tắc sống còn khi cherry-pick

Trước khi pick, hãy tự hỏi:

  1. Commit này có tự đứng độc lập được không?
  2. Nó có phụ thuộc vào commit nền nào khác không?
  3. Mình có đang dùng cherry-pick để né một branch strategy đang sai không?

So sánh nhanh bằng tình huống thật

Tình huống A: “Tôi có 3 commit WIP local và muốn dọn lại trước khi mở PR”

Dùng: git reset --mixed HEAD~3

Vì sao: bạn đang làm sạch lịch sử local, chưa động vào shared history.

Không dùng: git revert vì bạn chưa cần tạo audit trail cho team.


Tình huống B: “Một commit lỗi đã lên production, cần rollback an toàn”

Dùng: git revert <bad-commit>

Vì sao: đây là shared history, rollback phải an toàn và minh bạch.

Không dùng: git reset trên main.


Tình huống C: “Có một hotfix trên branch khác, tôi chỉ cần đúng commit fix đó”

Dùng: git cherry-pick <hotfix-commit>

Vì sao: bạn cần selective transplant, không cần merge cả branch.

Không dùng: cherry-pick như cách chính để đồng bộ branch lâu dài.


Recovery playbook: nếu lỡ dùng sai thì làm gì?

Bạn lỡ reset --hard và mất commit local

bash
git reflog
git switch -c rescue-branch <sha-cu>

Hoặc:

bash
git reset --hard <sha-cu>

Bạn revert nhầm commit trên shared branch

bash
git log --oneline
git revert <sha-cua-commit-revert>

Bạn cherry-pick bị conflict và chưa muốn giải quyết

bash
git cherry-pick --abort

Bạn cherry-pick nhầm lên shared branch và đã push

bash
git revert <sha-cua-commit-da-pick>

Quy tắc nhớ nhanh

TIP

Hãy nhớ câu này:

  • Reset = local-history surgery
  • Revert = shared-history-safe undo
  • Cherry-pick = selective transplant

Nếu bạn nhớ đúng ba câu này, bạn sẽ tránh được phần lớn tai nạn Git liên quan tới undo và recovery.

🧠 Quiz

Câu 1: Khi một commit lỗi đã được push lên main, lựa chọn an toàn nhất là gì?

  • [ ] A. git reset --hard HEAD~1 rồi force-push
  • [x] B. git revert <bad-commit-sha>
  • [ ] C. git cherry-pick <bad-commit-sha>
  • [ ] D. git checkout <bad-commit-sha>

💡 Giải thích: main là shared history. revert tạo commit mới để rollback an toàn mà không phá lịch sử của cả team.

Câu 2: Khi nào git reset là lựa chọn phù hợp nhất?

  • [x] A. Khi dọn local WIP trước khi mở PR trên branch riêng của bạn
  • [ ] B. Khi cần rollback production trên main
  • [ ] C. Khi cần mang hotfix từ branch khác sang main
  • [ ] D. Khi muốn giữ nguyên shared history nhưng đảo ngược một commit đã push

💡 Giải thích: reset sinh ra để chỉnh lịch sử local của bạn. Nó không phải công cụ an toàn cho branch shared.

Câu 3: Phát biểu nào đúng nhất về git cherry-pick?

  • [ ] A. Đây là cách tốt nhất để đồng bộ hai branch dài hạn
  • [x] B. Đây là công cụ cấy ghép commit chọn lọc, thường dùng cho hotfix hoặc backport
  • [ ] C. Đây là phiên bản an toàn hơn của merge
  • [ ] D. Đây là lệnh dùng để xóa commit khỏi lịch sử

💡 Giải thích: cherry-pick rất tốt cho tình huống chọn đúng một commit cần thiết, nhưng không nên biến nó thành branch strategy chính.

Câu 4: Nếu lỡ git reset --hard làm mất commit local, bước recovery đầu tiên nên là gì?

  • [ ] A. Clone lại repo
  • [x] B. Chạy git reflog để tìm SHA trước khi reset
  • [ ] C. Chạy git revert HEAD
  • [ ] D. Tạo pull request mới

💡 Giải thích: reflog là nơi lần lại dấu vết HEAD sau reset, rebase, checkout và nhiều thao tác nguy hiểm khác.