🐛 fix(vscode): 同一天再切发布分支自动编号 .2 / .3,不再报已存在

This commit is contained in:
cruldra committed 2026-09-17 17:52:26 +08:00
1 parent b77f2b1b63
commit 936097757b
3 files changed
+76 -42

No files matched your search

+34 -20
View File
@@ -11,11 +11,11 @@ import {
indexUnreleasedByMergePr,
listReleaseBranches,
listUnreleasedCommits,
nextReleaseBranch,
normalizeReleasePrefix,
parseAlembicRevision,
parseGiteaMergeSubject,
pickCurrentRelease,
todayReleaseBranch,
} from '../../src/git/release'
const gitEnv = { ...process.env, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', GIT_TERMINAL_PROMPT: '0', GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t' }
@@ -44,6 +44,8 @@ function migration(cwd: string, file: string, rev: string, down: string | null,
mkdirSync(path.dirname(path.join(cwd, file)), { recursive: true })
return commit(cwd, file, `revision = '${rev}'\ndown_revision = ${down === null ? 'None' : `'${down}'`}\n`, msg)
}
const AUG01 = new Date(2026, 7, 1)
const AUG31 = new Date(2026, 7, 31)
let fx: Fx | undefined
afterEach(() => {
if (fx)
@@ -57,11 +59,19 @@ describe('纯函数', () => {
expect(normalizeReleasePrefix(' rel ')).toBe('rel/')
expect(normalizeReleasePrefix('release/')).toBe('release/')
})
it('todayReleaseBranch', () => {
expect(todayReleaseBranch('release/', new Date(2026, 7, 31))).toBe('release/2026-08-31')
it('nextReleaseBranch 当天首次不带序号,再切从 .2 起,取最大序号加一', () => {
expect(nextReleaseBranch('release/', ['main'], AUG31)).toBe('release/2026-08-31')
expect(nextReleaseBranch('release/', ['release/2026-08-30', 'release/2026-08-30.2'], AUG31)).toBe('release/2026-08-31')
expect(nextReleaseBranch('release/', ['release/2026-08-31'], AUG31)).toBe('release/2026-08-31.2')
expect(nextReleaseBranch('release/', ['release/2026-08-31', 'release/2026-08-31.2'], AUG31)).toBe('release/2026-08-31.3')
expect(nextReleaseBranch('release/', ['release/2026-08-31.3'], AUG31)).toBe('release/2026-08-31.4')
expect(nextReleaseBranch('release/', ['release/2026-08-31.x'], AUG31)).toBe('release/2026-08-31')
})
it('pickCurrentRelease 取前缀内字典序最大', () => {
it('pickCurrentRelease 先按日期再按同日序号,.10 排在 .9 后', () => {
expect(pickCurrentRelease('release/', ['main', 'release/2026-08-01', 'release/2026-08-31', 'hotfix/2027'])).toBe('release/2026-08-31')
expect(pickCurrentRelease('release/', ['release/2026-08-31.2', 'release/2026-08-31'])).toBe('release/2026-08-31.2')
expect(pickCurrentRelease('release/', ['release/2026-08-31.10', 'release/2026-08-31.9'])).toBe('release/2026-08-31.10')
expect(pickCurrentRelease('release/', ['release/2026-08-31.3', 'release/2026-09-01'])).toBe('release/2026-09-01')
expect(pickCurrentRelease('release/', ['main'])).toBeUndefined()
})
it('parseGiteaMergeSubject 认 Gitea 合并说明', () => {
@@ -98,17 +108,20 @@ describe('集成', () => {
expect(s).toMatchObject({ devBranch: 'main', current: undefined, unreleased: 0 })
expect(s.unavailable).toBeUndefined()
})
it('cutRelease 纯远端切分支,不建本地分支', async () => {
it('cutRelease 纯远端切分支,不建本地分支;同一天再切得到 .2', async () => {
fx = await setup()
const { repo, origin } = fx
await cutRelease({ cwd: repo, devBranch: 'main', branch: 'release/2026-08-31' })
expect(await cutRelease({ cwd: repo, devBranch: 'main', prefix: 'release/', now: AUG31 })).toEqual({ branch: 'release/2026-08-31', count: 0 })
expect(git(origin, ['rev-parse', 'release/2026-08-31'])).toBe(git(origin, ['rev-parse', 'main']))
expect(() => git(repo, ['rev-parse', '--verify', 'refs/heads/release/2026-08-31'])).toThrow()
await expect(cutRelease({ cwd: repo, devBranch: 'main', branch: 'release/2026-08-31' })).rejects.toThrow(/已存在/)
expect(await cutRelease({ cwd: repo, devBranch: 'main', prefix: 'release/', now: AUG31 })).toEqual({ branch: 'release/2026-08-31.2', count: 0 })
expect(git(origin, ['rev-parse', 'release/2026-08-31.2'])).toBe(git(origin, ['rev-parse', 'main']))
const s = await getReleaseStatus({ cwd: repo, devBranch: 'main', prefix: 'release/' })
expect(s.current).toBe('release/2026-08-31.2')
})
it('unreleased 计数忽略已 cherry-pick 的提交;cherryPickToRelease 推远端并清理 worktree', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
const fix = commit(fx.peer, 'b.txt', 'b', 'fix b')
commit(fx.peer, 'c.txt', 'c', 'feat c')
git(fx.peer, ['push', 'origin', 'main'])
@@ -129,7 +142,7 @@ describe('集成', () => {
})
it('cherry-pick 冲突 → 返回冲突现场、worktree 留在中间态、远端不变;没处理完拒绝再摘,处理完复用同一 worktree', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
commit(fx.peer, 'a.txt', 'a2', 'change a')
const conflict = commit(fx.peer, 'a.txt', 'a3', 'change a again')
const after = commit(fx.peer, 'd.txt', 'd', 'feat d')
@@ -159,22 +172,23 @@ describe('集成', () => {
})
it('发布 worktree 目录解析到工作区本身 → 拒绝', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
const fix = commit(fx.peer, 'b.txt', 'b', 'fix b')
git(fx.peer, ['push', 'origin', 'main'])
await expect(cherryPickToRelease({ cwd: fx.repo, branch: 'release/2026-08-31', shas: [fix], worktreePath: fx.repo })).rejects.toThrow(/工作区本身/)
expect(git(fx.repo, ['status', '--porcelain'])).toBe('')
})
it('listReleaseBranches 新→旧', async () => {
it('listReleaseBranches 新→旧,同日序号大的在前', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-01' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG01 })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
git(fx.repo, ['fetch', 'origin'])
expect(await listReleaseBranches({ cwd: fx.repo, prefix: 'release/' })).toEqual(['release/2026-08-31', 'release/2026-08-01'])
expect(await listReleaseBranches({ cwd: fx.repo, prefix: 'release/' })).toEqual(['release/2026-08-31.2', 'release/2026-08-31', 'release/2026-08-01'])
})
it('indexUnreleasedByMergePr 用本地合并记录把分支提交对上 PR', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
git(fx.peer, ['checkout', '-b', 'feature'])
const a = commit(fx.peer, 'b.txt', 'b', 'fix(admin): 会员档位隐藏')
const b = commit(fx.peer, 'c.txt', 'c', 'fix(membership): 年付月度发卡')
@@ -193,7 +207,7 @@ describe('集成', () => {
})
it('发布分支把同一改动拆成两条,即使标题不同也不再列出', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
writeFileSync(path.join(fx.peer, 'a.txt'), 'A\n')
writeFileSync(path.join(fx.peer, 'b.txt'), 'B\n')
git(fx.peer, ['add', 'a.txt', 'b.txt'])
@@ -215,7 +229,7 @@ describe('集成', () => {
})
it('解过冲突的复制品内容不同,但带 -x 尾注就算已摘,角标计数同步扣掉', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
const fix = commit(fx.peer, 'a.txt', 'a2', 'fix a')
commit(fx.peer, 'c.txt', 'c', 'feat: other')
git(fx.peer, ['push', 'origin', 'main'])
@@ -233,7 +247,7 @@ describe('集成', () => {
})
it('标题相同但改的文件不同,仍算未发布', async () => {
fx = await setup()
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
git(fx.peer, ['fetch'])
git(fx.peer, ['checkout', '-b', 'release/2026-08-31', 'origin/release/2026-08-31'])
writeFileSync(path.join(fx.peer, 'a.txt'), 'release-a\n')
@@ -253,7 +267,7 @@ describe('集成', () => {
fx = await setup()
migration(fx.peer, 'alembic/versions/0001_init.py', '0001', null, 'feat: init schema')
git(fx.peer, ['push', 'origin', 'main'])
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
migration(fx.peer, 'alembic/versions/0002_x.py', '0002', '0001', 'feat: m2')
migration(fx.peer, 'alembic/versions/0003_y.py', '0003', '0002', 'feat: m3')
commit(fx.peer, 'b.txt', 'b', 'fix: plain')
@@ -270,7 +284,7 @@ describe('集成', () => {
fx = await setup()
migration(fx.peer, 'alembic/versions/0001_init.py', '0001', null, 'feat: init schema')
git(fx.peer, ['push', 'origin', 'main'])
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
await cutRelease({ cwd: fx.repo, devBranch: 'main', prefix: 'release/', now: AUG31 })
migration(fx.peer, 'alembic/versions/0002_x.py', '0002', '0001', 'feat: m2')
git(fx.peer, ['push', 'origin', 'main'])
git(fx.peer, ['fetch'])