git revert

Xem thử

Vấn đề cần giải quyết

Một commit vừa được deploy lên production gây lỗi, và cả đội đã kéo nó về. Bạn cần đưa hệ thống về trạng thái trước đó ngay, mà không làm ai bị lệch lịch sử. Xóa commit khỏi nhánh chung là điều không được làm. git revert giải quyết bằng cách thêm một commit đảo ngược thay vì xóa.

Sau bài này, bạn có thể

  • Dùng git revert để hoàn tác một commit đã chia sẻ.
  • Dùng --no-edit và -n để làm việc gọn khi hoàn tác nhiều commit.
  • Giải thích khác biệt giữa revert và reset về lịch sử.

Tiên quyết: bài Tổng quan các cách hoàn tác và git log.

Dùng cơ bản

bash
git revert <commit-hash>
git revert --no-edit <commit-hash>

Git tạo một commit mới hủy tác dụng của commit chỉ định và mở trình soạn thảo để xác nhận thông điệp (hoặc bỏ qua bước này với --no-edit). Vì revert thêm lịch sử thay vì xóa, nó an toàn cho nhánh đã chia sẻ.

Thử và quan sát

Hãy dự đoán: C3 là commit lỗi đã push. Sau git revert C3, lịch sử có còn C3 không, và push có cần force không? Chạy mô phỏng, rồi thử biến thể hoàn tác hai commit bằng -n.

Đang tải Animation…

Bảng bước (bản đọc tĩnh của mô phỏng)

BướcLệnhĐiều xảy ra
1git log --oneline -1Xác định commit lỗi: C3 đã nằm trên cả máy bạn lẫn remote.
2git revert 9c4e7d1 --no-editGit tạo commit mới C4 hủy tác dụng của C3. Nội dung quay về như C2, nhưng C3 vẫn nằm trong lịch sử.
3git push origin mainLịch sử chỉ dài thêm nên push thông thường, không cần force. Đồng đội kéo về bình thường.

Biến thể: Hoàn tác hai commit bằng -n

BướcLệnhĐiều xảy ra
1git revert -n C4Cờ -n áp dụng phần đảo ngược vào Working Directory và Staging Area nhưng chưa tạo commit.
2git revert -n C3Revert tiếp commit thứ hai. Cả hai đảo ngược dồn vào cùng một vùng chờ.
3git commit -m "Hoàn tác hai commit lỗi"Một commit duy nhất đảo ngược cả C3 lẫn C4, gọn hơn hai lần revert riêng.

Giải đáp. C3 vẫn còn trong lịch sử; commit mới C4 hủy tác dụng của nó và nội dung quay về như C2. Push thông thường, không cần force, vì lịch sử chỉ dài thêm. Ở biến thể, -n (--no-commit) áp phần đảo ngược vào Staging Area mà chưa commit, nên bạn gộp nhiều revert thành một commit.

Revert khác reset thế nào

Cách đọc hình. Khung trên là git reset --hard C2: main trỏ lại C2 và C3 không còn nằm trên nhánh, nên ai đã lấy C3 về sẽ lệch lịch sử với bạn. Khung dưới là git revert C3: main trỏ tới C4 mới, C3 vẫn nằm trong lịch sử. Cả hai đưa nội dung về như C2, nhưng reset viết lại lịch sử còn revert thì không.

  • git reset dời con trỏ nhánh về quá khứ và có thể làm commit rời khỏi nhánh: nguy hiểm khi đã push.
  • git revert giữ nguyên lịch sử và thêm commit đảo ngược: an toàn cho nhánh chung.

Tình huống thực tế

Một commit lên production gây lỗi:

bash
git revert a1b2c3d --no-edit
git push origin main

Chỉ vài giây, hệ thống trở lại trạng thái trước khi lỗi được đưa vào, và lịch sử vẫn minh bạch về việc đã revert.

Hiểu lầm thường gặp

  • "Revert xóa commit lỗi." Revert không xóa gì; commit lỗi vẫn nằm trong lịch sử.
  • "Revert luôn thành công." Nếu các commit sau đã sửa cùng vùng, revert có thể gây xung đột và bạn phải giải quyết như khi merge.
  • "Revert một merge commit là giống revert commit thường." Với merge commit cần chỉ định cha bằng -m để Git biết đảo ngược về phía nào.

Tự kiểm tra

  1. Vì sao revert an toàn cho nhánh đã chia sẻ còn reset thì không?
  2. Cờ -n của git revert giúp gì?
  3. Sau revert, nội dung giống commit nào và lịch sử có gì khác so với trước?

Đáp án gợi ý. (1) Revert chỉ thêm commit nên lịch sử của mọi người vẫn khớp; reset viết lại lịch sử đã chia sẻ. (2) Áp đảo ngược mà chưa commit, để gộp nhiều lần revert thành một commit. (3) Giống commit trước commit lỗi; lịch sử có thêm một commit đảo ngược.

Điều cần nhớ

  • Revert thêm commit đảo ngược, không xóa gì.
  • --no-edit bỏ bước soạn thông điệp; -n gộp nhiều revert thành một commit.
  • Nhánh đã chia sẻ: dùng revert, không dùng reset.
  • Bài sau: git reset và ba chế độ của nó.

Nguồn tham khảo

Tài liệu git-revert (git-scm.com/docs/git-revert); Pro Git, chương 7.7 (git-scm.com/book).