TL;DR: git revert thêm một commit mới để hoàn tác một commit cũ — lịch sử được giữ nguyên, an toàn trên các nhánh mà người khác đã pull. git reset đẩy con trỏ nhánh lùi về sau — lịch sử bị viết lại, chỉ an toàn với những commit bạn chưa push. Dùng revert trên nhánh chung, reset để dọn local. reset --hard còn xóa luôn các thay đổi chưa commit trong working tree — thứ đó mới là thứ nuốt công sức thật.
git revert và git reset khác nhau thế nào?
Cả hai đều khiến dự án của bạn trông như thể một commit trong quá khứ chưa từng xảy ra. Chúng bất đồng về cách làm:
git revert <sha>tính inverse patch của một commit và commit nó. Lịch sử của bạn tăng thêm một commit ghi “hoàn tác cái kia”. Commit xấu vẫn nằm trong log, theo sau là bản hủy của nó. SHA của mọi thứ khác không hề động đến.git reset <sha>đẩy con trỏ nhánh hiện tại về<sha>. Các commit phía sau bị cắt rời — vẫn nằm trong object database cho tới garbage collection, nhưng không còn chạm tới được từ bất kỳ nhánh nào hay từgit log.
Một bên không viết lại gì, bên kia giả vờ chưa từng có gì xảy ra. Chính tính chất đó quyết định tình huống nào cần cái nào:
# Demo: một repo dùng thử chạy được
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# Revert: lịch sử giữ cả hai commit, cộng thêm một cái thứ ba hoàn tác "two"
git revert --no-edit HEAD
git log --oneline # 3 commit: revert, two, one
# Reset: con trỏ nhánh lùi lại, "two" biến mất khỏi log
git reset --hard HEAD~1
git log --oneline # 1 commit: one Chạy cả hai rồi xem git log — sự bất đối xứng chính là toàn bộ câu trả lời.
git reset —soft, —mixed và —hard thực sự làm gì?
Ba chế độ điều khiển thay đổi của bạn sống sót ở đâu sau khi con trỏ di chuyển:
| Chế độ | Con trỏ nhánh | Staging area | Working tree | Thay đổi của bạn |
|---|---|---|---|---|
--soft | lùi lại | giữ nguyên staged | không đụng tới | giữ trọn vẹn |
--mixed (mặc định) | lùi lại | unstaged | không đụng tới | giữ, unstaged |
--hard | lùi lại | bị reset | bị reset | bị xóa |
Đọc cụ thể từ bảng đó:
git reset --soft HEAD~1— “Tôi commit quá sớm.” Mọi thứ quay về trạng thái staged, sẵn sàng commit lại, có thể cùng với công việc mới.git reset HEAD~1(mixed) — “Tôi staged nhầm file.” Các chỉnh sửa còn nguyên trong working tree, không gì được staged. Kết hợp với git remove untracked files nếu bạn cũng muốn đuổi luôn mấy file lạc.git reset --hard HEAD~1— “Commit này cùng mấy chỉnh sửa chưa commit của tôi là rác.” Git sẽ không hỏi lại; phần working tree không có nút hoàn tác trừ khi bạn đã stash hoặc commit nó từ trước.
revert không có chế độ nào vì nó chẳng bao giờ phá hủy gì — nó chỉ thêm một commit đền bù mà thôi.
Khi nào nên dùng git revert thay vì git reset?
Hỏi một câu: đã ai khác pull commit này chưa? Nếu rồi, reset bị loại.
Reset một nhánh mà người khác đã dựa vào để làm việc đồng nghĩa với viết lại lịch sử chung. Lần pull kế tiếp của họ gặp hai nhánh phân kỳ, và “cách sửa” thường là một cú force-push biến lỗi của bạn thành vấn đề của mọi người. Revert nối thêm một commit bình thường, nên một git pull đơn thuần merge sạch sẽ cho tất cả — vì thế revert là câu trả lời mặc định trên main và trên GitHub pull request, nơi nút “Revert” tồn tại chính vì viết lại lịch sử một PR đã merge là điều không được phép.
Reset dành cho khung thời gian trước khi chia sẻ: commit bạn vừa tạo ba mươi giây trước, lần staged nhầm file, nhánh thử nghiệm local. Nếu bản sao duy nhất của công việc nằm trên máy bạn, bạn tự do sắp xếp lại lịch sử — đó là toàn bộ ý nghĩa của khung cửa sổ trước khi push.
Có một trường hợp giữa: bạn đã push lên feature branch của chính mình và không ai xây dựng trên đó. Force-push sau reset ở đó được xã hội chấp nhận, nhưng revert vẫn tốn ít sự phối hợp hơn. Để dành force-push cho những nhánh bạn sở hữu một mình.
git revert có an toàn hơn git reset trên nhánh chung?
Có, về mặt cấu trúc — không chỉ theo thông lệ:
- Revert tạo ra một commit bình thường; CI chạy trên nó, diff đọc review được, và
git loggiải thích cho bạn-của-tương-lai vì sao thay đổi biến mất. - Reset âm thầm vứt bỏ trạng thái trung gian. Không ai review sau đó nhìn thấy cái gì đã bị hoàn tác hay khi nào, vì log hiện một đường thẳng chưa từng chứa nó.
Hai cạnh sắc trong thực tế:
- Revert một merge commit cần
git revert -m 1 <sha>(giữ đường của parent đầu tiên). Thiếu-m, git từ chối và để bạn tự đọc lỗi. - Revert không phải cỗ máy thời gian: nó hoàn tác diff của đúng một commit. Nếu các commit sau đó đã đụng tới cùng những dòng đó, bạn có thể phải resolve conflict — đó là git báo cho bạn biết việc hoàn tác đã dính vào nhau, một thông tin hữu ích chứ không phải trục trặc.
Riêng việc hoàn tác đúng commit cuối cùng của bạn — giữ hoặc vứt các thay đổi của nó — các lựa chọn được xếp sẵn trong hoàn tác commit cuối cùng: giữ lại các thay đổi.
Còn git restore — nó đứng ở đâu?
git restore (cùng git switch, cặp bài trùng của nó) xuất hiện từ git 2.23 để tiếp quản những việc mà reset làm tệ vì cái tên bị chồng chéo quá nhiều nghĩa:
| Việc cần làm | Cách cũ | Cách rõ ràng |
|---|---|---|
| Vứt các chỉnh sửa working tree trong một file | git checkout -- file / git reset --hard | git restore file |
| Bỏ staged một file | git reset HEAD file | git restore --staged file |
| Chuyển nhánh sang commit khác | git reset <sha> | git reset <sha> (không có thay thế — cái này ở lại) |
Vậy phân công hiện đại là: restore sửa file, reset di chuyển nhánh, revert hoàn tác commit đã công bố. Autocomplete cho thấy người ta tìm “git revert vs reset vs restore” cùng nhau là có lý — chúng là một mental model bị xẻ thành ba lệnh. Các dạng git checkout cũ vẫn chạy ở mọi nơi; các dạng mới chỉ ngăn bạn đưa cả một nhánh vào máy hủy nhỏ khi bạn chỉ định unstage một file thôi.
Nếu mục tiêu của bạn là mang một commit tốt đi tới thay vì xóa một commit xấu, đó là việc của cherry-pick — xem git cherry-pick: nhiều commit, nhiều nhánh, xung đột.
Tóm tắt ra quyết định nhanh
- Commit đã công khai (đã push, người khác đã pull) →
git revert <sha>. - Commit chỉ nằm local, muốn làm lại →
git reset --softhoặc--mixedrồi commit lại. - Chỉ nằm local, muốn nó biến mất cùng mớ bòng bong chưa commit →
git reset --hard, sau khi nhìn thẳng một cái xem working tree còn giữ gì khác. - Một file bị hỏng →
git restore <file>và để nhánh yên chỗ.
Mục duy nhất thực sự nguy hiểm trong danh sách đó là --hard — mọi thứ khác đều có thể moi lại bằng git reflog. Mất mát vĩnh viễn trong git hẹp lắm và phần lớn đòi bạn phải gõ tường minh; thói quen đáng xây dựng là khựng nửa giây trước --hard, chứ không phải né toàn bộ các công cụ sắc của git.
— mrsaynothing
— mrsaynothing
Ghi chú thực địa về AI, Linux và self-hosting.
Thảo luận bài này trên dev.to dev.to ↗
Nhận how-to tiếp theo qua email
Một email mỗi bài viết. Sửa xong rồi đi tiếp.
cái này là gì?Systemd Service Not Starting? Cách Sửa
Thích kiểu viết này? Tôi build như vậy để kiếm sống. thuê tôi