【Git】从入门到精通(十)—— 撤销与恢复:git checkout与git restore

前言

"改错了怎么办?""add错了怎么办?""提交错了怎么办?"------这是Git初学者最常问的问题。撤销与恢复是版本控制最核心的价值之一,也是最容易出事的地方(撤销错了可能丢失工作成果)。

本篇将系统讲解 git checkout 和 git restore 两个命令,帮你应对各种"后悔"场景。其中 git restore 是Git 2.23版本引入的新命令,专门用来替代 git checkout 中容易混淆的"恢复文件"功能。


一、什么时候需要撤销

开发中常见的"后悔"场景:

场景 描述 解决命令
改错了文件 工作区改了一堆,想恢复原样 git restore / git checkout
add错了 暂存了不该暂存的文件 git restore --staged
提交错了 commit了不该commit的 git reset HEAD~1
提交信息写错 想修改提交信息 git commit --amend
已经push了 远程已经有了错误提交 git revert
删错了文件 误删了文件,想恢复 git restore --source
想回到某个旧版本 想查看或恢复历史某个版本 git checkout

本篇重点讲前几种场景,git reset 和 git revert 将在后续篇章详解。


二、git checkout 的多面性

git checkout 是Git中功能最多、也最容易混淆的命令。它能做这些事:

  1. 切换分支 :git checkout main
  2. 创建并切换分支 :git checkout -b feature
  3. 恢复工作区文件 :git checkout -- <file>
  4. 切换到某个提交 :git checkout <commit>(分离头指针)
  5. 从某提交恢复文件 :git checkout <commit> -- <file>

问题在于:切换分支 和恢复文件用同一个命令,语义完全不同,初学者很容易搞混。比如:

复制代码
git checkout main          # 切换到main分支
git checkout -- main.js    # 恢复main.js文件(--分隔)

如果没有 --,git checkout main 到底是分支还是文件?Git会优先当作分支名,但如果恰好有个文件叫 main,就会产生歧义。

正因为这种混淆,Git 2.23引入了两个新命令:

  • git switch:专门切换分支
  • git restore:专门恢复文件

本篇重点讲 git restore,同时保留 git checkout 的讲解(因为大量老项目和教程仍在使用)。


三、恢复工作区文件

3.1 git checkout -- :恢复单个文件到暂存区状态

场景:你修改了 app.js,改了一堆发现不对,想恢复到修改前的状态。

复制代码
# 修改了app.js
echo "错误的修改" >> src/app.js

# 查看状态
git status

输出:

复制代码
修改尚未加入提交:
        修改:    src/app.js

恢复:

复制代码
# 恢复app.js到暂存区的状态(即上次git add时的状态)
git checkout -- src/app.js

执行后 src/app.js 回到了暂存区的版本,刚才的修改丢失了。

3.2 git checkout -- .:恢复所有文件

复制代码
# 恢复当前目录下所有被修改的文件
git checkout -- .

# 恢复整个项目
git checkout -- *

警告:这会丢弃所有未暂存的修改,无法恢复!

3.3 注意:会丢失未保存的修改!

git checkout -- <file> 是危险操作,执行后工作区的修改直接消失,无法通过Git找回(因为还没add,不在版本库里)。

安全做法 :如果不确定要不要丢弃,先用 git diff 看看改了什么:

复制代码
git diff src/app.js   # 先看看改了什么
git stash             # 或者先stash保存起来
git checkout -- src/app.js  # 确认后再恢复

四、git restore(Git 2.23+ 新命令)

git restore 专门用于恢复文件,语义清晰,推荐使用。

4.1 git restore :恢复工作区文件

等价于 git checkout -- <file>:

复制代码
# 恢复app.js到暂存区的状态
git restore src/app.js

# 恢复多个文件
git restore src/app.js src/router.js

# 恢复所有文件
git restore .

4.2 git restore --staged :取消暂存

场景:git add 了一个不该add的文件,想把它从暂存区撤回工作区(不丢失修改)。

