✨ feat(vscode): 摘修复识别 alembic 迁移,接不上发布分支链的高亮禁用不可摘
按 writing-alembic-migrations 的规矩判断:迁移的前任必须已在发布分支上, 且发布分支上该前任不能已有别的后继;根迁移接不上已有链、删迁移会断链也不可摘。 列表里带迁移的提交打「迁移」角标,不可摘的高亮警告色、禁用勾选并写明原因; 确认接口再校验一遍,绕过前端也摘不进去。 Claude-Session: https://claude.ai/code/session_01QWxLb6X8Vd2G5vHnp6R7qm
This commit is contained in:
1 parent
df0660cff7
commit
028195ab2b
8 files changed
+261
-26
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync, promises as fsp, rmSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, promises as fsp, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
listReleaseBranches,
|
||||
listUnreleasedCommits,
|
||||
normalizeReleasePrefix,
|
||||
parseAlembicRevision,
|
||||
parseGiteaMergeSubject,
|
||||
pickCurrentRelease,
|
||||
todayReleaseBranch,
|
||||
@@ -39,6 +40,10 @@ function commit(cwd: string, file: string, content: string, msg: string): string
|
||||
git(cwd, ['commit', '-m', msg])
|
||||
return git(cwd, ['rev-parse', 'HEAD'])
|
||||
}
|
||||
function migration(cwd: string, file: string, rev: string, down: string | null, msg: string): string {
|
||||
mkdirSync(path.dirname(path.join(cwd, file)), { recursive: true })
|
||||
return commit(cwd, file, `revision = '${rev}'\ndown_revision = ${down === null ? 'None' : `'${down}'`}\n`, msg)
|
||||
}
|
||||
let fx: Fx | undefined
|
||||
afterEach(() => {
|
||||
if (fx)
|
||||
@@ -66,6 +71,13 @@ describe('纯函数', () => {
|
||||
})
|
||||
expect(parseGiteaMergeSubject('fix: no merge')).toBeUndefined()
|
||||
})
|
||||
it('parseAlembicRevision 认裸赋值、带类型标注、元组和 None', () => {
|
||||
expect(parseAlembicRevision('revision = \'a1\'\ndown_revision = \'a0\'\n')).toEqual({ revision: 'a1', downRevisions: ['a0'] })
|
||||
expect(parseAlembicRevision('revision: str = "a1"\ndown_revision: Union[str, None] = "a0"\nbranch_labels = None\n')).toEqual({ revision: 'a1', downRevisions: ['a0'] })
|
||||
expect(parseAlembicRevision('revision = \'m\'\ndown_revision = (\'a\', \'b\')\n')).toEqual({ revision: 'm', downRevisions: ['a', 'b'] })
|
||||
expect(parseAlembicRevision('revision: str = \'root\'\ndown_revision: Union[str, None] = None\n')).toEqual({ revision: 'root', downRevisions: [] })
|
||||
expect(parseAlembicRevision('print(1)\n')).toBeUndefined()
|
||||
})
|
||||
it('groupCommitsByPr 按 Gitea 给的 pr 号分组,说明里没有 Closes 也能归组', () => {
|
||||
const groups = groupCommitsByPr([
|
||||
{ sha: '3e519ea', subject: 'fix(admin): 会员档位隐藏', author: 'chehongweo', date: '2026-08-31', body: '', pr: 260, prTitle: '连续包月会员订阅体系' },
|
||||
@@ -237,4 +249,37 @@ describe('集成', () => {
|
||||
const list = await listUnreleasedCommits({ cwd: fx.repo, devBranch: 'main', current: 'release/2026-08-31' })
|
||||
expect(list.map(c => c.subject)).toEqual(['fix(jobs): 一次性命令收口'])
|
||||
})
|
||||
it('带迁移的提交:前任在发布分支上才能摘,前任缺失的标不可摘,不带迁移的不打标', async () => {
|
||||
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' })
|
||||
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')
|
||||
git(fx.peer, ['push', 'origin', 'main'])
|
||||
git(fx.repo, ['fetch', 'origin'])
|
||||
const list = await listUnreleasedCommits({ cwd: fx.repo, devBranch: 'main', current: 'release/2026-08-31' })
|
||||
expect(list.map(c => c.subject)).toEqual(['fix: plain', 'feat: m3', 'feat: m2'])
|
||||
expect(list[0].migration).toBeUndefined()
|
||||
expect(list[1].migration?.files).toEqual(['alembic/versions/0003_y.py'])
|
||||
expect(list[1].migration?.blocked).toContain('0002')
|
||||
expect(list[2].migration).toEqual({ files: ['alembic/versions/0002_x.py'] })
|
||||
})
|
||||
it('发布分支上同一前任已有后继 → 多 head,不可摘', async () => {
|
||||
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' })
|
||||
migration(fx.peer, 'alembic/versions/0002_x.py', '0002', '0001', 'feat: m2')
|
||||
git(fx.peer, ['push', 'origin', 'main'])
|
||||
git(fx.peer, ['fetch'])
|
||||
git(fx.peer, ['checkout', '-b', 'release/2026-08-31', 'origin/release/2026-08-31'])
|
||||
migration(fx.peer, 'alembic/versions/0002_hotfix.py', '0002b', '0001', 'fix: hotfix schema')
|
||||
git(fx.peer, ['push', 'origin', 'release/2026-08-31'])
|
||||
git(fx.repo, ['fetch', 'origin'])
|
||||
const list = await listUnreleasedCommits({ cwd: fx.repo, devBranch: 'main', current: 'release/2026-08-31' })
|
||||
expect(list.map(c => c.subject)).toEqual(['feat: m2'])
|
||||
expect(list[0].migration?.blocked).toContain('多个 head')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user