✨ feat(vscode): 同一天再切发布直接把 main 新提交推进到当天分支,不再另起编号分支

This commit is contained in:
cruldra committed 2026-09-17 17:57:47 +08:00
1 parent 4aae4df1f1
commit 17570749dd
4 files changed
+128 -94

No files matched your search

+58 -32
View File
@@ -20,7 +20,7 @@ import {
indexUnreleasedByMergePr,
listReleaseBranches,
listUnreleasedCommits,
nextReleaseBranch,
todayReleaseBranch,
} from '../../git/release'
import { DEFAULT_WORKTREE_TEMPLATE, resolveWorktreeDir } from '../../git/worktreePath'
import { triggerJenkinsBuild } from '../../jenkins/trigger'
@@ -35,7 +35,7 @@ export async function handleReleaseStatus(panel: KanbanWebviewPanel): Promise<vo
const base = {
type: 'release/status' as const,
devBranch: s.devBranch,
nextBranch: nextReleaseBranch(s.releaseBranchPrefix, []),
nextBranch: todayReleaseBranch(s.releaseBranchPrefix),
running: panel.releaseRunning,
jenkinsConfigured: s.jenkinsJob.length > 0,
}
@@ -45,7 +45,7 @@ export async function handleReleaseStatus(panel: KanbanWebviewPanel): Promise<vo
}
try {
const st = await getReleaseStatus({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
panel.postMessage({ ...base, nextBranch: st.next, current: st.current, unreleased: st.unreleased, unavailable: st.unavailable })
panel.postMessage({ ...base, current: st.current, unreleased: st.unreleased, unavailable: st.unavailable })
}
catch (err) {
const message = err instanceof Error ? err.message : String(err)
@@ -88,13 +88,26 @@ export function handleReleaseCut(panel: KanbanWebviewPanel): Promise<void> {
return runReleaseAction(panel, async () => {
const root = requireRoot()
const s = getSettings(panel.context)
const { branch, count } = await cutRelease({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
toast(panel, 'success', `已切发布分支 ${branch}(${count} 个提交)`)
const r = await cutRelease({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
if (r.kind === 'diverged') {
const picked = await pickUnreleased(panel, { root, devBranch: s.devBranch, current: r.branch })
if (picked !== 'done')
return
}
else if (r.kind === 'advanced' && r.count === 0) {
toast(panel, 'info', `${r.branch} 已经和 ${s.devBranch} 一致,没有新提交`)
return
}
else {
toast(panel, 'success', r.kind === 'created'
? `已切发布分支 ${r.branch}(${r.count} 个提交)`
: `已把 ${s.devBranch} 上 ${r.count} 个新提交推进到 ${r.branch}`)
}
if (!s.jenkinsJob)
return
const pick = await window.showInformationMessage(`现在部署 ${branch} 到${s.jenkinsProdHost}?`, '部署')
const pick = await window.showInformationMessage(`现在部署 ${r.branch} 到${s.jenkinsProdHost}?`, '部署')
if (pick === '部署')
await deploy(panel, branch)
await deploy(panel, r.branch)
})
}
@@ -140,35 +153,48 @@ export function handleReleaseCherryPickConfirm(panel: KanbanWebviewPanel, shas:
const status = await getReleaseStatus({ cwd: root, devBranch: s.devBranch, prefix: s.releaseBranchPrefix })
if (status.unavailable)
throw new Error(status.unavailable)
const current = status.current
if (!current)
if (!status.current)
throw new Error('尚无发布分支,先切新发布')
const commits = await listUnreleasedCommits({ cwd: root, devBranch: s.devBranch, current })
const wanted = new Set(shas)
const blocked = commits.find(c => wanted.has(c.sha) && c.migration?.blocked)
if (blocked)
throw new Error(`${blocked.sha.slice(0, 7)} ${blocked.subject}:${blocked.migration!.blocked}`)
const order = new Map(commits.map((c, i) => [c.sha, i]))
const ordered = [...shas].sort((a, b) => (order.get(b) ?? 0) - (order.get(a) ?? 0))
// 发布 worktree 走和实施 worktree 同一套目录模板,分支名里的 / 换成 - 当目录名。
const worktreePath = resolveWorktreeDir(s.worktreeDirectory || DEFAULT_WORKTREE_TEMPLATE, root, current.replace(/\//g, '-'))
const spinner = toast(panel, 'info', `正在摘 ${ordered.length} 个提交到 ${current}…`, { spinner: true, dismissOnTimer: 0 })
let conflict: CherryPickConflict | undefined
try {
conflict = await cherryPickToRelease({ cwd: root, branch: current, shas: ordered, worktreePath })
}
finally {
dismiss(panel, spinner)
}
if (!conflict) {
toast(panel, 'success', `已摘 ${ordered.length} 个提交到 ${current}`)
return
}
openReleaseConflictSession(panel, { branch: current, conflict, subjectOf: new Map(commits.map(c => [c.sha, c.subject])) })
toast(panel, 'info', `${conflict.sha.slice(0, 7)} ${conflict.subject} 冲突,已开 ${current} 冲突解决会话接手`, { dismissOnTimer: 10_000 })
await pickUnreleased(panel, { root, devBranch: s.devBranch, current: status.current, shas })
})
}
/**
* 把 devBranch 上未发布的提交摘到 current,shas 不传就全摘。
* 冲突时开会话接手并返回 'conflict',调用方据此决定后续(比如要不要问部署)。
*/
async function pickUnreleased(panel: KanbanWebviewPanel, o: { root: string, devBranch: string, current: string, shas?: string[] }): Promise<'done' | 'conflict' | 'nothing'> {
const s = getSettings(panel.context)
const commits = await listUnreleasedCommits({ cwd: o.root, devBranch: o.devBranch, current: o.current })
const wanted = o.shas && new Set(o.shas)
const chosen = wanted ? commits.filter(c => wanted.has(c.sha)) : commits
if (chosen.length === 0) {
toast(panel, 'info', '没有待摘的提交')
return 'nothing'
}
const blocked = chosen.find(c => c.migration?.blocked)
if (blocked)
throw new Error(`${blocked.sha.slice(0, 7)} ${blocked.subject}:${blocked.migration!.blocked}`)
const ordered = chosen.map(c => c.sha).reverse() // ← 列表是新→旧,摘要旧→新
// 发布 worktree 走和实施 worktree 同一套目录模板,分支名里的 / 换成 - 当目录名。
const worktreePath = resolveWorktreeDir(s.worktreeDirectory || DEFAULT_WORKTREE_TEMPLATE, o.root, o.current.replace(/\//g, '-'))
const spinner = toast(panel, 'info', `正在摘 ${ordered.length} 个提交到 ${o.current}…`, { spinner: true, dismissOnTimer: 0 })
let conflict: CherryPickConflict | undefined
try {
conflict = await cherryPickToRelease({ cwd: o.root, branch: o.current, shas: ordered, worktreePath })
}
finally {
dismiss(panel, spinner)
}
if (!conflict) {
toast(panel, 'success', `已摘 ${ordered.length} 个提交到 ${o.current}`)
return 'done'
}
openReleaseConflictSession(panel, { branch: o.current, conflict, subjectOf: new Map(commits.map(c => [c.sha, c.subject])) })
toast(panel, 'info', `${conflict.sha.slice(0, 7)} ${conflict.subject} 冲突,已开 ${o.current} 冲突解决会话接手`, { dismissOnTimer: 10_000 })
return 'conflict'
}
/**
* 摘修复冲突后的接力:worktree 里 cherry-pick 停在中间态,开一个 cc 会话进去解决、
* 按原顺序摘完剩余提交并推远端。同名终端还活着就把提示词直接打进去,不再新开。