mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-03 23:54:06 +00:00
三处都是"文档说的"与"仓库实际做的"不一致,读文档的人会被误导。
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 取最大)。
158 lines
6.9 KiB
Markdown
158 lines
6.9 KiB
Markdown
# 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/<desc>` | short (dev → merge → delete) | New features / ordinary fixes. Born from `main`, merged back into `main`. |
|
|
| Release | `release/vX.Y.x` | 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.6.x release/v1.7.x
|
|
│ │
|
|
tag: v1.6.0 tag: v1.7.6
|
|
│ │
|
|
hotfix ◄─────┘ hotfix ◄─────┘
|
|
│ │
|
|
└── cherry-pick back ──────────┘
|
|
```
|
|
|
|
The patch position in the branch name is a literal `x`, while the tag carries
|
|
the concrete version: one `release/v1.7.x` can hold tags v1.7.0 … v1.7.6.
|
|
Spanning several patches on a single minor branch is deliberate — patches are
|
|
revisions of the same feature batch, hotfixes land on one branch, and back-port
|
|
to main never has to resolve dependencies between several release branches.
|
|
|
|
### 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.7.x main
|
|
git tag -a v1.7.0 -m "ModelRouter v1.7.0"
|
|
git push origin release/v1.7.x v1.7.0
|
|
```
|
|
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.7.x # fix in the release branch
|
|
git commit -m "fix: ..."
|
|
git checkout main
|
|
git cherry-pick <hotfix-commit> # 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.7.x`). 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.
|
|
|
|
> **Where practice diverged from this section (checked 2026-10-01)**:
|
|
> `release/v1.4.x` and `release/v1.5.x` still exist locally and on the remote,
|
|
> so "retire the previous branch when the next version ships" was never
|
|
> carried out. Keeping them is harmless (hotfixes were back-ported), but it
|
|
> contradicts the rule above and makes a reader wonder whether they should be
|
|
> there at all. **Feature branches, by contrast, are cleaned up**:
|
|
> `feature/key-quota-control`, `feature/toolcall-id-sanitize`,
|
|
> `feature/anthropic-usage-cache` and `feature/agentrouter-id-sanitize` were
|
|
> deleted on 2026-10-01 after confirming with a per-commit `git patch-id`
|
|
> comparison that their work had already landed in main.
|
|
|
|
## 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.x` branch +
|
|
the concrete `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)。 |