复制代码
# 误add了
git add src/config.js

# 取消暂存(修改保留在工作区)
git restore --staged src/config.js

这等价于老命令 git reset HEAD src/config.js。

查看效果:

复制代码
git add src/config.js
git status

输出:

复制代码
要提交的变更:
        修改:    src/config.js

git restore --staged src/config.js
git status

输出:

复制代码
修改尚未加入提交:
        修改:    src/config.js

文件修改还在,只是从暂存区撤回了工作区。

4.3 git restore --source= :恢复到指定提交的版本

场景:你想把某个文件恢复到某个历史提交时的状态。

复制代码
# 恢复app.js到上一次提交时的状态
git restore --source=HEAD~1 src/app.js

# 恢复到指定commit
git restore --source=8f3a2d1 src/app.js

# 恢复到某个tag
git restore --source=v1.0 src/app.js

4.4 git restore --staged --source=HEAD :同时取消暂存并恢复

复制代码
# 既取消暂存,又恢复工作区文件到HEAD状态
git restore --staged --source=HEAD src/app.js

这相当于:

复制代码
git restore --staged src/app.js   # 先取消暂存
git restore src/app.js            # 再恢复工作区

4.5 --worktree 和 --staged 选项

git restore 默认操作工作区(--worktree),可以显式指定:

复制代码
# 只恢复工作区(默认)
git restore --worktree src/app.js

# 只取消暂存
git restore --staged src/app.js

# 两者都做
git restore --worktree --staged src/app.js

五、各种撤销场景

5.1 场景1:修改了文件还没add

症状:工作区改了一堆,想丢弃修改。

复制代码
# 当前状态
echo "不要的修改" >> src/app.js
git status

输出:

复制代码
修改尚未加入提交:
        修改:    src/app.js

解决:

复制代码
# 方法1:用restore(推荐)
git restore src/app.js

# 方法2:用checkout
git checkout -- src/app.js

验证:

复制代码
git status
# 无文件要提交,干净的工作区

5.2 场景2:add了但还没commit

症状:暂存了文件,但不想提交了。

复制代码
git add src/app.js
git status

输出:

复制代码
要提交的变更:
        修改:    src/app.js

解决:

复制代码
# 方法1:用restore(推荐)
git restore --staged src/app.js

# 方法2:用reset
git reset HEAD src/app.js

验证:

复制代码
git status

输出:

复制代码
修改尚未加入提交:
        修改:    src/app.js

修改还在工作区,只是从暂存区撤回了。

如果想连工作区的修改也丢弃:

复制代码
git restore --staged src/app.js   # 先取消暂存
git restore src/app.js            # 再恢复工作区
# 或一步到位
git restore --staged --worktree src/app.js

5.3 场景3:commit了但还没push

症状:提交了,但发现提交错了,想撤销提交(保留修改)。

复制代码
git commit -m "错误的提交"

解决(下篇详讲,这里先给出命令):

复制代码
# 撤销提交,保留修改在暂存区
git reset --soft HEAD~1

# 撤销提交,保留修改在工作区
git reset --mixed HEAD~1  # 默认mixed
git reset HEAD~1

# 撤销提交,丢弃修改(危险!)
git reset --hard HEAD~1

5.4 场景4:已经push了

症状:提交并推送了,发现错了。

解决 :用 git revert 创建一个"反向提交"(后面详讲):

复制代码
# 撤销最近一次提交(创建一个新提交来抵消它)
git revert HEAD

# 撤销指定提交
git revert 8f3a2d1

不要用 git reset --hard + git push --force,这会改写历史,影响其他协作者。

5.5 场景对比表

场景 命令 修改是否保留
改了没add git restore 丢弃
add了没commit git restore --staged 保留在工作区
commit了没push git reset --soft HEAD~1 保留在暂存区
commit了没push git reset HEAD~1 保留在工作区
commit了没push git reset --hard HEAD~1 丢弃
已经push git revert HEAD 创建反向提交

