Skip to content

多版本管理(快照)指南

本指南介绍如何启用并使用 OpenViking 的多版本管理(快照)能力。多版本管理在 VikingFS 之上提供基于 Git 的 commit/log/show/restore 原语,让你把账号下的资源树保存成一系列不可变快照,随时回溯历史、对比版本,并把工作区恢复到任意历史状态。

多版本管理由内嵌在 Rust RAGFS 层的 gitoxide 驱动,以 account_id 为粒度维护一个逻辑 Git 仓库(每个账号一个仓库),对调用方完全透明——你无需手动执行任何 git 命令。

关于各命令参数和响应结构的完整 API 参考,见 多版本管理 API

前置条件

  • 已有可用的 ov.conf
  • 已确认资源的读写正常(多版本管理建立在文件系统资源之上)。
  • 如果选择 S3 后端存放 Git 对象,已准备好 bucket、region、endpoint 和访问凭据。

启用多版本管理

多版本管理默认开启git.enabled 默认为 true)。Git 对象的存储后端可以选择 local(本地文件系统)或 s3(S3 兼容对象存储);当不显式设置 git.backend 时,会自动继承 storage.agfs.backendstorage.agfs.backendmemory 时映射为 local)。如需关闭多版本管理,把 git.enabled 设为 false 即可。

本地后端(推荐用于单机部署)

json
{
  "storage": {
    "workspace": "./data"
  },
  "git": {
    "enabled": true,
    "backend": "local",
    "default_branch": "main",
    "author_name": "viking-bot",
    "author_email": "bot@viking.local",
    "local": {
      "base_dir": "",
    }
  }
}

配置说明:

字段默认值说明
git.enabledtrue是否启用多版本管理。设为 false 可关闭快照功能
git.backend继承 storage.agfs.backendGit 对象后端:locals3。不显式设置时继承 storage.agfs.backendmemory 映射为 local
git.default_branchmain未显式指定时使用的默认分支名
git.author_nameviking-bot调用方未传 author_name 时使用的默认提交者名字
git.author_emailbot@viking.local默认提交者邮箱
git.local.base_dir""Git 对象/引用的存放目录。留空时默认使用 {storage.workspace}/.ovgit

通常把 git.local.base_dir 留空即可,让快照数据自动落在工作区下的 .ovgit 目录,便于和资源数据一起备份与迁移。

S3 后端(推荐用于分布式/云端部署)

把 Git 对象与引用存到 S3 兼容对象存储(如火山引擎 TOS、MinIO、AWS S3)。当 backends3 时,必须提供 git.s3 段,且 bucketregion 不能为空。

提示:git.s3bucketregionendpointaccess_keysecret_key 在未显式设置时会自动继承 storage.agfs.s3 的对应字段。因此当 storage.agfs 已经配置为 s3 后端时,通常无需重复填写 git.s3——只要不显式设置 git.backend,多版本管理会直接复用 storage.agfs 的 bucket 与访问凭据。

json
{
  "storage": {
    "workspace": "./data"
  },
  "git": {
    "enabled": true,
    "backend": "s3",
    "default_branch": "main",
    "author_name": "viking-bot",
    "author_email": "bot@viking.local",
    "s3": {
      "bucket": "your-tos-bucket",
      "region": "cn-beijing",
      "endpoint": "https://tos-s3-cn-beijing.volces.com",
      "access_key": "<your-volcengine-ak>",
      "secret_key": "<your-volcengine-sk>",
      "prefix": ".ovgit",
      "use_path_style": false,
      "cas_mode": "native"
    }
  }
}

配置说明:

字段默认值说明
git.s3.bucket继承 storage.agfs.s3.bucket存放 Git 对象/引用的 bucket,必填(可由 storage.agfs.s3 继承)
git.s3.region继承 storage.agfs.s3.region,否则 us-east-1bucket 所在区域,必填
git.s3.prefix.ovgit键前缀,所有数据存放在 {prefix}/{account}/...
git.s3.endpoint继承 storage.agfs.s3.endpoint,否则 ""自定义 S3 端点(MinIO/TOS 等);标准 AWS S3 留空
git.s3.access_key / git.s3.secret_key继承 storage.agfs.s3 对应字段,否则 null直接读取的凭据;留空则走 SDK 默认凭据链
git.s3.use_path_styletruetrue 用 path-style 寻址(MinIO 等);false 用 virtual-host 寻址(TOS 等)
git.s3.cas_modenative引用 CAS 模式。native 使用 S3 条件写(If-Match)

