跳转到内容
关系图谱

Git reset 详解

7,635 字 29 分钟

属性 1
status
学习中

Git reset 详解

git reset 是 Git 中最常用的撤销操作命令,用于移动 HEAD 指针和当前分支引用,从而实现取消暂存、撤销提交、重置工作区等操作。它是理解 Git 内部工作原理的关键命令之一。

潜在破坏性

git reset --hard永久丢弃工作区和暂存区的未提交修改。使用前请确认不需要这些更改,或已通过 git stash 保存。

基本语法

bash
git reset [<mode>] [<commit>]
git reset [-q] [<tree-ish>] [--] <pathspec>
git reset [-q] [--pathspec-from-file=<file>] [<tree-ish>]
git reset --patch [<tree-ish>] [--] [<pathspec>…]

核心概念:Git 的三棵树

要理解 git reset,必须先理解 Git 内部管理的三个区域(通常称为「三棵树」):

区域说明
Working Directory(工作区)你在编辑器中看到的实际文件
Staging Area / Index(暂存区)git add 后的文件快照,等待被提交
HEAD(版本库)当前分支最新提交指向的快照,git commit 后更新

git reset 的核心作用就是控制这三棵树的状态回退到哪一步

三种模式

模式对比总览

模式HEAD暂存区 (Index)工作区 (Working Dir)安全性
--soft✅ 移动❌ 不变❌ 不变安全
--mixed✅ 移动✅ 重置❌ 不变安全
--hard✅ 移动✅ 重置✅ 重置危险
记忆口诀

soft 只动头,mixed 加暂存,hard 全部清。

  • --soft:只移动 HEAD(提交记录回退,但修改保留在暂存区)
  • --mixed:移动 HEAD + 重置暂存区(修改回到工作区,变成未 add 状态)
  • --hard:移动 HEAD + 重置暂存区 + 重置工作区(所有修改全部丢弃)

完整五种模式对比

实际上 git reset 共有五种模式,除了常见的三种外,还有 --merge--keep

模式HEAD暂存区工作区安全性
--soft安全
--mixed安全
--hard✅ 全部重置危险
--merge✅ 仅重置与目标提交不同的文件较安全
--keep✅ 仅重置与目标提交不同的文件(保护本地修改)较安全

--merge:安全地中止合并

--merge 会重置暂存区,并将工作区中与目标提交不同的文件重置,但会保留你本地未暂存的修改(前提是这些修改的文件没有在 reset 涉及的提交中被改动)。如果发现工作区有未暂存的修改会与 reset 冲突,则操作会被拒绝

bash
# 典型场景:中止一个有冲突的合并
git merge feature-branch
# 产生了合并冲突,不想继续了
git reset --merge
# 等价于 git merge --abort

# 另一个场景:回退提交,但保留不冲突的本地修改
git reset --merge HEAD~1
--merge vs --hard

--hard 无条件丢弃所有未提交的修改;--merge 则会检查:如果你有本地修改的文件不在 reset 涉及的变更范围内,这些修改会被保留。如果冲突,reset 会直接报错而不是悄悄覆盖。

--keep:保守的 reset

--keep--merge 类似,但更加保守。它会重置暂存区和工作区中与目标提交不同的文件,但如果工作区中存在任何未提交的修改(无论是否与 reset 冲突),都会拒绝执行

bash
# 只有在工作区干净时才能成功
git reset --keep HEAD~1
--keep 的适用场景

--keep 最常见的用途是在分支间快速移动 HEAD 时保护未提交的工作

bash
# 你在 feature 分支上有未提交的修改
# 想把分支指针移动到 origin/feature 的最新提交
# 但不想丢失本地修改
git fetch origin
git reset --keep origin/feature
# 如果本地修改与远程变更冲突,会报错而不是覆盖

--soft:只移动 HEAD

将 HEAD 指针移动到指定提交,但暂存区和工作区保持不变。所有被「撤销」的提交中的修改会出现在暂存区中,处于「已 add 待 commit」的状态。

bash
# 撤销最近一次提交,修改保留在暂存区
git reset --soft HEAD~1

