Git 撤销与恢复完全指南(超级详细版)

Git 提供了丰富的撤销和恢复机制,可以应对从简单的"撤销刚才的编辑"到复杂的"找回丢失的提交"等各种场景。理解这些命令能让你在犯错时从容应对,不再害怕误操作。本文将系统介绍 Git 中所有与撤销、恢复相关的命令,每个命令都配有详细说明和实际示例。


一、核心概念与原理

概念 说明
工作区(Working Directory) 你正在编辑的文件系统状态
暂存区(Staging Area / Index) 下一次提交要保存的快照
版本库(Repository) 存储所有提交对象的数据库
HEAD 指向当前分支最新提交的指针
引用(Refs) 指向提交的指针(分支、标签、HEAD、reflog 等)
可访问对象(Reachable) 从某个引用(如分支)可以到达的提交
悬空对象(Dangling) 没有任何引用指向的提交(可通过 reflog 或 fsck 找回)

Git 的撤销哲学

  • Git 通常不会立即删除数据,而是让数据变得"不可达"
  • 即使执行了 reset --hard 或删除了分支,提交仍然存在于 .git/objects 中,直到被垃圾回收
  • reflog 记录了 HEAD 和分支的所有移动,是恢复的重要工具

二、工作区撤销(未暂存的修改)

1. git restore -- 丢弃工作区的修改(推荐,Git 2.23+)

语法

bash 复制代码
git restore <文件>...
git restore --source=<提交> <文件>   # 从指定提交恢复文件

示例

bash 复制代码
# 丢弃 README.md 的所有工作区修改(恢复到暂存区或 HEAD 的状态)
$ git restore README.md

# 丢弃当前目录下所有工作区修改
$ git restore .

# 从某个提交恢复文件到工作区(覆盖当前修改)
$ git restore --source=abc123 README.md

# 从上一个提交恢复文件
$ git restore --source=HEAD~1 src/app.js

# 交互式丢弃部分修改块(精细控制)
$ git restore -p README.md
# 会逐个显示修改块,询问是否丢弃

2. git checkout -- -- 丢弃工作区修改(传统方式,依然有效)

语法

bash 复制代码
git checkout -- <文件>...

示例

bash 复制代码
# 丢弃工作区修改
$ git checkout -- README.md

# 丢弃所有修改
$ git checkout -- .

3. git clean -- 删除未跟踪的文件和目录

语法

bash 复制代码
git clean [选项]

常用选项

选项 说明
-n / --dry-run 预览要删除的文件(安全)
-f / --force 强制删除(必须)
-d 同时删除未跟踪的目录
-x 删除被 .gitignore 忽略的文件
-X 只删除被 .gitignore 忽略的文件
-i / --interactive 交互式删除

示例

bash 复制代码
# 预览会删除哪些未跟踪文件
$ git clean -n

# 删除所有未跟踪文件(不包含目录)
$ git clean -f

# 删除未跟踪文件和目录
$ git clean -fd

# 删除包括被忽略的文件(如 .log, node_modules/)
$ git clean -fdx

# 只删除被 .gitignore 忽略的文件
$ git clean -fX

# 交互式删除(逐个确认)
$ git clean -i

三、暂存区撤销

4. git restore --staged -- 将文件移出暂存区(取消暂存)

语法

bash 复制代码
git restore --staged <文件>...

示例

bash 复制代码
# 取消暂存 README.md(保留工作区修改)
$ git restore --staged README.md

# 取消暂存所有文件
$ git restore --staged .

# 效果等同于旧的:git reset HEAD <file>

5. git reset (mixed) -- 重置暂存区到指定提交

语法

bash 复制代码
git reset [--mixed] <提交>   # --mixed 是默认模式
git reset HEAD <文件>        # 取消暂存特定文件(旧语法)

示例

bash 复制代码
# 取消所有暂存(将暂存区重置为 HEAD 的内容,工作区不变)
$ git reset
# 或
$ git reset HEAD

# 取消特定文件的暂存(旧语法)
$ git reset HEAD README.md

