Files
ycode/src/main/workspace-revert.ts
T

49 lines
2.5 KiB
TypeScript

import { promises as fs } from 'node:fs'
import { dirname, isAbsolute, relative, resolve, sep, join } from 'node:path'
import { randomUUID } from 'node:crypto'
import type { RevertChange } from '@shared/revert'
import { getRevertChanges } from '@shared/revert'
import type { ChatMessage } from '@shared/types'
async function replaceFile(path: string, content: string | null): Promise<void> {
if (content === null) { await fs.unlink(path); return }
const temporary = join(dirname(path), `.ycode-restore-${randomUUID()}.tmp`)
try { await fs.writeFile(temporary, content, 'utf8'); await fs.rename(temporary, path) }
finally { await fs.unlink(temporary).catch(() => undefined) }
}
/** Validate every current file before applying any change; roll back if a write fails. */
export async function restoreWorkspaceFiles(workspace: string, changes: RevertChange[]): Promise<() => Promise<void>> {
const root = await fs.realpath(workspace)
const validated: RevertChange[] = []
for (const change of changes) {
const path = await fs.realpath(resolve(change.path))
const rel = relative(root, path)
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error('恢复文件不在当前工作区内')
if (await fs.readFile(path, 'utf8') !== change.after) throw new Error(`文件已被外部修改,未执行恢复:${rel}`)
validated.push({ ...change, path })
}
const applied: RevertChange[] = []
const undo = async (): Promise<void> => {
for (const change of [...applied].reverse()) await replaceFile(change.path, change.after)
}
try {
for (const change of validated) {
if (await fs.readFile(change.path, 'utf8') !== change.after) throw new Error('文件在恢复前已变化')
await replaceFile(change.path, change.before)
applied.push(change)
}
} catch (error) { await undo(); throw error }
return undo
}
/**
* 编辑提交前的产物回退(原版 userMessage.edit.hint:产物回退至此消息前版本)。
* 返回撤销函数;该消息之后没有可回退的写入时返回 undefined。
* 记录不完整时由 getRevertChanges 抛错,调用方应中止提交而不是静默保留文件修改。
*/
export async function rollbackEditedArtifacts(workspace: string, messages: ChatMessage[], userMessageId: string): Promise<(() => Promise<void>) | undefined> {
const changes = getRevertChanges(messages, userMessageId)
if (!changes.length) return undefined
return restoreWorkspaceFiles(workspace, changes)
}