# 撤销最近三次提交,所有修改合并到暂存区
git reset --soft HEAD~3

典型场景

场景 1:合并多次提交(squash)

你提交了三次琐碎的 commit,想合并成一个有意义的提交:

bash
# 当前历史:C1 → C2 → C3 → C4 (HEAD)
# 想把 C2、C3、C4 合并成一个提交

git reset --soft HEAD~3
git commit -m "feat: 完成用户认证模块"

# 结果:C1 → C5 (HEAD),C5 包含 C2+C3+C4 的所有修改
场景 2:修改上一次提交的信息

虽然 git commit --amend 更常用,但 --soft 也能做到:

bash
git reset --soft HEAD~1
git commit -m "新的提交信息"

--mixed:移动 HEAD + 重置暂存区(默认模式)

这是 git reset默认模式(不指定任何标志时的行为)。HEAD 移动到指定提交,暂存区重置为该提交的状态,但工作区保持不变。被撤销的修改会出现在工作区中,处于「未 add」的状态。

bash
# 以下两条命令等价
git reset HEAD~1
git reset --mixed HEAD~1

# 取消暂存某个文件(不改变提交历史)
git reset HEAD file.txt

典型场景

场景 1:取消暂存(最常用)

你执行了 git add . 把所有文件都加入了暂存区,但其中某个文件不想提交:

bash
# 取消暂存单个文件
git reset HEAD config/local.yml

# 取消暂存所有文件
git reset HEAD
Git 2.23+ 的替代命令

从 Git 2.23 起,可以使用语义更清晰的命令:

bash
git restore --staged file.txt
场景 2:撤销提交但重新整理暂存

你想撤销上一次提交,并重新选择要提交的文件:

bash
git reset HEAD~1
# 此时所有修改在工作区(未暂存状态)
git add src/feature.ts
git commit -m "feat: 只提交核心功能"
# 剩余修改留在工作区,可以在下次提交

--hard:移动 HEAD + 重置暂存区 + 重置工作区

HEAD 移动到指定提交,暂存区和工作区都被强制重置为该提交的状态。所有未提交的修改(包括暂存的和未暂存的)都会被永久丢弃。

bash
# 彻底丢弃最近一次提交及其所有修改
git reset --hard HEAD~1

# 彻底回退到指定提交
git reset --hard abc1234

# 丢弃所有未提交的修改(不改变提交历史)
git reset --hard HEAD
不可逆操作

--hard 模式下,未提交的修改会永久丢失,无法通过 git reflog 找回(reflog 只记录提交,不记录工作区和暂存区的状态)。

唯一可能的补救:如果文件之前被 git add 过,其 blob 对象可能还在 .git/objects/ 中,可以尝试 git fsck --lost-found 找回。

典型场景

场景 1:放弃所有本地修改

你的工作区搞乱了,想恢复到最新提交的干净状态:

bash
git reset --hard HEAD
场景 2:彻底撤销错误的提交

你提交了错误的代码,想完全抹除这次提交:

bash
git reset --hard HEAD~1
场景 3:同步到远程分支的最新状态

本地分支和远程分支偏离了,想强制与远程保持一致:

bash
git fetch origin
git reset --hard origin/main

三种模式的可视化对比

假设当前状态如下,HEAD 在 C3 提交上,且有未提交的修改:

执行 git reset <mode> HEAD~2(回退两个提交到 C1)后的结果:

文件级别的 reset

git reset 还可以作用于单个文件,但此时不会移动 HEAD,只会将指定文件在暂存区的状态回退。

语法

bash
# 将文件从暂存区移回工作区(取消 git add)
git reset [<commit>] -- <file>

# 等价于(Git 2.23+)
git restore --staged <file>

工作原理

常用操作

bash
# 取消暂存单个文件
git reset HEAD -- src/config.ts

# 取消暂存某个目录下的所有文件
git reset HEAD -- src/

# 将暂存区的文件回退到某个历史版本(工作区不变)
git reset abc1234 -- src/config.ts
文件级别 reset 不支持 --hard

