rebase 和 merge 的争论是 Git 工作流中最常见的讨论之一。两个命令都将一个分支的更改整合到另一个分支,但方式完全不同。理解何时使用哪个对于维护清晰、可导航的项目历史至关重要。
Git Merge 工作原理
Git merge 创建一个新的"合并提交"来连接两个分支的历史。它保留了完整的历史记录和提交的时间顺序。
当你运行 git merge feature 时,Git 找到两个分支的公共祖先,然后创建一个新的提交来合并两者的更改。
A---B---C feature
/ \
D---E---F---G---H main (merge commit)
$ git checkout main
$ git merge featureGit Rebase 工作原理
Git rebase 将你的提交移动(或"重放")到另一个分支的顶部。它重写提交历史以创建线性的提交序列。
当你在功能分支上运行 git rebase main 时,Git 取出你分支中的每个提交,临时移除它们,将分支更新到 main 的最新状态,然后逐个在顶部重新应用每个提交。
Before rebase:
A---B---C feature
/
D---E---F---G main
After rebase:
A'--B'--C' feature
/
D---E---F---G main
$ git checkout feature
$ git rebase main可视化对比
╔═══════════════════════════════════════════╗
║ MERGE RESULT ║
║ ║
║ D---E---F---G---H main ║
║ \ / ║
║ A---B---C feature ║
║ ║
║ ✓ Preserves all commits ║
║ ✓ Shows merge point ║
║ ✗ Non-linear history ║
╚═══════════════════════════════════════════╝
╔═══════════════════════════════════════════╗
║ REBASE RESULT ║
║ ║
║ D---E---F---G---A'--B'--C' main ║
║ ║
║ ✓ Clean linear history ║
║ ✓ Easy to read ║
║ ✗ Rewrites commit hashes ║
╚═══════════════════════════════════════════╝何时使用 Merge
- 公共/共享分支——Merge 不会重写历史,对其他人正在使用的分支是安全的。
- 保留上下文——合并提交显示了功能分支何时以及由谁整合。
- 团队协作——多人在同一分支工作时,merge 避免了历史重写导致的冲突。
- 发布分支——合并发布分支保留了确切的整合点。
# Merge with merge commit (recommended for main)
$ git checkout main
$ git merge --no-ff feature
$ git push origin main何时使用 Rebase
- 功能分支(合并到 main 之前)——Rebase 在最终合并前创建干净的线性历史。
- 保持更新——将功能分支 rebase 到最新的 main 上,获取新更改而不产生合并提交。
- 提交 PR 之前——经过 rebase 的干净提交更容易审查。
- 清理本地提交——交互式 rebase 让你在分享之前压缩、重排和编辑提交。
# Update feature branch with latest main
$ git checkout feature
$ git rebase main
# If conflicts occur, resolve them, then:
$ git add .
$ git rebase --continue交互式 Rebase:压缩、修正、重排
交互式 rebase(git rebase -i)是 Git 最强大的功能之一。它允许你在分享前修改提交。
$ git rebase -i HEAD~4
# Editor opens:
pick a1b2c3d Add user authentication
pick e4f5g6h Fix typo in auth
pick i7j8k9l Add password validation
pick m0n1o2p Fix lint error
# Change to:
pick a1b2c3d Add user authentication
fixup e4f5g6h Fix typo in auth # merge into previous, discard message
pick i7j8k9l Add password validation
fixup m0n1o2p Fix lint error # merge into previous, discard message
# Result: 2 clean commits instead of 4
# Commands:
# pick = use commit as-is
# squash = merge into previous commit, keep message
# fixup = merge into previous commit, discard message
# reword = use commit but edit message
# drop = remove commit entirely黄金法则:永远不要 Rebase 公共分支
如果分支已推送且其他人正在使用它,不要 rebase。Rebase 会重写提交哈希,其他开发者将面临分叉的历史和痛苦的合并冲突。
黄金法则:只 rebase 没有推送到共享远程仓库的提交,或只有你自己在使用的分支。
# ❌ DANGEROUS: Rebasing a shared branch
$ git checkout main
$ git rebase feature # NEVER do this!
# ✅ SAFE: Rebasing your own feature branch
$ git checkout my-feature # only I work on this
$ git rebase main # safe!
$ git push --force-with-lease origin my-featureMerge vs Rebase:完整对比
| 标准 | Merge | Rebase |
|---|---|---|
| 历史记录 | 非线性,保留所有分支 | 线性,整洁 |
| 提交哈希 | 保持不变 | 改变(新哈希) |
| 冲突解决 | 一次性解决 | 每个提交可能都要解决 |
| 可追溯性 | 高(合并提交显示上下文) | 中(线性但缺少分支信息) |
| 安全性 | 安全(不重写历史) | 有风险(重写历史) |
| 适合共享分支 | ✅ | ❌ |
| 适合个人分支 | ✅ | ✅ |
| 可撤销性 | 简单(git revert) | 较难(需要 reflog) |
| git bisect | 较难(非线性) | 更容易(线性历史) |
常见 Git 工作流
功能分支工作流
创建功能分支,开发,rebase 到 main,然后用 --no-ff 合并。这提供了带有清晰合并点的线性历史。
$ git checkout -b feature/login
# ... develop ...
$ git rebase main # clean up history
$ git checkout main
$ git merge --no-ff feature/login # merge with commit
$ git branch -d feature/loginGitflow 工作流
使用 develop、feature、release 和 hotfix 分支。长期分支之间始终 merge(不要 rebase)。
# Always merge between long-lived branches
$ git checkout develop
$ git merge --no-ff feature/login
$ git checkout release/1.0
$ git merge --no-ff develop
$ git checkout main
$ git merge --no-ff release/1.0
$ git tag v1.0主干开发
短期功能分支(< 1 天)。频繁 rebase 到 main,快速合并。高效团队的首选。
# Short-lived branch, rebase often
$ git checkout -b fix/typo
# ... quick fix ...
$ git rebase main
$ git checkout main
$ git merge fix/typo # fast-forward
$ git push危险场景
| 场景 | 风险 | 预防 |
|---|---|---|
| Rebase main/master | 所有人的历史都被破坏 | 永远不要 rebase main |
| Rebase 共享功能分支 | 协作者面临分叉历史 | 对共享分支使用 merge |
| git push --force | 覆盖远程更改 | 使用 --force-with-lease |
| 冲突解决后继续 rebase 出错 | 代码丢失或损坏 | 仔细检查每个冲突,使用 git rebase --abort 重来 |
| Rebase 有合并提交的分支 | 合并提交被展平,导致困惑 | 使用 --rebase-merges 或避免 |
常见问题
可以撤销 rebase 吗?
可以。使用 git reflog 找到 rebase 前的提交哈希,然后用 git reset --hard <hash> 恢复。reflog 默认保留所有 HEAD 移动记录 90 天。
更新功能分支时应该 rebase 还是 merge?
如果只有你一个人在功能分支上工作,rebase 更好,历史更干净。如果其他人也在使用该分支,用 merge 避免重写共享历史。
什么是 git pull --rebase?
git pull --rebase 将你的本地提交重放到远程更改之上,而不是创建合并提交。它保持历史线性。
如果 rebase 了已推送的分支会怎样?
你需要强制推送(git push --force-with-lease)。已拉取旧提交的人将面临分叉历史。这就是为什么只应 rebase 你独自工作的分支。
squash merge 和 rebase + squash 一样吗?
结果类似,机制不同。GitHub 的 "Squash and merge" 按钮将所有提交压缩为一个并创建合并提交。Rebase -i 的 squash 让你选择压缩哪些提交,控制力更强。
开源贡献应该用哪个?
大多数开源项目倾向于经过 rebase 的干净提交。提交 PR 前,rebase 到最新 main 并压缩修正提交。