六、git checkout vs git restore vs git reset 对比表

功能 git checkout git restore git reset
切换分支 git checkout ❌(用git switch) ❌
创建分支 git checkout -b ❌(用git switch -c) ❌
恢复工作区文件 git checkout -- git restore ❌
取消暂存 ❌ git restore --staged git reset HEAD
恢复到指定提交 git checkout -- git restore --source ❌
移动HEAD指针 git checkout ❌ git reset
重置到某提交(丢弃) ❌ ❌ git reset --hard

记忆要点:

  • git switch:切换分支(新)
  • git restore:恢复文件(新)
  • git reset:移动HEAD、重置暂存区
  • git checkout:以上都能做(老,易混淆)

七、恢复删除的文件

7.1 误删了工作区的文件

复制代码
# 误删
rm src/config.js

git status

输出:

复制代码
修改尚未加入提交:
        删除:    src/config.js

恢复:

复制代码
# 方法1:用restore
git restore src/config.js

# 方法2:用checkout
git checkout -- src/config.js

文件回来了!因为暂存区(或HEAD)里还有这个文件。

7.2 用git rm删除了并暂存

复制代码
git rm src/config.js
git status

输出:

复制代码
要提交的变更:
        删除:    src/config.js

恢复:

复制代码
# 先取消暂存
git restore --staged src/config.js
# 再恢复文件
git restore src/config.js

# 或用reset
git reset HEAD src/config.js
git checkout -- src/config.js

7.3 已经commit了删除操作

复制代码
git rm src/config.js
git commit -m "删除config.js"

恢复:

复制代码
# 方法1:从上一次提交恢复
git checkout HEAD~1 -- src/config.js
git restore --source=HEAD~1 src/config.js
git add src/config.js
git commit -m "恢复config.js"

# 方法2:用revert撤销删除提交
git revert HEAD

八、从历史提交中恢复某个文件

8.1 实战场景

场景:不小心删了 config.js,而且已经提交了,3天后才发现。想从3天前的提交恢复这个文件。

步骤1:找到包含该文件的提交

复制代码
# 查看该文件的历史
git log --oneline -- src/config.js

输出:

复制代码
8f3a2d1 删除config.js
2b5c8e9 修改config配置
a1b2c3d 添加config.js

2b5c8e9 是删除前最后一次修改该文件的提交。

步骤2:恢复该文件

复制代码
# 方法1:用checkout
git checkout 2b5c8e9 -- src/config.js

# 方法2:用restore
git restore --source=2b5c8e9 src/config.js

此时文件已恢复到工作区并自动暂存。

步骤3:提交

复制代码
git status

输出:

复制代码
要提交的变更:
        新文件:   src/config.js

git commit -m "恢复误删的config.js"

8.2 恢复某个文件的旧版本(不覆盖当前)

如果你只是想查看某文件的历史版本,而不想覆盖当前文件:

复制代码
# 查看某提交时该文件的内容
git show 2b5c8e9:src/config.js

# 导出到文件
git show 2b5c8e9:src/config.js > config_old.js

8.3 恢复整个项目到某个历史版本

复制代码
# 查看某个历史版本(分离头指针,只读)
git checkout 2b5c8e9

# 看完后回到最新
git checkout main

# 如果想把当前分支重置到那个版本(丢弃之后的提交)
git reset --hard 2b5c8e9

8.4 恢复某个目录下的所有文件

复制代码
# 恢复src/pages目录下所有文件到某提交的状态
git checkout 2b5c8e9 -- src/pages/
git restore --source=2b5c8e9 -- src/pages/

8.5 交互式恢复部分修改

如果你只想恢复文件的部分修改(而不是整个文件),可以用交互式模式:

复制代码
# 交互式选择要恢复的代码块
git checkout -p src/app.js
# 或
git restore -p src/app.js

Git会逐块显示diff,让你选择:

