商城首页欢迎来到中国正版软件门户

您的位置:首页 >Git之reset、restore、revert与reflog的使用及说明

Git之reset、restore、revert与reflog的使用及说明

  发布于2026-07-30 阅读(0)

扫一扫,手机访问

在 Git 的使用过程中,很多人都遇到过这样的事:git add 加错了文件,刚提交就发现漏改了一处,已经推送的代码需要撤回,或者误执行了 git reset --hard。这些情况,显然不能用同一条命令来处理。

Git之reset、restore、revert与reflog的使用及说明

关键是要先确认改动在哪一层——工作区、暂存区、本地提交,还是已经推送给他人?然后从 restoreresetrevertreflog 中选出合适的工具,这样就能把影响控制在期望的范围内。

本文以 Windows PowerShell 为例,但命令同样适用于 Git Bash、macOS 和 Linux。示例中的分支名、提交哈希和文件路径,请替换成你自己的实际值。

一、先记住:Git 的“撤回”有四个目标

多数误操作都发生在下面这条链路的某一个位置:

工作区(正在编辑的文件)
        |
        | 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 的说明可以作为判断依据。

二、任何撤回前,先做 30 秒检查

不要在看到当前状态之前就直接输入破坏性命令。在项目根目录执行:

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 --versiongit 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

三、场景一:git add 加错文件,只取消暂存

例如你原本只想提交 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 diffgit 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~2src/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

1. 只需要修改刚刚提交的内容或提交说明:优先 commit --amend

如果只是漏 git add 了一个文件、提交说明写错了,且该提交尚未推送,通常不必 reset:

# 修改文件后暂存
git add src/profile/profile.service.ts

# 修改最近一次提交;会进入编辑器修改提交说明
git commit --amend

只改提交说明时:

git commit --amend -m "feat: add profile validation"

--amend 同样会改写最新本地提交,因此不要对已经被其他人基于其开发的共享提交直接使用。

2. 想撤销最近一次提交,但保留内容为“已暂存”:--soft

git reset --soft HEAD~1

执行结果:

工作区:保留最新提交中的内容
暂存区:保留最新提交中的内容,仍处于已暂存状态
HEAD:退回到上一个提交

适合“这次提交需要拆分、删掉其中一个文件后重新提交”的情况。

接着可以只取消不该提交的文件:

git restore --staged -- config/local.json
git diff --cached
git commit -m "feat: add profile validation"

3. 想撤销最近一次提交,并把内容改回“未暂存”:默认 --mixed

git reset HEAD~1

--mixedgit 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"

4. 连同本地内容都确认不要:最后才考虑 --hard

git reset --hard HEAD~1

这会让当前分支、暂存区和工作区都匹配 HEAD~1。它会覆盖已跟踪文件的本地修改;在写入受跟踪路径时,也可能删除阻碍写入的未跟踪文件。不要把它当成“清理一下”的快捷键。

在执行前,至少满足以下三个条件:

  1. git status 已确认没有其他要保留的工作区改动;
  2. git show HEAD 已确认最新提交整体都不要;
  3. 已确认提交没有推送到共享分支,或已和相关协作者沟通。

reset--soft--mixed--hard 区别,本质上是它是否同时更新暂存区和工作区。详细状态表见 git-reset 官方文档。

七、场景五:提交已经推送,使用 git revert 安全撤回

假设 a1b2c3d 已经推送到 maindevelop,或其他人可能已经拉取的分支。此时不要直接 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 出现冲突怎么办

后续提交已经改过同一段代码时,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

合并提交不要盲目 revert

对 merge commit 执行 revert 通常需要指定主线父提交,例如 git revert -m 1 。这里的 1 不是“第一个看起来对的数字”,而是你要保留哪一侧历史的选择;选错会影响以后再次合并的结果。

除非已经理解该合并的两条父线和后续合并计划,否则先在临时分支演练,或由熟悉该分支历史的人处理。Git 官方对 -m 的语义有专门说明:git-revert。

八、场景六:已经 reset 错了,用 reflog 找回提交

误执行 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 --forcegit push -f
  • 在 IDE、GUI 中勾选了“Force push”;
  • 使用了带 + 的强制推送 refspec;
  • 某个脚本、镜像配置或具备绕过权限的账号重写了远程分支。

如果确认自己没有输入 --force,但远程历史确实被改写,仍按本节流程恢复,同时保留终端输出并检查 IDE 的推送选项、分支保护规则、remote..mirror 配置和自动化脚本。

这类事故的目标不是马上再 push 一次,而是先找到“远程分支被覆盖前的正确提交”,把它固定下来,再由一个人有条件地恢复远程引用。

1. 立刻停止会改变引用的操作

先在团队里说明受影响的远程和分支,例如 origin/main。在确认恢复方案前,不要继续执行以下操作:

# 以下命令此时都不要执行
git push --force
git push -f
git reset --hard
git gc
git reflog expire

也不要让所有人先 pull 后各自处理。pull 只能同步当前已经被覆盖的远程状态,并不能把丢失的远程分支指针恢复回来;多人同时 push 还会增加判断难度。

2. 记录当前错误远程提交,并寻找正确提交

先只读查询远程分支当前指向的提交。下面输出的完整哈希记为“错误提交” 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

3. 先创建救援分支,不要直接改回 main

假设 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 对不可达对象的保留不是永久承诺,仓库管理员可能需要从服务端备份或审计记录恢复。

4. 确认后,用“精确租约”恢复远程分支

只有在以下条件全部满足时,才应恢复远程 main

  1. 团队已确认 good 是正确提交;
  2. 已经创建并推送了救援分支;
  3. 当前远程 main 仍指向刚才记录的 bad,没有新的合法提交进入;
  4. 由一名有权限的人执行恢复,并已告知受影响协作者。

使用 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 有时能补回缺失的改动,但它不能可靠地把远程分支指针恢复到事故前状态,也未必能移除误推入的内容。

5. 恢复完成后,协作者如何重新同步

远程分支恢复后,每位协作者应先保护自己尚未提交的工作和本地提交,再同步。下面以本地 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

6. 防止下一次覆盖

  • maindevelop、发布分支开启保护,禁止或严格限制 force push;
  • 推送前先执行 git fetch origin,再检查 git status 和提交图;
  • 需要重写个人功能分支历史时,只推一个明确分支,不使用影响面更大的裸 --force
  • 对需要强推的场景,优先写明目标分支和期望提交,而不是依赖模糊的默认值;
  • 不把 --force-with-lease 视为免沟通开关,它只能降低覆盖新提交的概率,不能替代分支保护和协作约定。

十、已经推送过,能不能 reset 后强推?

技术上可以,但是否应该做取决于分支是否共享:

分支情况推荐做法原因
个人本地分支,尚未推送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,并立即创建救援分支;
  • 误强推覆盖远程分支时,先冻结写入、找到正确提交并创建救援分支,再由一人使用精确 lease 恢复。

每次操作前保留 git statusgit diffgit show 这三步检查,绝大多数“代码不见了”的事故都能提前避免或及时找回。

本文转载于:https://www.jb51.net/program/3683482f0.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注