# 将暂存区重置为上一个提交的状态(撤销 add 和 commit)
$ git reset HEAD~1
# 此时修改保留在工作区,但暂存区已清空

6. git rm --cached -- 停止跟踪文件但保留工作区

语法

bash 复制代码
git rm --cached <文件>...

示例

bash 复制代码
# 将文件从 Git 跟踪中移除(但保留工作区),常用于修改 .gitignore 后
$ git rm --cached config.local.json

# 递归移除目录
$ git rm --cached -r logs/

四、提交撤销

7. git commit --amend -- 修改上一次提交

语法

bash 复制代码
git commit --amend [-m "<新消息>"]

说明

  • 用一个新的提交替换上一次提交(不增加提交数量)
  • 可以修改提交消息、添加遗漏的文件、修改内容

示例

bash 复制代码
# 修改上一次提交的消息
$ git commit --amend -m "修正的提交消息"

# 添加遗漏的文件到上一次提交
$ git add forgotten-file.txt
$ git commit --amend --no-edit   # 保持原消息

# 修改上一次提交的内容(编辑文件后)
$ echo "new content" >> file.txt
$ git add file.txt
$ git commit --amend

# 完全重置上一次提交(相当于撤销 commit,保留修改)
$ git commit --amend --no-edit --reset-author   # 重置作者信息

注意 :如果已经推送了提交,--amend 后需要 --force-with-lease 推送,且应避免在公共分支上这样做。

8. git reset -- 移动 HEAD 并可选重置暂存区/工作区

这是最强大的撤销命令,可以"回退"到任意历史状态。

三种模式

模式 HEAD 指向 暂存区 工作区 典型用途
--soft 改变 不变 不变 撤销提交,保留修改在暂存区
--mixed (默认) 改变 重置到目标提交 不变 撤销提交和暂存,保留修改在工作区
--hard 改变 重置到目标提交 重置到目标提交 彻底丢弃提交及所有修改(危险)

示例

bash 复制代码
# 撤销最后一次提交,保留修改在暂存区(相当于取消 commit)
$ git reset --soft HEAD~1
# 现在修改仍在暂存区,可直接再次 commit

# 撤销最后一次提交,并将修改移回工作区(取消 commit 和 add)
$ git reset HEAD~1          # 等价于 --mixed

# 撤销最近 3 次提交,保留修改在工作区
$ git reset --mixed HEAD~3

# 彻底丢弃最近 2 次提交的所有更改(不可恢复,除非有 reflog)
$ git reset --hard HEAD~2

# 将当前分支指向另一个提交(放弃当前分支之后的所有提交)
$ git reset --hard abc123

# 将分支指针移动到某个标签位置
$ git reset --hard v1.0

# 撤销合并(假设合并后未提交任何新内容)
$ git reset --hard ORIG_HEAD   # ORIG_HEAD 保存了合并前的 HEAD

9. git revert -- 通过创建新提交来撤销某次提交(安全)

reset 不同,revert 不删除历史,而是生成一个反向的新提交。这是公共分支上推荐的撤销方式

语法

bash 复制代码
git revert <提交哈希>...

常用选项

选项 说明
-n / --no-commit 只应用更改,不自动提交(可批量 revert)
--continue 解决冲突后继续
--abort 中止 revert
-m <编号> 指定主线(用于 revert 合并提交)
--edit 编辑提交消息

示例

bash 复制代码
# 撤销某次提交(生成一个新的反向提交)
$ git revert abc123
# 会打开编辑器确认撤销消息,然后创建新提交

# 撤销最近一次提交
$ git revert HEAD

# 撤销多个提交(按顺序,从旧到新)
$ git revert abc123 def456

# 撤销一个范围(不包括 abc123,包括 def456)
$ git revert abc123..def456

# 撤销但不自动提交(用于多次 revert 后统一提交)
$ git revert -n abc123 def456
$ git commit -m "撤销 abc123 和 def456"

# 撤销一个合并提交(需要指定主线,通常是 -m 1)
$ git revert -m 1 merge-commit-hash

