✨ feat(vscode): ensureWorktreeFromRemote 从远端分支重建 worktree
Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
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'
|
||||
import { ensureWorktreeFromRemote } from '../../src/git/worktree'
|
||||
|
||||
const gitEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
GIT_CONFIG_NOSYSTEM: '1',
|
||||
GIT_CONFIG_GLOBAL: '/dev/null',
|
||||
GIT_TERMINAL_PROMPT: '0',
|
||||
GIT_AUTHOR_NAME: 'remote-test',
|
||||
GIT_AUTHOR_EMAIL: 'remote@test.local',
|
||||
GIT_COMMITTER_NAME: 'remote-test',
|
||||
GIT_COMMITTER_EMAIL: 'remote@test.local',
|
||||
}
|
||||
|
||||
function git(cwd: string, args: string[]): string {
|
||||
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', env: gitEnv }).trim()
|
||||
}
|
||||
|
||||
interface Fixture { root: string, origin: string, repo: string, templatePath: string }
|
||||
|
||||
/** origin(bare)← 同事的 clone push feature 分支;repo 是接管方的 clone,只有 main。 */
|
||||
async function setup(): Promise<Fixture> {
|
||||
const root = await fsp.mkdtemp(path.join(os.tmpdir(), 'sw-remote-'))
|
||||
const origin = path.join(root, 'origin.git')
|
||||
git(root, ['init', '--bare', '-b', 'main', origin])
|
||||
const peer = path.join(root, 'peer')
|
||||
git(root, ['clone', origin, peer])
|
||||
writeFileSync(path.join(peer, 'README.md'), 'hello\n')
|
||||
git(peer, ['add', 'README.md'])
|
||||
git(peer, ['commit', '-m', 'init'])
|
||||
git(peer, ['push', '-u', 'origin', 'main'])
|
||||
git(peer, ['checkout', '-b', 'feature/x'])
|
||||
writeFileSync(path.join(peer, 'x.txt'), 'x\n')
|
||||
git(peer, ['add', 'x.txt'])
|
||||
git(peer, ['commit', '-m', 'feat x'])
|
||||
git(peer, ['push', '-u', 'origin', 'feature/x'])
|
||||
const repo = path.join(root, 'repo')
|
||||
git(root, ['clone', origin, repo])
|
||||
const templatePath = path.join(root, 'wt', 'x')
|
||||
return { root, origin, repo, templatePath }
|
||||
}
|
||||
|
||||
let fx: Fixture | undefined
|
||||
afterEach(() => {
|
||||
if (fx)
|
||||
rmSync(fx.root, { recursive: true, force: true })
|
||||
fx = undefined
|
||||
})
|
||||
|
||||
describe('ensureWorktreeFromRemote', () => {
|
||||
it('从 origin 分支建 worktree,本地分支跟踪远端', async () => {
|
||||
fx = await setup()
|
||||
const out = await ensureWorktreeFromRemote({ workspaceRoot: fx.repo, worktreePath: fx.templatePath, branch: 'feature/x' })
|
||||
expect(out).toBe(fx.templatePath)
|
||||
expect(existsSync(path.join(fx.templatePath, 'x.txt'))).toBe(true)
|
||||
expect(git(fx.templatePath, ['rev-parse', '--abbrev-ref', 'HEAD'])).toBe('feature/x')
|
||||
expect(git(fx.templatePath, ['rev-parse', '--abbrev-ref', '@{u}'])).toBe('origin/feature/x')
|
||||
})
|
||||
|
||||
it('该分支已有 live worktree 时直接复用', async () => {
|
||||
fx = await setup()
|
||||
const first = await ensureWorktreeFromRemote({ workspaceRoot: fx.repo, worktreePath: fx.templatePath, branch: 'feature/x' })
|
||||
const other = path.join(fx.root, 'wt', 'other')
|
||||
const second = await ensureWorktreeFromRemote({ workspaceRoot: fx.repo, worktreePath: other, branch: 'feature/x' })
|
||||
expect(second).toBe(first)
|
||||
expect(existsSync(other)).toBe(false)
|
||||
})
|
||||
|
||||
it('模板路径被非 worktree 目录占着 → 删掉重建', async () => {
|
||||
fx = await setup()
|
||||
await fsp.mkdir(fx.templatePath, { recursive: true })
|
||||
writeFileSync(path.join(fx.templatePath, 'junk'), '')
|
||||
await ensureWorktreeFromRemote({ workspaceRoot: fx.repo, worktreePath: fx.templatePath, branch: 'feature/x' })
|
||||
expect(existsSync(path.join(fx.templatePath, 'junk'))).toBe(false)
|
||||
expect(existsSync(path.join(fx.templatePath, 'x.txt'))).toBe(true)
|
||||
})
|
||||
|
||||
it('远端没有该分支 → 抛错', async () => {
|
||||
fx = await setup()
|
||||
await expect(
|
||||
ensureWorktreeFromRemote({ workspaceRoot: fx.repo, worktreePath: fx.templatePath, branch: 'feature/none' }),
|
||||
).rejects.toThrow(/feature\/none/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user