✨ feat(vscode): 摘修复冲突不再中止,保留 worktree 自动开冲突解决会话接手
摘修复改在固定的发布 worktree(worktreeDirectory 模板,目录名为分支名 / 换 -)里按序 cherry-pick -x。全部成功推远端并删 worktree;中途冲突则 保留冲突现场、远端不变,开 `<发布分支>-冲突解决` claude 会话解冲突、 --continue、按原顺序摘完剩余提交再推远端。同名终端活着直接注入提示词。 - 上次冲突没处理完再摘会被拒绝;目录模板解析到工作区本身也拒绝 - 目录被非 worktree 的垃圾占着时清掉重建 - 测试覆盖成功清理、冲突接力(含 -x 尾注保留)、拒绝工作区本身 - CLAUDE.md 补一条:收尾三步默认直接做,不问不提醒 Claude-Session: https://claude.ai/code/session_018HDxMmNDyfLa3bMTUctBzt
This commit is contained in:
1 parent
ee33ae2440
commit
706057075d
6 files changed
+186
-24
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { promises as fsp, rmSync, writeFileSync } from 'node:fs'
|
||||
import { existsSync, promises as fsp, rmSync, writeFileSync } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
@@ -104,23 +104,55 @@ describe('集成', () => {
|
||||
expect(s).toMatchObject({ current: 'release/2026-08-31', unreleased: 2 })
|
||||
const list = await listUnreleasedCommits({ cwd: fx.repo, devBranch: 'main', current: 'release/2026-08-31' })
|
||||
expect(list.map(c => c.subject)).toEqual(['feat c', 'fix b'])
|
||||
await cherryPickToRelease({ cwd: fx.repo, branch: 'release/2026-08-31', shas: [fix] })
|
||||
// 目录被一个不是 worktree 的垃圾目录占着,应当清掉重建而不是撞路径
|
||||
const worktreePath = path.join(fx.root, 'wt', 'release-2026-08-31')
|
||||
await fsp.mkdir(worktreePath, { recursive: true })
|
||||
writeFileSync(path.join(worktreePath, 'junk.txt'), 'junk\n')
|
||||
await expect(cherryPickToRelease({ cwd: fx.repo, branch: 'release/2026-08-31', shas: [fix], worktreePath })).resolves.toBeUndefined()
|
||||
expect(git(fx.origin, ['log', '--format=%s', 'release/2026-08-31'])).toBe('fix b\ninit')
|
||||
expect(git(fx.repo, ['worktree', 'list']).split('\n')).toHaveLength(1)
|
||||
expect(existsSync(worktreePath)).toBe(false)
|
||||
s = await getReleaseStatus({ cwd: fx.repo, devBranch: 'main', prefix: 'release/' })
|
||||
expect(s.unreleased).toBe(1)
|
||||
})
|
||||
it('cherry-pick 冲突 → 抛错、远端不变、worktree 已清理', async () => {
|
||||
it('cherry-pick 冲突 → 返回冲突现场、worktree 留在中间态、远端不变;没处理完拒绝再摘,处理完复用同一 worktree', async () => {
|
||||
fx = await setup()
|
||||
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
|
||||
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')
|
||||
git(fx.peer, ['push', 'origin', 'main'])
|
||||
const before = git(fx.origin, ['rev-parse', 'release/2026-08-31'])
|
||||
await expect(cherryPickToRelease({ cwd: fx.repo, branch: 'release/2026-08-31', shas: [conflict] })).rejects.toThrow(/冲突/)
|
||||
const worktreePath = path.join(fx.root, 'wt', 'release-2026-08-31')
|
||||
const o = { cwd: fx.repo, branch: 'release/2026-08-31', worktreePath }
|
||||
await expect(cherryPickToRelease({ ...o, shas: [conflict, after] })).resolves.toEqual({
|
||||
sha: conflict,
|
||||
subject: 'change a again',
|
||||
worktreePath,
|
||||
remaining: [after],
|
||||
})
|
||||
expect(git(fx.origin, ['rev-parse', 'release/2026-08-31'])).toBe(before)
|
||||
expect(git(fx.repo, ['worktree', 'list']).split('\n')).toHaveLength(2)
|
||||
expect(git(worktreePath, ['rev-parse', '--verify', 'CHERRY_PICK_HEAD'])).toBe(conflict)
|
||||
await expect(cherryPickToRelease({ ...o, shas: [after] })).rejects.toThrow(/还没处理完/)
|
||||
// 模拟冲突解决会话:解冲突、continue、推远端
|
||||
writeFileSync(path.join(worktreePath, 'a.txt'), 'a3\n')
|
||||
git(worktreePath, ['add', 'a.txt'])
|
||||
git(worktreePath, ['-c', 'core.editor=true', 'cherry-pick', '--continue'])
|
||||
git(worktreePath, ['push', 'origin', 'HEAD:refs/heads/release/2026-08-31'])
|
||||
expect(git(fx.origin, ['log', '-1', '--format=%b', 'release/2026-08-31'])).toContain(`(cherry picked from commit ${conflict})`)
|
||||
await expect(cherryPickToRelease({ ...o, shas: [after] })).resolves.toBeUndefined()
|
||||
expect(git(fx.origin, ['log', '--format=%s', 'release/2026-08-31'])).toBe('feat d\nchange a again\ninit')
|
||||
expect(git(fx.repo, ['worktree', 'list']).split('\n')).toHaveLength(1)
|
||||
})
|
||||
it('发布 worktree 目录解析到工作区本身 → 拒绝', async () => {
|
||||
fx = await setup()
|
||||
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-31' })
|
||||
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 () => {
|
||||
fx = await setup()
|
||||
await cutRelease({ cwd: fx.repo, devBranch: 'main', branch: 'release/2026-08-01' })
|
||||
|
||||
Reference in new issue
Block a user