From 3806aaee03426217fb77a04af50b75a5e31a40f2 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 31 Aug 2026 12:36:17 +0800 Subject: [PATCH] =?UTF-8?q?docs(workflow):=20codify=20the=20branch=20model?= =?UTF-8?q?=20=E2=80=94=20main=20/=20feature=20/=20release=20branches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt GitHub Flow + release branches, replacing "everything straight to main plus a tag" which caused the 1.4.2 pain (a fix had to be retro-fitted to the released version, forcing a remote-tag delete + full re-upload). - main: only long-lived branch, always deployable, accumulates the next version - feature/: born from main, merged back when done - release/vX.Y.Z: cut from main, tagged, installers built from the tag - hotfixes land on the release branch AND are cherry-picked back to main so main never loses a fix - end of lifecycle = retire the release branch (delete; or keep for long-term maintenance), no wholesale merge back — hotfixes already flowed - explicitly no rebase of main, no release-branch-merge, no quick edits on main Companion release checklist includes the upload lessons (PUT --http1.1) and the replace-artifacts-by-deleting-the-tag catch. Docs in docs/git-workflow.md (zh) and docs/git-workflow-en.md, linked from both READMEs. --- README.md | 9 +++ README_EN.md | 11 ++++ docs/git-workflow-en.md | 141 ++++++++++++++++++++++++++++++++++++++++ docs/git-workflow.md | 128 ++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 docs/git-workflow-en.md create mode 100644 docs/git-workflow.md diff --git a/README.md b/README.md index a50a67d..65372f4 100644 --- a/README.md +++ b/README.md @@ -490,6 +490,15 @@ internal/gateway # OpenAI 兼容 HTTP 服务 + 鉴权 + SDK/流式 + Web internal/types # 统一格式 & OpenAI wire 类型 ``` +## 开发协作 + +- **Git 分支工作流**:主分支 / 特性分支 / 发布分支的规范见 + **[docs/git-workflow.md](docs/git-workflow.md)**([English](docs/git-workflow-en.md))。 + 主线:`main` 永可部署 → 特性合入 `main` → 切 `release/vX.Y.Z` 打 tag 发布 → + hotfix 提交发布分支并 cherry-pick 回 `main`。 +- **Lua 适配器协议**:见 **[docs/lua-adapters.md](docs/lua-adapters.md)** + ([English](docs/lua-adapters-en.md))。 + ## 测试 ```bash diff --git a/README_EN.md b/README_EN.md index af0e278..f623c85 100644 --- a/README_EN.md +++ b/README_EN.md @@ -507,6 +507,17 @@ internal/gateway # OpenAI-compatible HTTP + auth + SDK/streaming + WebUI internal/types # unified format & OpenAI wire types ``` +## Development & collaboration + +- **Git branching workflow**: main / feature / release-branch conventions in + **[docs/git-workflow-en.md](docs/git-workflow-en.md)** + ([中文](docs/git-workflow.md)). The line: `main` is always deployable → + features merge into `main` → cut `release/vX.Y.Z` and tag → release → hotfixes + land on the release branch and are cherry-picked back into `main`. +- **Lua adapter protocol**: see + **[docs/lua-adapters-en.md](docs/lua-adapters-en.md)** + ([中文](docs/lua-adapters.md)). + ## Tests ```bash diff --git a/docs/git-workflow-en.md b/docs/git-workflow-en.md new file mode 100644 index 0000000..8c3064b --- /dev/null +++ b/docs/git-workflow-en.md @@ -0,0 +1,141 @@ +# Git Branching Workflow + +ModelRouter uses a **GitHub Flow + release-branch** model: `main` is the only +long-lived branch and is always deployable; new work branches off it as a +feature branch; each version gets its own release branch with a tag; hotfixes +land on the release branch and MUST flow back to `main`. + +Goal: make "patching an already-released version during its support window" a +first-class operation, while `main` always contains every fix and stays +deployable. + +## Branch roles + +| Branch | Name | Lifetime | Purpose | +|---|---|---|---| +| Main | `main` | permanent | Only long-lived branch. Always deployable. Accumulates the next version. | +| Feature | `feature/` | short (dev → merge → delete) | New features / ordinary fixes. Born from `main`, merged back into `main`. | +| Release | `release/vX.Y.Z` | one version cycle | Cut from `main`, tagged for release. Version-specific hotfixes land here. | + +## Change flow (important) + +``` + feature/foo ───────┐ + ▼ + main ──────────────── next version ──────► + │ │ + │ cut │ cut + ▼ ▼ + release/v1.4.2 release/v1.4.3 + │ │ + tag: v1.4.2 tag: v1.4.3 + │ │ + hotfix ◄─────┘ hotfix ◄─────┘ + │ │ + └── cherry-pick back ──────────┘ +``` + +### Key rules + +1. **main is always deployable**: never leave half-done work on `main`. + Unfinished work lives on a feature branch. +2. **Features start from main and merge back**: `git checkout -b feature/xxx main`, + then `git merge --no-ff feature/xxx` (or squash) when done. +3. **Release = cut a release branch from main + tag**: + ```bash + git checkout -b release/v1.4.2 main + git tag -a v1.4.2 -m "ModelRouter v1.4.2" + git push origin release/v1.4.2 v1.4.2 + ``` + Build installers and upload the GitCode Release from this tag so the + published state is exactly reproducible. +4. **Only version-specific hotfixes go into a release branch during its + window**: new features always go to `main` for the next version, never into + an already-released branch (unless you deliberately ship a minor revision). +5. **Hotfixes MUST flow back to main**: + ```bash + git checkout release/v1.4.2 # fix in the release branch + git commit -m "fix: ..." + git checkout main + git cherry-pick # and into main + ``` + Most important rule: **main only moves forward and never loses fixes**. If a + hotfix never reaches main, the next release ships with the old bug. + +### End of a version lifecycle + +When the next version ships, the previous release branch retires: + +- **Default: delete the remote release branch** + (`git push origin :release/v1.4.2`). All hotfixes were already + cherry-picked into main, so main contains everything; no merge needed. +- **Long-term maintenance** (e.g. an enterprise client pinned to an old + version): keep the branch, accept only security fixes, keep the + commit-then-cherry-pick loop. + +## Explicit non-goals + +- **Never rebase main**: main's history stays append-only; anyone pulling gets + directly usable history. +- **Release branches are not merged back wholesale**: hotfixes were already + cherry-picked; a whole-branch merge only creates conflicts with no benefit. +- **No "quick edit" directly on main**, even single-person: at minimum use + `feature/xxx → merge main` so history keeps its why-boundaries. + +## Single vs multi person + +- **Single** (this repo today): skip PRs — `git checkout -b feature/xxx` → + finish → merge back to main. Release branch + tag flow unchanged. +- **Multi / open source**: feature branches go through PR + Code Review, + maintainers merge; resolve conflicts on the feature branch, never on main. + +## Commit message conventions + +- `feat(...)`: new feature +- `fix(...)`: bug fix +- `chore(...)`: build / version / deps / non-code changes +- `docs(...)`: documentation +- `refactor(...)`: refactoring +- Scope in parens, e.g. `fix(adapters)`, `feat(webui)`, `chore(version)` +- One-line summary; expand with problem/root-cause/fix/verification when needed + +## Release checklist (companion) + +```bash +# 1. main is ready +git checkout main && git pull + +# 2. cut release branch + tag +git checkout -b release/vX.Y.Z main +git tag -a vX.Y.Z -m "ModelRouter vX.Y.Z" + +# 3. push branch + tag +git push origin release/vX.Y.Z +git push origin vX.Y.Z + +# 4. build installers (core + GUI platforms) +make core-dist VERSION=X.Y.Z + +# 5. create GitCode Release + upload +# https://gitcode.com/JianFeeeee/ModelRouter/releases +# upload MUST use PUT --http1.1, otherwise the OBS callback fails and the +# file never lands in the release; replacing artifacts = delete the tag +# (release disappears with it) then recreate. +``` + +## Why this model (background) + +Before 2026-08 everything went straight to `main` plus a tag, which caused two +pain points: +1. After 1.4.2 shipped, a scheduler-scoring fix had to be added to the same + version — the only option was deleting the remote tag to clear the release + and re-uploading all installers. The published state was detached from code + history; "what exactly shipped" could not be traced. +2. No feature branches meant two independent efforts could not proceed in + parallel without colliding. + +With release branches: the published state = `release/vX.Y.Z` branch + +`vX.Y.Z` tag, exactly reproducible; hotfixes have a clear landing spot; main +stays "latest + all fixes + deployable". + +> 中文版见 [docs/git-workflow.md](git-workflow.md)。 \ No newline at end of file diff --git a/docs/git-workflow.md b/docs/git-workflow.md new file mode 100644 index 0000000..363d5a7 --- /dev/null +++ b/docs/git-workflow.md @@ -0,0 +1,128 @@ +# Git 分支工作流(Branching Workflow) + +ModelRouter 采用 **GitHub Flow + 发布分支** 模型:`main` 是唯一长命分支且永远可部署, +新工作从它开出特性分支;每个版本从 `main` 分出独立的发布分支并打 tag;hotfix 提交到 +发布分支且必须回流 `main`。 + +目的:让「已发版的版本在生命周期内可被单独修补」成为一等操作,同时 `main` 始终包含 +全部修复、永远可部署。 + +## 分支角色 + +| 分支 | 命名 | 生命周期 | 用途 | +|---|---|---|---| +| 主分支 | `main` | 永久 | 唯一长命分支。永远可部署。积攒下一个版本的功能。 | +| 特性分支 | `feature/<描述>` | 短命(开发→合并即删) | 新特性 / 一般 bug 修复。从 `main` 开出,完成后合回 `main`。 | +| 发布分支 | `release/vX.Y.Z` | 一个版本周期 | 从 `main` 分出,打 tag 发布。该版本生命周期内的 hotfix 都提交在此分支。 | + +## 变更流向(重要) + +``` + feature/foo ───────┐ + ▼ + main ──────────────── 下一个版本 ──────► + │ │ + │ 切出 │ 切出 + ▼ ▼ + release/v1.4.2 release/v1.4.3 + │ │ + tag: v1.4.2 tag: v1.4.3 + │ │ + hotfix ◄─────┘ hotfix ◄─────┘ + │ │ + └── cherry-pick 回 main ───────┘ +``` + +### 关键规则 + +1. **main 永远可部署**:不在 main 上留半成品。任何未完成的工作必须在特性分支上。 +2. **特性从 main 开,完成后合回 main**:`git checkout -b feature/xxx main`, + 开发完 `git merge --no-ff feature/xxx` 或 squash 合回。 +3. **发布 = 从 main 切 release 分支 + 打 tag**: + ```bash + git checkout -b release/v1.4.2 main + git tag -a v1.4.2 -m "ModelRouter v1.4.2" + git push origin release/v1.4.2 v1.4.2 + ``` + 构建安装包、上传 GitCode Release 都基于这个 tag,保证可精确回溯发布态。 +4. **版本生命周期内只收该版本的 hotfix**:新特性一律并入 `main` 等下一个版本, + 绝不塞进已发布的 release 分支(除非主动选择在该版本内发次要版)。 +5. **hotfix 必须回流 main**: + ```bash + git checkout release/v1.4.2 # 在发布分支提交修复 + git commit -m "fix: ..." + git checkout main + git cherry-pick # 回主分支 + ``` + 这是本模型最重要的一条:**main 只前进、不丢修复**。如果 hotfix 不回 main, + 下一个版本就会带着旧 bug 发布。 + +### 版本生命周期结束 + +下一个版本发布时,上一个 release 分支退役: + +- **默认:直接删除远端 release 分支**(`git push origin :release/v1.4.2`)。 + 因为 hotfix 都已逐个 cherry-pick 回 main,main 已包含全部修复,无需再合并。 +- **如需要长期维护旧版**(例如企业大客户卡在旧版本):保留分支,仅 stopship 接受 + 该版本的安全修复,继续走「提交 + cherry-pick 回 main」循环。 + +## 明确不做的事 + +- **不 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)` +- 一句话总结,必要时正文展开「问题 / 根因 / 修复 / 验证」 + +## 发布操作速查(配套) + +每次发布严格按此顺序: + +```bash +# 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](git-workflow-en.md)。 \ No newline at end of file