您的位置:首页 >Git之reset、restore、revert与reflog的使用及说明
发布于2026-07-30 阅读(0)
扫一扫,手机访问
在 Git 的使用过程中,很多人都遇到过这样的事:git add 加错了文件,刚提交就发现漏改了一处,已经推送的代码需要撤回,或者误执行了 git reset --hard。这些情况,显然不能用同一条命令来处理。

关键是要先确认改动在哪一层——工作区、暂存区、本地提交,还是已经推送给他人?然后从 restore、reset、revert 或 reflog 中选出合适的工具,这样就能把影响控制在期望的范围内。
本文以 Windows PowerShell 为例,但命令同样适用于 Git Bash、macOS 和 Linux。示例中的分支名、提交哈希和文件路径,请替换成你自己的实际值。
多数误操作都发生在下面这条链路的某一个位置:
工作区(正在编辑的文件)
|
| git add
v
暂存区(下一次提交的候选内容)
|
| git commit
v
本地提交历史(当前分支 HEAD)
|
| git push
v
远程仓库 / 其他协作者已经看到的历史
不同命令的职责并不相同:
| 命令 | 主要处理对象 | 会不会改写当前分支历史 | 典型用途 |
|---|---|---|---|
| git restore | 工作区或暂存区中的文件内容 | 否 | 取消编辑、取消暂存、取回某个旧版本的文件 |
| git reset | 暂存区或当前分支的 HEAD | 取决于用法 | 撤销尚未共享的本地提交,或取消暂存 |
| git revert | 已存在的一个或多个提交 | 否,会新增提交 | 撤回已经推送、可能被他人拉取的改动 |
| git reflog | 本地引用移动记录 | 否 | 找回被 reset、rebase、切换分支前指向的提交 |
一句话判断:
restore;reset;revert 生成一个反向提交;HEAD 移错了,先用 reflog 找回旧位置,再决定怎么恢复。Git 官方也明确区分三者的职责:restore 恢复文件或暂存区,reset 会移动分支顶端,revert 则创建新的反向提交。官方文档中 Reset, restore and revert 的说明可以作为判断依据。
不要在看到当前状态之前就直接输入破坏性命令。在项目根目录执行:
git status git diff git diff --cached git log --oneline --decorate -n 8
这四条命令分别回答四个问题:
| 要确认的事 | 对应命令 | 为什么重要 |
|---|---|---|
| 工作区是否还有未提交修改 | git status | 避免把别的任务一起覆盖 |
| 还没暂存的内容是什么 | git diff | 判断能否安全恢复工作区 |
| 已暂存、准备提交的内容是什么 | git diff --cached | 判断是否只是 git add 加错 |
| 当前分支最近有哪些提交 | git log --oneline --decorate -n 8 | 确认目标提交和 HEAD~1 指向 |
本文使用的 git restore 适用于较新的 Git 版本。执行 git --version 或 git restore -h 即可确认本机支持;如果团队仍在使用很旧的 Git,建议先升级 Git for Windows,再统一使用本文的写法,避免混用语义较宽的旧版 git checkout。
如果本地分支已经设置了上游,还可以确认哪些提交尚未推送:
git log --oneline "@{u}..HEAD"
有输出,表示这些提交只在本地分支;没有输出,表示本地 HEAD 没有领先上游。
没有设置上游时,这条命令会失败,此时需要根据 git status、远程平台的提交记录和团队协作情况判断,不能把失败误解为“肯定没有推送”。
重要:复制提交哈希前,先阅读 git show 。不要只凭提交标题判断内容。
git show --stat --oneline a1b2c3d git show a1b2c3d
例如你原本只想提交 src/api/user.ts,却把本地配置 config/local.json 一起加入了暂存区。
先确认暂存区:
git status git diff --cached -- config/local.json
确认无误后,取消这个文件的暂存:
git restore --staged -- config/local.json
执行后的结果:
工作区:保留 config/local.json 当前内容 暂存区:恢复为 HEAD 中的版本 提交历史:不变
因此它适合“文件还要继续留在本地,只是不该进入这次提交”的情况。随后再次检查:
git status git diff --cached
旧写法 git reset HEAD -- config/local.json 也能取消暂存;git restore --staged 的意图更直接。
Git 官方文档说明,不带移动 HEAD 语义的路径形式 git reset 只更新暂存区,等价于 git restore --staged 。git-reset
# 取消多个指定文件的暂存 git restore --staged -- config/local.json README.md # 取消当前项目所有已暂存修改;工作区内容仍会保留 git restore --staged :/
:/ 表示仓库顶层。执行“全部”版本前务必先运行 git diff --cached,否则很容易把本来准备提交的内容一起从暂存区移走。
如果某个已跟踪文件被改乱了,且确认不要当前未暂存的改动,可以恢复它:
git restore -- src/components/UserCard.tsx
默认情况下,git restore <文件> 用暂存区的版本覆盖工作区。因此它会丢弃这个文件未暂存的编辑,但不会移动 HEAD,也不会影响其他文件。
执行前最好先看差异:
git diff -- src/components/UserCard.tsx
如果这段编辑可能以后还需要,先导出补丁到仓库外的安全位置,再恢复:
git diff --binary -- src/components/UserCard.tsx > ..UserCard-before-restore.patch git restore -- src/components/UserCard.tsx
下面命令将暂存区和工作区都恢复到当前 HEAD,影响范围比上一条大:
git restore --source=HEAD --staged --worktree -- src/components/UserCard.tsx
先做 git diff 和 git diff --cached,确认该文件的两层修改都可以丢弃后再使用。restore 的默认来源会随 --staged 是否出现而变化;指定 --source=HEAD 能明确告诉 Git 从当前提交取内容。git-restore
git restore 只能恢复 Git 已跟踪的文件。新建但未跟踪的文件不会被它删除;清理未跟踪文件应单独使用 git clean,那是另一类更危险的操作,本文不建议把两者混在一次命令里。
有时问题只在一个文件:例如最新版本删掉了一段配置,而你要把它恢复成两次提交前的内容。
此时不需要 reset 整个分支:
# 先确认旧版本中该文件的内容 git show HEAD~2:src/config/app.ts # 将工作区中的该文件恢复为 HEAD~2 的版本 git restore --source=HEAD~2 -- src/config/app.ts # 检查并决定是否提交 git diff -- src/config/app.ts git add src/config/app.ts git commit -m "fix: restore app configuration"
这条操作只把 HEAD~2 中 src/config/app.ts 的内容取到工作区,分支顶端和其他文件都不动。
若目标提交中不存在该文件,Git 会为了匹配来源而删除工作区中的对应已跟踪路径,所以执行前必须确认来源和文件名。
如果希望拿旧版本直接进入暂存区,而不是先放到工作区,可以加 --staged:
git restore --source=HEAD~2 --staged -- src/config/app.ts git diff --cached -- src/config/app.ts
这条命令只会更新暂存区,工作区仍保留原内容;如果确认两个位置都应同步成旧版本,才显式写出 --staged --worktree:
git restore --source=HEAD~2 --staged --worktree -- src/config/app.ts
这是 git reset 最常见、也最容易被误用的场景。假设当前分支是 feature/profile,最新提交 HEAD 还没有推送,提交内容或提交信息需要重新整理。
先确认最新提交确实只在本地:
git status git log --oneline --decorate -n 3 git show --stat HEAD
如果只是漏 git add 了一个文件、提交说明写错了,且该提交尚未推送,通常不必 reset:
# 修改文件后暂存 git add src/profile/profile.service.ts # 修改最近一次提交;会进入编辑器修改提交说明 git commit --amend
只改提交说明时:
git commit --amend -m "feat: add profile validation"
--amend 同样会改写最新本地提交,因此不要对已经被其他人基于其开发的共享提交直接使用。
git reset --soft HEAD~1
执行结果:
工作区:保留最新提交中的内容 暂存区:保留最新提交中的内容,仍处于已暂存状态 HEAD:退回到上一个提交
适合“这次提交需要拆分、删掉其中一个文件后重新提交”的情况。
接着可以只取消不该提交的文件:
git restore --staged -- config/local.json git diff --cached git commit -m "feat: add profile validation"
git reset HEAD~1
--mixed 是 git reset 的默认模式。它会把 HEAD 和暂存区回到前一个提交,但保留工作区文件内容。
适合你想重新选择哪些文件进入下一次提交的情况:
git reset HEAD~1 git status git add src/profile/profile.service.ts git add src/profile/profile.service.test.ts git commit -m "feat: add profile validation"
git reset --hard HEAD~1
这会让当前分支、暂存区和工作区都匹配 HEAD~1。它会覆盖已跟踪文件的本地修改;在写入受跟踪路径时,也可能删除阻碍写入的未跟踪文件。不要把它当成“清理一下”的快捷键。
在执行前,至少满足以下三个条件:
git status 已确认没有其他要保留的工作区改动;git show HEAD 已确认最新提交整体都不要;reset 的 --soft、--mixed、--hard 区别,本质上是它是否同时更新暂存区和工作区。详细状态表见 git-reset 官方文档。
假设 a1b2c3d 已经推送到 main、develop,或其他人可能已经拉取的分支。此时不要直接 reset 后强推来“删历史”,通常应该新增一个提交来抵消它:
# 先查看将要撤回的提交 git show --stat a1b2c3d # 创建一个反向提交;--no-edit 使用 Git 生成的默认提交说明 git revert --no-edit a1b2c3d # 验证新提交与工作区 git status git log --oneline --decorate -n 4 git show --stat HEAD # 验证通过后再推送 git push
提交历史会像这样保留下来:
P -- Q -- R -- S
^ ^
| |
错误提交 revert R 生成的新提交
revert 不会删除 R,而是创建 S 来反转 R 的效果。其他人同步后能看清“哪次改动出了问题、何时被撤回”,并且不会因历史被重写而需要处理非快进推送。
后续提交已经改过同一段代码时,revert 可能冲突。处理顺序如下:
# 1. 查看冲突文件 git status # 2. 手工解决冲突并保存文件后,标记为已解决 git add src/affected-file.ts # 3. 继续完成 revert git revert --continue
如果发现撤回目标选错了,或当前冲突不应继续处理:
git revert --abort
git revert 要求工作区干净,因为它会把反向补丁应用到当前状态。官方文档也提供了 --continue、--abort 等序列控制命令。git-revert
对 merge commit 执行 revert 通常需要指定主线父提交,例如 git revert -m 1 。这里的 1 不是“第一个看起来对的数字”,而是你要保留哪一侧历史的选择;选错会影响以后再次合并的结果。
除非已经理解该合并的两条父线和后续合并计划,否则先在临时分支演练,或由熟悉该分支历史的人处理。Git 官方对 -m 的语义有专门说明:git-revert。
误执行 git reset --hard HEAD~1 后,提交在普通 git log 中可能不见了,但 Git 往往还记录着 HEAD 曾经指向的位置。此时先停止继续提交、拉取、清理或运行垃圾回收,立刻查看 reflog:
git reflog --date=local
可能看到:
7f8e9d0 HEAD@{0}: reset: moving to HEAD~1
a1b2c3d HEAD@{1}: commit: feat: add profile validation
4c5d6e7 HEAD@{2}: commit: chore: update dependencies
这里 a1b2c3d 就是 reset 前的提交。不要急着直接在当前分支上再 reset,先建一条救援分支,把这个对象固定下来:
git switch -c rescue-before-reset a1b2c3d git log --oneline --decorate -n 3
现在提交已经由 rescue-before-reset 引用保护。接下来可以比较它与原分支:
git diff --stat feature/profile..rescue-before-reset git diff feature/profile..rescue-before-reset
确认需要整个恢复时,再切回原分支并恢复到救援分支:
git switch feature/profile git reset --hard rescue-before-reset
如果只想找回其中一个文件,使用更小范围的恢复即可:
git restore --source=rescue-before-reset -- src/profile/profile.service.ts git diff -- src/profile/profile.service.ts
reflog 是本地引用变动记录,不是远程备份,也不会永久保留。默认过期策略和垃圾回收可能最终清掉不再可达的对象,所以发现误操作后应尽快创建救援分支或打标签。git-reflog
先纠正一个容易混淆的点:如果远程分支包含你本地没有的新提交,普通的 git push 通常会因非快进更新被拒绝,不能直接覆盖远程代码。真正造成覆盖的,通常是下面几种操作:
git push --force 或 git push -f;+ 的强制推送 refspec;如果确认自己没有输入 --force,但远程历史确实被改写,仍按本节流程恢复,同时保留终端输出并检查 IDE 的推送选项、分支保护规则、remote. 配置和自动化脚本。
这类事故的目标不是马上再 push 一次,而是先找到“远程分支被覆盖前的正确提交”,把它固定下来,再由一个人有条件地恢复远程引用。
先在团队里说明受影响的远程和分支,例如 origin/main。在确认恢复方案前,不要继续执行以下操作:
# 以下命令此时都不要执行 git push --force git push -f git reset --hard git gc git reflog expire
也不要让所有人先 pull 后各自处理。pull 只能同步当前已经被覆盖的远程状态,并不能把丢失的远程分支指针恢复回来;多人同时 push 还会增加判断难度。
先只读查询远程分支当前指向的提交。下面输出的完整哈希记为“错误提交” bad:
git ls-remote origin refs/heads/main
然后从下面几个位置寻找覆盖前的“正确提交” good:
# 当前仓库里所有本地、远程跟踪和救援分支 git log --oneline --decorate --all -n 40 # 曾经观察过 origin/main 的机器,通常值得优先检查 git reflog show --date=local origin/main # 执行强推者或协作者本地 main 的移动记录 git reflog show --date=local main
最可靠的来源通常是:
| 来源 | 能否作为恢复依据 |
|---|---|
| 某位协作者尚未同步事故前远程的本地 main | 可以,先检查提交内容 |
| origin/main 或本地分支的 reflog | 可以,但取决于该机器曾保存过该引用记录 |
| 代码托管平台的提交图、审计日志、分支恢复功能 | 可以,必要时请仓库管理员协助 |
| CI 工作目录、镜像仓库、另一台开发机 | 可以,确认提交哈希和代码内容后使用 |
找到候选哈希后,不要只看提交标题,必须核对内容:
git show --stat a1b2c3d git show a1b2c3d
假设 a1b2c3d 是确认无误的 good 提交,先把它推到一个全新的远程救援分支:
git switch -c rescue/main-before-overwrite a1b2c3d git push -u origin rescue/main-before-overwrite
这一步不会修改 origin/main,但会让正确代码重新拥有一个明确的远程引用。随后可以在托管平台上审查 rescue/main-before-overwrite,并让相关协作者确认它确实是覆盖前的预期状态。
如果一个本地 clone 中也找不到候选提交,不要运行清理命令。尽快检查其他开发机、CI、镜像和托管平台日志;Git 对不可达对象的保留不是永久承诺,仓库管理员可能需要从服务端备份或审计记录恢复。
只有在以下条件全部满足时,才应恢复远程 main:
good 是正确提交;main 仍指向刚才记录的 bad,没有新的合法提交进入;使用 PowerShell 变量保存两个完整提交哈希。下面的 bad 必须来自第 2 步的 git ls-remote 输出:
$good = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678" # 替换为覆盖前正确的完整提交哈希
$bad = "7f8e9d0a1b2c3d4e5f60718293a4b5c6d7e8f901" # 替换为当前远程 main 的完整提交哈希
# 仅当远程 main 仍等于 $bad 时,才允许把它恢复到 $good
git push "--force-with-lease=refs/heads/main:$bad" origin "${good}:refs/heads/main"
这里故意不使用裸 git push --force。--force-with-lease=refs/heads/main: 会要求远程 main 仍等于指定的期望值,否则推送失败,不会覆盖事故发生后其他人新增的提交。Git 官方文档明确说明,带明确期望值的 lease 会在远程引用不是该值时拒绝更新。git-push
如果分支保护规则拒绝这条命令,不要通过临时关闭所有保护来让每个人重试。应由管理员按平台流程恢复分支,或在确认范围后临时授予一位负责人恢复权限。普通 PR 有时能补回缺失的改动,但它不能可靠地把远程分支指针恢复到事故前状态,也未必能移除误推入的内容。
远程分支恢复后,每位协作者应先保护自己尚未提交的工作和本地提交,再同步。下面以本地 main 为例:
# 先检查;有未提交修改时,先提交、stash 或导出补丁 git status # 为当前本地 main 建一个提交级备份分支,防止本地提交丢失 git branch backup/main-before-remote-recovery main # 获取恢复后的远程状态,再让本地 main 精确对齐 git fetch origin git switch main git reset --hard origin/main
最后验证团队看到的是同一条远程提交:
git log --oneline --decorate -n 5 git status
main、develop、发布分支开启保护,禁止或严格限制 force push;git fetch origin,再检查 git status 和提交图;--force;--force-with-lease 视为免沟通开关,它只能降低覆盖新提交的概率,不能替代分支保护和协作约定。技术上可以,但是否应该做取决于分支是否共享:
| 分支情况 | 推荐做法 | 原因 |
|---|---|---|
| 个人本地分支,尚未推送 | reset | 只影响自己 |
| 个人远程功能分支,确定没有协作者基于它开发 | 沟通后可 reset,再用指定目标分支的 git push --force-with-lease | 可以整理历史,但仍要防止覆盖他人的新推送 |
| main、develop、发布分支或多人协作分支 | revert | 保持公开历史稳定,减少其他人的恢复成本 |
| 已进入发布、部署或被其他提交依赖 | revert,并补充问题说明 | 可追溯、可审计,回滚范围更清楚 |
--force-with-lease 比裸 --force 多一层远程状态检查:远程分支如果不符合期望值,它会拒绝覆盖。恢复事故时,优先使用上一节的 --force-with-lease=:<完整哈希> 形式;不带期望值的简写依赖本地远程跟踪引用,在 IDE 或其他工具后台自动 fetch 时,保护效果可能被削弱。它不是“可以不沟通”的许可证,尤其不能绕过受保护分支规则。
下面演示“刚提交的内容里混入了一个调试文件,确认还没推送”的完整处理方式。选择 --soft 是为了先保留所有内容在暂存区,再按文件重新整理:
# 1. 确认当前位置和最新提交内容 git status git log --oneline --decorate -n 3 git show --stat HEAD # 2. 撤销最近一次本地提交,但保留全部内容为已暂存 git reset --soft HEAD~1 # 3. 将误加入的调试文件移出暂存区;文件仍保留在工作区 git restore --staged -- src/debug/request-log.ts # 4. 再次确认这次真正要提交的内容 git status git diff --cached # 5. 按需删除、修改或保留调试文件,然后重新提交 git commit -m "feat: add request validation" # 6. 先检查提交,再推送 git show --stat HEAD git push
如果第 2 步后发现不确定哪些内容应该保留,不要继续 git add .。先用 git diff --cached 逐项检查,或只写明确的文件路径。
这样可以避免构建产物、本地配置、调试输出再次混入提交。
| 你遇到的情况 | 首选命令 | 是否改写历史 |
|---|---|---|
| git add 加错文件,文件内容要保留 | git restore --staged -- <文件> | 否 |
| 丢弃一个文件尚未暂存的编辑 | git restore -- <文件> | 否 |
| 取回某个文件的历史版本 | git restore --source=<提交> -- <文件> | 否 |
| 修改最新且未推送的提交 | git commit --amend | 是,仅本地 |
| 撤销最新未推送提交,保留内容为已暂存 | git reset --soft HEAD~1 | 是,仅本地 |
| 撤销最新未推送提交,保留内容为未暂存 | git reset HEAD~1 | 是,仅本地 |
| 确认不要最新本地提交及其改动 | git reset --hard HEAD~1 | 是,仅本地 |
| 撤回已推送、可能被他人使用的提交 | git revert <提交> | 否,新增提交 |
| reset、rebase 后找回旧提交 | git reflog 后创建救援分支 | 否 |
| 强推覆盖远程共享分支 | 先推送救援分支,再用精确 --force-with-lease 恢复 | 是,需团队确认 |
Git 的撤回不是寻找一条“万能后悔药”,而是先确定影响范围:改动还在工作区、暂存区、本地提交,还是已经成为共享历史。
restore,范围最小;reset 或 --amend 整理;revert 留下清晰、可协作的反向记录;HEAD 后先看 reflog,并立即创建救援分支;每次操作前保留 git status、git diff、git show 这三步检查,绝大多数“代码不见了”的事故都能提前避免或及时找回。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8