mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-19 16:39:15 +00:00
docs(workflow): codify the branch model — main / feature / release branches
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/<desc>: 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.
This commit is contained in:
141
docs/git-workflow-en.md
Normal file
141
docs/git-workflow-en.md
Normal file
@ -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/<desc>` | 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 <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.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)。
|
||||
128
docs/git-workflow.md
Normal file
128
docs/git-workflow.md
Normal file
@ -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 <hotfix-commit> # 回主分支
|
||||
```
|
||||
这是本模型最重要的一条:**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)。
|
||||
Reference in New Issue
Block a user