git reset

Xem thử

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

Bạn vừa commit nhưng nhận ra thông điệp sai và lỡ gom nhầm một thay đổi vào commit. Commit chưa push, và bạn muốn "lùi" lại để làm cho đúng. git reset dời con trỏ nhánh về một commit cũ, và tùy chế độ, quyết định thay đổi vừa bỏ nằm ở đâu: vẫn đã staged, trở về chưa staged, hoặc biến mất.

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

  • Mô tả git reset trước hết là việc dời con trỏ nhánh.
  • Chọn giữa --soft, --mixed và --hard theo thứ cần giữ.
  • Biết cứu commit bằng reflog và biết --hard làm mất gì không cứu được.

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

Ba chế độ

Cách đọc hình. Hàng đầu: cả ba chế độ đều dời HEAD và nhánh. Hàng Staging Area: --soft giữ nguyên, --mixed và --hard đưa về commit đích. Hàng Working Directory: --soft và --mixed giữ nguyên, chỉ --hard đưa về commit đích. Cách nhớ: soft chạm ít nhất, mixed chạm thêm index, hard chạm tất cả, nên chỉ hard làm mất thay đổi chưa commit.

bash
git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1

Dòng đầu bỏ commit cuối nhưng giữ thay đổi đã staged; dòng hai (mixed, mặc định) giữ thay đổi nhưng chưa staged; dòng ba xóa hẳn commit cuối và mọi thay đổi kèm theo.

Thử và quan sát: soft và mixed

Hãy dự đoán: bạn muốn bỏ commit cuối nhưng giữ thay đổi để sửa lại rồi commit lại. --soft và --mixed khác nhau ở chỗ nào? Chạy mô phỏng --soft, rồi bấm biến thể để xem --mixed.

Đ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 reset --soft HEAD~1Chỉ con trỏ main lùi về C2. Staging Area và Working Directory giữ nguyên nên thay đổi vẫn ở trạng thái đã staged.
2git status -sChữ M ở cột bên trái: thay đổi đã staged, sẵn sàng commit lại ngay.
3git commit -m "Gộp lại cho gọn"Commit lại tạo commit mới C3b thay cho C3 cũ. Đây là cách làm sạch lịch sử cục bộ.

Biến thể: Chế độ --mixed (mặc định)

BướcLệnhĐiều xảy ra
1git reset HEAD~1Không có cờ thì là --mixed: con trỏ lùi về C2 và Staging Area cũng về giống HEAD, còn Working Directory giữ nguyên.
2git status -sChữ M ở cột bên phải: thay đổi còn nguyên nhưng chưa staged.
3git add a.txt && git commit -m "Gộp lại cho gọn"Bạn có thể chọn lại những gì add trước khi commit, điều --soft không cho.

Giải đáp. Cả hai đều giữ nội dung ở Working Directory. Khác biệt duy nhất là Staging Area: --soft giữ thay đổi ở trạng thái đã staged (cột M bên trái), --mixed đưa Staging Area về giống commit đích nên thay đổi thành chưa staged (cột M bên phải) và bạn chọn lại phần nào add.

Thử và quan sát: --hard

Hãy dự đoán: bạn có một thay đổi chưa commit và một commit cuối. Sau git reset --hard HEAD~1, thay đổi chưa commit còn không? Commit cuối còn tìm lại được không?

Đ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 status -sTrước khi reset: có một thay đổi chưa commit (v4) trong Working Directory.
2git reset --hard HEAD~1Hard đưa cả ba nơi về C2. Thay đổi chưa commit v4 biến mất và không có bản sao nào trong lịch sử.
3git status -sMọi thứ sạch. Không còn dấu vết của v4.

Biến thể: Cứu bằng reflog

BướcLệnhĐiều xảy ra
1git reflogReflog ghi lại các lần HEAD di chuyển, trong đó có commit C3 vừa bị bỏ.
2git reset --hard 9c4e7d1Dời main trở lại C3 theo hash lấy từ reflog. Commit được cứu.
3git status -sThay đổi chưa commit v4 vẫn mất, vì nó chưa từng được lưu vào kho. Reflog chỉ nhớ commit.

Giải đáp. Thay đổi chưa commit mất hẳn vì --hard đưa cả Working Directory về commit đích và nó chưa từng nằm trong kho. Commit cuối thì cứu được: reflog ghi lại các lần HEAD di chuyển, và git reset --hard <hash> đưa nhánh quay lại. Reflog chỉ nhớ commit, không nhớ thay đổi chưa commit.

Reset với đường dẫn

bash
git reset <tệp>

Reset kèm đường dẫn tệp không dời nhánh. Nó chỉ đưa bản trong Staging Area về giống HEAD, còn Working Directory giữ nguyên, tương đương git restore --staged <tệp>.

Không bao giờ git reset --hard hay dời con trỏ trên nhánh đã push chung. Nó viết lại lịch sử và gây rối cho cả đội. Khi cần hoàn tác commit đã public, hãy dùng git revert.

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

  • "Reset xóa commit khỏi kho." Reset chỉ dời con trỏ; commit bị bỏ vẫn còn một thời gian và cứu được qua reflog.
  • "Reset nào cũng làm mất thay đổi." Chỉ --hard. --soft và --mixed giữ nội dung ở Working Directory.
  • "Reset là cách hoàn tác commit đã push." Với commit đã chia sẻ, dùng revert.

Tự kiểm tra

  1. Chế độ nào giữ cả thay đổi đã staged lẫn Working Directory?
  2. Vì sao git reset --hard làm mất thay đổi chưa commit nhưng không làm mất commit?
  3. Bạn lỡ reset --hard và muốn lấy lại commit cuối. Bước đầu tiên là gì?

Đáp án gợi ý. (1) --soft. (2) --hard ghi đè Working Directory (không có bản sao), còn commit vẫn có thể tìm lại qua reflog. (3) git reflog để tìm hash, rồi git reset --hard <hash>.

Điều cần nhớ

  • Reset trước hết là dời con trỏ nhánh; commit bị bỏ không biến mất ngay.
  • Soft chạm ít nhất, mixed chạm thêm index, hard chạm tất cả.
  • Chỉ hard làm mất thay đổi chưa commit, và reflog không cứu được chúng.
  • Bài sau: git rm, xóa tệp trong Git.

Nguồn tham khảo

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