回退操作,本质就是:
移动指针,或者创建“反向提交”。
你先要了解:
- commit 是不可变的
- 分支是指针
- HEAD 指向当前分支
先说结论:Git 有 3 种常见“回退”方式:分别是reset(重置)、revert(回滚)和restore(复原)
| 场景 | 命令 | 是否改历史 |
|---|---|---|
| 回退提交(危险) | git reset | 会 |
| 安全撤销提交 | git revert | 不改历史 |
| 撤销工作区修改 | git restore | 不改历史 |
一个个讲清楚。
这篇先说reset
reset 本质是:
移动历史,而不是移动代码。
代码不动
所以不会冲突。
总结:
❌ 不会改 commit 结构
❌ 不会改 parent
❌ 不会断开 C → B
✅ 只会移动分支指针
1.真正的“回退历史”
假设当前:
A --- B --- C --- D (master)
你想回到 B。
最常见:回退到某个 commit
git reset --hard B
结果:
A --- B (master)----C --- D (孤儿状态,没有被分支引用)
master 指针被移动回 B。
C、D 还在仓库里,但没有分支指向,C 的 parent 仍然是 B ,只是 master 分支不再指向 C 了
reset分三种:
1)强reset
git reset --hard B
- 移动 HEAD 指针
- 重置暂存区
- 重置工作目录(危险!会丢失未提交的更改)
即完全恢复到指定的快照状态
2)软reset
git reset --soft B
效果:
- 分支回到 B
- C、D 的修改进入“暂存区”
这是很多人最容易搞混的地方
关键点在于理解 Git 的 三层结构:
工作区 (Working Directory)
暂存区 (Index / Staging Area)
提交历史 (HEAD / Commit)
假设历史是:
A --- B --- C --- D (master, HEAD)
现在:
C、D是两个已经提交的 commit
同时你又有:
- 工作区修改
- 暂存区修改
状态可能是:
commit: A --- B --- C --- D (HEAD)
暂存区:
file2 修改
工作区:
file3 修改
执行
git reset --soft B
结果会变成:
A --- B (master, HEAD)
C --- D
但:
commit C、D 的改动会进入暂存区
因为 HEAD 从 D 回到了 B。
所以 Git 会认为:
B → D 之间的改动现在是“未提交状态”。
暂存区:
C 的修改 + D 的修改 + 原来就有的暂存修改
工作区:
原来就有的工作区修改
2.历史仍然记得他们
看到这里,我们暂时不管第三种reset,不管是强reset还是软reset
C和D这两个commit是不是就被删了?
其实,commit 对象仍然存在于 .git/objects
-
C 的 parent 仍然是 B
-
D 的 parent 仍然是 C
只是分支指针不在他们头上了,当后续出现新的提交,因为 Git 的 log 默认只沿着 当前分支向上走
所以看起来,就消失了
但 Git 仍然记得它们
Git 有一个非常重要的机制:
reflog
查看:
git reflog
你会看到类似:
D HEAD@{1}: commit
B HEAD@{0}: reset --soft
这说明:
Git 仍然记得 HEAD 曾经在 D。
所以你可以直接恢复:
git reset --hard D
历史立刻回来:
A --- B --- C --- D (master)
什么时候 C、D 才会真正消失?
只有当满足两个条件:
1️⃣ 没有任何引用指向它们
(branch、tag、reflog)
2️⃣ Git 垃圾回收执行
git gc
默认情况下:
- reflog 会保存 90 天
- unreachable commit 也会保存一段时间
所以:
你有很长时间可以找回。
3.为什么 git reset --mixed 是默认模式?
reset 三种模式的区别
| 命令 | HEAD | 暂存区 | 工作区 |
|---|---|---|---|
--soft | 移动 | 不变 | 不变 |
--mixed (默认) | 移动 | 重置 | 不变 |
--hard | 移动 | 重置 | 重置 |
因为 它最符合开发者最常见的需求:
撤销 commit,但保留代码修改。
开发中最常见的错误是:
- commit 太早
- commit message 写错
- 多提交了一次
- add 错文件
例如:
git commit -m "fix bug"
结果发现:
- message 写错了
**代码还没写完**
你希望:
撤销 commit
但代码还在
并且不要在暂存区
这就是 --mixed 的效果。
举个例子:
假设你刚提交了C:
A --- B --- C (HEAD)
但你发现:
- commit message 不好
- 代码还需要再改
执行:
git reset HEAD~1
(默认就是 --mixed)
结果:
A --- B (HEAD)
\
C
但是代码仍然在 工作区:
git status
会看到:
modified: file1
modified: file2
你可以:
继续修改代码
重新 add
重新 commit
非常符合开发习惯。
比起soft:
很多时候开发者其实希望:
重新选择哪些文件 add
所以 --mixed 更灵活。