git reset --hard -- <file>不合法的。如果你想丢弃工作区中某个文件的修改,应该使用:

bash
git checkout -- <file>
# 或 Git 2.23+
git restore <file>

--patch 交互模式

git reset --patch(或 -p)允许你逐块选择要从暂存区移除的修改,而不是一次性操作整个文件。

bash
# 交互式选择要取消暂存的代码块
git reset -p

# 针对特定文件
git reset -p -- src/main.ts

交互提示中的选项:

按键含义
y取消暂存此代码块
n保持此代码块在暂存区
s将当前块拆分成更小的块
e手动编辑此代码块
q退出,后续代码块保持不变
a取消暂存当前文件的此块和后续所有块
d保留当前文件的此块和后续所有块
精细化暂存管理

git reset -pgit add -p 是一对互补操作:

  • git add -p:逐块选择要加入暂存区的修改
  • git reset -p:逐块选择要移出暂存区的修改

<commit> 参数的常见写法

写法含义
HEAD当前提交
HEAD~1HEAD~上一个提交
HEAD~3往前第 3 个提交
HEAD^当前提交的第一个父提交(与 HEAD~1 类似)
HEAD^2当前提交的第二个父提交(合并提交时才有意义)
abc1234指定 commit SHA(可以只写前几位)
origin/main远程分支的最新提交
v1.0标签指向的提交
@{2}HEAD 两次移动前的位置(来自 reflog)

误操作恢复:git reflog

git reflog 记录了 HEAD 的每一次移动,是 git reset 误操作后的救命稻草。

工作原理

恢复步骤

bash
# 1. 查看 reflog,找到误操作前的 SHA
git reflog

# 输出类似:
# abc1234 HEAD@{0}: reset: moving to HEAD~3    ← 这是误操作
# def5678 HEAD@{1}: commit: 重要的功能提交      ← 这是你想恢复的
# ghi9012 HEAD@{2}: commit: 另一个提交

# 2. 恢复到误操作前的提交
git reset --hard def5678

# 或使用 reflog 语法
git reset --hard HEAD@{1}
reflog 的有效期

reflog 记录默认保留 90 天。超过此期限或执行 git reflog expire --expire=now + git gc 后,记录会被清除。

另外,reflog 是本地的,不会被 push 到远程仓库,也不会被 clone 下来。

reflog 只能恢复提交

git reflog 记录的是提交级别的 HEAD 移动。如果你用 --hard 丢弃了从未被 git commit 过的工作区修改,reflog 无法帮你找回。

ORIG_HEAD:自动备份机制

Git 在执行 resetmergerebase危险操作前,会自动将当前 HEAD 的值保存到 ORIG_HEAD 引用中。这是一个比 reflog 更直接的恢复手段。

bash
# 执行 reset 后立即后悔
git reset --hard HEAD~3

# 一步恢复(无需查看 reflog)
git reset --hard ORIG_HEAD
ORIG_HEAD 只保留最近一次

ORIG_HEAD 只记录最近一次危险操作前的 HEAD 位置。如果连续执行两次 reset,第一次的 ORIG_HEAD 会被第二次覆盖。需要更完整的历史请使用 git reflog

查看 ORIG_HEAD
bash
# 查看 ORIG_HEAD 指向的提交
git log -1 ORIG_HEAD

# 对比当前 HEAD 和 ORIG_HEAD 的差异
git diff ORIG_HEAD..HEAD

Reset 与特殊状态

Detached HEAD 状态下的 reset

当 HEAD 不指向任何分支(detached HEAD)时,git reset 仍然可以使用,但行为有所不同:

bash
# 进入 detached HEAD 状态
git checkout abc1234

# 此时 reset 只移动 HEAD,不影响任何分支
git reset --soft HEAD~1
# HEAD 移到上一个提交,但没有分支跟着移动

# 回到正常状态
git checkout main
Detached HEAD 下 reset 的风险

在 detached HEAD 状态下使用 git reset --hard,被跳过的提交如果没有任何分支或标签指向它们,将成为悬空对象,最终被 git gc 清理。务必先创建分支保存重要提交:

