发布分支落后于主干,MR 全是冲突?聊聊业界怎么治这个"祖传"难题
一个被问烂却永远在踩的 Git 协作坑。本文用一次真实对话,讲清根因、规范流程,以及为什么"在 dev 上 merge 一下 pre-release"能合但不对。
0. 引子
前两天有个同事在群里吐槽:
我刚把开发分支 rebase 到最新的 release 主干,结果提 MR 到月度的 pre-release 分支,冲突炸了。pre-release 是另一个同事一周前从 release 拉的,现在明显落后了。我该咋办?
评论区立刻出现三种"民间偏方":
- A:你 rebase 错了,应该 rebase pre-release!
- B:在 dev 上先 merge 一下 pre-release 再提,冲突就没了。
- C:让 pre-release 的人把 release merge 进去不就完了。
三个答案里,只有 C 是根治方案;B 能合但不规范;A 在冻结期才对。大多数人踩坑,是因为没分清"发布分支的状态"。
这篇文章就把这件事讲透。
1. 先把场景画清楚
arduino
release: A──B────C────D ← 主干(最新)
\
pre-release: X──Y ← 一周前从 release 拉的月度预发布分支
\
你的 dev: D──E ← 你刚 rebase 到 D
你提 MR:dev(E) → pre-release(Y)。
冲突从哪来?看三方合并的原理:Git 会找两分支的最近公共祖先(merge base = B),把 B→Y 和 B→E 的差异叠加。
B→Y包含了 X/YB→E包含了 C/D/E
只要 C/D 和 X/Y 改了同一批文件,冲突必然发生。而 pre-release 落后 release 一天,任何基于 release 的 dev 提 MR 过去都会撞上同一批冲突。
所以结论先立住:根因在 pre-release 落后主干,不在你的开发分支。你 rebase release 是对的。
2. 为什么这个问题"野火烧不尽"
很多团队把发布分支当成一条"稳定不变"的线,但其实:
- release 主干在持续收功能(C、D 不断长出来)
- pre-release 拉出去后就很少回主干同步
- 于是两条线的分叉 gap 只会越拉越大
这时候让每个开发分支自己去"适配" pre-release,等于用个人分支的复杂度去掩盖团队流程的缺陷。冲突不会消失,只是被分摊到了每个人的 MR 里,每次都重新解一遍。
业界给这类模型起了个名字:Release Branch 模型(主干常合 + 发布前拉短命 release 分支)。它的铁律只有一条:
发布分支应当持续从主干同步,而不是主干去迁就发布分支,更不是开发分支各自 merge 发布分支来绕过冲突。
3. 规范操作:先看状态,再动手
关键判断点只有一个------pre-release 是否处于冻结期(为发版稳定性,有意不收主干新功能)。
状态一:未冻结(常规同步期)------ 根治方案
正解是让 pre-release 自己追上 release,这是 pre-release 维护者的活:
bash
git fetch
git checkout pre-release
git merge origin/release # 推荐 merge:历史可追溯、不改写共享分支
# 解决 release ↔ pre-release 自身冲突后
git push
pre-release 追平后:
- 你的 MR merge base 变成
D,D→E与D→Y几乎无重叠 → 冲突消失 - 团队所有人后续 MR 都不再撞冲突
若允许改写历史,也可
rebase origin/release+ force push,但共享分支 rebase 必须先在群里广播 ,否则其他人的本地分支会乱。一般发布分支首选merge。
状态二:已冻结 ------ 把 dev 接到 pre-release 顶端
冻结期严禁动 pre-release 基底。改在自己分支上:
bash
git fetch
git checkout 你的dev
git rebase origin/pre-release # 个人分支,安全
# 解决冲突后
git push --force-with-lease
再提 MR,基底一致,无分叉冲突。
硬约束 :你的功能不能依赖 release 上 pre-release 没有的那部分提交。如果依赖,必须协调把依赖提交 cherry-pick 进 pre-release,或等解冻------绝不能绕过冻结期把主干硬塞进发布分支。
4. 那个"能合但不对"的偏方
回头看偏方 B:在 dev 上 git merge pre-release 再提 MR。
它为什么能合?merge 之后 pre-release 的 tip 成了 dev 的祖先,MR 的 merge base 落到 Y,git 基本直接接纳 dev 的内容,MR 通常干净合入。
但它不规范:
- dev 多了一个 merge commit,历史不线性,reviewer 看着脏;后续再 rebase 会被这个 merge commit 反复困扰。
- 根因没解决------pre-release 落后还在,其他人照样冲突。
- 视角不一致风险:你本地 merge(dev 含 D vs pre-release)和 MR 实际合并(base=Y)视角不同,边界情况下可能"你以为解好了、合进去却不对"。
一句话:能合、不报错,但不是正解。
5. 成熟团队靠机制,不靠纪律
规范流程靠人记容易忘,业界用机制兜底:
① 分支保护 + 冲突预检(Mergeability Check) 对 pre-release 开启保护,禁止直推,必须走 MR;MR 创建/更新时跑一个 job:git merge --no-commit origin/pre-release 检测冲突,冲突即标红、阻断合并。把"提了才发现冲突"变成"提之前就看见"。
② 自动同步机器人(Merge Train / Merge Queue) pre-release 未冻结时,用定时/事件触发 release → pre-release 的 auto-merge,让发布分支持续小幅领先,冲突被切碎成每次小量、易解决。GitLab 叫 Merge Train,GitHub 叫 Merge Queue。
③ 发布分支生命周期约束 月度 pre-release 在冻结前应是一条持续从主干同步的活分支;进入冻结期后仅收 hotfix / cherry-pick。把状态写进团队 Wiki 或 CI 配置,避免每个人凭记忆判断。
6. 一页式操作清单
| 步骤 | 动作 | 责任人 |
|---|---|---|
| 1 | 判断 pre-release 是否冻结 | 你 & 同事 |
| 2a | 未冻结:pre-release 上 merge origin/release 追平 |
pre-release 维护者 |
| 2b | 冻结:你的 dev 上 rebase origin/pre-release |
你(个人分支) |
| 3 | git fetch 确保操作的是最新远程 |
双方 |
| 4 | 提 MR,CI 跑冲突预检 + 构建 | 自动 |
| 5 | review 通过后按团队约定合入 | reviewer |
7. 写在最后
你 rebase release 永远是对的;pre-release 落后 release 是异常状态。正确的修复点是 pre-release 自身去追平 release,而不是每个开发分支各自把 pre-release merge 进来绕过。
下次再有人问"MR 到发布分支冲突了怎么办",别急着教他 merge 一下------先问一句:那个发布分支,是不是又落后主干了?