复制代码
diff --git a/src/app.js b/src/app.js
@@ -10,3 +10,4 @@
   init();
   run();
+  console.log('debug');
(1/1) 应用此改动到工作区?[y,n,q,a,d,e,?]? 

选项含义:

  • y:是,恢复这个hunk
  • n:否,跳过
  • q:退出,不再处理
  • a:应用本文件所有hunk
  • d:跳过本文件所有hunk
  • e:手动编辑这个hunk
  • ?:帮助

这在"改了一堆但只想丢弃其中一部分"时非常有用。


九、注意事项

9.1 撤销是危险的

未提交的修改,一旦撤销,无法通过Git找回。

复制代码
git restore src/app.js        # 工作区修改丢失,找不回
git checkout -- src/app.js    # 同上
git reset --hard HEAD~1       # 提交和修改都丢失,找不回

这些命令执行后,修改不在版本库里,git reflog 也救不了(reflog只记录HEAD的移动,不记录工作区内容)。

安全习惯:

  1. 撤销前先 git status 和 git diff 看清楚
  2. 不确定就先 git stash 保存
  3. 重要修改先commit一次(哪怕信息写"临时保存")

9.2 git stash:安全的中转

复制代码
# 暂存当前修改
git stash

# 查看stash列表
git stash list

# 恢复stash
git stash pop

# 恢复指定stash
git stash apply stash@{1}

# 丢弃stash
git stash drop

git stash 是撤销前的"保险绳"。

9.3 已推送的提交不要reset

复制代码
# 错误做法:重写已推送的历史
git reset --hard HEAD~1
git push --force  # 危险!可能覆盖别人的工作

正确做法:用 git revert 创建反向提交。

9.4 amend不要用于已推送的提交

复制代码
git commit --amend -m "新信息"
git push --force  # 同样危险

十、常见问题 FAQ

Q1:git restore 和 git checkout -- 有什么区别?

功能上等价,git restore 是Git 2.23+的新命令,语义更清晰,推荐使用。老版本Git只能用 git checkout --。

Q2:恢复文件后为什么自动暂存了?

git checkout <commit> -- <file> 和 git restore --source=<commit> <file> 会把文件恢复到工作区并暂存。如果不想要暂存:

复制代码
git restore --source=<commit> --staged --worktree <file>
# 或恢复后取消暂存
git restore --staged <file>

Q3:git restore --staged 和 git reset HEAD 一样吗?

是的,都是把文件从暂存区撤回工作区。git restore --staged 是新命令,更直观。

Q4:如何撤销git restore?

如果 git restore 丢弃了工作区修改,无法直接撤销。但如果之前有stash或commit,可以从中恢复。

Q5:git checkout . 和 git restore . 一样吗?

基本一样,都是恢复当前目录所有文件。但 git checkout . 在某些情况下可能行为不同(如遇到分支名和文件名冲突),git restore . 更明确。

Q6:如何只恢复文件的一部分修改?

Git不支持部分恢复,但可以:

  1. 恢复整个文件
  2. 然后手动重新做需要的修改

或用交互式add的反向操作:

复制代码
git checkout -p  # 交互式选择要恢复的hunk

Q7:restore --source=HEAD 和 不加source有什么区别?

复制代码
git restore <file>                    # 恢复到暂存区的状态
git restore --source=HEAD <file>      # 恢复到HEAD的状态

如果暂存区和HEAD不同,结果不同。通常没add时两者相同。

Q8:git switch 和 git checkout 切换分支有什么区别?

git switch 是Git 2.23+引入的,专门用于切换分支,语义更清晰:

复制代码
# 切换分支
git switch main           # 等价于 git checkout main
git switch -c feature     # 等价于 git checkout -b feature

# 切换到上一个分支
git switch -              # 等价于 git checkout -

git switch 不能用来恢复文件,避免了 git checkout 的语义混淆问题。

Q9:如何撤销git stash?

复制代码
# 误stash了,想取回来
git stash pop

