变基
在 Git 中整合来自不同分支的修改主要有两种方法:merge 和 rebase(变基)。本章讲解变基是什么、怎么用,以及什么时候不该用它。
什么是变基
假设有如下历史:
A ── B ── C ── F (main)
\
D ── E (feature)在 feature 分支上执行变基:
git switch feature
git rebase mainGit 会找到两个分支的共同祖先 C,把 feature 上的提交 D、E 依次"摘下来",然后在 main 的最新提交 F 之后重新应用:
A ── B ── C ── F (main)
\
D' ── E' (feature)注意 D'、E' 是新的提交,内容与 D、E 相同,但哈希值不同。此时再切回 main 合并 feature,就是一次快进合并,历史变成一条直线。
merge 与 rebase 的对比
| merge | rebase | |
|---|---|---|
| 历史形态 | 保留分叉,产生合并提交 | 线性,没有合并提交 |
| 是否改写历史 | 否 | 是(产生新提交) |
| 冲突处理 | 一次解决 | 可能逐个提交解决 |
| 适用场景 | 合并公共分支、保留真实历史 | 整理本地提交、同步主分支更新 |
变基时的冲突
变基过程中,每应用一个提交都可能产生冲突。解决方式与合并类似:
# 解决冲突后
git add <文件>
git rebase --continue
# 跳过当前这个提交
git rebase --skip
# 放弃变基,回到开始前的状态
git rebase --abort交互式变基
交互式变基是整理提交历史的利器,可以合并、修改、删除、重排提交:
# 整理最近 3 个提交
git rebase -i HEAD~3Git 会打开编辑器,列出这些提交(注意顺序是从旧到新):
pick a1b2c3d 添加登录页面
pick e4f5g6h 修复拼写错误
pick i7j8k9l 添加登录接口把每行开头的命令改成你想要的操作:
| 命令 | 缩写 | 作用 |
|---|---|---|
pick | p | 保留该提交 |
reword | r | 保留提交,但修改提交说明 |
edit | e | 保留提交,但停下来让你修改内容 |
squash | s | 与上一个提交合并,并合并提交说明 |
fixup | f | 与上一个提交合并,丢弃本提交说明 |
drop | d | 删除该提交 |
例如,把拼写修复合并到第一个提交里:
pick a1b2c3d 添加登录页面
fixup e4f5g6h 修复拼写错误
pick i7j8k9l 添加登录接口保存并关闭编辑器,Git 就会按照你的指令重写历史。调换行的顺序还可以重排提交。
自动 fixup
如果已经知道某个修改属于之前的哪个提交,可以这样提交:
git commit --fixup a1b2c3d
git rebase -i --autosquash mainGit 会自动把 fixup 提交排到目标提交后面并标记为 fixup。
用 rebase 拉取代码
多人在同一分支上开发时,git pull 默认会产生一个合并提交。使用变基方式拉取可以保持历史整洁:
git pull --rebase
# 设置为默认行为
git config --global pull.rebase true变基的黄金法则
警告
永远不要对已经推送到公共仓库、并且可能被别人使用的提交执行变基。
变基会丢弃旧提交、创建新提交。如果其他人已经基于旧提交开展了工作,你强制推送后,他们的历史就会和你的产生混乱的分叉。
实践中的建议:
- 只对自己本地、尚未推送的提交变基;
- 对只有自己使用的个人功能分支,变基后可以用
git push --force-with-lease推送; - 对
main、develop等公共分支,只用 merge。
--force-with-lease 比 --force 更安全:如果远程分支在你上次获取之后被别人更新过,它会拒绝推送,避免覆盖他人的提交。