← 返回博客首页

Git Rebase 与 Merge:选择正确的合并策略

一个经典场景

团队里最常见的 Git 争论:合入功能分支时,用 merge 还是 rebase

main:  A --- B --- C --- D
                        \
feature:                E --- F

两种方式都能把 feature 合回 main,但历史看起来完全不同,并直接影响到代码评审、回滚和协作体验。

一句话总结

  • merge:保留真实历史,有合并提交,适合共享分支
  • rebase:重写历史,得到直线提交,适合整理本地提交

Merge:保留真实历史

操作

git checkout main
git merge feature

结果

main:  A --- B --- C --- D -------- G (merge commit)
                              \     /
feature:                       E --- F

优点

  • 历史完整可追溯--graph 能看到分支真实的分叉与汇合
  • 不改变已有提交EF 的 hash 不变,已推送的分支安全
  • 失败可安全重试:冲突解决失败随时可 git merge --abort
  • 团队无认知负担:所有提交保持原样

缺点

  • 历史出现大量合并提交(尤其频繁合 main 时),git log 变得杂乱
  • 难以看出"这条功能到底改了哪几行"——需要 --first-parent 辅助
  • 非线性历史给 git bisect(二分定位 bug)增加一点噪音

适合场景

  • 团队共享的长期分支(main/develop)
  • 多人协作、禁止改写历史的环境
  • 需要完整审计轨迹的发布流程

Rebase:重写为直线历史

操作

git checkout feature
git rebase main
git checkout main
git merge feature   # 此时是 fast-forward,无合并提交

结果

main:    A --- B --- C --- D --- E' --- F'

E'F'EF新副本(hash 完全不同),只是内容相同。

优点

  • 历史干净直线git log 一眼看懂,评审体验佳
  • 代码评审友好git log --oneline --graph 无噪音分叉
  • bisect 更精确:直线历史让二分定位更可靠
  • 配合 squash 整理提交:多个 WIP 提交可合并成一个逻辑提交

缺点

  • 改写历史:提交 hash 改变,已推送的提交会污染共享仓库
  • 冲突要多次解决:每个被搬运的提交都可能重复冲突
  • 误操作恢复成本高:需要 reflog 或备份分支
  • fast-forward 丢失分支上下文:看不出功能分支的存在与合并时机

适合场景

  • 本地开发分支的整理(合入前 squash/reorder)
  • 个人仓库 / 单人项目
  • 开源项目贡献(提交可 review 后 rebase 到上游最新)

对比总表

维度 merge rebase
历史形态 分叉 + 合并提交 直线
提交 hash 不变 全部重写
共享分支安全 ❌(黄金法则)
冲突解决 一次性 每个提交重复
误操作恢复 --abort 即可 需 reflog
代码评审体验 一般 更好
bisect 略嘈杂 更精确
记录合并时机 ✅ 合并提交 ❌ 丢失

黄金法则与工作流建议

黄金法则(不可违背)

永远不要 rebase 已经推送到共享仓库的提交。

一旦提交被其他人 pull 过,rebase 会让所有人的历史分叉,pull 时产生重复提交,只能 force push 修复——这是团队协作事故的高发源头。

单人开发工作流

# 本地随便怎么整理都行
git commit -m "wip: 搭好骨架"
git commit -m "wip: 实现核心逻辑"
git rebase -i HEAD~2   # squash 成 1 个清晰提交
git push

功能分支工作流(推荐组合)

# 1. 合入前把功能分支 rebase 到最新 main,消除冲突且保持直线
git fetch origin
git rebase origin/main

# 2. 再合入 main(此时为 fast-forward)
git checkout main
git pull --ff-only
git merge feature
git push

纯 merge 工作流(保守团队)

git checkout main
git pull
git merge --no-ff feature   # 强制生成合并提交,保留分支上下文
git push

--no-ff 是很多团队对共享分支的强制约定:即使可以 fast-forward 也创建合并提交,保证"这条功能何时合入"在历史里清晰可查。

冲突处理对比

merge 冲突(一次性):

git merge feature
# 解决冲突 → git add → git commit

rebase 冲突(逐提交处理):

git rebase main
# 第 1 个提交冲突 → 解决 → git add → git rebase --continue
# 第 2 个提交冲突 → 解决 → git add → git rebase --continue
# 实在不行 → git rebase --abort 回到原点

误操作恢复:reflog 是安全网

rebase 只是把提交"挪走",并没有立刻物理删除。每次 HEAD 移动都被记入 reflog:

git reflog
# 找到操作前的提交 hash,例如 abc1234
git reset --hard abc1234
场景 恢复命令
rebase 结果不对 git refloggit reset --hard <hash>
误删分支 git reflog 找 hash → git branch <name> <hash>
rebase --abort 后想找回 reflog 中 rebase 前的记录
squash 后后悔 reflog 中找到 squash 前提交

注意:reflog 默认保留 30 天;若提交已被 gc 或从未进入任何分支引用,恢复会更麻烦。


决策建议

场景 推荐
本地整理 WIP 提交 rebase -i(squash + reword)
单人项目 随意,rebase 更清爽
功能分支合入共享 main 推荐 rebase 到最新 + merge(直线)
保守团队 / 审计需求 merge --no-ff(保留完整历史)
已推送的提交 禁止 rebase,用 merge
开源贡献 按上游规范,通常要求 rebase

一句话决策公共历史用 merge,私有历史用 rebase——这条法则能避免 90% 的 Git 协作事故。

更多 Git 日常命令与速查,见本站的 Git 命令速查表

广告

常见问题

rebase 和 merge 的核心区别是什么?

merge 保留真实历史:创建合并提交(merge commit),记录'分支在此汇合'的事实,历史有分叉但完整可追溯。rebase 重写历史:把当前分支的提交逐个'搬运'到目标分支顶端,像从未分叉过,历史是干净的直线,但提交被重新复制(hash 全变),等于改写了原提交。

为什么 rebase 有'黄金法则'?什么情况绝对不能 rebase?

黄金法则:**永远不要 rebase 已经推送到共享仓库的提交**。因为 rebase 会重写提交 hash,若别人已经基于这些提交开发,重写后他们的仓库会与远端分叉,pull 时产生混乱的重复提交,需要 force push 才能修复,极易造成协作事故。只有**本地尚未推送**的提交才能安全 rebase。

交互式 rebase 能做什么?

`git rebase -i` 打开提交清单编辑器,可对任意提交执行:pick(保留)、reword(改提交信息)、edit(修改内容)、squash(与前一个提交合并)、fixup(合并但丢弃信息)、drop(删除)。常用于:把 WIP 提交整理成逻辑清晰的提交、合并琐碎提交、修正历史提交信息。整理后再推送,代码评审体验大幅提升。

误操作 rebase 后提交丢了,怎么找回?

rebase 不会立刻物理删除提交,Git 的 reflog 记录着 HEAD 的每次移动,**30 天内可通过 reflog 找回**:先 `git reflog` 找到操作前的提交 hash,再 `git reset --hard <hash>` 恢复。若已 force push 且远端是唯一副本,恢复会变困难——这也是为什么共享分支上严禁 rebase 的另一个原因。

← 返回博客首页