变基就是 N 次合并
一句话总结
提交对象中包含父提交哈希。父提交一旦改变,提交哈希必然改变。因此,rebase 不是移动提交的命令,而是重写提交的命令。
为什么需要它
“不要 rebase 已经推送的提交”这句话并不完全准确。关键不在于是否推送,而在于其他人的仓库中是否已经存在该提交对象,以及是否有人在它之上继续工作。因此,黄金法则的准确表述是:不要重写已经承载他人工作的提交。
为了整理只有自己使用的远程分支而执行 rebase 没有问题。真正的问题发生在别人已经基于该分支工作时。
它如何运作
相同冲突在 rebase 中反复出现,也是由其结构决定的。merge 只比较两个端点和共同祖先这三个位置;rebase 则逐个重新应用提交,每次都会重新执行一次三方合并。rebase 不是一次合并,而是 N 次合并。因此,rebase 五个提交时,可能会看到同一个冲突五次。启用 rerere.enabled true 后,Git 会记住首次解决冲突的方法,并在之后重新应用。
所谓冲突状态,是索引中同一路径存在三个候选版本(1 共同祖先 / 2 ours / 3 theirs)。这正是推荐使用 merge.conflictStyle zdiff3 的原因。只有知道双方共同从什么内容开始,而不只是对方修改了什么,才能正确合并。
交互式 rebase 有六个动作:pick(保留)、reword(只改消息)、edit(修改内容)、squash(合并内容与消息)、fixup(合并内容但丢弃消息)、drop(删除)。
--force-with-lease 会确认远程跟踪引用是否仍与自己最后一次看到的值相同。但它存在漏洞:如果 IDE 刚刚执行过 fetch,引用已经被更新,那么即使出现了自己从未看过的新提交,检查仍可能通过。可以用 push.useForceIfIncludes true 进行补强。
生产现场中的表现
rebase 发生冲突时,最常见的错误是直接选择完整的一侧。使用 --ours 或 --theirs 快速跳过,会让另一方的工作悄无声息地消失。而且在 rebase 过程中,ours 与 theirs 的含义会让人感觉与直觉相反,因为正在重放的提交属于 theirs。
另一个常见问题是不知道如何中止。git rebase --abort 会准确恢复到开始前的状态。遇到阻塞时,与其勉强继续推进,几乎总是应该先中止并重新规划。
什么时候 rebase,什么时候 merge
规则只有一条:不要重写别人正在查看的提交。
| 场景 | 使用什么 |
|---|---|
| 把自己的本地分支移到最新 main 之上 | rebase——历史更整洁 |
| 已经 push 的共享分支 | merge——重写会破坏他人的历史 |
| 把 PR 合入 main | 遵循团队规则(统一选择 squash、rebase 或 merge) |
| 撤销已公开的内容 | revert——通过新提交取消 |
如果必须重写已经推送的分支,应使用 --force-with-lease。与 --force 不同,它只会在远程仍与自己最后一次看到的状态相同时推送。如果期间有人推送了新内容,操作就会被拒绝,从而避免删除他人的提交。
减少冲突的工具
rerere——再次遇到相同冲突时,会自动应用之前的解决方法。多次 rebase 长期分支时尤其有用。
git config --global rerere.enabled true
--onto——只移动分支的根部。它适用于把从功能分支分出的另一个分支移动到 main 之上。
A---B---C main
D---E feature
F---G hotfix ← D,E 없이 F,G 만 main 위로
git rebase --onto main feature hotfix
autosquash——根据评审意见修改时,先用 git commit --fixup <해시> 标记,再通过 git rebase -i --autosquash 一次性折叠。无需手动选择要合并到哪个提交。
发生事故后如何找回
即使在 rebase 中操作错误,通常也能恢复。Git 不会立即删除提交。
git reflog # HEAD 가 거쳐 온 모든 자리
git reset --hard HEAD@{5} # 그중 하나로 되돌아간다
# 리베이스 중이라면 그냥 그만둘 수 있다
git rebase --abort
reflog 默认保留 90 天。所谓**“提交消失了”通常并非事实**,只是指向它的分支不存在了。只要知道哈希,就能找回。
git fsck --lost-found 甚至可以找到没有被任何引用指向的对象,是 reflog 中也不存在记录时的最后手段。
下一项实验要做什么
你将创建两个分叉分支,亲自比较 rebase 前后的哈希;通过快进生成直线历史;故意制造冲突,并在解决时保留双方修改;还会主动中止一次,确认恢复原状。随后使用交互式 rebase 把三个提交合并为一个并丢弃其中一个,最后用自己的语言总结黄金法则。