feat: 上架版本号按分支拆分,master / develop 各自独立

原来 master 与 develop 共用一条 release 轨,版本号和构建号绑在一起。
现改为每个上架分支各有一条轨道(轨道名即分支名),自测分支仍共用 feature 轨。

- 轨道记录新增 kind(release/feature),branch_track 存轨道名
- 旧的共用 release 轨及更早的单轨结构,加载时自动按上架分支拆开,
  已用构建号原样复制,拆分后各分支从原来的号继续递增
- 构建号递增时跨上架轨取同一 App_Ver 的最大值 +1:App Store 的构建号
  唯一性按 MARKETING_VERSION 全局判定,避免两分支同版本号时重号被拒
- 分支管理页轨道选择改为「上架(本分支独立版本号)/ 自测(共用)」,
  打包设置页按上架分支分块填写版本号

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJaZ913U5GVdTkYHiRpvDU
This commit is contained in:
shenlei
2026-09-02 10:32:43 +09:00
co-authored by Claude Opus 5
parent dae14a8a10
commit bf7a5a0449
9 changed files with 430 additions and 189 deletions
+60 -15
View File
@@ -229,15 +229,20 @@ def test_update_versions_rejects_invalid_app_version(client, tmp_config):
def test_versions_tracks_are_independent(client, tmp_config):
# 两条轨道的 App_Ver 各自独立,互不影响
assert client.put("/api/config/versions", json={"track": "release", "app_ver": "2.196.0"}).status_code == 200
# 每个上架分支一条轨道(轨道名即分支名),App_Ver 各自独立,互不影响
assert client.put("/api/config/versions", json={"track": "main", "app_ver": "2.196.0"}).status_code == 200
assert client.put("/api/config/versions", json={"track": "dev", "app_ver": "2.198.0"}).status_code == 200
assert client.put("/api/config/versions", json={"track": "feature", "app_ver": "2.100.0"}).status_code == 200
data = client.get("/api/config/versions").json()
assert data["tracks"]["release"]["app_ver"] == "2.196.0"
assert data["tracks"]["main"]["app_ver"] == "2.196.0"
assert data["tracks"]["dev"]["app_ver"] == "2.198.0"
assert data["tracks"]["feature"]["app_ver"] == "2.100.0"
assert data["tracks"]["feature"]["build_ver"] == "2.100.0.0"
# 顶层字段镜像 release 轨,兼容旧客户端
assert data["tracks"]["main"]["kind"] == "release"
assert data["tracks"]["feature"]["kind"] == "feature"
# 顶层字段镜像主上架轨(第一个上架分支),兼容旧客户端
assert data["primary_track"] == "main"
assert data["app_ver"] == "2.196.0"
@@ -246,7 +251,8 @@ def test_update_versions_rejects_unknown_track(client, tmp_config):
assert resp.status_code == 400
def test_legacy_versions_migrate_to_release_track(client, tmp_config):
def test_legacy_versions_split_per_release_branch(client, tmp_config):
"""最旧的单轨配置:拆成每个上架分支一条轨,各自沿用原来的已用构建号"""
import json
with open(tmp_config, "r", encoding="utf-8") as f:
cfg = json.load(f)
@@ -255,28 +261,67 @@ def test_legacy_versions_migrate_to_release_track(client, tmp_config):
json.dump(cfg, f)
data = client.get("/api/config/versions").json()
assert data["tracks"]["release"] == {
"app_ver": "2.195.0",
"build_ver": "2.195.0.7",
"build_map": {"2.195.0": 7},
}
for branch in ("main", "dev"):
assert data["branch_track"][branch] == branch
assert data["tracks"][branch] == {
"kind": "release",
"app_ver": "2.195.0",
"build_ver": "2.195.0.7",
"build_map": {"2.195.0": 7},
}
# feature 轨以同一 App_Ver 初始化,构建号从 .0 起
assert data["tracks"]["feature"]["app_ver"] == "2.195.0"
assert data["tracks"]["feature"]["build_ver"] == "2.195.0.0"
# 拆分后各轨独立:改 main 不影响 dev
assert client.put("/api/config/versions", json={"track": "main", "app_ver": "2.196.0"}).status_code == 200
data = client.get("/api/config/versions").json()
assert data["tracks"]["main"]["app_ver"] == "2.196.0"
assert data["tracks"]["dev"]["app_ver"] == "2.195.0"
def test_legacy_shared_release_track_splits_per_branch(client, tmp_config):
"""旧的 master/develop 共用 release 轨:拆成各分支自己的轨,构建号原样带过去"""
import json
with open(tmp_config, "r", encoding="utf-8") as f:
cfg = json.load(f)
cfg["versions"] = {"tracks": {
"release": {"app_ver": "2.195.0", "build_map": {"2.195.0": 4}},
"feature": {"app_ver": "2.100.0", "build_map": {}},
}}
cfg["branch_track"] = {"main": "release", "dev": "release"}
with open(tmp_config, "w", encoding="utf-8") as f:
json.dump(cfg, f)
data = client.get("/api/config/versions").json()
assert "release" not in data["tracks"]
assert data["branch_track"] == {"main": "main", "dev": "dev"}
for branch in ("main", "dev"):
assert data["tracks"][branch]["app_ver"] == "2.195.0"
assert data["tracks"][branch]["build_ver"] == "2.195.0.4"
assert data["tracks"]["feature"]["app_ver"] == "2.100.0"
def test_branch_track_defaults_and_override(client, tmp_config):
assert client.post("/api/config/branches", json={"name": "develop"}).status_code == 200
assert client.post("/api/config/branches", json={"name": "feature/login"}).status_code == 200
tracks = client.get("/api/config/versions").json()["branch_track"]
assert tracks["develop"] == "release"
assert tracks["feature/login"] == "feature"
data = client.get("/api/config/versions").json()
# 上架分支各有一条以分支名命名的轨道,自测分支共用 feature 轨
assert data["branch_track"]["develop"] == "develop"
assert data["branch_track"]["feature/login"] == "feature"
assert data["tracks"]["develop"]["kind"] == "release"
# 手动改归属
# 手动改归属:改成上架后拥有自己的轨道
resp = client.put("/api/config/branches/track", json={"branch": "feature/login", "track": "release"})
assert resp.status_code == 200
assert client.get("/api/config/versions").json()["branch_track"]["feature/login"] == "release"
assert resp.json()["track"] == "feature/login"
data = client.get("/api/config/versions").json()
assert data["branch_track"]["feature/login"] == "feature/login"
assert data["tracks"]["feature/login"]["kind"] == "release"
# 改回自测则重新共用 feature 轨
assert client.put("/api/config/branches/track", json={"branch": "feature/login", "track": "feature"}).status_code == 200
assert client.get("/api/config/versions").json()["branch_track"]["feature/login"] == "feature"
assert client.put("/api/config/branches/track", json={"branch": "nope", "track": "release"}).status_code == 404
assert client.put("/api/config/branches/track", json={"branch": "develop", "track": "beta"}).status_code == 400