# 处理冲突后继续
$ git revert abc123
# 解决冲突
$ git add .
$ git revert --continue

# 中止撤销
$ git revert --abort

reset vs revert 对比

特性 reset revert
是否改变历史 否(追加新提交)
适合公共分支 否(会破坏他人历史)
撤销合并提交 可(但复杂) 可(需 -m 参数)
提交数量 减少 增加
可追溯性 丢失历史 完整记录

五、恢复丢失的提交/分支

10. git reflog -- 引用日志(救回误删的数据)

Git 会在本地记录 HEAD 和分支引用的每一次移动。即使执行了 reset --hard 或删除了分支,只要提交未被垃圾回收,就能通过 reflog 找回。

语法

bash 复制代码
git reflog [<分支名>]
git reflog expire ...   # 清理过期的引用(通常不需要手动)

示例

bash 复制代码
# 查看 HEAD 的所有移动历史
$ git reflog
abc123 HEAD@{0}: commit: 添加新功能
def456 HEAD@{1}: commit: 修复bug
789xyz HEAD@{2}: reset: moving to HEAD~2
012abc HEAD@{3}: commit: 初始化项目

# 查看特定分支的 reflog
$ git reflog main

# 查看所有引用(包括远程跟踪分支、stash 等)
$ git reflog --all

# 找回被 reset --hard 丢弃的提交
$ git reflog
# 找到丢弃前的提交哈希(如 abc123)
$ git checkout abc123   # 进入分离 HEAD
# 或基于它创建新分支
$ git branch recovered-branch abc123

# 找回误删的分支(通过 reflog 找到分支最后一次指向的提交)
$ git reflog show feature/login
# 假设显示 abc123 HEAD@{5}: branch: Created from main
$ git checkout -b feature/login-recovered abc123

reflog 的默认过期时间

  • 可访问的引用:90 天
  • 不可访问的引用(如 reset 丢弃的):30 天

11. git fsck -- 文件系统检查,查找悬空对象

当 reflog 也被清理后,可以使用 git fsck 查找所有悬空对象(没有引用的提交)。

语法

bash 复制代码
git fsck [选项]

常用选项

选项 说明
--unreachable 显示所有不可达对象
--lost-found 将悬空对象写入 .git/lost-found/
--no-dangling 不显示悬空对象
--full 完整检查

示例

bash 复制代码
# 检查仓库完整性,显示悬空对象
$ git fsck
Checking object directories: 100% (256/256), done.
dangling commit abc123def...
dangling blob def456ghi...

# 只显示不可达的提交(可能是丢失的提交)
$ git fsck --unreachable --no-reflogs

# 将悬空对象保存到 lost-found 目录
$ git fsck --lost-found

# 查看悬空提交的内容
$ git show abc123
# 如果确认是需要恢复的提交,创建分支
$ git branch recovered abc123

12. git reflog + git cherry-pick -- 选择性恢复

如果只想恢复某个丢失的提交中的部分文件或更改,可以使用 cherry-pick。

bash 复制代码
# 从 reflog 找到丢失的提交
$ git reflog
abc123 HEAD@{2}: commit: 重要功能

# 将该提交的更改应用到当前分支
$ git cherry-pick abc123

六、远程分支撤销与恢复

13. 撤销已推送的提交(安全方式)

方法1:使用 git revert(推荐)

bash 复制代码
# 撤销最近一次推送的提交,并推送新提交
$ git revert HEAD
$ git push origin main

方法2:使用 git reset + --force-with-lease(危险,改变历史)

bash 复制代码
# 本地回退
$ git reset --hard HEAD~1
# 强制推送(会覆盖远程历史)
$ git push --force-with-lease origin main

14. 恢复被删除的远程分支

如果远程分支被删除,但有本地跟踪引用或本地分支,可以重新推送。

bash 复制代码
# 方法1:如果有本地分支
$ git push origin local-branch:remote-branch

