# 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.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 # 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)。