SessionRevert
SessionRevert 是 OpenCode 的版本控制和回滚系统,允许用户撤销和重做 AI 生成的文件更改。
概述
SessionRevert 使用独立的 Git 仓库来管理文件系统快照,不依赖项目的版本控制系统。当用户请求回滚到某个消息或部分时,系统会捕获当前状态、恢复到指定的历史点,并计算变更摘要。
定义位置
packages/opencode/src/session/revert.ts
主要函数
revert(input)
回滚到指定的消息/部分。
参数:
input.sessionID: 会话 IDinput.messageID: 要回滚到的消息 IDinput.partID(可选): 要回滚到的部分 ID
返回值: 更新后的 Session 对象
工作流程:
- 检查会话是否忙碌
- 获取所有消息和会话信息
- 从指定点开始收集所有 patch 类型的部分
- 捕获当前 Git 快照(或使用已有的快照)
- 使用
Snapshot.revert()恢复文件到回滚点之前的状态 - 计算并存储差异信息
- 更新会话的
revert状态和summary信息 - 发布
session.diff事件
位置: packages/opencode/src/session/revert.ts:23
unrevert(input)
恢复所有之前回滚的消息(撤销回滚)。
参数:
input.sessionID: 会话 ID
返回值: 更新后的 Session 对象
工作流程:
- 检查会话是否忙碌
- 获取会话信息
- 如果没有回滚状态,直接返回
- 使用
Snapshot.restore()恢复到保存的快照 - 清除会话的
revert状态
位置: packages/opencode/src/session/revert.ts:80
cleanup(session)
永久删除回滚的消息。
参数:
session: Session 对象
工作流程:
- 检查是否有回滚状态
- 获取所有消息
- 找到保留和删除的消息边界
- 删除回滚点之后的所有消息
- 如果指定了 partID,删除该部分之后的所有部分
- 发布删除事件
- 清除会话的
revert状态
位置: packages/opencode/src/session/revert.ts:92
TypeScript 类型定义
export namespace SessionRevert {
export const RevertInput = z.object({
sessionID: string,
messageID: string,
partID?: string,
})
export async function revert(input: RevertInput): Promise<Session.Info>
export async function unrevert(input: { sessionID: string }): Promise<Session.Info>
export async function cleanup(session: Session.Info): Promise<void>
}
Session 中的回滚状态
export type Session = {
// ... 其他字段
revert?: {
messageID: string // 要回滚到的消息 ID
partID?: string // 要回滚到的部分 ID(可选)
snapshot?: string // 快照 ID(可选)
diff?: string // 差异字符串(可选)
}
}
与 Snapshot 系统的集成
SessionRevert 依赖 Snapshot 系统进行版本控制管理。
Snapshot 系统函数
track() - 创建 Git 快照
const hash = await Snapshot.track()
// 返回 Git tree hash
位置: packages/opencode/src/snapshot/index.ts:50
restore(snapshot) - 恢复到指定快照
await Snapshot.restore(hash)
// 恢复工作目录到指定的快照状态
位置: packages/opencode/src/snapshot/index.ts:108
revert(patches) - 回滚特定的文件补丁
await Snapshot.revert(patches)
// 只回滚指定的文件更改
位置: packages/opencode/src/snapshot/index.ts:118
diff(hash) - 生成当前状态与快照的差异
const diff = await Snapshot.diff(hash)
// 返回差异信息
位置: packages/opencode/src/snapshot/index.ts:92
快照存储位置
快照存储在独立的 Git 仓库中,不使用项目的 Git 仓库:
~/.opencode/data/snapshot/{projectID}/.git/
这确保回滚操作不会影响用户的版本控制。
事件
回滚操作通过事件系统通知 UI:
| 事件名 | 类型 | 说明 |
|---|---|---|
session.diff | { sessionID, diff: FileDiff[] } | 会话差异更新 |
message.removed | { sessionID, messageID } | 消息删除 |
part.removed | { sessionID, messageID, partID } | 部分删除 |
API 路由
POST /sessions/:sessionID/revert
回滚到指定的消息/部分。
请求体:
{
"messageID": "msg_abc123",
"partID": "part_xyz789"
}
位置: packages/opencode/src/server/routes/session.ts
POST /sessions/:sessionID/unrevert
撤销回滚,恢复所有消息。
请求体: 无
位置: packages/opencode/src/server/routes/session.ts
UI 集成
Web UI
位置: packages/app/src/pages/session.tsx
Web UI 提供撤销/重做按钮:
// 撤销按钮
await sdk.client.session.revert({
sessionID: session.id,
messageID: msg.id,
partID: part.id,
})
// 重做按钮
await sdk.client.session.unrevert({
sessionID: session.id,
})
TUI
位置: packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
TUI 支持键盘快捷键:
// 消息重做快捷键
messages_redo: {
keys: ["ctrl+r"],
action: () => {
sdk.client.session.unrevert({ sessionID })
}
}
典型使用场景
1. 回滚到特定消息
// 用户想要撤销从某个消息开始的所有更改
await SessionRevert.revert({
sessionID: "sess_abc123",
messageID: "msg_xyz789",
})
// 会话现在处于回滚状态
// 文件系统已恢复到该消息之前的状态
2. 回滚到特定部分
// 用户想要撤销部分消息内的更改
await SessionRevert.revert({
sessionID: "sess_abc123",
messageID: "msg_xyz789",
partID: "part_def456",
})
// 只回滚该部分之后的更改部分
3. 撤销回滚(重做)
// 用户改变主意,想要恢复所有更改
await SessionRevert.unrevert({
sessionID: "sess_abc123",
})
// 文件系统恢复到最新状态
// 所有消息再次可见
4. 清理回滚状态
// 当创建新提示词时自动调用
const session = await Session.get("sess_abc123")
await SessionRevert.cleanup(session)
// 回滚点之后的消息被永久删除
5. 检查回滚状态
const session = await Session.get("sess_abc123")
if (session.revert) {
console.log("会话处于回滚状态")
console.log(`回滚到消息: ${session.revert.messageID}`)
if (session.revert.partID) {
console.log(` 部分: ${session.revert.partID}`)
}
if (session.revert.snapshot) {
console.log(` 快照: ${session.revert.snapshot}`)
}
if (session.revert.diff) {
console.log(` 差异: ${session.revert.diff}`)
}
}
6. 获取回滚差异
// 回滚时自动计算的差异
const session = await Session.get("sess_abc123")
if (session.revert && session.summary) {
console.log(`回滚统计:`)
console.log(` 新增行数: ${session.summary.additions}`)
console.log(` 删除行数: ${session.summary.deletions}`)
console.log(` 修改文件数: ${session.summary.files}`)
}
// 从存储读取详细差异
const diffs = await Storage.read(["session_diff", "sess_abc123"])
console.log(diffs)
工作流程
回滚流程
用户请求回滚
↓
SessionRevert.revert()
├─ 检查会话是否忙碌
├─ 获取所有消息
├─ 收集回滚点之后的所有 patch
├─ Snapshot.track() - 捕获当前快照
├─ Snapshot.revert(patches) - 回滚文件
├─ Snapshot.diff() - 计算差异
├─ 更新 session.revert 状态
├─ 更新 session.summary 信息
└─ 发布 session.diff 事件
撤销回滚流程
用户请求撤销回滚
↓
SessionRevert.unrevert()
├─ 检查会话是否忙碌
├─ 检查是否存在回滚状态
├─ Snapshot.restore(snapshot) - 恢复快照
├─ 清除 session.revert
└─ 所有消息再次可见
清理流程
创建新提示词时
↓
SessionRevert.cleanup()
├─ 检查是否存在回滚状态
├─ 找到回滚边界
├─ 删除回滚点之后的消息
├─ 删除消息中的部分(如果指定了 partID)
├─ 发布删除事件
└─ 清除 session.revert
对象关系
架构概览
存回储
回滚差异
;["session_diff", sessionID] = FileDiff[]
Git 快照
存储在独立的 Git 仓库中:
~/.opencode/data/snapshot/{projectID}/.git/
不使用项目的 Git 仓库,确保安全性。
相关文档
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v1 | 初始 SessionRevert 架构 | - |
| v1.1 | 添加差异计算和摘要 | - |
| v1.2 | 添加 cleanup 自动清理 | - |