用 git 管理 workbook
可以把 workbook 的内容以文件形式取出到手边的仓库,在 git 中做 diff、评审、提 PR,并原样写回。聊天里的 Agent 写的 transform 也会出现在同一个位置,因此”先评审 AI 写的 SQL 再定稿”的运作方式可以直接成立。
d2b pull --workbook WB # → transforms/ sheets/ charts/ + d2b.jsond2b 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/*.csvd2b 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 versiongit commit -am "d2b push" # push updates d2b.json (sync hashes) — commit it too| 目录 | 内容 | pull | push |
|---|---|---|---|
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": "…"}}}按工作区同步
Section titled “按工作区同步”数百到数千个工作簿以 一个仓库 = 一个工作区 的方式处理。根目录的 d2b.json(台账)固定工作区,每个工作簿落在 workbooks/<标题>--<id 前 8 位>/,沿用上面的布局,并带有各自的 d2b.json。
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 Actions 自动化往返
Section titled “用 GitHub Actions 自动化往返”不需要 GitHub App,也不需要在 D2B 侧做任何集成配置,仅靠 CLI 就能闭环仓库与 workbook 之间的往返。
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(...) 是同一个面。