Fundamentals
Git, Phần 3: Restore, revert và reset đụng các lớp khác nhau
Restore sửa file và staging; revert thêm lịch sử; reset dịch tip branch — hard reset có thể vứt việc chưa commit.
- Gieo
- Chăm lần cuối

Phần 1 đặt tên ba lớp: working tree, index và commit trên branch. Phần 2 cho thấy merge di chuyển nhãn. Hoàn tác không phải một nút. Sau bài này bạn chọn restore, revert hay reset bằng cách hỏi lớp nào cần đổi, và biết mất gì trước khi chạy.
Đây là Phần 3 trong loạt sáu phần. Remote, rebase và quy trình nhóm tiếp theo. Không lệnh nào “an toàn” trừu tượng — mỗi lệnh an toàn cho một ý định khi bạn hiểu nó chạm gì.
Nó là gì?
git restore (Git 2.23+) đưa nội dung file khớp snapshot chọn. Không cờ thêm thì cập nhật working tree từ index. Với --staged, copy từ HEAD vào index và không đụng working tree trừ khi thêm --worktree. Không dịch tip branch hay xóa commit.
git revert thêm commit mới áp dụng nghịch đảo thay đổi cũ. Lịch sử công khai theo thời gian vẫn thấy lỗi và sửa. Dùng khi commit xấu đã trên branch người khác có thể đã fetch.
git reset dịch tip branch hiện tại (và thường HEAD theo). --soft chỉ dịch tip; thay đổi staged vẫn staged. --mixed (mặc định) dịch tip và reset index cho khớp; chỉnh sửa unstaged trong working tree thường còn. --hard dịch tip, reset index và ghi đè working tree cho khớp. Commit không còn tên branch trỏ tới khó tìm, dù id thường còn trong git reflog một lúc.
Git cũ dùng git checkout -- file và reset chồng việc. Ưu tiên git restore cho file và staging; coi reset là “dịch con trỏ branch (và có thể cả tree).”
Khi nào dùng?
Dùng git restore khi sửa file tracked và muốn nội dung commit cuối lại, hoặc git add quá nhiều và muốn unstage không mất chỉnh sửa trong folder.
Dùng git revert khi commit đã chia sẻ và cần sửa tiến trên cùng branch — hotfix production trên main là ví dụ kinh điển.
Dùng git reset --soft khi commit quá sớm nhưng muốn giữ thay đổi staged để sửa message hoặc tách. --mixed khi muốn commit biến khỏi branch nhưng file vẫn sửa trong folder. --hard chỉ khi chấp nhận mất việc chưa commit và muốn folder khớp tip cũ — thường sau thí nghiệm local chưa push.
Đừng dùng reset --hard để “hoàn tác” push người khác đã dựa vào. Viết lại lịch sử họ đang dùng. Phần 5 nói rebase và force-push; Phần 4 vì sao ref chia sẻ quan trọng.
Demo tương tác
Bấm Watch demo hoặc từng bước. Xem lớp nào sáng: working tree, index hay tip branch. restore nên nhấp nháy lớp file không thêm node commit. revert thêm commit mới phía trên. reset --hard trượt nhãn branch lùi và readout tree khớp tip cũ.
Minh họa tương tác
Edits live in the working tree until you stage or commit them. Nothing moves HEAD yet.
Three areas show Working tree, Index, and a commit node C1 with HEAD pointing at it. Edit file marks the working tree dirty. git restore clears it. git add copies changes into the index. git restore --staged empties the index while the tree may stay dirty. git commit clears both and advances to C2. git revert adds C3. git reset --soft moves the tip back but keeps the index. git reset HEAD~1 also clears staging while leaving working-tree edits. Reset restores the starting frame.
- Start clean: working tree, index, and commit C1 with HEAD on the tip.
- edit file dirties only the working tree. Commits and the index stay put.
- git add copies those edits into the index.
- git commit writes C2 and clears both layers. HEAD jumps forward.
- git revert HEAD adds C3 on top. History is not deleted.
- git reset --soft HEAD~1 slides the tip back but leaves the index staged.
- git reset HEAD~1 also unstages while keeping working-tree edits. Reset to replay.
tip: C1 · WT dirty: false · index staged: false
Code
Repo throwaway cùng máy. Hash khác trên máy bạn; hành vi lớp không. Git 2.43.0.windows.1.
Restore working tree và unstage index
$ git init -b main
Initialized empty Git repository in .../git-undo-demo/.git/
$ # note.txt contains "hello"
$ git add note.txt
$ git commit -m "Add note"
[main (root-commit) cd5b79b] Add note
1 file changed, 1 insertion(+)
$ # edit note.txt to add a "wip" line without committing
$ git status --short
M note.txt
$ git restore note.txt
$ git status --short
$ # clean: working tree matches index again
$ git add note.txt
$ git status --short
M note.txt
$ git restore --staged note.txt
$ git status --short
M note.txt
M (space rồi M) là chỉnh unstaged. M là staged. restore --staged kéo index về HEAD trong khi dòng wip vẫn trong folder.
Revert tạo commit hoàn tác tiến
$ git add note.txt
$ git commit -m "WIP line"
[main 3be37c4] WIP line
1 file changed, 1 insertion(+)
$ git revert --no-edit HEAD
[main 8ad2792] Revert "WIP line"
1 file changed, 1 deletion(-)
$ git log --oneline -3
8ad2792 Revert "WIP line"
3be37c4 WIP line
cd5b79b Add note
Commit giữa vẫn tồn tại. 8ad2792 là bản sửa người clone sẽ thấy. Đó là lý do revert lịch sự trên branch chia sẻ.
Reset soft, mixed và hard
Từ 8ad2792 sau revert, reset --soft HEAD~1 bỏ commit revert và giữ thay đổi WIP staged tại tip 3be37c4.
$ git reset --soft HEAD~1
$ git status --short
M note.txt
$ git log --oneline -1
3be37c4 WIP line
$ git reset HEAD~1
Unstaged changes after reset:
M note.txt
$ git log --oneline -1
cd5b79b Add note
Sau bước --mixed, commit WIP line không còn trên branch; dòng wip unstaged trong working tree đến khi bạn bỏ hoặc commit lại. Repo mới cho thấy pattern tương tự không cần vòng revert:
$ git commit -m "WIP line" # tip 3be37c4
$ git reset --soft HEAD~1
$ git log --oneline -1
cd5b79b Add note
$ git status --short
M note.txt
--soft dịch tip tới cd5b79b nhưng giữ thay đổi WIP staged.
$ git -c core.autocrlf=false init -b main
$ git commit -m "Add note" # dd4a058
$ git commit -m "Extend note" # 3375b22
$ git reset --hard HEAD~1
HEAD is now at dd4a058 Add note
$ git reflog -3
dd4a058 HEAD@{0}: reset: moving to HEAD~1
3375b22 HEAD@{1}: commit: Extend note
dd4a058 HEAD@{2}: commit (initial): Add note
--hard dịch main tới dd4a058 và working tree khớp. Commit 3375b22 không còn trên branch, nhưng reflog vẫn gọi tên. git switch -c recover 3375b22 (hoặc git reset --hard 3375b22 nếu thật sự muốn tip đó) gắn tên trước khi GC quên object unreachable.
Mất gì với reset --hard: mọi thay đổi chưa commit trong working tree và index, cộng truy cập dễ tới commit không ref nào trỏ khi reflog hết hạn. Thường giữ một lúc: commit id trong git reflog đến khi bị prune.
Mã nguồn
Mã nguồn
Chỉ đọcTooling/Git/undo-layers.sh
Chỉ đọc
# Three layers: working tree, index, and commits
git status
# Edit the file (working tree only)
echo tweak >> readme.txt
# Discard working-tree edits
git restore readme.txt
# Stage into the index
git add readme.txt
# Unstage (index only)
git restore --staged readme.txt
# Record a snapshot (moves HEAD / branch tip)
git add readme.txt
git commit -m "Update readme"
# Undo a published commit with a new commit
git revert HEAD
# Move the branch tip; soft keeps the index staged
git reset --soft HEAD~1
# Mixed reset (default): tip back, index cleared, edits stay in WT
git reset HEAD~1
Ưu và nhược điểm
+ Ưu điểm
- restore nhắm một lớp mỗi lần, khớp mô hình Phần 1 và tránh dịch branch nhầm.
- revert giữ lịch sử kiểm toán trên branch chia sẻ vì không gì biến mất khỏi đồ thị người clone.
- reflog là lưới an toàn theo thời gian cho dịch tip local đến khi bạn biết branch hay tag nên giữ id.
− Nhược điểm
- Ba họ hoàn tác cộng ba chế độ reset choáng người mới đến khi hỏi lớp nào sai.
- revert trên chuỗi commit xấu dài nghĩa nhiều commit revert hoặc sửa tay.
- reset --hard cộng reflog cũ vẫn căng; phục hồi có thể, không đảm bảo mãi.
Mẹo
- 01Nói to lớp: working tree, index hay tip branch. Khớp câu đó với restore, revert hay reset.
- 02Chạy git status trước và sau. Short status cho biết unstaged, staged hay chỉ trong lịch sử.
- 03Trên branch chia sẻ, ưu tiên git revert cho commit đã rời laptop.
- 04Dùng git reset --soft khi chỉ muốn sửa message hoặc tách commit cuối; nội dung staged sẵn sàng.
- 05Sau reset --hard, chạy git reflog trước khi hoảng. Copy hash tip mất và gắn branch nếu vẫn cần.
- 06Giữ restore và switch cho việc hàng ngày; để checkout cho lệnh legacy trong script cũ.
Series
- Phần 1: Git, Phần 1: Commit là snapshot, branch là nhãn dán
- Phần 2: Git, Phần 2: Merge nối hai nhánh snapshot
- Phần 3: Git, Phần 3: Restore, revert và reset đụng các lớp khác nhau
- Phần 4: Git, Phần 4: Remote là một repository khác
- Phần 5: Git, Phần 5: Rebase replay commit lên parent mới
- Phần 6: Git, Phần 6: Team thống nhất lịch sử thế nào
Tiếp theo
Bài kế trong đường đọc: Git, Phần 4: Remote là một repository khác