bash
# 在 reset 前先保存当前位置
git branch backup-branch
git reset --hard HEAD~2

Reset 与进行中的合并

当你正在合并(git merge)过程中遇到冲突时,可以用 reset 中止合并:

bash
# 开始合并
git merge feature-branch
# 产生冲突...

# 方式 1:使用 reset 中止(回到合并前的状态)
git reset --hard HEAD
# 等价于
git merge --abort

# 方式 2:使用 --merge 模式(保留与合并无关的本地修改)
git reset --merge
合并中的 MERGE_HEAD

合并过程中,Git 会创建 .git/MERGE_HEAD 文件。git reset --hard HEADgit reset --merge 会自动清理这个文件,使仓库回到干净状态。

Reset 与进行中的 rebase

如果 git rebase 过程中出了问题:

bash
# rebase 过程中遇到冲突,想完全放弃
git rebase --abort
# 这比 git reset 更合适,因为它会恢复 rebase 前的完整状态

# 但如果 rebase 已经完成了,想撤销整个 rebase 的结果
git reset --hard ORIG_HEAD

Reset 与 stash 的配合

git stashgit reset 经常配合使用,形成安全的工作流。

场景 1:安全地执行 hard reset

bash
# 有未提交的修改,但想 hard reset
# 先 stash 保存修改
git stash push -m "reset 前的备份"

# 安全地执行 hard reset
git reset --hard HEAD~3

# 如果需要恢复之前的修改
git stash pop
# 注意:如果 stash 的修改与 reset 后的代码冲突,会产生合并冲突

场景 2:在 reset 后选择性恢复修改

bash
# stash 当前修改
git stash

# reset 到目标提交
git reset --hard origin/main

# 查看 stash 中的内容
git stash show -p

# 选择性应用(不自动删除 stash)
git stash apply
# 或完全不应用(丢弃 stash)
git stash drop

场景 3:reset + stash 实现「移动提交到另一个分支」

bash
# 你不小心在 main 上做了提交,想移到 feature 分支
# 当前:main 上有 2 个不该提交的 commit

# 1. soft reset 保留修改在暂存区
git reset --soft HEAD~2

# 2. stash 暂存区的修改
git stash

# 3. 切到正确的分支
git checkout -b feature-branch

# 4. 恢复修改并提交
git stash pop
git commit -m "feat: 正确的功能提交"

Git 内部原理

理解 git reset 的内部实现有助于更深入地掌握它。

Git 对象模型

Git 的核心是一个内容寻址的文件系统,所有数据以四种对象存储:

reset 在对象层面的操作

bash
# 假设当前状态
git log --oneline
# abc1234 (HEAD -> main) 第三次提交
# def5678 第二次提交
# ghi9012 第一次提交

git reset --soft HEAD~1 的内部操作:

实际上 git reset 做了这些事:

  1. 更新分支引用 — 将 .git/refs/heads/main 的内容从 abc1234 改为 def5678
  2. (mixed/hard)更新 Index — 将 .git/index 文件重写为 def5678 提交对应的 tree 快照
  3. (hard)更新工作区 — 将工作目录中的文件内容恢复为 def5678 的 tree 中记录的状态
提交对象不会被立即删除

abc1234 这个 commit 对象仍然存在于 .git/objects/ 中,只是没有任何引用(分支、标签)指向它了。它会在以下情况被清理:

  • git gc 运行时(自动或手动触发)
  • 对象创建超过 2 周(默认的 gc.pruneExpire 值)
  • reflog 中对该对象的引用也已过期

查看 Git 内部状态

bash
# 查看 HEAD 指向
cat .git/HEAD
# ref: refs/heads/main

# 查看分支指向的提交
cat .git/refs/heads/main
# def5678...

# 查看某个提交对象的内容
git cat-file -p def5678

# 查看暂存区内容
git ls-files --stage

# 查看悬空对象
git fsck --dangling

常见陷阱与注意事项

陷阱 1:reset 后 push --force 导致团队灾难