修改配置后,重启 OpenViking 服务(或重新初始化 SDK 客户端)使其生效。

仓库中提供了可直接参考的完整示例:ov.conf.git-local.exampleov.conf.git-s3-tos.example

目录结构变化:.ovgit 目录

启用 local 后端且 base_dir 留空时,OpenViking 会在工作区下新增一个 .ovgit 目录用于存放 Git 对象和引用:

text
data/                      # storage.workspace
├── viking/                # 用户可见的资源树(viking:// 映射到这里)
│   └── ...
└── .ovgit/                # 多版本管理数据(新增)
    └── {account_id}/      # 每个账号一个逻辑 Git 仓库
        ├── objects/       # Git 对象(commit/tree/blob),标准 fanout 布局 aa/bb...
        ├── refs/
        │   └── heads/
        │       └── main   # 分支引用,内容为 40 位十六进制 OID
        └── HEAD           # 当前分支指针,内容为 "ref: refs/heads/main"

要点:

  • .ovgit 是内部数据目录,不会通过 viking:// 暴露,用户在文件系统 API(ls/read 等)中看不到也无法修改它。
  • 它与 Git 的标准对象库布局一致(内容寻址的 objects/、loose 引用的 refs/),但由 OpenViking 自动管理,无需也不应手动运行 git 命令去操作它。
  • 备份或迁移工作区时,把 .ovgit 一并复制即可保留完整的版本历史。
  • 选择 s3 后端时,不会创建本地 .ovgit 目录,数据改为存放在 bucket 的 {prefix}/{account}/... 键下。

使用方法

启用后,三种调用方式都会出现快照相关命令。下面以一个"提交 → 修改 → 恢复"的最小流程演示。

Python SDK

快照方法挂在 client.snapshot.* 命名空间下。

python
import openviking as ov

client = ov.OpenViking()
client.initialize()

root = "viking://resources/my_project"

# 1. 写入初始内容并提交 v1
client.write(f"{root}/guide.md", "# Guide\n\nv1 content\n", mode="create", wait=True)
v1 = client.snapshot.commit(message="v1 initial import")
print("v1:", v1["commit_oid"])

# 2. 修改后再提交 v2
client.write(f"{root}/guide.md", "# Guide\n\nv2 content\n", mode="replace", wait=True)
v2 = client.snapshot.commit(message="v2 update")

# 3. 查看历史
for c in client.snapshot.log(limit=10):
    print(c["oid"][:8], c["message"])

# 4. 查看某个提交的元数据
print(client.snapshot.show(v1["commit_oid"])["message"])

# 5. 把工作区恢复到 v1(会在 v2 之上生成一个新的“正向”提交)
client.snapshot.restore(project_dir=root, source_commit=v1["commit_oid"], message="restore to v1")

client.close()

CLI

CLI 子命令位于 ov snapshot 下:

bash
# 提交当前工作区状态
ov snapshot commit -m "v1 initial import" -o json

# 回溯历史(最新在前)
ov snapshot log --limit 10 -o json

# 查看提交元数据
ov snapshot show <commit_oid> -o json

# 读取某个提交中的文件内容(默认输出到 stdout,可用 --out-file 写入本地文件)
ov snapshot show <commit_oid> --path viking://resources/my_project/guide.md --out-file ./guide.md

# 把目录恢复到某个历史快照(位置参数依次为 <source_commit> <project_dir>)
ov snapshot restore <commit_oid> viking://resources/my_project -m "restore to v1" -o json

# 先预演,确认会改动哪些文件
ov snapshot restore <commit_oid> viking://resources/my_project --dry-run -o json

HTTP API

bash
# 提交
curl -X POST "http://localhost:1933/api/v1/snapshot/commit" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{"message": "v1 initial import"}'

# 回溯历史
curl -X GET "http://localhost:1933/api/v1/snapshot/log?branch=main&limit=10" \
  -H "X-API-Key: your-key"

# 查看提交元数据
curl -X GET "http://localhost:1933/api/v1/snapshot/show?target_ref=<commit_oid>" \
  -H "X-API-Key: your-key"

