Files
ModelRouter/docs/git-workflow.md
JianFeeeee ad54616bee docs: 对齐分支命名实际用法 + 修正示例配置的存储位置说明
三处都是"文档说的"与"仓库实际做的"不一致,读文档的人会被误导。

1. 分支命名:文档写 release/vX.Y.Z 且举例 release/v1.4.2,实际从
   release/v1.4.x 起一律用字面 x(release/v1.5.x / v1.7.x)。改成实际情况,
   并说明"一条 minor 分支跨多个 patch"是刻意的:patch 是同批功能的修订,
   hotfix 落同一条分支,回流 main 时不必处理多条 release 分支间的依赖。

2. 退役规则与实践不符:文档说"下个版本发布就删上一个 release 分支",但
   release/v1.4.x / v1.5.x 至今仍在。保留无害(hotfix 已回流),但与规则
   矛盾。两份文档都如实记下这个出入,并记录特性分支**确实**在清理——本次
   删除的四个分支都逐提交用 git patch-id 核对过,工作已全部进入 main
   (唯一 patch-id 不同的是 bf0657b 的发布前变体,diff 过 api.go 改动逐字节
   相同)。留着只是给下个版本制造 cherry-pick/merge 陷阱。

3. 存储位置:示例配置说"WebUI 新增/编辑的源会写入 runtime_file",且默认
   模型注释说 AUTO"按各源 priority 自动选最高可用源"。两处都与实现相反——
   上游源、网关密钥、AUTO 链都在 config.yaml(AddSource 走
   UpsertSourceInYAML,密钥与链走 cfg.Save);runtime.json 只剩源模板、
   删除标记和预置模板名单。已核实 store.Upsert(runtime 源)已无调用点,
   store 里的 Keys/Auto 只被加解密、从不写入,是遗留字段。

priority 本身不是完全没用,所以注释保留并说清它的两个真实用途:首次启动
seedAuto 的初值,以及 AUTO 生图链未配置时挑"最佳模型"(bestChatModel /
bestImageModel 按 priority 取最大)。
2026-10-01 20:41:57 +08:00

6.9 KiB
Raw Blame History

Git 分支工作流(Branching Workflow)

ModelRouter 采用 GitHub Flow + 发布分支 模型:main 是唯一长命分支且永远可部署, 新工作从它开出特性分支;每个版本从 main 分出独立的发布分支并打 tag;hotfix 提交到 发布分支且必须回流 main。

目的:让「已发版的版本在生命周期内可被单独修补」成为一等操作,同时 main 始终包含 全部修复、永远可部署。

分支角色

分支 命名 生命周期 用途
主分支 main 永久 唯一长命分支。永远可部署。积攒下一个版本的功能。
特性分支 feature/<描述> 短命(开发→合并即删) 新特性 / 一般 bug 修复。从 main 开出,完成后合回 main。
发布分支 release/vX.Y.x 一个版本周期 从 main 分出,打 tag 发布。该版本生命周期内的 hotfix 都提交在此分支。

变更流向(重要)

        feature/foo ───────┐
                           ▼
                        main ──────────────── 下一个版本 ──────►
                           │                        │
                           │ 切出                   │ 切出
                           ▼                        ▼
                     release/v1.6.x            release/v1.7.x
                           │                        │
                      tag: v1.6.0               tag: v1.7.6
                           │                        │
              hotfix ◄─────┘                hotfix ◄─────┘
                   │                              │
                   └── cherry-pick 回 main ───────┘

分支名里 patch 位是字面的 x,而 tag 打具体版本号:release/v1.7.x 这一条 发布分支上的 tag 可以有 v1.7.0 … v1.7.6 多个。一条 minor 分支跨多个 patch 是 刻意的:patch 是同一批功能的不同修订,hotfix 落在同一条分支上,回流 main 时 也不必处理多条 release 分支之间的依赖。

