Git中文社区

变基

在 Git 中整合来自不同分支的修改主要有两种方法:merge 和 rebase(变基)。本章讲解变基是什么、怎么用,以及什么时候不该用它。

什么是变基

假设有如下历史:

A ── B ── C ── F        (main)
           \
            D ── E      (feature)

在 feature 分支上执行变基:

git switch feature
git rebase main

Git 会找到两个分支的共同祖先 C,把 feature 上的提交 D、E 依次"摘下来",然后在 main 的最新提交 F 之后重新应用:

A ── B ── C ── F                (main)
                \
                 D' ── E'       (feature)

注意 D'、E' 是新的提交,内容与 D、E 相同,但哈希值不同。此时再切回 main 合并 feature,就是一次快进合并,历史变成一条直线。

merge 与 rebase 的对比

mergerebase
历史形态保留分叉,产生合并提交线性,没有合并提交
是否改写历史否是(产生新提交)
冲突处理一次解决可能逐个提交解决
适用场景合并公共分支、保留真实历史整理本地提交、同步主分支更新

变基时的冲突

变基过程中,每应用一个提交都可能产生冲突。解决方式与合并类似:

# 解决冲突后
git add <文件>
git rebase --continue

# 跳过当前这个提交
git rebase --skip

# 放弃变基,回到开始前的状态
git rebase --abort

交互式变基

交互式变基是整理提交历史的利器,可以合并、修改、删除、重排提交:

# 整理最近 3 个提交
git rebase -i HEAD~3

Git 会打开编辑器,列出这些提交(注意顺序是从旧到新):

pick a1b2c3d 添加登录页面
pick e4f5g6h 修复拼写错误
pick i7j8k9l 添加登录接口

把每行开头的命令改成你想要的操作:

命令缩写作用
pickp保留该提交
rewordr保留提交,但修改提交说明
edite保留提交,但停下来让你修改内容
squashs与上一个提交合并,并合并提交说明
fixupf与上一个提交合并,丢弃本提交说明
dropd删除该提交

例如,把拼写修复合并到第一个提交里:

pick a1b2c3d 添加登录页面
fixup e4f5g6h 修复拼写错误
pick i7j8k9l 添加登录接口

保存并关闭编辑器,Git 就会按照你的指令重写历史。调换行的顺序还可以重排提交。

自动 fixup

如果已经知道某个修改属于之前的哪个提交,可以这样提交:

git commit --fixup a1b2c3d
git rebase -i --autosquash main

Git 会自动把 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 更安全:如果远程分支在你上次获取之后被别人更新过,它会拒绝推送,避免覆盖他人的提交。