# 恢复
curl -X POST "http://localhost:1933/api/v1/snapshot/restore" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{"project_dir": "viking://resources/my_project", "source_commit": "<commit_oid>", "message": "restore to v1"}'

重要语义:正向恢复

restore 采用正向恢复(forward-commit):它读取 source_commit 的内容,把差异写回工作区,并在当前 HEAD 之上生成一个新的提交。因此:

  • 新提交的父提交是恢复操作发生前的 HEAD,不是 source_commit
  • HEAD 始终单调向前推进,历史永远不会被改写或丢失——回到旧版本本身也是一次新的提交。
  • restore 只影响 project_dir(省略时为整棵账号树)范围内的文件,范围之外的文件保持不变。

使用 .ovgitignore 排除文件

账号根目录下的 .ovgitignore 是一个账号级的排除规则文件,作用类似根 .gitignore:匹配该规则的文件在 commit 时被排除出快照。它与系统内置的剪枝规则(_systemtasks、向量索引派生文件等)叠加生效。

要点:

  • 规则文件本身不会被 .ovgitignore 规则忽略,即使规则匹配 .ovgitignore 也会被正常纳入快照——这样规则的变更可追溯、可恢复。
  • 规则只影响 commitrestoreshowlog 仍以提交内容为准,不把当前 .ovgitignore 当作过滤器。因此恢复一个历史快照时,即便其中某些文件匹配当前规则,仍会被正常恢复。
  • 若某个文件在更早的提交中已被跟踪、之后新增规则匹配到它,下一次 commit 会把它从新快照中移除(工作区的文件本身不受影响)。
  • .ovgitignore 不会进入向量索引/检索。

规则语法

.ovgitignore 为 UTF-8 文本,支持常见的 glob 子集:

  • 空行被忽略。
  • 首个非空白字符为 # 的行是注释。
  • 行首/行尾空白会被裁剪。
  • 不支持 ! 取反(出现会让 commit 失败并报错)。
  • 不支持 Git 风格的反斜杠转义。
  • 文件大小上限 64 KiB。

匹配路径使用账号相对的 Git 树路径(/ 分隔),如 resources/proj/a.log。例如 *.log 匹配任意深度的 .log 文件,build/ 匹配名为 build 的目录及其内容,/cache/** 仅匹配账号根下的 cache/

Python SDK

python
# 写入规则
client.snapshot.set_gitignore(content="*.log\n")

# 读取(不存在时返回空字符串)
print(client.snapshot.get_gitignore())

# 删除(不存在也视为成功,幂等)
client.snapshot.delete_gitignore()

随后提交时,匹配规则的文件会被排除,响应里的 ignored 字段给出本次被排除的候选路径数:

python
v = client.snapshot.commit(message="with ignore")
print(v["result"], v.get("ignored"))  # created, 1

CLI

bash
# 设置(用 --content 直接传内容,或用 --file 从文件读取)
ov snapshot ignore-set --content "*.log" -o json
ov snapshot ignore-set --file ./my-rules -o json

# 读取(-o json 返回 {"result": "<内容>"};不加 -o json 时直接把内容打到 stdout)
ov snapshot ignore-get -o json

# 删除(幂等)
ov snapshot ignore-delete -o json

HTTP API

bash
# 读取
curl -X GET "http://localhost:1933/api/v1/snapshot/ignore" \
  -H "X-API-Key: your-key"

# 写入
curl -X PUT "http://localhost:1933/api/v1/snapshot/ignore" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{"content": "*.log\n"}'

# 删除
curl -X DELETE "http://localhost:1933/api/v1/snapshot/ignore" \
  -H "X-API-Key: your-key"

注意事项

  • 修改 git 配置后必须重启服务 / 重新初始化客户端才能生效。
  • 启用 s3 后端时,git.s3.bucketgit.s3.region 为必填项,缺失会导致初始化失败。
  • 恢复操作如涉及向量副作用(写入/删除文件),响应会返回一个 task_id,可通过 GET /api/v1/tasks/{task_id} 轮询后台向量重建进度(参见 系统指南API 概览)。
  • .ovgitignore 内容过大(超过 64 KiB)或包含 ! 取反、反斜杠转义等不支持语法时,commit 会失败并报 invalid operation 错误;写入时(set_gitignore)会预先校验大小。
  • 不要手动用外部 git 工具去操作 .ovgit 目录,它由 OpenViking 维护。

相关文档

Released under the Apache-2.0 License.