Git rebase 与 reset:先分清三个区域,再安全地改历史
“撤销上一次提交”和“把分支整理干净”看似都是 Git 的日常操作,但 git reset 与 git rebase 改动的对象并不相同。把它们当作互相替代的命令,尤其是在已经推送的共享分支上操作,很容易给协作带来麻烦。
本文不提供“一键后悔药”,而是建立一套判断方式:要改的是提交指针、暂存区、工作区,还是提交历史本身?这些提交是否已经被其他人依赖?
先看 Git 的三个区域
可以把本地状态拆成三个层次:
- HEAD:当前分支指向的提交,也就是下一次提交的父节点。
- 暂存区(index):准备写入下一次提交的文件快照。
- 工作区(working tree):磁盘上正在编辑的文件。
git reset 的核心是把 HEAD 或暂存区对齐到指定提交;不同模式决定工作区是否也被修改。官方文档对其定义是“将 HEAD 或 index 设为已知状态”,具体行为取决于模式和参数。git-reset 文档
git rebase 则会把一串提交重新应用到新的基点上。它产生新的提交对象,因此提交 ID 会变化;官方将这一过程描述为把提交重新应用到新的 base tip。git-rebase 文档
reset:根据要保留的内容选择模式
假设当前历史为:
1 | |
你希望“撤销 C”,但不同场景要保留的内容不同。
只撤销提交,保留暂存状态:—soft
1 | |
HEAD 回到 B,但 C 中的变更仍留在暂存区。适合刚提交后发现提交说明需要调整,或想把 C 与后续已暂存的改动合并成一次提交。
撤销提交并取消暂存,保留工作区:—mixed
1 | |
这是不带模式时的默认行为。HEAD 回到 B,暂存区回到 B,C 的文件修改仍留在工作区。适合希望重新挑选要提交的文件,或拆分刚才的提交。
同时丢弃工作区修改:—hard
1 | |
这会让 HEAD、暂存区和工作区都对齐到 B。它适合明确确认目标提交之后的本地变更都不需要保留的情形。执行前应先检查:
1 | |
不要把 --hard 当作清理工作区的常规手段;未提交且未被其他引用保存的修改可能难以恢复。若只是取消某个文件的暂存,优先使用更精确的命令:
1 | |
rebase:用于整理自己的未共享提交
设远端主线已经前进:
1 | |
在自己的功能分支上执行:
1 | |
概念上,Git 会把 C、E 的补丁依次应用到 D 之后,得到新的 C’、E’:
1 | |
这便于在合并前消除不必要的分叉,也能更早发现与主线的冲突。但 C’ 和 E’ 不再是原来的提交,所以已被其他协作者拉取、基于其继续开发的分支,不应随意 rebase 后再强推。
交互式 rebase 可用于合并、重排或编辑近期提交:
1 | |
它适合整理仍只属于自己的提交。编辑 todo 列表时,squash 会合并提交,reword 会改说明,edit 会暂停让你修改内容。操作前先确认范围,避免把已合并或已共享的提交也纳入。
冲突不是“随便选一边”
rebase 中出现冲突时,Git 会暂停。可按下面的闭环处理:
1 | |
如果发现方向不对,优先退出:
1 | |
这会终止当前 rebase 并尝试回到开始时的状态;具体语义以当前 Git 版本的官方文档为准。不要为了“尽快结束冲突”直接使用 --skip,除非你已经确认被跳过的提交不应保留。
已推送提交,先判断是否共享
对于已经推送到远端的提交,最安全的默认策略是新增一个反向提交,而不是改写公开历史:
1 | |
revert 会生成一个新提交来抵消指定提交的改动,因此通常更适合共享主分支。
如果团队明确允许改写某个个人远端分支,完成 rebase 后也应使用带租约的推送:
1 | |
--force-with-lease 会在远端引用不符合预期时拒绝推送,能避免无意覆盖后来提交;它仍是重写远端历史的操作,不应对保护分支或他人共同使用的分支轻率执行。
一份实用决策表
| 目标 | 优先选择 | 前提 |
|---|---|---|
| 修改刚提交的内容或说明 | reset --soft / commit --amend |
提交尚未共享 |
| 取消暂存并重新拆分提交 | reset 或 restore --staged |
保留工作区修改 |
| 放弃明确不需要的本地改动 | reset --hard |
已确认目标与影响范围 |
| 让个人功能分支跟上主线 | fetch 后 rebase |
提交未被他人依赖 |
| 撤销已发布的共享提交 | revert |
保留可追溯历史 |
命令本身没有“万能安全模式”。真正可靠的做法是在执行前先用 git status、git log --oneline --graph 和 git diff 看清状态,确认提交是否共享,并在改写历史前保留可回退的引用。理解 HEAD、暂存区和工作区分别会怎样变化后,reset 与 rebase 就不再是容易误触的命令,而是可以精确使用的工具。