跳转到内容

用 git 管理 workbook

可以把 workbook 的内容以文件形式取出到手边的仓库,在 git 中做 diff、评审、提 PR,并原样写回。聊天里的 Agent 写的 transform 也会出现在同一个位置,因此”先评审 AI 写的 SQL 再定稿”的运作方式可以直接成立。

Terminal window
d2b pull --workbook WB # → transforms/ sheets/ charts/ + d2b.json
d2b pull --data customers # also track a small base table as data/customers.csv (export = branch)
git add -A && git commit -m "pull from D2B"
# ... edit transforms/*.sql|py, sheets/*.json, data/*.csv
d2b push --dry-run # what would be sent (only files that changed)
d2b push --commit "$(git rev-parse --short HEAD)" # apply the changes → pin the git sha as a named version
git commit -am "d2b push" # push updates d2b.json (sync hashes) — commit it too
目录内容pullpush
transforms/SQL / Python transform(可分层,如 agg/monthly.sql)✅✅ 只把变更部分通过 POST /transforms 重新执行(计入数据操作),按依赖顺序(自上游起)
sheets/展示用 Sheet {"blocks": [...]}✅✅ PUT /sheets/{name}
charts/图表的 config + recipe(生成时使用的工具和参数)✅❌ 只读 — 图表应从 recipe 重新生成,手改 config 无法再生成。仅用于历史与确认
data/通过 --data 选择性纳入的 base 表 CSV(带 __d2b_row_id)✅ export = branch✅ 在服务器端做以行 ID 为键的单元格级 3-way merge。两侧都改动的单元格进入 workbook 的 conflict 队列(保留 D2B 的值)

--data 只适用于 base 表(其行本身即事实来源的表)。mode=auto 的结构化输出是 derived(变换的输出),无法分支 — 若要让某张表的行经 git 往返,请用 --mode staged 摄取、确认 parse spec 后 materialize(即成为 base 表),或通过行 API 创建。

d2b.json 是 manifest。transform 的条目为 {name, artifact_name, args, layer, hash}(新增 transform 时,要同时写入文件和这条条目)。hash 是最近一次同步时点的 digest(= merge base),由 CLI 维护 — push 之后请把 d2b.json 提交进 git。

手写新条目时不要写 hash(省略即可,null 同义)— 首次 push 会生成并写回。

{
"workbook_id": "…",
"transforms": {
"agg/monthly.sql": {
"name": "agg/monthly",
"artifact_name": "product_sales", "args": {"src": "sales"}, "layer": null,
"hash": "…"
}
},
"sheets": {"summary.json": {"name": "summary", "hash": "…"}},
"charts": {"trend.json": {"name": "trend", "readonly": true, "hash": "…"}},
"data": {"customers.csv": {"table": "customers", "branch_id": "…", "hash": "…"}}
}

数百到数千个工作簿以 一个仓库 = 一个工作区 的方式处理。根目录的 d2b.json(台账)固定工作区,每个工作簿落在 workbooks/<标题>--<id 前 8 位>/,沿用上面的布局,并带有各自的 d2b.json。

Terminal window
d2b pull --workspace WS # 首次:写入台账,并拉取工作区的全部工作簿(并行,--jobs N)
d2b pull # 之后:从台账读取;新工作簿会自动出现
d2b pull --prune # 删除已离开工作区的工作簿目录(删除会体现在 diff 中)
d2b status --strict # 台账、目录与服务器不一致时 exit 2(作为 CI 的必需检查)
d2b push --commit "$(git rev-parse --short HEAD)" # 只发送有变更的工作簿
  • 归属是服务器端的事实:pull 列出工作区的全部工作簿,绝不写入台账之外的目录。不能把另一个工作区拉进同一个仓库——没有覆盖开关。
  • 工作簿目录在首次 pull 时固定,改标题不会移动它。id 写在目录名和每个 transform 文件的首行 -- d2b ws=… wb=… transform=…(这一行不会发送到服务器,也不算作变更)。
  • push 一旦发现台账之外的目录、头部指向其他工作簿的文件、或 workbook_id 与台账不符的清单,就什么也不发送并拒绝。
  • 给 CI 使用 固定到工作区的 PAT(POST /api/control/account/keys 的 workspace_id,或 POST /api/v1/me/tokens 的 resource: "workspace:<id>"):即使配置出错,其他工作区也会被服务器以 403 拒绝。d2b login 的凭证覆盖整个账户,不适合 CI。
  • data/(行数据)仍按工作簿逐个选择加入(在 workbooks/<dir>/ 内执行 d2b pull --data TABLE)。

没有台账的目录保持上面的单工作簿行为。

  • 文本类(transforms / sheets / charts)不会静默覆盖(与行 API 的乐观锁是同一契约): 若自最近一次同步以来两侧都有改动,pull / push 都会拒绝,并把涉及的文件以 JSON 返回。--force 的方向因命令而异:push --force 用本地一侧覆盖;pull --force 采用服务器一侧(丢弃本地编辑)
  • data/ 不同: 并发编辑由服务器的 3-way merge 逐单元格裁定,因此不会拒绝。push 之后会按 merge 结果重新取回 CSV,并切出新的 branch(D2B 侧的改动也会落到本地)。派生表不能 branch(400)。merge 上限 100,000 行 — 面向主数据、对照表这类小表
  • 本地删除的文件不会删除服务器侧(只在 deleted_locally 中报告)。--prune 会删除 transform 的输出表和 Sheet(删除输出表需要 workbooks:delete)。data/ 只是解除追踪,不会删表
  • d2b.json 的键只能是所属 section 目录内的规范化相对路径:绝对路径、..、Windows 盘符以及未规范化的路径在 pull / push 时都会被拒绝。若同步对象文件(如 transforms/*.sql|py)是符号链接,同样拒绝 — 不会通过链接读写(指向该 section 不读取的文件的链接则直接忽略)

不需要 GitHub App,也不需要在 D2B 侧做任何集成配置,仅靠 CLI 就能闭环仓库与 workbook 之间的往返。

Terminal window
d2b github-workflow > .github/workflows/d2b.yml
# Secrets: D2B_API_KEY (a workbooks:write PAT). Variables: D2B_BASE_URL
  • merge 到 main(同步对象文件有变更)→ d2b push --commit <sha> → 自动提交更新后的 d2b.json
  • 定时(默认每 1 小时)与手动触发 → d2b pull → 有差异则开 PR(d2b/pull 分支)。形成”Agent 在 workbook 侧的改动由人评审后 merge”的流程

在 SDK 中,client.transforms.list(wb) / client.sheets.list(wb) / client.charts.list(wb) / client.export.branch(wb, [table]) / client.tables.merge(...) 是同一个面。