# 方法2:如果有本地远程跟踪引用(origin/deleted-branch)
$ git push origin origin/deleted-branch:deleted-branch

# 方法3:从远程仓库的 reflog(如果托管平台提供)
# 联系管理员从服务器备份恢复

15. 撤销 git push --force 覆盖的远程分支

如果不小心用 --force 覆盖了远程分支,可以尝试从本地 reflog 找回。

bash 复制代码
# 1. 在本地找到被覆盖前的提交(通过 reflog)
$ git reflog
abc123 HEAD@{5}: commit: 被覆盖前的最新提交

# 2. 强制推送回去
$ git push --force-with-lease origin abc123:main

16. git push --force-with-lease -- 安全的强制推送

bash 复制代码
# 只有当远程分支与你本地记录的远程分支状态一致时才推送
$ git push --force-with-lease origin main

# 还可以指定预期的远程提交哈希
$ git push --force-with-lease=origin/main:abc123 origin main

七、文件级别的恢复

17. 从历史恢复某个文件的旧版本

方法1:git restore(推荐)

bash 复制代码
# 从某个提交恢复文件到工作区
$ git restore --source=abc123 README.md

# 从上一个提交恢复
$ git restore --source=HEAD~1 src/app.js

方法2:git checkout(传统)

bash 复制代码
# 从某个提交恢复文件
$ git checkout abc123 -- README.md

方法3:git show + 重定向

bash 复制代码
# 查看旧版本内容并重定向到文件
$ git show abc123:README.md > README.md

18. 查看文件的修改历史并选择恢复

bash 复制代码
# 查看文件的所有提交历史
$ git log --oneline README.md

# 查看某个历史版本的内容
$ git show abc123:README.md

# 对比两个版本
$ git diff abc123 def456 -- README.md

八、分支与标签的恢复

19. 恢复已删除的分支

bash 复制代码
# 方法1:通过 reflog
$ git reflog
# 找到分支最后一次指向的提交(如 abc123)
$ git checkout -b recovered-branch abc123

# 方法2:通过 git fsck 查找悬空提交
$ git fsck --unreachable --no-reflogs | grep commit
$ git show abc123
$ git branch recovered abc123

20. 恢复已删除的标签

bash 复制代码
# 如果标签在本地 reflog 中
$ git reflog show refs/tags/v1.0

# 从远程获取标签(如果已推送)
$ git fetch origin tag v1.0

# 从其他克隆获取
$ git fetch <other-remote> tag v1.0

九、暂存区与工作区的批量恢复

21. 放弃所有本地修改(回到上次提交的干净状态)

bash 复制代码
# 丢弃所有已跟踪文件的修改
$ git reset --hard HEAD

# 删除所有未跟踪文件
$ git clean -fd

# 一步到位(组合命令)
$ git reset --hard HEAD && git clean -fd

# 仅丢弃已跟踪文件的修改(保留未跟踪文件)
$ git checkout -- .

22. 将当前工作区恢复到任意提交的状态(不改变提交历史)

bash 复制代码
# 方法1:使用 reset --hard(会移动分支指针)
$ git reset --hard abc123

# 方法2:使用 checkout 进入分离 HEAD(不移动分支)
$ git checkout abc123
# 此时处于 detached HEAD 状态,可以查看/测试,但新提交不会属于任何分支

# 如果需要基于旧状态创建新分支
$ git switch -c new-branch abc123

十、交互式恢复与精细控制

23. git add -p 的反向操作 -- git reset -p

交互式地选择要从暂存区移除的修改块。

bash 复制代码
# 交互式取消暂存
$ git reset -p HEAD
# 类似 add -p,选择要保留在暂存区还是移出

24. git checkout -p -- 交互式丢弃工作区修改

bash 复制代码
# 交互式选择要丢弃的修改块
$ git checkout -p README.md
# 或使用 restore -p
$ git restore -p README.md

25. git stash 的撤销与恢复

bash 复制代码
# 误删 stash(删除后但未执行其他操作)
$ git stash list
# 如果刚执行了 stash drop,可以通过 reflog 找回
$ git fsck --unreachable | grep commit
$ git show abc123
$ git stash apply abc123

