Git 的 merge 和 rebase 都能合并分支,但效果截然不同。团队协作中用错方案会导致提交历史混乱、冲突难解。本文从原理到实战,讲清楚何时用 merge、何时用 rebase。
merge 与 rebase 区别
merge:三路合并
merge 会创建一个新的合并提交,将两个分支的历史合并在一起:
1 2 3
| A---B---C feature / \ D---E---F---------G main (merge commit G)
|
1 2 3
| git checkout main git merge feature
|
特点:
- 不改变历史
- 保留完整的分支信息
- 合并提交可能看起来有点乱
rebase:变基
rebase 会将当前分支的提交重演到目标分支的顶部:
1 2 3
| A'--B'--C' feature (rebase 后的提交) / D---E---F main
|
1 2 3
| git checkout feature git rebase main
|
特点:
黄金法则
绝对不要对已经推送到远程的提交执行 rebase!
原因:
- rebase 会生成新的提交,内容相似但 hash 不同
- 其他人的分支基于旧提交,会产生分叉
- 强制推送会覆盖他人的工作
场景选择
何时用 merge
1. 合并主分支到特性分支
1 2 3 4 5 6 7 8
| git checkout feature git merge main
|
2. 合并特性分支到主分支(Pull Request)
1 2 3 4 5
| git checkout main git merge feature
|
3. 公共分支
永远不要 rebase main、develop 等公共分支。
何时用 rebase
1. 整理本地提交
在 feature 分支上整理提交历史:
1 2 3 4 5 6
| git rebase -i HEAD~3
|
2. 保持 feature 分支跟随 main
1 2 3 4 5 6 7 8 9 10 11 12
| git rebase main
|
3. 干净的线性历史
如果团队要求线性历史,用 rebase。
冲突解决技巧
rebase 冲突
rebase 遇到冲突时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| git rebase main
vim app.py git add app.py
git rebase --continue
git rebase --skip
git rebase --abort
|
多次冲突
rebase 会逐个提交重演,每个提交都可能冲突:
1 2 3 4 5 6 7 8 9 10
| git rebase main
git add app.py git rebase --continue
|
解决策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| git checkout --theirs app.py git add app.py git rebase --continue
git checkout --ours app.py git add app.py git rebase --continue
vim app.py git add app.py git rebase --continue
|
多人协作工作流
工作流1:Git Flow
适合有固定发布周期的项目。
1 2 3 4 5
| main ──────────────────────► M1 ──────► M2 ──────► │ │ develop ───► D1 ─► D2 ─► M1 ─┘ │ │ │ feature ──────► F1 ───────────────────────────────┘
|
1 2 3 4 5 6 7 8 9
| git checkout -b feature/login develop
git checkout develop git merge --no-ff feature/login
git branch -d feature/login
|
工作流2:Trunk-based Development
适合持续交付的团队。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| git checkout main git pull
git checkout -b feature/quick-fix
git fetch origin git rebase origin/main
git checkout main git merge --no-ff feature/quick-fix
|
工作流3:个人习惯(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| git checkout -b feature/new-feature main
git fetch origin git rebase origin/main
git rebase -i origin/main
git push --force-with-lease
|
实战:修复混乱的提交历史
问题背景
1 2 3 4 5
| git log --oneline a1b2c3d 修复 bug e5f6g7h WIP i9j0k1l WIP m2n3o4p 初始功能
|
方案1:压缩 WIP 提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| git rebase -i HEAD~4
pick m2n3o4p 初始功能 pick i9j0k1l WIP pick e5f6g7h WIP pick a1b2c3d 修复 bug
pick m2n3o4p 初始功能 squash i9j0k1l WIP squash e5f6g7h WIP squash a1b2c3d 修复 bug
|
方案2:重排、编辑、删除
1 2 3 4 5 6 7
| git rebase -i HEAD~4
pick m2n3o4p 初始功能 pick i9j0k1l WIP pick e5f6g7h WIP pick a1b2c3d 修复 bug
|
方案3:恢复误删的提交
1 2 3 4 5 6 7 8 9 10 11
| git reflog
a1b2c3d HEAD@{0}: rebase: 整理提交 e5f6g7h HEAD@{1}: commit: WIP ...
git checkout feature-branch git reset --hard e5f6g7h
|
方案4:拆分提交
1 2 3 4 5 6 7 8 9 10 11 12
| git rebase -i HEAD~1
pick a1b2c3d 修复 bug
git reset HEAD~1 git add file1.py git commit -m "第一部分修改" git add file2.py git commit -m "第二部分修改" git rebase --continue
|
安全规范
1. 永远不要 rebase 公共分支
1 2 3 4 5 6 7
| git checkout main git rebase feature
git checkout main git merge feature
|
2. 使用 –force-with-lease 代替 –force
1 2 3 4
| git push --force-with-lease
|
3. 保护重要分支
在 GitHub/GitLab 设置:
1 2 3 4 5 6 7 8 9
| - name: Protect main branch rules: branches: - main required_status_checks: strict: true enforce_admins: true prevent_force_push: true
|
4. 提交前检查
1 2 3 4 5 6 7 8 9
|
branch=$(git symbolic-ref --short HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "develop" ]; then echo "不允许直接推送公共分支,请使用 PR/MR" exit 1 fi
|
总结
merge vs rebase 选择指南:
| 场景 |
推荐 |
原因 |
| 本地整理提交 |
rebase |
保持历史整洁 |
| 同步 main 到 feature |
rebase |
线性历史 |
| 合并 feature 到 main |
merge |
保留完整历史 |
| PR/MR |
merge |
清晰的合并记录 |
| 公共分支 |
merge |
不改变他人历史 |
| 紧急修复 |
merge |
快速合并 |
黄金法则:
- 本地 rebase:可以,整理提交历史
- 已推送的分支 rebase:绝对不行
- 公共分支:永远只 merge,不 rebase