# 如果pop后发现有冲突,想撤销pop
# stash内容还在stash列表里(pop失败不会删除)
git stash list
git stash apply stash@{0}

Q10:如何查看某个文件在某个历史版本的内容?

不需要恢复,只想查看:

复制代码
# 查看某提交时文件的内容
git show 2b5c8e9:src/config.js

# 查看某分支中文件的内容
git show feature:src/config.js

# 查看某个tag时文件的内容
git show v1.0:src/config.js

# 导出到文件
git show 2b5c8e9:src/config.js > old_config.js

十一、最佳实践

11.1 撤销前先看

复制代码
git status   # 看状态
git diff     # 看改动内容

11.2 不确定就stash

复制代码
git stash        # 先存起来
# 尝试撤销操作
# 如果发现需要那些修改
git stash pop    # 取回来

11.3 用新命令

Git 2.23+优先使用:

复制代码
git switch <branch>      # 切换分支
git restore <file>       # 恢复文件
git restore --staged <file>  # 取消暂存

比 git checkout 语义清晰,不易出错。

11.4 重要操作前先commit

哪怕只是"临时保存",也先commit一次:

复制代码
git commit -m "WIP: 临时保存"

commit后的内容在版本库里,git reflog 能找回;未commit的工作区修改丢了就是丢了。

11.5 团队协作中慎用force push

复制代码
# 危险
git push --force

# 相对安全(如果远程有别人的提交会拒绝)
git push --force-with-lease

十二、小结

本篇详细讲解了撤销与恢复的两大命令:

知识点 要点
git checkout -- 恢复工作区文件(老命令)
git restore 恢复工作区文件(新命令,推荐)
git restore --staged 取消暂存(从暂存区撤回工作区)
git restore --source 恢复到指定提交的版本
git checkout -- 从历史提交恢复文件
场景1:改了没add git restore
场景2:add了没commit git restore --staged
场景3:commit了没push git reset HEAD~1(下篇详讲)
场景4:已经push git revert(后续详讲)
注意事项 未提交的修改撤销后无法恢复

核心记忆:

复制代码
# 恢复工作区文件(丢弃修改)
git restore <file>
git checkout -- <file>

# 取消暂存(保留修改)
git restore --staged <file>

# 恢复到指定版本
git restore --source=<commit> <file>
git checkout <commit> -- <file>

# 切换分支(新命令)
git switch <branch>
git checkout <branch>

撤销场景速查:

复制代码
改了没add     → git restore <file>
add了没commit → git restore --staged <file>
commit了没push→ git reset HEAD~1
已经push      → git revert HEAD

安全口诀:撤销前先看,不确定先stash,重要修改先commit,已推送的别reset。

下一篇我们将深入学习 git reset------理解 --soft、--mixed、--hard 三种模式的区别,彻底掌握HEAD指针的重置。


作者:全栈陈序员 出处:CSDN 声明:本文为原创文章,转载请注明出处

相关推荐
ss2737 小时前
为什么PostgreSQL适合AI场景?
数据库·人工智能·postgresql
瑞克-桑切斯7 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
全栈陈序员7 小时前
【Git】从入门到精通(五)—— git add:将文件加入暂存区
大数据·git·elasticsearch
z06.8 小时前
MySQL索引
数据库·mysql
考虑考虑8 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
天远Date Lab8 小时前
零信任架构实战:基于天远租凭分期报告构建自动化汽车以租代购免押风控网关
网络·人工智能·架构·自动化
云祺vinchin8 小时前
中小企业备份软件选型从恢复需求到总体成本评估
数据库·数据备份·中小企业备份·容灾备份软件
Zenova EdgeOS9 小时前
Go 工业边缘错误处理实战:用 errors 构建可追溯错误链
开发语言·数据库·golang
KKKlucifer9 小时前
国内数据安全服务的产品智能化转型:大模型赋能下的技术能力重构分析
大数据·数据库·重构