# 清空所有 stash 后恢复(较困难,需从 reflog 找)
$ git reflog show refs/stash

十一、撤销合并操作

26. 撤销尚未提交的合并

bash 复制代码
# 合并后出现冲突,想放弃合并
$ git merge --abort

# 如果合并已经完成但未提交,也可以
$ git reset --hard HEAD

27. 撤销已经提交的合并

方法1:使用 git revert(安全,推荐)

bash 复制代码
# 撤销合并提交(需要指定主线)
$ git revert -m 1 merge-commit-hash
# -m 1 表示保留第一个父提交(通常是合并前的当前分支)

方法2:使用 git reset(改变历史,仅限私有分支)

bash 复制代码
# 回到合并前的状态
$ git reset --hard HEAD~1
$ git push --force-with-lease

28. 撤销 git pull 的效果

bash 复制代码
# 如果 pull 造成了合并冲突或不想保留合并
$ git reset --hard ORIG_HEAD

# 如果 pull 使用了 rebase
$ git reflog
# 找到 pull 前的 HEAD
$ git reset --hard HEAD@{1}

十二、恢复误修改的配置

29. 恢复 Git 配置

bash 复制代码
# 查看全局配置
$ git config --global --list

# 删除某个配置项
$ git config --global --unset user.name

# 重新设置
$ git config --global user.name "正确的名字"

# 恢复默认配置(删除后重新添加)
$ git config --global --unset core.editor

十三、高级恢复技巧

30. 从 git gc 后的仓库恢复(数据恢复)

git gc 会打包和删除悬空对象。如果执行后丢失了数据,恢复难度极高,但仍有可能。

bash 复制代码
# 1. 立即停止所有 Git 操作,防止进一步覆盖
# 2. 使用文件恢复工具(如 photorec)扫描 .git/objects 目录
# 3. 或者从备份/远程仓库恢复

31. 恢复被 git clean -fdx 删除的文件

git clean 删除的文件不经过暂存区,无法通过 Git 直接恢复。需要依赖:

  • 文件系统的回收站
  • 编辑器自动保存的备份
  • IDE 本地历史
  • 外部备份

32. 使用 git filter-branch / git rebase -i 撤销批量更改(重写历史)

bash 复制代码
# 使用交互式变基删除或修改历史中的提交
$ git rebase -i HEAD~10
# 将不需要的提交改为 drop

# 或者使用 filter-branch 重写整个历史(谨慎使用)
$ git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch secrets.txt" \
  --prune-empty --tag-name-filter cat -- --all

十四、撤销恢复命令速查表

场景 命令
丢弃工作区单个文件修改 git restore <file>
丢弃工作区所有修改 git restore .
取消暂存单个文件 git restore --staged <file>
取消所有暂存 git restore --staged .
删除未跟踪文件(预览) git clean -n
删除未跟踪文件 git clean -fd
删除包括忽略文件 git clean -fdx
修改上一次提交消息 git commit --amend -m "新消息"
添加上次遗漏的文件 git add <file> + git commit --amend --no-edit
撤销提交(保留修改在暂存区) git reset --soft HEAD~1
撤销提交(保留修改在工作区) git reset HEAD~1
彻底撤销提交(丢弃修改) git reset --hard HEAD~1
撤销某次提交(生成新提交) git revert <commit>
撤销合并提交 git revert -m 1 <merge-commit>
查看操作历史 git reflog
找回丢失的提交 git reflog + git checkout <hash>
查找悬空对象 git fsck --unreachable
从历史恢复文件 git restore --source=<commit> <file>
放弃所有本地修改 git reset --hard HEAD && git clean -fd
中止正在进行的合并 git merge --abort
中止正在进行的变基 git rebase --abort
中止正在进行的 cherry-pick git cherry-pick --abort
撤销已推送的提交(安全) git revert HEAD + git push
撤销已推送的提交(强制) git reset --hard HEAD~1 + git push --force-with-lease
恢复误删的本地分支 git reflog + git branch <name> <hash>
恢复误删的标签 git fetch origin tag <tagname>