关键规则

  1. main 永远可部署:不在 main 上留半成品。任何未完成的工作必须在特性分支上。
  2. 特性从 main 开,完成后合回 main:git checkout -b feature/xxx main, 开发完 git merge --no-ff feature/xxx 或 squash 合回。
  3. 发布 = 从 main 切 release 分支 + 打 tag:
    git checkout -b release/v1.7.x main
    git tag -a v1.7.0 -m "ModelRouter v1.7.0"
    git push origin release/v1.7.x v1.7.0
    
    构建安装包、上传 GitCode Release 都基于这个 tag,保证可精确回溯发布态。
  4. 版本生命周期内只收该版本的 hotfix:新特性一律并入 main 等下一个版本, 绝不塞进已发布的 release 分支(除非主动选择在该版本内发次要版)。
  5. hotfix 必须回流 main:
    git checkout release/v1.7.x     # 在发布分支提交修复
    git commit -m "fix: ..."
    git checkout main
    git cherry-pick <hotfix-commit> # 回主分支
    
    这是本模型最重要的一条:main 只前进、不丢修复。如果 hotfix 不回 main, 下一个版本就会带着旧 bug 发布。

版本生命周期结束

下一个版本发布时,上一个 release 分支退役:

  • 默认:直接删除远端 release 分支(git push origin :release/v1.7.x)。 因为 hotfix 都已逐个 cherry-pick 回 main,main 已包含全部修复,无需再合并。
  • 如需要长期维护旧版(例如企业大客户卡在旧版本):保留分支,仅 stopship 接受 该版本的安全修复,继续走「提交 + cherry-pick 回 main」循环。

实践与本节的历史出入(2026-10-01 核对):release/v1.4.x 与 release/v1.5.x 至今仍在本地与远端,说明"下一个版本发布就删上一个分支"实际没有执行。 保留无害(hotfix 已回流),但它与上面写的规则不一致,读文档的人会以为 这些分支不该存在。特性分支则确实在清理:feature/key-quota-control、 feature/toolcall-id-sanitize、feature/anthropic-usage-cache、 feature/agentrouter-id-sanitize 四个分支在 2026-10-01 删除——它们的工作 早已全部进入 main(逐提交用 git patch-id 比对确认),留着只是给下个版本 制造 cherry-pick/merge 陷阱。

明确不做的事

  • 不 rebase main:main 的历史保持追加式,任何人拉取后 git pull 都得到直接可用的历史。
  • 发布分支不整体 merge 回 main:hotfix 已逐个 cherry-pick,整体合并只会制造冲突且无收益。
  • 不在 main 直接写"临时改一下":即使是单人项目,也至少走 feature/xxx → merge main 的形式,让历史保留"为什么改"的边界。

单人 vs 多人

  • 单人(本项目当前):特性分支可省去 PR,git checkout -b feature/xxx → 完成 → 合并回 main。发布分支与 tag 流程完全一致。
  • 多人 / 开源协作:特性分支走 PR(pull request)+ Code Review, 由维护者 merge;冲突在特性分支上解决,不在 main 上解决。

提交信息规范(与分支配套)

  • feat(...): 新功能
  • fix(...): 修复
  • chore(...): 构建 / 版本号 / 依赖 / 文档无关改动
  • docs(...): 文档
  • refactor(...): 重构
  • 括号内为影响域,如 fix(adapters)、feat(webui)、chore(version)
  • 一句话总结,必要时正文展开「问题 / 根因 / 修复 / 验证」

发布操作速查(配套)

每次发布严格按此顺序:

# 1. 确认 main 就绪
git checkout main && git pull

# 2. 切发布分支并打 tag
git checkout -b release/vX.Y.Z main
git tag -a vX.Y.Z -m "ModelRouter vX.Y.Z"

# 3. 推送分支 + tag
git push origin release/vX.Y.Z
git push origin vX.Y.Z

# 4. 构建安装包(核心包 + GUI 各平台)
make core-dist VERSION=X.Y.Z

# 5. 创建 GitCode Release + 上传安装包
#   见 https://gitcode.com/JianFeeeee/ModelRouter/releases
#   上传注意:必须 PUT --http1.1,否则 OBS 回调失败文件不进列表;
#   替换旧版附件 = 删 tag(release 随 tag 一起消失)后重建。

为什么是这套(背景)

2026-08 之前本项目直接在所有改动提交到 main + 打 tag,出现两个痛点:

  1. 1.4.2 已发版后又追了一个调度打分修复,只能删远端 tag 清空 release 再重传安装包 —— 版本发布态与代码历史脱节,无法精确回溯"当时发的是什么"。
  2. 没有特性分支,无法并行开发互不干扰的两件事。

引入发布分支后:版本发布态 = release/vX.Y.Z 分支 + vX.Y.Z tag,可精确回溯; hotfix 有明确落点;main 保持"最新 + 全部修复 + 可部署"。

英文版见 docs/git-workflow-en.md。