Appearance
git reset 详解
git reset 是 Git 中最强大也最容易误用的命令之一。它通过移动 HEAD(和当前分支指针)来"回退"历史,有三种不同的模式。
reset 的核心机制
git reset 做的事情是:移动 HEAD(和它指向的分支指针)到指定的提交。
reset 前:
A ← B ← C ← D (HEAD → main)
git reset B 后:
A ← B (HEAD → main)
(C 和 D 不再被 main 引用,但对象仍存在于 .git/objects)三种模式的区别在于:移动 HEAD 之后,如何处理暂存区和工作区。
--soft:只移动 HEAD
bash
git reset --soft HEAD~1
git reset --soft abc123效果:
- HEAD 移动到指定提交
- 暂存区不变(保留被撤销提交的改动在暂存区中)
- 工作区不变
reset --soft HEAD~1 前:
HEAD → D(已提交)
暂存区:空(与 D 一致)
工作区:无修改
reset --soft HEAD~1 后:
HEAD → C
暂存区:D 的改动(staged,等待重新提交)
工作区:无修改使用场景:
- 撤销提交,但保留改动在暂存区(准备重新提交)
- 将多个提交合并成一个(先 reset --soft,再一次性 commit)
bash
# 场景:合并最近 3 个小提交为一个
git reset --soft HEAD~3
git commit -m "feat: 完整的用户认证功能"--mixed(默认):移动 HEAD + 重置暂存区
bash
git reset HEAD~1
git reset --mixed HEAD~1 # 等价
git reset abc123效果:
- HEAD 移动到指定提交
- 暂存区重置为指定提交的状态(被撤销的改动变为未暂存状态)
- 工作区不变
reset --mixed HEAD~1 前:
HEAD → D(已提交)
暂存区:空(与 D 一致)
工作区:无修改
reset --mixed HEAD~1 后:
HEAD → C
暂存区:空(与 C 一致)
工作区:D 的改动(modified,未暂存)使用场景:
- 撤销提交和暂存,重新整理后再提交
git reset HEAD <file>:取消单个文件的暂存(不移动 HEAD)
bash
# 撤销最近提交,改动回到工作区(可以重新整理再提交)
git reset HEAD~1
# 取消某文件的暂存(不影响其他文件)
git reset HEAD src/app.js--hard:移动 HEAD + 重置暂存区 + 重置工作区
bash
git reset --hard HEAD~1
git reset --hard abc123效果:
- HEAD 移动到指定提交
- 暂存区重置为指定提交的状态
- 工作区重置为指定提交的状态(工作区改动丢失!)
reset --hard HEAD~1 前:
HEAD → D(已提交)
暂存区:部分改动
工作区:其他改动
reset --hard HEAD~1 后:
HEAD → C
暂存区:空(与 C 完全一致)
工作区:与 C 完全一致(所有未提交的改动都丢失!)警告:
--hard会永久丢失未提交的改动(工作区和暂存区中的内容)!执行前请确认。
使用场景:
- 完全放弃某次提交及其之后的所有改动
- 紧急回退到某个干净状态
- 放弃所有本地未提交的改动(
git reset --hard HEAD)
bash
# 完全放弃最近提交(包括改动)
git reset --hard HEAD~1
# 放弃所有本地未提交的改动(等价于全量 restore)
git reset --hard HEAD
# 回退到某个标签时的状态
git reset --hard v1.0.0三种模式的对比
HEAD 暂存区 工作区
--soft 移动 不变 不变
--mixed(默认) 移动 重置 不变
--hard 移动 重置 重置(危险)| 模式 | 命令 | HEAD | 暂存区 | 工作区 | 改动去向 |
|---|---|---|---|---|---|
| soft | reset --soft | 移动 | 不变 | 不变 | 暂存区 |
| mixed | reset | 移动 | 重置 | 不变 | 工作区 |
| hard | reset --hard | 移动 | 重置 | 重置 | 丢失 |
reset 与 checkout 的区别
git reset 和 git checkout 都可以移动 HEAD,但有重要区别:
| 对比项 | git reset | git checkout |
|---|---|---|
| 操作对象 | 移动当前分支指针 | HEAD 指针(可能进入分离状态) |
| 分支状态 | 当前分支也移动了 | 当前分支不变,HEAD 指向提交 |
| 影响 | 改变分支历史 | 不改变分支历史 |
| 用途 | 回退历史 | 检出历史状态 |
bash
# reset:main 分支也回退了
git switch main
git reset --hard HEAD~3
# main 现在指向 HEAD~3
# checkout:main 不变,进入分离 HEAD
git checkout HEAD~3
# main 仍在原位,HEAD 指向旧提交(分离状态)针对文件的 reset
git reset 也可以只操作特定文件(此时不移动 HEAD):
bash
# 将 src/app.js 的暂存区状态重置为 HEAD 版本(取消暂存)
git reset HEAD src/app.js
git reset -- src/app.js # 等价
# 将 src/app.js 的暂存区重置为 HEAD~2 版本
git reset HEAD~2 src/app.js如何恢复 reset --hard 后的提交
虽然 --hard 会"丢失"提交,但 Git 并不会立即删除这些对象,可以通过 reflog 找回:
bash
# 查看 reflog 找到被 reset 前的 SHA-1
git reflog
# abc123 HEAD@{0}: reset: moving to HEAD~3
# def456 HEAD@{1}: commit: feat: 功能C(这是被丢弃的提交)
# ...
# 恢复到 reset 之前的状态
git reset --hard def456
# 或创建新分支保存
git branch recovered-work def456常用 reset 场景
bash
# 撤销最近的错误提交(保留改动以便修改后重提)
git reset HEAD~1
# 彻底放弃最近的提交和所有本地改动
git reset --hard HEAD~1
# 合并最近 5 个小提交为一个
git reset --soft HEAD~5
git commit -m "feat: 完整的功能模块"
# 放弃所有本地未提交的改动(谨慎!)
git reset --hard HEAD
# 取消某文件的暂存
git reset HEAD config.json总结
--soft:后悔提交,但改动是对的 → 保留在暂存区--mixed(默认):后悔提交,需要重新整理 → 保留在工作区--hard:完全后悔,放弃所有改动 → 彻底清除(危险!)
使用 --hard 前要三思,如果不确定,先用 reflog 作为后盾。