十五、常见问题与解决方案

问题 原因 解决方案
执行 reset --hard 后丢失了未提交的修改 工作区修改被覆盖 无法恢复(除非有编辑器备份)
执行 reset --hard 后丢失了提交 HEAD 移动了 使用 git reflog 找回
误删分支 git branch -D git reflog 找到分支最后一次指向的提交
误删 stash git stash drop git fsck --unreachable 查找悬空提交
强制推送覆盖了远程分支 git push --force 从本地 reflog 恢复后重新推送
合并冲突想放弃合并 合并未完成 git merge --abort
变基冲突想放弃变基 变基未完成 git rebase --abort
误提交了敏感信息 文件被提交 使用 git filter-branch 或 BFG Repo-Cleaner 重写历史
git clean 删除了重要文件 未跟踪文件被删除 从系统回收站或备份恢复
执行 git gc 后丢失了对象 垃圾回收删除了悬空对象 很难恢复,依赖备份

十六、安全建议与最佳实践

  1. 养成使用 --dry-run 的习惯

    在执行破坏性命令(如 reset --hardcleanpush --force)前,先用预览模式查看影响。

  2. 多用 revert 代替 reset

    在公共分支上,使用 revert 而不是 reset 来撤销提交,避免影响协作者。

  3. 谨慎使用 --force

    使用 --force-with-lease 代替 --force,可以防止覆盖他人推送的提交。

  4. 定期备份

    除了远程仓库,定期将 .git 目录备份到安全位置。

  5. 善用 reflog
    reflog 是你的"后悔药",在遇到问题时首先查看它。

  6. 提交前检查
    git statusgit diff --cached 检查即将提交的内容,避免错误提交。

  7. 使用 git stash 临时保存

    在尝试危险操作前,先用 git stash 保存当前状态。

  8. 配置自动推送保护

    bash 复制代码
    # 禁止强制推送到 main 分支(通过 Git 钩子或服务器端规则)
  9. 学习 git refloggit fsck

    这两条命令是数据恢复的核心,熟悉它们可以让你在多数情况下找回丢失的数据。


结语

Git 的撤销与恢复机制非常强大,只要理解了 Git 的数据模型(提交不可变、数据在删除后仍存在一段时间),你就能从容应对各种误操作。记住:

  • git restore:丢弃工作区/暂存区修改
  • git reset:移动分支指针,可以撤销提交(改变历史)
  • git revert:安全地撤销提交(追加新提交)
  • git reflog:找回丢失的提交和分支
  • git fsck:查找悬空对象

在实际工作中,遇到不确定的情况,先备份,再尝试。多练习这些命令,你会逐渐建立起对 Git 的信心,不再害怕犯错。

相关推荐
VBsemi-专注于MOSFET研发定制2 小时前
共享汽车功率管理器件选型实战:空间、效率与可靠性的平衡之道
大数据·汽车
tonydf2 小时前
日志模块该如何设计
后端·elasticsearch
golang学习记2 小时前
Git 2.54 来了,这个新命令让我终于敢重写历史了
git·后端
其实防守也摸鱼2 小时前
AWVS下载和安装保姆级教程
linux·服务器·git
前端若水2 小时前
Git 可以做的所有操作(完整分类)
大数据·git·elasticsearch
Elasticsearch2 小时前
我们如何构建 Elasticsearch simdvec,使向量搜索成为世界上最快之一
elasticsearch
财经资讯数据_灵砚智能2 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年4月22日
大数据·人工智能·python·信息可视化·自然语言处理
叹一曲当时只道是寻常2 小时前
Reference 工具安装与使用教程:一条命令管理 Git 仓库引用与知识沉淀
人工智能·git·ai·开源·github
武子康2 小时前
大数据-276 Spark MLib-深入理解Bagging与Boosting:集成学习核心算法对比与GBDT实战
大数据·后端·spark