对已推送的提交执行 reset 后强制推送,是团队协作中最常见也最严重的 Git 事故

bash
# ❌ 危险操作
git reset --hard HEAD~3
git push --force origin main
# 其他人的 pull/push 全部出问题!

# ✅ 正确做法
git revert HEAD~3..HEAD
git push origin main
陷阱 2:忘记未跟踪文件

git reset --hard 不会删除未跟踪文件(untracked files)。如果你需要完全干净的工作区:

bash
git reset --hard HEAD
git clean -fd          # 删除未跟踪的文件和目录
git clean -fdx         # 还包括 .gitignore 中的文件(如 node_modules)
陷阱 3:mixed reset 后忘记重新 add

git reset HEAD~1(mixed 模式)会把修改放回工作区。如果你直接 git commit,会得到一个空提交(因为暂存区是空的)。

bash
git reset HEAD~1
# ❌ 直接 commit → 空提交(暂存区为空)
git commit -m "..."

# ✅ 正确:先 add 再 commit
git add .
git commit -m "..."
陷阱 4:在公共分支上 reset

即使你是唯一的开发者,如果 CI/CD 系统正在基于某个提交构建或部署,reset + force push 可能导致构建失败或部署混乱。

陷阱 5:reset 不影响 stash

git reset --hard 不会清除 stash 列表。stash 中保存的修改在 reset 后仍然存在:

bash
# stash 不受 reset 影响
git stash list
# stash@{0}: WIP on main: abc1234 some work

git reset --hard HEAD~5

git stash list
# stash@{0}: WIP on main: abc1234 some work  ← 仍然在
安全习惯

养成以下习惯,大幅降低 reset 误操作的风险:

  1. reset 前先 git stash — 保存当前工作区的修改
  2. reset 前先 git branch backup — 创建备份分支
  3. 优先使用 --soft--mixed — 除非确定要丢弃修改,否则不用 --hard
  4. 推送前检查 — 用 git log origin/main..HEAD 确认要推送的提交
  5. --force-with-lease 替代 --force — 防止覆盖他人的提交

reset vs 其他撤销命令

reset vs revert

