ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Gemini CLI Checkpointing 详解:基于 Shadow Git 仓库自动快照与 /restore 回滚机制

Gemini CLI Checkpointing 详解:基于 Shadow Git 仓库自动快照与 /restore 回滚机制 Gemini CLI Checkpointing 详解基于 Shadow Git 仓库自动快照与 /restore 回滚机制【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cliGemini CLI 的 Checkpointing检查点功能会在 AI 工具修改文件之前自动为项目状态保存一份快照让你可以放心地让write_file、edit等工具改动代码并在需要时通过/restore命令瞬间回滚到改动前的文件状态与对话历史。本文以 官方 Checkpointing 文档 为主线结合 核心实现代码 与 Shadow Git 服务 的源码完整讲解该功能的工作原理、配置方法、数据落盘位置以及回滚命令的底层调用链帮助你既会用、也懂其原理。一、Checkpointing 是什么何时触发Checkpointing 的核心价值在于安全实验当 Agent 即将执行一次会修改文件系统的操作时CLI 会先为当前项目打一个检查点。如果这次改动不理想你可以一键回到检查点时刻的完整状态。从源码看检查点并不是对每一次工具调用都创建而是有明确的触发条件。在 流式处理钩子 中CLI 会筛选出可回滚的工具调用restorable tool callsconst restorableToolCalls toolCalls.filter( (toolCall) EDIT_TOOL_NAMES.has(toolCall.request.name) toolCall.status CoreToolCallStatus.AwaitingApproval, );其中EDIT_TOOL_NAMES定义在 工具名常量表export const EDIT_TOOL_NAMES new Set([EDIT_TOOL_NAME, WRITE_FILE_TOOL_NAME]);也就是说只有write_file和edit对应文档中提到的replace一类文件修改工具这两类会改动文件的工具且处于等待用户批准状态时才会进入快照流程。这符合文档描述的语义当你批准一个修改文件系统的工具时CLI 自动创建检查点。二、一个检查点包含什么文档指出每个检查点由三部分组成这三部分恰好对应ToolCallData结构体定义于 checkpointUtils.tsGit 快照commitHash在用户主目录下的一个影子 Git 仓库shadow Git repository位于~/.gemini/history/project_hash中提交一次 commit完整记录当时项目文件的状态。它不会干扰项目自己的 Git 仓库。对话历史history / clientHistory截至该时刻与 Agent 的全部对话既保存了 UI 层的HistoryItem列表也保存了模型层的clientHistorygoogle/genai的Content[]。工具调用toolCall即将执行的那次工具调用本身包括工具名和参数nameargs另附带messageId用于关联来源消息。export interface ToolCallDataHistoryType unknown, ArgsType unknown { history?: HistoryType; clientHistory?: readonly Content[]; commitHash?: string; toolCall: { name: string; args: ArgsType; }; messageId?: string; }写入磁盘前该结构还会经过 Zod 校验getToolCallDataSchema确保toolCall.name、toolCall.args等字段类型正确恢复时同样会用这套 schema 做safeParse校验失败的文件会被判定为无效检查点。检查点文件命名规则检查点文件名由 generateCheckpointFileName 生成const timestamp new Date() .toISOString() .replace(/:/g, -) .replace(/\./g, _); const toolName toolCall.name; const fileName path.basename(toolFilePath); return ${timestamp}-${fileName}-${toolName};即ISO 时间戳冒号与点被替换为连字符/下划线 被修改文件的基础名 工具名例如2025-06-22T10-00-00_000Z-my-file.txt-write_file与文档中的示例完全一致。值得注意的是如果工具调用参数中没有file_path字符串generateCheckpointFileName返回null该调用会被跳过源码会记录 Skipping restorable tool call due to missing file_path 错误。三、Shadow Git 仓库的隔离原理检查点之所以不干扰你自己的 Git 仓库靠的是GitService精心构造的一套隔离环境gitService.ts。3.1 独立的仓库身份与配置影子仓库位于~/.gemini/history/project_idStorage.getHistoryDir()返回该路径见 storage.ts。初始化时setupShadowGitRepository()会在仓库目录写入一份专属的.gitconfig作者固定为Gemini CLI gemini-cligoogle.com并显式关闭 GPG 签名commit.gpgsign false避免继承用户的签名偏好用GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM环境变量把 git 的全局与系统配置指到这份隔离配置上并清空继承来的GIT_DIR、GIT_WORK_TREE防止用户环境变量破坏隔离对首次初始化的仓库执行git init初始分支main 一个空提交保证仓库始终有 HEAD。影子仓库的操作通过shadowGitRepository访问器完成它把GIT_DIR指向~/.gemini/history/id/.git、GIT_WORK_TREE指向你的项目根目录——这是典型的bare worktree 分离用法因此你的项目目录里不会出现任何影子仓库的文件。3.2 打快照createFileSnapshotasync createFileSnapshot(message: string): Promisestring { const repo this.shadowGitRepository; await repo.add(.); const status await repo.status(); if (status.isClean()) { // If no changes are staged, return the current HEAD commit hash return await this.getCurrentCommitHash(); } const commitResult await repo.commit(message, { --no-verify: null }); return commitResult.commit; }逻辑是先git add .把项目全部文件加入影子仓库索引如果状态干净相对上一个快照无变化直接复用当前 HEAD 的 commit hash避免产生冗余提交否则以Snapshot for tool_name为消息提交。另外影子仓库的 git 操作开启了 simple-git 的整套 unsafe 选项SHADOW_REPO_UNSAFE_OPTIONS源码注释说明这是为了让这个内部、隔离的状态管理仓库不受用户本地PAGER、EDITOR、SSH等环境的影响而稳定工作。3.3 回滚restoreProjectFromSnapshotasync restoreProjectFromSnapshot(commitHash: string): Promisevoid { const repo this.shadowGitRepository; await repo.raw([restore, --source, commitHash, .]); // Removes any untracked files that were introduced post snapshot. await repo.clean(f, [-d]); }恢复分两步git restore --source commitHash .把所有已跟踪文件恢复到快照时刻的内容随后git clean -fd删除快照之后新增的未跟踪文件——这正是文档所说把项目所有文件恢复到快照捕获的状态。由于 worktree 指向你的项目目录这个操作直接改写工作区文件但对项目自己的.git目录没有任何影响。四、检查点数据存在哪里两类数据分别落盘均为纯本地存储数据位置说明Git 文件快照~/.gemini/history/project_id/含.git影子仓库commit 历史即项目状态史对话历史 工具调用~/.gemini/tmp/project_id/checkpoints/每个检查点一个 JSON 文件ToolCallData序列化路径实现见 Storage 类getHistoryDir()拼接~/.gemini/history/idgetProjectTempCheckpointsDir()拼接~/.gemini/tmp/id/checkpoints。一个值得注意的细节文档中写作project_hash而从源码结构看id实际来自 项目注册表 的getShortId()且performMigration()会把旧的 sha256 哈希目录迁移为新的短 ID 目录。因此旧版用户看到的十六进制哈希目录当前版本下可能呈现为短标识符两者指代的都是当前项目的唯一标识。检查点的创建与写盘流程汇总在processRestorableToolCalls()checkpointUtils.ts对每个待执行的可回滚工具调用先调createFileSnapshot()拿 commit hash若快照失败则降级为使用当前 HEAD hashgetCurrentCommitHash()并记录告警若两者都拿不到 hash例如 Git 未安装记错误并跳过该调用组装ToolCallData含当时的完整对话历史与clientHistory以文件名.json存入checkpointsToWrite映射最终由 useGeminiStream.ts 写入检查点目录。五、如何启用 Checkpointing该功能默认关闭配置 schema 中general.checkpointing.enabled的default: false且requiresRestart: true见 settingsSchema.ts配置读取逻辑在 config.ts。启用方式是编辑settings.json加入{ general: { checkpointing: { enabled: true } } }注意--checkpointing命令行标志已在版本 0.11.0 中移除现在只能通过settings.json配置文件启用。启用后还有一个硬性前提Git 必须可用。GitService.initialize()会先执行git --version探测若失败直接抛出 Checkpointing is enabled, but Git is not installed 错误要求你安装 Git 或关闭该功能。集成测试 checkpointing.test.ts 覆盖了启用后的端到端行为可作为功能可用性的验证参考。六、使用 /restore 命令回滚启用后检查点自动创建管理统一走/restore命令。该命令的实现位于 restoreCommand.ts。6.1 列出可用检查点不带参数执行/restore命令会读取~/.gemini/tmp/project_id/checkpoints下的所有.json文件若为空提示 No restorable tool calls found.否则输出可用检查点列表经formatCheckpointDisplayList格式化去掉.json后缀后逐行展示。文件名即时间戳-文件名-工具名例如2025-06-22T10-00-00_000Z-my-file.txt-write_file。6.2 恢复到指定检查点/restore checkpoint_file例如/restore 2025-06-22T10-00-00_000Z-my-file.txt-write_file参数可以带或不带.json后缀源码会自动补全若文件不存在则报 File not found。执行后的完整动作由 performRestore 这个异步生成器按序产出load_history把检查点中的historyUI 历史项与clientHistory模型对话加载回会话CLI 中的对话即回到检查点时刻Git 恢复调用gitService.restoreProjectFromSnapshot(commitHash)把工作区文件与快照对齐成功则提示 Restored project to the state before the tool call.重新提议工具调用/restore命令最终返回{ type: tool, toolName, toolArgs }也就是把原来那次工具调用重新提交到审批界面——你可以选择再次执行、修改参数或者干脆忽略它这正是文档所说的 Re-propose the original tool call。6.3 恢复失败的典型场景源码对 Git 恢复做了针对性的错误处理当git restore报出 unable to read tree 时说明检查点引用的 commit hash 已不在影子仓库中——通常发生在仓库被重新克隆、重置或旧 commit 被垃圾回收之后此时会明确告知 This checkpoint cannot be restored 并终止若 Git 服务本身不可用则提示需处于 git 环境。这类hash 失联是影子仓库方案的固有边界检查点只在本地、生命周期与本地磁盘上的~/.gemini/history/绑定。七、边界情况与故障降级小结结合文档与源码可以梳理出检查点机制的完整健壮性设计场景行为Git 未安装但功能已启用初始化阶段直接报错要求安装 Git 或关闭 checkpointing创建快照失败如目录不可访问降级使用当前 HEAD hash并记录告警继续快照与当前 hash 均不可得跳过该工具调用记录 Checkpointing may not be working properly工具参数缺少file_path跳过检查点记录 missing file_path快照后工作区无变化复用现有 HEAD commit不产生冗余提交检查点 JSON 损坏/字段不符 schema/restore时解析失败并报错不影响其他检查点commit hash 已被 GC/重置提示 unable to read tree该检查点不可恢复八、与 Rewind 的区别及适用建议Gemini CLI 还有一个 Rewind 功能同样面向回退场景从功能定位看Checkpointing 专注于工具调用前的文件会话原子快照由文件修改类工具的审批流程自动触发数据落在影子 Git 仓库与项目临时目录中。适用建议频繁让 Agent 改写项目文件时开启general.checkpointing.enabled把/restore当作安全网注意检查点目录会随工具调用次数增长~/.gemini/history/与~/.gemini/tmp/id/checkpoints/可按需清理均为本地目录删除即放弃对应回滚能力在容器或 CI 等环境中使用前先确认git --version可用这是功能的前置依赖。参考路径功能文档docs/cli/checkpointing.md检查点核心逻辑packages/core/src/utils/checkpointUtils.ts影子 Git 仓库实现packages/core/src/services/gitService.ts恢复命令核心packages/core/src/commands/restore.ts/restore 斜杠命令packages/cli/src/ui/commands/restoreCommand.ts检查点触发点packages/cli/src/ui/hooks/useGeminiStream.ts存储路径定义packages/core/src/config/storage.ts配置 schemapackages/cli/src/config/settingsSchema.ts集成测试integration-tests/checkpointing.test.ts【免费下载链接】gemini-cliAn open-source AI agent that brings the power of Gemini directly into your terminal.项目地址: https://gitcode.com/GitHub_Trending/gemi/gemini-cli创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进