一个经典场景
团队里最常见的 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能看到分支真实的分叉与汇合 - 不改变已有提交:
E、F的 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' 是 E、F 的新副本(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 reflog → git 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 命令速查表。