Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • SessionRevert

SessionRevert

SessionRevert 是 OpenCode 的版本控制和回滚系统,允许用户撤销和重做 AI 生成的文件更改。

概述

SessionRevert 使用独立的 Git 仓库来管理文件系统快照,不依赖项目的版本控制系统。当用户请求回滚到某个消息或部分时,系统会捕获当前状态、恢复到指定的历史点,并计算变更摘要。

定义位置

packages/opencode/src/session/revert.ts

主要函数

revert(input)

回滚到指定的消息/部分。

参数:

  • input.sessionID: 会话 ID
  • input.messageID: 要回滚到的消息 ID
  • input.partID (可选): 要回滚到的部分 ID

返回值: 更新后的 Session 对象

工作流程:

  1. 检查会话是否忙碌
  2. 获取所有消息和会话信息
  3. 从指定点开始收集所有 patch 类型的部分
  4. 捕获当前 Git 快照(或使用已有的快照)
  5. 使用 Snapshot.revert() 恢复文件到回滚点之前的状态
  6. 计算并存储差异信息
  7. 更新会话的 revert 状态和 summary 信息
  8. 发布 session.diff 事件

位置: packages/opencode/src/session/revert.ts:23

unrevert(input)

恢复所有之前回滚的消息(撤销回滚)。

参数:

  • input.sessionID: 会话 ID

返回值: 更新后的 Session 对象

工作流程:

  1. 检查会话是否忙碌
  2. 获取会话信息
  3. 如果没有回滚状态,直接返回
  4. 使用 Snapshot.restore() 恢复到保存的快照
  5. 清除会话的 revert 状态

位置: packages/opencode/src/session/revert.ts:80

cleanup(session)

永久删除回滚的消息。

参数:

  • session: Session 对象

工作流程:

  1. 检查是否有回滚状态
  2. 获取所有消息
  3. 找到保留和删除的消息边界
  4. 删除回滚点之后的所有消息
  5. 如果指定了 partID,删除该部分之后的所有部分
  6. 发布删除事件
  7. 清除会话的 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 仓库,确保安全性。

相关文档

  • Session - 会话对象
  • Message - 消息对象
  • Part - 消息部分
  • Snapshot - 快照系统详解

变更历史

版本变更内容日期
v1初始 SessionRevert 架构-
v1.1添加差异计算和摘要-
v1.2添加 cleanup 自动清理-