对比项git resetgit revert
原理移动 HEAD,删除提交记录新建一个反向提交
提交历史改写历史(提交消失)保留历史(新增一个撤销提交)
远程协作需要 --force 推送,影响协作者正常 push,不影响协作者
适用场景本地未推送的提交已推送到远程的提交
安全性可能丢失数据(--hard安全,不丢失任何数据
黄金法则

已推送到远程的提交,用 revert;本地未推送的提交,用 reset

对已推送的提交使用 reset + force push 会破坏其他协作者的提交历史,可能导致灾难性后果。

reset vs checkout vs restore

命令作用是否移动 HEAD是否修改暂存区是否修改工作区
git reset --soft回退提交
git reset --mixed回退提交 + 取消暂存
git reset --hard彻底回退
git reset HEAD <file>取消暂存某文件
git checkout -- <file>丢弃工作区某文件的修改
git restore <file>丢弃工作区某文件的修改(2.23+)
git restore --staged取消暂存(2.23+)
Git 2.23+ 的命令拆分

从 Git 2.23 开始,git checkout 被拆分为两个语义更清晰的命令:

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

推荐使用新命令,避免 checkout 的多重含义带来的混淆。

reset vs commit --amend

目标resetcommit --amend
修改最后一次提交的信息git reset --soft HEAD~1 + 重新 commitgit commit --amend -m "新信息"
补充遗漏的文件到上次提交git reset --soft HEAD~1 + add + commitgit add <file> + git commit --amend
合并多次提交git reset --soft HEAD~N + 重新 commit不支持
彻底丢弃上次提交git reset --hard HEAD~1不支持

实战示例

示例 1:合并最近的多次提交

bash
# 当前历史
# abc1234 fix: 修复空指针
# def5678 fix: 补充单元测试
# ghi9012 feat: 用户登录功能
# jkl3456 chore: 初始化项目

# 想把最近三个提交合并成一个
git reset --soft HEAD~3
git commit -m "feat: 完成用户登录功能(含测试和修复)"

# 结果:jkl3456 → 新提交 (HEAD)

示例 2:撤销 git add(取消暂存)

bash
# 不小心 git add 了不该提交的文件
git add .

# 取消暂存特定文件
git reset HEAD -- .env
git reset HEAD -- node_modules/

# 或取消所有暂存
git reset HEAD

示例 3:丢弃所有本地修改

bash
# 工作区和暂存区都乱了,想回到最新提交的干净状态
git reset --hard HEAD

# 如果还有新创建的未跟踪文件,需要额外清理
git clean -fd
reset --hard + clean -fd 组合

git reset --hard HEAD 只会重置已被 Git 跟踪的文件。新创建的未跟踪文件(untracked files)不受影响。 要彻底清理工作区,需要配合 git clean -fd(删除未跟踪的文件和目录)。

示例 4:强制同步远程分支

bash
# 本地 main 分支和 origin/main 偏离了
# 放弃本地修改,强制与远程保持一致
git fetch origin
git reset --hard origin/main

示例 5:从其他提交恢复单个文件

bash
# 不改变 HEAD,只把某个文件回退到 3 个版本前的状态
git reset HEAD~3 -- src/config.ts
# 此时 src/config.ts 在暂存区是旧版本
# 需要 commit 才能生效

# 或者用 restore(更直观)
git restore --source HEAD~3 -- src/config.ts

示例 6:拆分一个大提交为多个小提交

bash
# 上一次提交包含了太多修改,想拆分
git reset HEAD~1
# 所有修改回到工作区

# 分批 add 和 commit
git add src/auth/
git commit -m "feat: 认证模块"

git add src/api/
git commit -m "feat: API 路由"

git add tests/
git commit -m "test: 添加测试"

常见问题

git resetgit reset HEAD 有什么区别?

没有区别。当不指定 <commit> 参数时,默认是 HEAD

bash
# 以下两条命令等价
git reset
git reset HEAD

# 以下两条也等价
git reset -- file.txt
git reset HEAD -- file.txt
reset 后的提交去哪了?还能找回吗?

提交对象并没有被立即删除,它们仍然存在于 .git/objects/ 中,只是没有任何引用指向它们了(成为了「悬空对象」)。

找回方法:

bash
# 通过 reflog 找回
git reflog
git reset --hard <sha>

# 通过 fsck 查找悬空对象
git fsck --lost-found

这些悬空对象会在 git gc 运行时被清理(默认在对象创建 2 周后)。

能否 reset 已推送到远程的提交?

技术上可以,但强烈不推荐

bash
# 本地 reset
git reset --hard HEAD~1

# 强制推送到远程(危险!)
git push --force origin main

# 或使用更安全的 --force-with-lease
git push --force-with-lease origin main

--force-with-lease--force 更安全,它会检查远程分支是否有其他人推送的新提交。如果有,推送会被拒绝。

但最佳实践仍然是对已推送的提交使用 git revert

git reset HEAD~1git reset HEAD^ 一样吗?

在大多数情况下是一样的。但含义略有不同:

  • HEAD~1:HEAD 的第一代祖先(沿第一父提交回溯 1 步)
  • HEAD^ / HEAD^1:HEAD 的第一个父提交

区别体现在合并提交(merge commit)中:

# 合并提交有两个父提交
#      C4 (HEAD) ← merge commit
#     /  \
#   C3    C2
#     \  /
#      C1
#
# HEAD~1 = HEAD^ = HEAD^1 = C3(第一个父提交,通常是你所在的分支)
# HEAD^2 = C2(第二个父提交,通常是被合并进来的分支)
# HEAD~2 = C1(沿第一父提交回溯 2 步)
在 Windows PowerShell 中 HEAD~1 报错?

PowerShell 中 ~ 是用户主目录的别名,可能导致解析问题。解决方式:

powershell
# 方式 1:用引号包裹
git reset --soft "HEAD~1"

# 方式 2:使用 HEAD^ 替代
git reset --soft "HEAD^"

# 方式 3:使用 Git Bash 代替 PowerShell
--merge--keep 什么时候用?

这两个模式在日常开发中不太常见,但在特定场景下非常有用:

  • --merge:最适合中止一个有冲突的合并。它等价于 git merge --abort,但在回退提交时比 --hard 更安全,因为它会保护与 reset 无关的本地修改。
  • --keep:适合在拉取远程更新时保护本地修改。如果有冲突风险,它会直接拒绝而不是覆盖。

简单记忆:不确定用哪个时,优先试 --keep。它是最保守的,不会意外丢失数据。

ORIG_HEADreflog 有什么区别?
对比项ORIG_HEADgit reflog
记录数量只保留最近一次保留所有(默认 90 天)
覆盖范围reset / merge / rebase所有 HEAD 移动(含 commit)
使用便捷性直接使用,无需查找需要先查看再选择 SHA
适用场景刚刚执行完想立即撤销需要回到更早的某个状态
reset 对子模块(submodule)有影响吗?

git reset 默认不会递归地重置子模块。子模块的工作区和提交状态不受影响。如果需要同时重置子模块:

bash
# 重置主仓库
git reset --hard HEAD~1

# 同步子模块到主仓库记录的版本
git submodule update --init --recursive
如何只撤销某个特定的历史提交(不是最近的)?

git reset 只能回退到某个提交,会连带撤销该提交之后的所有提交。如果你只想撤销中间某一个提交,应该使用 git revert

bash
# reset 会撤销 C3 和 C4
# C1 → C2 → C3 → C4 (HEAD)
git reset --hard HEAD~2  # 回到 C2,C3 和 C4 都消失

# revert 只撤销 C3,保留 C4
git revert <C3-的-SHA>   # 创建 C3' 反向提交,C4 不受影响

速查表

基础操作

目标命令
取消暂存所有文件git reset HEAD
取消暂存单个文件git reset HEAD -- <file>
撤销提交,保留在暂存区git reset --soft HEAD~1
撤销提交,保留在工作区git reset HEAD~1
彻底撤销提交git reset --hard HEAD~1
丢弃所有本地修改git reset --hard HEAD
丢弃所有修改(含未跟踪)git reset --hard HEAD + git clean -fd
合并最近 N 次提交git reset --soft HEAD~N + git commit
同步远程分支git fetch + git reset --hard origin/<branch>
交互式取消暂存git reset -p

进阶操作

目标命令
中止有冲突的合并git reset --mergegit merge --abort
安全回退(保护本地修改)git reset --keep HEAD~1
恢复上一次 resetgit reset --hard ORIG_HEAD
恢复任意 reset 误操作git reflog + git reset --hard <sha>
移动提交到另一个分支git reset --soft HEAD~Ngit stash → 切换分支 → git stash pop + git commit
安全 hard reset(先备份)git stash push -m "备份"git reset --hard ...
撤销已完成的 rebasegit reset --hard ORIG_HEAD
查看 ORIG_HEADgit log -1 ORIG_HEAD
对比 reset 前后差异git diff ORIG_HEAD..HEAD
查看悬空对象git fsck --dangling

决策流程图

当你需要撤销操作时,参考以下决策流程:

误操作恢复决策

相关命令

  • git filter-repo — 批量重写仓库历史(官方推荐)
  • Git filter-branch 详解 — 批量重写仓库历史(已弃用)
  • git revert — 通过新建反向提交来撤销已推送的提交
  • git switch / git restore — Git 2.23+ 引入的分支切换和文件恢复命令
  • git stash — 临时保存未提交的修改,常与 reset 配合使用
  • git rebase — 变基操作,整理提交历史和合并分支
  • git cherry-pick — 将指定提交应用到当前分支,跨分支移植代码
  • git merge — 合并分支,reset 可撤销未推送的合并
  • git reflog — 查看 HEAD 的移动历史,用于恢复 reset 误操作
  • git clean — 删除未跟踪的文件,配合 reset --hard 彻底清理工作区

参考资料