分支与 Tag 管理
研发开发规范
- 看懂分支模型,知道 main / release / feature / hotfix / custom / dev 各自干嘛
- 按规范给分支命名,能独立从正确的基点拉出新分支
- 理解 tag 为什么不可移动、为什么分支一律从 release 分支头拉
- 分支 branch
- 代码的「平行宇宙」。主线(main)是正式版本,开发者从主线拉出一个分支做修改,不影响主线代码,做完再合并回去。
- 标签 tag
- 给某个时刻的代码打一个不可移动的标记,像书签。分支会随提交移动,tag 不会。我们用 tag 标记每个发布版本。
- 合并 merge
- 把一个分支的改动「融合」到另一个分支。
- GitFlow
- 一种流行的分支管理模型,定义了不同分支的角色和协作方式。我们的分支模型参考了 GitFlow,但做了简化(不设 develop 分支)。
本文档定义公司全部产品研发的分支模型、命名规范、生命周期管理与 Tag 规范,适用于所有前后端开发人员。
- 与现有产物的关系(以下路径取自 EasyPro 仓库,其他产品参照同理):
- data/sql/:初始建库脚本(多数为空占位文件),已迁移为 sql/release/2.1.0/ 全量建库。
- doc/驱动设计文档/modbus...升级SQL.sql:已实践的「游标遍历 + 幂等保护」增量脚本范式,本规范将其作为 SQL 增量脚本标准模板(现已汇总为 sql/release/2.1.1/3_modbus.sql)。
- doc/智问/、doc/驱动设计文档/:散落的 SQL 脚本,已按分支镜像规则归入 sql/ 对应目录(详见 06·SQL 版本化)。
本文档不含强制自动化(如 Git hook、CI 校验),属人工约定。涉及破坏性操作(分支重命名、SQL 迁移、文件清理)的清单见各章末,由负责人确认后另行执行。
本项目采用发布版本化部署(每个版本打成集成安装包,本地化部署到项目现场),因此沿用「长期版本分支」模式,而非纯 GitFlow 或 GitHub Flow:
feature/2.1.0/*功能开发 → 回 release/2.1.0hotfix/2.1.0/*现场修复 → 回 release/2.1.0 + release/2.1.1custom/2.1.0/*客户定制 → 禁止回 releaseline/eos/2.1.0子产品产品线 → 下行同步、上行默认禁止(详见产品线分支)release/2.1.1 — 下个开发中版本,承接 hotfix 回流
release/2.2.0 — 更远的规划版本
多版本并行时,版本号前缀能一眼看出分支基于哪个版本、回流到哪个 release,避免跨版本误操作。
子产品(节能 EOS、轻量版、碳排等)走独立的 line/<产品>/<版本> 产品线,自带 feature/hotfix,长期独立演进、不合并回主版本(详见下方产品线分支节)。
与 GitFlow 的取舍:
- 保留 GitFlow 的「长期 release 分支 + 现场修复回流」思想,契合现场版本化部署。
- 不设独立
develop分支(项目规模与团队节奏不需要集成分支,release 分支直接承担集成职责)。
通用格式:<前缀>/<版本号>/<语义>,其中版本号为语义化版本点分(如 2.1.1,不带 v 前缀)。
release/2.1.1 — 版本主线feature/2.1.1/bacnet-cov — BACnet COV 功能hotfix/2.1.0/knx隧道假死优化 — 语义段中文短语,同样合规custom/2.1.0/huzhou-energy — 湖州定制2.1.1-bacnet — 缺 feature/ 前缀feature/bacnet-cov — 缺版本号feature/2.1.1_智问 — 语义段禁用下划线(中文本身可以)dev-huzhou — 定制应该用 custom/| 类型 | 前缀 | 示例 | 合并去向 |
|---|---|---|---|
| 版本主线 | release/ | release/2.1.1 | — (作为发布基线,长期保留) |
| 功能开发 | feature/ | feature/2.1.1/bacnet-cov | 回所属 release/2.1.1 |
| 现场修复 | hotfix/ | hotfix/2.1.0/cov-fix | 回 release/2.1.0 + 下个开发中 release/2.1.1 |
| 客户定制 | custom/ | custom/2.1.0/huzhou-energy | 禁止合入任何 release |
| 产品线(子产品) | line/ | line/eos/2.1.0 | 长期保留;下行同步、上行默认禁止(详见产品线分支) |
| 个人临时 | dev/ | dev/2.1.1/zhangsan-simulate | 用完即删,不长期保留 |
版本号单独成段、置于语义之前,用 / 分隔。这样按分支名排序时,同一前缀下同版本的分支自然聚拢。
规则细则:
feature//hotfix//custom//dev/必须带版本号,标明基于哪个版本(多版本并行下避免归属不明)。- feature 只面向未发布版本:已打 tag 的版本禁止新开
feature/分支;已发布版本的演进只有两条路:不改库的 hotfix(03·规则三)或 custom 定制(改库需求的唯一出口)。 - 语义部分中文或英文均可:中文用简明短语(如
knx隧道假死优化),英文用小写 + 连字符(如bacnet-cov);禁止空格与特殊符号;应体现「功能/问题」,而非人名。 dev/版本号可省略(个人临时分支、基点随意),但带版本号便于归类时仍推荐。- 前端工程同样遵守本表;如需区分前后端,可在语义部分加
-fe后缀(如feature/2.1.1/optiview-sso-fe)。 - 子产品(产品线)用四段式:
<前缀>/<产品代号>/<版本号>/<语义>(如feature/eos/2.1.0/择机算法),产品代号占第二段、版本号降至第三段;主产品视为默认产品线,省略产品段、仍用三段式(详见产品线分支节)。 - 避免与版本主线节点同名:版本号作为独立
/段时,不能与同前缀下的 release 节点同名(即release/2.1.1与release/2.1.1/xxx不能并存)。由于版本主线用release/2.1.1、其余前缀各异,实际不冲突。
规范命名
feature/2.1.1/bacnet-cov— 功能+版本+语义清晰hotfix/2.1.0/knx隧道假死优化— 中文语义段简明确切custom/2.1.0/huzhou-energy— 客户+功能明确dev/2.1.1/zhangsan-test— 个人实验有归属
不规范命名
feature/bacnet— 缺版本号,不知道基于哪个版本hotfix/v2.1.0/fix— 版本号不该带 vfeature/2.1.1_智问— 下划线禁用(中文可以)dev-zhangsan— 缺前缀斜杠,看着像定制
创建基点规则:
| 分支类型 | 从哪里拉 | 命令 |
|---|---|---|
feature/ | 当前开发中(未打 tag)的 release/x.x.x;已发布版本禁止 | git checkout -b feature/2.1.1/xxx release/2.1.1 |
hotfix/ | 对应版本的 release 分支头 | git checkout -b hotfix/2.1.0/xxx release/2.1.0 |
custom/ | 对应版本的 release 分支头,创建后保持下行同步 | git checkout -b custom/2.1.0/huzhou-xxx release/2.1.0 |
dev/ | 任意基点(个人临时) | git checkout -b dev/2.1.1/zhangsan-xxx |
line/ | 对应主产品版本的 release 分支头(分叉基线),之后保持下行同步 | git checkout -b line/eos/2.1.0-1.0.0 release/2.1.0 |
feature/<产品>/(产品线内) | 该产品线主线 line/<产品>/<版本> | git checkout -b feature/eos/2.1.0/xxx line/eos/2.1.0 |
hotfix/<产品>/(产品线内) | 该产品线主线头 line/<产品>/<版本> | git checkout -b hotfix/eos/2.1.0/xxx line/eos/2.1.0 |
为什么一律从 release 分支头拉,不从 tag 拉?回流门禁(03·规则二)保证 release 分支头始终对应「现场已验证状态」,即 tag + 历次已验证修复,只接收通过验证的回流。这样从 release 头拉分支,现场验证的产物(release 头 + 修复)与回流后的 release 内容完全一致,验证即对回流生效;若从 tag 拉,验证的只是「tag + 修复」,回流到已前进的 release 后该组合从未被验证。custom/ 与 line/ 同理:release 头比冻结的 tag 更完整,下行同步一次即把全部通用修复带给客户/子产品。若分叉时必须与某个精确发布包对齐,先 git reset 到对应 tag 再拉。
git checkout -b feature/2.1.1/bacnet release/2.1.1 · 期限 ≤2 周
git checkout -b hotfix/2.1.0/fix release/2.1.0 · 只改代码不改库
git checkout -b custom/2.1.0/huzhou-energy release/2.1.0 · 单独打安装包交付
git checkout -b dev/2.1.1/zhangsan-test · 期限 ≤3 天,用完即删
关键:feature / hotfix / custom / line 一律从 release 分支头拉取,不从 tag 拉。前提是回流门禁保证 release 头 =「tag + 历次已验证修复」,基线可信,且比冻结的 tag 更完整;分叉必须与某个精确发布包对齐时,先 git reset 到对应 tag 再拉。
使用期限:
feature/≤ 2 周,超期需拆分。dev/≤ 3 天,仅作临时实验。hotfix/现场验证通过、双向回流完成后即删除,不长期保留。custom/可长期存在(持续服务某客户),但需在doc/备案其定制内容。line/主线及产品线内分支随子产品生命周期长期存在;新建产品线须先在doc/规则/产品线登记.md登记(详见产品线登记)。
为什么 feature 要限时 2 周、dev 限时 3 天?分支活得越久,与 release 头的偏差就越大,合回时冲突面指数上涨,现场验证的范围也跟着膨胀,最后变成「不敢合、合不动」。期限是在逼着把大功能拆成可验证的小步,每一步独立走「开发 → 现场验证 → 回流」。dev 分支同理:实验超过 3 天还没转正,要么转 feature 走正式流程,要么删掉。
清理流程:
# 1. 删除本地已合并分支(保留 main 与所有 release/) git branch --merged | grep -vE '^\*|main|release/' | xargs -n1 git branch -d # 2. 清理远程已删除分支的本地引用 git fetch -p # 3. 删除远程已合并分支(需人工确认) git push origin --delete <branch-name> # 建议每月执行一次清理,负责人统一处理
为什么每月都要清理分支?这是踩过的坑:仓库曾积累 90+ 个分支,大量已合并的 dev-人名 分支长期残留,命名空间被污染,定制分支混在个人分支里差点被误合并进新版本(见 历史问题与迁移任务)。已合并的分支留着没有任何价值,只会让下一个人拉错基线。
- 格式:统一
vX.Y.Z(语义化版本 SemVer);维护性微版本用四段vX.Y.Z.N(如v2.1.0.1,见 03·规则五)。tag 段数即类型:三段 = 正式版,四段 = 微版本。
- MAJOR:不兼容的大版本(2.x → 3.x)
- MINOR:新功能版本(2.1.x → 2.2.0)
- PATCH:补丁版本(2.1.0 → 2.1.1)
- 打 tag 时机:版本发布的当天,tag 号与安装包版本号完全一致。
- 必须用 annotated tag(带说明信息):
git tag -a v2.1.0 -m "Release v2.1.0 功能: - 新增 BACnet IP 驱动 COV 订阅 - 智问大模型配置 修复: - Modbus 写功能码切换 - COV 订阅判断逻辑"
- 废弃混用:当前存在的
2.0.0(无 v)、v2.0.0-pre、ems-lite_V1.0.0应统一为vX.Y.Z格式;pre-release 用vX.Y.Z-rc.N(如v2.1.0-rc.1)。 - 子产品 tag 格式:
<产品代号>-vX.Y.Z(如 EOS 用eos-v2.1.0),主产品 tag 格式vX.Y.Z不变。注意子产品hotfix/<产品>/、custom/<产品>/的拉取基点是其产品线主线头(line/<产品>/<版本>),tag 用于标记发布版本(详见产品线命名格式)。
除主产品外,公司还有若干独立子产品:节能 EOS、轻量版(ems-lite)、碳排(carbon)等。它们有三个共同点:
- 基于主产品某个版本分叉而来,但长期独立演进、不合并回主版本;
- 有自己的多版本节奏(基于 2.1.0 → 演进到自己的 2.1.1 → ……);
- 内部同样需要
feature/、hotfix/这套二级分支。
现有的 custom/(见 03-合并回流与定制隔离)是为单客户交付设计的,承载不了子产品的二级结构与多版本演进;而让它们裸名(reduceEnergy、ems-lite、carbon)与版本分支同处一层,又会污染命名空间。为此引入产品线分支 line/。
核心思路是在主产品三段式的第二段插入产品代号,前缀不变,这样前缀仍表达「做什么」,第二段表达「给哪个产品」,两个维度正交。
不把子支线挂到 line/ 下(如 line/eos/feature/...),而是让前缀即类型——与主产品体系统一、GitLab 按类型折叠、Hook 与服务端保护规则可按 feature/* 一套复用。
| 前缀 | 主产品(三段) | 产品线(四段) | 产品线示例 | 从哪拉 | 回流去向 |
|---|---|---|---|---|---|
| 主线 | release/2.1.0 | line/eos/2.1.0 | line/eos/2.1.0 | 主产品 release/2.1.0 头 | 发布基线,长期保留 |
| 功能 | feature/2.1.0/x | feature/eos/2.1.0/x | feature/eos/2.1.0/择机算法 | line/eos/2.1.0 | 回 line/eos/2.1.0 |
| 修复 | hotfix/2.1.0/x | hotfix/eos/2.1.0/x | hotfix/eos/2.1.0/电价bug | line/eos/2.1.0 头 | 回 line/eos/2.1.0 + line/eos/2.1.1 |
| 定制 | custom/2.1.0/客户-x | custom/eos/2.1.0/客户-x | custom/eos/2.1.0/huzhou-报表 | line/eos/2.1.0 头,保持下行同步 | 禁回 line/eos,也禁回主产品 |
| 临时 | dev/2.1.1/x | dev/eos/2.1.1/x | dev/eos/2.1.1/zhangsan-x | 任意 | 用完即删 |
- 产品代号英文小写(
eos/lite/carbon),全局唯一,在doc/规则/产品线登记.md备案后使用。 - 语义段复用 §2.2 规则;产品代号不计入语义段。
- 产品线主线
line/<产品>/<版本>长期保留,地位等同release/<版本>。 - 子产品版本号二选一,登记时定死(一条产品线只允许一种):独立编号(推荐,子产品有自身发布节奏时)主线用双版本段
line/<产品>/<基线版本>-<自身版本>(如line/eos/2.1.0-1.0.0,读作「基于 2.1.0 的 EOS 1.0.0」,基线在前、创建后不变);同号(完全跟随主产品节奏时)基线=自身,单段即可,如line/eos/2.1.0。 - 子支线与交付物只带自身版本(基线由所属主线决定):
feature/eos/1.1.0/<语义>、sql/line/eos/1.0.0/、sql/tag/eos/1.0.0/;tag 用自身版本(如eos-v1.0.0),annotated 说明中记录对应基线。 - 大版本换基 = 新主线分支:下个自身大版本若换到新主产品基线(如 2.1.1),新建
line/eos/2.1.1-2.0.0(旧主线保留),登记表更新并在备注追加换基记录;日常下行同步是 merge,不改基线段。 - 维护性微版本 tag 一律四段:子产品
eos-v1.0.0.1、主产品v2.1.0.1——三段 = 正式版,四段 = 微版本,看段数即知类型;子产品正式小版本仍用修订位递增(1.0.0 → 1.0.1,三段 tag)。 - Tag 格式
<产品>-vX.Y.Z(如eos-v2.1.0),主产品 tag 不变。
hotfix/eos、custom/eos 的基点是 EOS 自己的产品线主线头(line/eos/2.1.0),不是主产品 release/2.1.0——确保基线与 EOS 实际演进状态一致,并随下行同步获得最新修复。
- ① 下行同步(主线→子产品):允许、选择性。子产品负责人按需
git merge或rebase主产品通用更新。 - ② 上行回流(子产品→主线):默认禁止(与
custom/同思想)。 - ③ 例外:通用修复回流。子产品上修了主产品也存在的通用 bug,负责人判断后
cherry-pick单个提交回主产品release/——人工判断、逐提交、严禁整分支合并。 - ④ 子产品内部双向回流。
hotfix/eos/2.1.0/xxx须回流到line/eos/2.1.0+ 下个line/eos/2.1.1,规则同主产品 hotfix,只是release/换成line/eos/。 - ⑤ 子产品内的客户定制(
custom/eos/...)双重不回流。既禁回line/eos/,也禁回主产品release/;交付方式为从该custom/eos/单独打安装包。
# ① 下行同步:把主产品通用更新搬进子产品 git checkout line/eos/2.1.0 git merge release/2.1.0 # 或按需 cherry-pick 特定提交 # ③ 例外:子产品上的通用 bug 回流主产品(逐提交,人工确认) git checkout release/2.1.1 git cherry-pick <eos-通用修复-commit> # ④ 子产品 hotfix 双向回流 git checkout line/eos/2.1.0 && git merge hotfix/eos/2.1.0/xxx git checkout line/eos/2.1.1 && git merge hotfix/eos/2.1.0/xxx git branch -d hotfix/eos/2.1.0/xxx
line/(产品线)vs custom/(客户定制)| 维度 | custom/ 客户定制 | line/ 产品线 |
|---|---|---|
| 分叉原因 | 某客户的定制需求 | 一个独立子产品 |
| 二级结构 | 无(单线) | 有(内部 feature/hotfix) |
| 版本节奏 | 跟随主产品基线 | 独立多版本演进 |
| 回主产品 | 禁止 | 默认禁止(通用 bug 可逐提交 cherry-pick) |
| 从主产品取更新 | 单向 cherry-pick | 下行同步 merge/rebase |
| 生命周期 | 服务某客户,可长期 | 子产品存活期,长期 |
每个子产品在 doc/规则/产品线登记.md 备案:产品代号、中文名、基线主产品版本、维护人、当前版本、产品线主线分支、交付现场、文档链接。
无登记不开产品线分支——避免产品代号泛滥、归属与维护人不明确。
我的分支名写了 v 前缀:hotfix/v2.1.0/fix-login,被说不对?
v2.1.0)。分支里写 hotfix/2.1.0/fix-login 即可。这样分支和 tag 一眼能区分:带 v 的是 tag,不带 v 的是分支。规范让我从 release/2.1.0 分支头拉 hotfix,可分支头上不是可能有未发布代码吗?
git reset 到对应 tag 再拉分支即可。我把客户定制的代码合进了 release,想着大家一起用,结果被批评了?
custom/ 分支严禁合入任何 release。因为定制代码是给某一个客户的,一旦进了 release,所有客户升级时都会拿到——可能引发意料外的问题。定制只在该 custom/ 分支单独打安装包交付给那个客户。我不知道该用 feature/ 还是 dev/,感觉都是我自己开发?
feature/ 是正式的功能开发(要合回 release、会被评审,期限 ≤2 周);dev/ 是个人临时实验(用完即删、期限 ≤3 天,不进正式流程)。如果你只是想试试某个想法,用 dev/;如果是要交付的功能,用 feature/。分支名里我用了中文或下划线,会有什么后果?
hotfix/2.1.0/knx隧道假死优化;英文则用小写 + 连字符)。真正禁止的是空格与特殊符号,以及用下划线做分隔(fix_login 应写 fix-login)——它们在脚本校验、URL 转义、跨平台时都可能出问题。语义段应体现「功能/问题」而非人名。我把 EOS 的功能分支挂到了 line 下面,写成 line/eos/feature/择机算法,感觉层级很清晰,为什么说不规范?
feature/eos/2.1.0/择机算法(前缀 / 产品 / 版本 / 语义)。这样和主产品的 feature/2.1.0/xxx 体系统一:前缀即类型,GitLab 能按 feature/* 一套规则折叠与保护。挂成 line/eos/feature/... 会让前缀含义混乱、按类型聚合失效。我在 EOS 上修了个 bug,直接从主产品的 v2.1.0 拉了 hotfix/eos/2.1.0/电价bug,结果基线对不上?
line/eos/2.1.0)拉,不是主产品 release/2.1.0,更不是主产品 tag。主产品分支里没有 EOS 分叉后的演进代码——从主产品拉,基线既不是 EOS 现场版本、又缺了 EOS 的功能,要修的 bug 也无从附着。主产品自己的分支一律从 release 头拉,产品线内部对应的就是 line/<产品>/<版本> 主线头。我在 EOS 上修了个主产品也有的通用 bug,顺手把 line/eos 整支 merge 回了 release/2.1.1,结果被要求回退?
cherry-pick。因为 line/eos 上混着 EOS 专属功能(择机算法等)和通用修复,整支 merge 会把 EOS 专属代码也灌进主产品、污染所有客户的版本。正确做法:人工挑出那几个通用修复的 commit,git cherry-pick <commit> 逐个搬到 release/;判断标准是这个 bug 主产品是否也存在。EOS 专属的东西永远不回主产品。