Git rebase 变基

rebase(变基)是 Git 里最强大也最让新手畏惧的命令。它和 merge 都能合并分支,但思路完全不同:merge 是"把两条线汇到一起",rebase 是"把一条线的起点挪到另一条线的末尾",让历史变成一条直线。这一章讲清楚 rebase 的原理、交互式 rebase 的妙用,以及那条不能违反的黄金法则。

1. rebase 是什么

场景:你从 main 拉了 feature 分支开发,期间别人往 main 提交了新代码。现在你的 feature 落后于 main。git rebase main 会把你 feature 上的提交"摘下来",接到 main 最新位置的后面,就像你是在最新 main 基础上开发的一样。

# rebase:把当前分支的起点"挪"到目标分支最新位置
# 场景:你在 feature 开发时,main 已经被别人更新了

# 合并前(有分叉):
#   A - D - E        (main)
#    \
#     B - C          (feature)
#
git switch feature
git rebase main
# Git 把 B、C 摘下来,接到 E 后面,重新生成 B'、C'

# rebase 后(历史"拉直"了):
#   A - D - E - B' - C'   (main, feature)
#
# B' C' 是新的提交,哈希变了(因为是"重新播放"出来的)

# 如果有冲突:
# 1. Git 暂停,提示哪些文件冲突
# 2. 手动解决冲突文件
# 3. git add 解决后的文件
git add .
git rebase --continue        # 继续下一个提交
# 中途想放弃:
git rebase --abort           # 回到 rebase 前

注意:rebase 会改写提交哈希——B 变成 B',C 变成 C',因为它们是"在新基底上重新播放"出来的新提交。内容一样,但身份证号变了。

2. rebase vs merge

这是 Git 协作里永恒的话题。两者没有绝对优劣,理解区别后根据场景选择:

# rebase vs merge 的本质区别
#
# 用 merge 合并 feature 到 main:
#   A - D - E - M            (main, M 是合并提交)
#    \         /
#     B - C ---              (feature)
#   历史保留了"分叉"的痕迹,真实但杂
#
# 用 rebase 后再 merge:
#   A - D - E - B' - C'      (main, feature)
#   历史是一条直线,干净
#
# 总结:
#   merge  :保留分叉历史,产生合并提交(真实,但历史有岔)
#   rebase :把提交"拉直",历史是一条线(干净,但改写了提交哈希)
#
# 团队约定好规则即可。常见做法:
#   - 个人 feature 分支开发时,经常 rebase 主干,避免分叉太远
#   - 最终合并回主干时,用 merge 或 squash merge 保留功能边界

一个常见的折中策略:个人 feature 分支开发期间经常 rebase 主干(保持和最新代码同步、减少最终冲突),合并回主干时用 merge 或 squash(保留功能边界,方便日后追溯"这个功能是哪个 PR 加的")。

3. 交互式 rebase — 整理历史的神器

普通 rebase 只是把分支挪位置,而 git rebase -i(interactive)能让你逐个审视提交:压扁、改 message、删掉、重排顺序。这是 push 之前"美化历史"的最强工具。

# 交互式 rebase:整理历史的最强工具
# 场景:push 之前,把 feature 上的多个提交"整理"一下

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

# Git 会打开编辑器,列出这 4 个提交:
# pick   a1b2c3d feat: 添加登录表单
# pick   e4f5g6h fix: 修了个 typo
# pick   i7j8k9l wip
# pick   m0n1o2p feat: 添加校验
#
# 把 pick 改成下面的动作:
#   pick(p)   保留(默认)
#   reword(r) 保留提交但改 commit message
#   squash(s) 把这个提交合并到上一个(共用 message)
#   fixup(f)  同 squash 但丢弃这个的 message
#   drop(d)   删除这个提交
#   edit(e)   暂停,让你修改这个提交的内容
#   reorder   调整行的顺序 = 调整提交顺序
#
# 示例:压成 1 个干净的提交
pick   a1b2c3d feat: 添加登录表单
fixup  e4f5g6h fix: 修了个 typo
drop   i7j8k9l wip
fixup  m0n1o2p feat: 添加校验
# 保存退出后,Git 把 4 个提交整理成 1 个(登录表单)

典型用途:

4. rebase 的黄金法则

绝对不要 rebase 已经 push 到公共分支的提交。

原因:rebase 会改写提交哈希。如果同事已经基于你的旧哈希在工作,你 rebase 后他们的历史就对不上了,pull 时会遇到噩梦般的冲突——同样的改动出现两份,Git 无法自动合并。

安全的做法:

5. 让 pull 自动 rebase

很多人不喜欢 git pull 产生的"Merge branch main"提交——明明只是同步代码,却污染了历史。配置一下让 pull 默认用 rebase:

# 让 pull 自动用 rebase(避免无意义的 merge 提交)
git config --global pull.rebase true
# 之后 git pull 等价于 git pull --rebase

# 设置默认只 rebase 当前 push 出去的分支(安全)
git config --global rebase.autoStash true
# rebase 前自动 stash 工作区改动,rebase 完自动 pop

# 查看当前 rebase 配置
git config --get pull.rebase

# GitHub 网页合并时选 "Rebase and merge"
# 也能保持 main 历史线性(不产生 merge 提交)

6. rebase 中断与恢复

rebase 过程中遇到冲突会暂停,你会看到提示。几个常用操作:

新手建议

← 上一篇 Git 工作流

下一篇 Git stash 暂存

✈️💬