| 编辑推荐: |
本文主要讲清了 Git 里 Merge、Rebase、Squash 三种"合入代码"方式各自解决什么问题——Merge 保留真实分支结构、Rebase 改写基线让历史成直线、Squash 把一堆零散提交压成一个完整提交——并给出各自的适用场景、常用命令和"别对多人共用的已推送分支乱用 Rebase"这类边界。希望对你的学习有帮助。
本文来自于微信公众号脑补空间,由火龙果软件Alice编辑、推荐。 |
|
同一个功能分支,最后都能“合进主分支”,为什么 Git 还要提供 Merge、Rebase
和 Squash?
因为它们处理的并不是同一件事。
Merge 负责汇合分支,Rebase 负责改写提交的起点,Squash
负责压缩提交数量。 三者都会影响最终代码,但留下的提交历史完全不同。
先记住这个简化版判断:
- 想完整保留两条分支如何汇合,用 Merge。
- 想让自己的功能分支跟上主分支,同时保持线性历史,用 Rebase。
- 想把一串“修一下、再修一下”的提交收成一个完整功能,用 Squash。
Merge:把两条历史汇合起来
Merge 不会改写已有提交。它找到两个分支的共同祖先,把双方变化汇合,并在需要时生成一个
merge commit。
这意味着提交历史会保留真实的分支结构:功能从哪里开始、何时合入主线,都能看出来。
适合这些场景:
- 多人共同维护、已经推送的分支;
- 希望保留完整开发过程;
- 发布分支、长期分支之间的合并;
- 团队更看重操作稳妥,而不是提交图绝对整齐。
把feature/payment功能分支合入 main:
git
switch main
git pull --ff-only
git merge --no-ff feature/payment
git push origin main
|
--no-ff 会明确生成一次 merge commit。以后回看历史时,可以一眼看出这批改动来自哪个功能分支。如果团队允许
fast-forward,也可以直接执行:
git
merge feature/payment
|
发生冲突时:
git
status
#
编辑冲突文件
git add <已解决的文件>
git merge --continue
|
不想继续这次合并:
Merge 的代价也很直观:分支多、合并频繁时,提交图容易出现大量交叉线和
merge commit。它保留了现场,也保留了现场的复杂度。
Rebase:把提交搬到新的基线上
假设你从 main 拉出功能分支后,主分支又向前走了几个提交。Rebase
会先取下你的功能提交,把分支基线移动到最新的 main,再逐个重放这些提交。
结果看上去像是:你一开始就是基于最新主分支开发的。
同步最新主分支的常用命令:
git
switch feature/payment
git fetch origin
git rebase origin/main
|
遇到冲突时:
git
status
#
编辑冲突文件
git add <已解决的文件>
git rebase --continue
|
如果发现方向不对,可以退回 Rebase 开始前:
完成后,如果这个分支从未推送过:
git
push -u origin feature/payment
|
如果分支之前已经推送,并且只有你自己在使用:
git
push --force-with-lease origin feature/payment
|
这里不要随手换成 --force。--force-with-lease
会先确认远端分支仍是你预期的状态,能减少覆盖别人新提交的风险。
Rebase 适合个人功能分支,也适合提交合并请求前同步主线。它能让历史保持线性,代码审查和问题定位通常更轻松。
但它有一条明确边界:不要随意 Rebase 多人共用、已经发布的分支。
Rebase 会为被重放的提交生成新的哈希。别人如果仍基于旧提交继续工作,下一次同步会很难受。
Squash:把多个提交压成一个
Squash 不是另一种“同步主分支”的方法。它解决的是提交粒度问题,用于将多个提交合并成一个。
比如,开发过程中进行了4次提交,提交信息如下:
feat:
add payment form
fix: correct button state
fix: lint
fix: really fix lint
|
这些记录可能都是开发同一个需求的时候产生的,对开发者当时有用,但对于主分支并不需要知道这么细节。Squash
可以把它们整理成一个完整提交:
最省事的做法,是在合入主分支时执行 squash merge:
git
switch main
git pull --ff-only
git merge --squash feature/payment
git commit -m "feat:
add payment flow"
git push origin main
|
注意,git merge --squash 只会把功能分支的最终变化放入暂存区,不会自动提交,也不会生成
merge commit。因此下一条 git commit 不能省。
GitHub、GitLab 等平台里的 “Squash and merge”,通常就是把一个合并请求压成一个提交后再放进目标分支。
如果希望在提交合并请求前整理当前分支,可以使用交互式 Rebase。比如压缩最近
4 个提交:
git
switch feature/payment
git rebase -i HEAD~4
|
编辑器打开后,保留第一行的 pick,把后续提交改成 fixup
或 squash:
pick
a1b2c3d feat: add payment form
fixup b2c3d4e fix:
correct button state
fixup c3d4e5f fix:
lint
fixup d4e5f6g fix:
really fix lint
|
fixup 会丢弃后续提交信息,squash 会让你重新整理提交信息。保存退出后,如果这个分支已经推送过,再执行:
git
push --force-with-lease origin feature/payment
|
Squash 很适合短期功能分支。一个功能完成后,对应主分支中的一个提交,回滚和阅读都很直接。
它也有代价:中间提交的上下文会消失。对于需要保留阶段性决策、多人长期协作的分支,不要为了“历史好看”把所有东西都压平。
实际工作中怎么选
三者不是互斥选项。很多团队采用的是组合流程:
#
先在个人功能分支上同步最新主线
git switch feature/payment
git fetch origin
git rebase origin/main
#
测试通过后推送更新
git push --force-with-lease
origin feature/payment
#
合并请求通过后,在平台选择 Squash and merge
|
这种方式让功能分支保持最新,同时让 main 中每个功能只留下一个清晰提交。
如果团队希望保留完整分支关系,则可以换成:
git
switch main
git pull --ff-only
git merge --no-ff feature/payment
git push origin main
|
选择时看三个问题就够了:
- 这个分支是否已经被多人使用?是的话,优先 Merge,谨慎 Rebase。
- 中间提交是否有长期保留价值?有的话保留;没有的话可以 Squash。
- 团队希望看到真实分支结构,还是更偏好线性主线?前者用 Merge,后者常用 Rebase
配合 Squash。
一份可以保存的速查
合并分支并保留分支结构:
git
switch feature/payment
git fetch origin
git rebase origin/main
git push --force-with-lease
origin feature/payment
|
让个人功能分支基于最新主线:
git
switch feature/payment
git fetch origin
git rebase origin/main
git push --force-with-lease
origin feature/payment
|
把整个功能分支压成一个提交:
git
switch main
git pull --ff-only
git merge --squash feature/payment
git commit -m "feat:
add payment flow"
git push origin main
|
整理当前分支最近 4 个提交:
真正需要统一的,不是所有人的 Git 历史长得一模一样,而是团队对历史用途有相同理解:哪些过程值得留下,哪些噪音应该收起,哪些已经公开的提交不能再随意改写。
|