4. 环境分支与发布分支体系

当团队规模扩大、部署环境增多之后,仅靠一条 main 分支已经无法同时满足“稳定可发布”“持续集成”“预发验证”“生产热修”这几类互相冲突的需求。环境分支与发布分支体系的核心思路,是把代码的成熟度与运行环境的稳定性要求对应起来:越接近生产的分支,变更越少、准入越严;越靠近开发的分支,集成越频繁、节奏越快。

本节讨论的是一套典型的多环境分支模型,包含 main、develop、staging、production 以及临时性的 release/* 分支。需要先明确一个前提:分支不是越多越好,每增加一条长期分支,就增加一份合并与同步成本。因此下面这套体系适用于“有独立预发环境、有明确发布窗口、需要生产热修”的中大型团队;如果团队只有测试与生产两套环境,可以裁剪掉 staging 或 production 中的一条。

各分支的职责定位

先看每条分支“为什么存在”,这决定了它的准入规则和生命周期。

  • main:主干分支,代表“当前已发布到生产的最新状态”。它应当始终可构建、可回滚,是生产版本的唯一事实来源。任何进入 main 的提交,理论上都对应一次已上线的发布。
  • develop:集成分支,代表“下一个版本正在开发中的状态”。日常功能分支(feature/*)合并到这里,CI 在此高频运行。develop 允许短暂不稳定,但不允许长期红灯。
  • staging:预发分支,代表“即将发布、等待验收的候选状态”。它部署到与生产高度一致的预发环境,用于回归测试、性能压测和业务验收。
  • production:生产分支,代表“生产环境实际运行的代码”。在采用“main 即生产”的团队里,production 可以与 main 合一;当需要区分“已合并”和“已部署”时,才单独保留 production,用于记录部署标记与热修。
  • release/*:发布分支,临时存在,代表“某个版本从冻结到上线之间的稳定化过程”。命名如 release/2.7.0,从 develop 切出,只接受缺陷修复,不再接受新功能。

这里有一个容易混淆的点:staging 与 release/* 都服务于“发布前”,但职责不同。release/* 是代码维度的冻结线,控制哪些改动还能进入本版本;staging 是环境维度的部署线,控制哪份代码被部署到预发环境验证。一个版本可以对应多次 staging 部署,但通常只对应一条 release 分支。

多环境分支流转与合并方向 开发环境 高频集成 预发环境 验收回归 发布冻结 仅修缺陷 生产环境 稳定运行 develop staging release/2.7.0 main / production 部署候选 切出冻结 回归验证 发布上线 热修回灌 hotfix/* 从 main 切出 功能提交 预发部署 发布冻结 生产发布 热修回灌

流转规则:谁可以合并到谁

分支体系能否落地,关键不在“画出来”,而在“合并方向被强制约束”。下面给出这套体系的核心流转规则,建议通过 GitLab 的 Protected Branch 与 Merge Request 审批规则固化下来。

源分支目标分支触发时机准入条件
feature/*develop功能开发完成CI 通过 + 至少 1 人 Code Review
developstaging需要预发验证CI 通过 + 部署流水线成功
developrelease/*版本冻结版本范围确认 + 冻结公告
release/*staging候选版本回归仅缺陷修复提交
release/*main验收通过、正式发布回归通过 + 发布审批
maindevelop发布后回灌自动或手动同步,防止版本漂移
hotfix/*main生产紧急缺陷从 main 切出 + 快速审批
hotfix/*develop热修完成后确保修复不丢失

其中最容易出错的是最后两条:热修分支必须同时合并回 main 和 develop。如果只合并到 main,下一次从 develop 发布时,这个修复就会被“覆盖掉”,形成典型的版本回退事故。建议在 GitLab 中为 hotfix/* 配置一个合并后自动创建回灌 MR 的规则,或至少在 MR 模板中强制勾选“已回灌 develop”。

版本号与分支命名的对应

release/* 的命名建议直接采用语义化版本号,例如 release/2.7.0、release/2.7.1。这样分支名、Tag 名、镜像 Tag 三者可以对齐,便于追溯。发布完成后,在 main 上打 Tag v2.7.0,并删除 release/2.7.0 分支(或保留只读归档)。删除分支不会删除提交历史,因为提交已经通过合并进入 main,这一点可以在团队内明确说明,减少“不敢删分支”的心理负担。

# 从 develop 切出发布分支
git checkout develop
git pull origin develop
git checkout -b release/2.7.0
git push -u origin release/2.7.0

# 发布完成后打 Tag 并合并回主干
git checkout main
git pull origin main
git merge --no-ff release/2.7.0
git tag -a v2.7.0 -m "Release 2.7.0"
git push origin main --tags

# 回灌 develop,避免版本漂移
git checkout develop
git merge --no-ff main
git push origin develop

环境部署与分支的绑定关系

分支体系要真正支撑持续交付,必须让“分支 → 环境”的映射在流水线中固定下来,而不是靠人工选择。推荐做法是:develop 的每次推送自动部署到开发环境;staging 分支的推送自动部署到预发环境;main 上的 Tag 触发生产部署,且生产部署需要手动确认(manual job)。这样既保证了开发与预发的自动化,又保留了生产发布的最后一道人工闸门。

如果团队使用 GitLab CI,可以用 rules 按分支名决定部署目标,例如 $CI_COMMIT_BRANCH == "staging" 时执行预发部署,$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ 时执行生产部署。这样分支职责与流水线行为一一对应,减少人为误操作。

常见问题与判断依据

体系运行一段时间后,通常会出现两类信号,需要据此调整。第一类是“develop 长期落后于 main”,说明回灌没有执行,判断依据是 git log main..develop 中缺少已发布的修复提交;第二类是“staging 与 release 内容不一致”,说明有人绕过 release 直接向 staging 推送,判断依据是对比两条分支的 diff 出现非缺陷类改动。前者通过自动化回灌解决,后者通过把 staging 设为 Protected Branch、只允许 release/* 合并进入来解决。

最后要强调,分支模型是团队协作的契约,不是 Git 的强制功能。main、develop、staging、production、release/* 这套体系的价值,在于让每个环境都有明确的代码来源,让每次发布都有可追溯的冻结线,让生产热修有规范的回归路径。团队规模、发布频率和环境数量不同,可以裁剪分支,但“环境与分支对应、合并方向受控、热修必须回灌”这三条原则应当保留。