# 历史浏览与删除

> 文档：`vjcad/doc/version/versioning.md`
> 示例：`vjcad/example/version/04versionhistory.js`

## 1. 取历史

`listBranches` 收 **一个对象参数**（不是位置参数），返回 `IBranchInfo[]`：

```js
const drawingManager = new DrawingManagerService();

const branches = await drawingManager.listBranches({
    type: 'imports', mapid: 'test', version: 'v1'
});
branches.forEach(b => {
    // IBranchInfo: name / createdAt / createdBy / sourceInfo{branch,patchId} / patchCount / latestPatchId / patches
    const src = b.sourceInfo ? `（基于 ${b.sourceInfo.branch}@${b.sourceInfo.patchId}）` : '';
    console.log(`分支 ${b.name}${src}`);
    (b.patches || []).forEach((p, i) => {
        // IPatchInfo: id / parentId / time / author / remark
        const t = p.time ? new Date(p.time).toLocaleString() : '';
        console.log(`  ${i + 1}. ${p.id} by ${p.author ?? ''} @ ${t} - ${p.remark || ''}`);
    });
});
```

> 完整代码：`vjcad/example/version/04versionhistory.js`

`patches` 数组按时间顺序排列；最后一条就是该分支当前 head（也即 `b.latestPatchId`）。

## 2. 切换到某个 patch（只读对照）

没有 `openDbDoc({mapid, lastPatchId})` 这种调用——真实流程是 `openDrawing` 取数据 + `openDbDoc(file, data, readOnly)` 加载（同 `OpenFromServerCommand`）：

```js
const openResult = await drawingManager.openDrawing({
    type: 'imports', mapid: 'test', version: 'v1',
    branch: 'main',
    patchId: 'p3',             // 任意历史 patch（id 形如 p1、p2…）；省略则取最新
    readOnly: true,
});
if (openResult.success) {
    const virtualFile = new File([openResult.webcadJson], 'history_view', { type: 'application/json' });
    await Engine.view.openDbDoc(virtualFile, openResult.webcadData, true);
    // 如需保留来源信息（再保存时以该 patch 为 parent）：
    Engine.currentDoc.serverSource = {
        type: 'imports', mapid: 'test', version: 'v1',
        branchName: 'main',
        lastPatchId: openResult.latestPatchId || 'p3',
    };
}
```

历史 patch 默认不应直接编辑；如需基于历史出发新建分支，用 `createBranch({ branchName, sourceBranch, sourcePatchId: 'p3' })`——注意字段名是 `sourcePatchId` 不是 `parentPatchId`。

## 3. 删除补丁

`deletePatch` 必填 `type` + `patchId`；分支字段名是 `branch`：

```js
await drawingManager.deletePatch({
    type:    'imports',
    mapid:   'test',
    version: 'v1',
    branch:  'main',          // 不是 branchName
    patchId: 'p3'
});
```

> ⚠️ 删除补丁是 **危险操作**：服务端会删除指定 patch **及其之后的全部 patch**（链被截断到它之前，不会留下断链），返回 `{ status, deletedPatchId, branch }`；`patchId: 'base'` 会被拒绝（`Cannot delete base version`）。只想撤销最近一次保存就删 head 补丁。

## 4. 删除分支

`deleteBranch` 必填 `type` + `branchName`：

```js
await drawingManager.deleteBranch({
    type: 'imports', mapid: 'test', version: 'v1',
    branchName: 'feature-abc'
});
```

删除分支会连带删除该分支的所有补丁。**main 不可删**（服务端返回 `Cannot delete main branch`）；要删 main 只能用 `deleteDrawing` 删除整张图纸。

## 5. 数据结构字段

源码真实接口：

```ts
interface IBranchInfo {
    name: string;                    // 分支名（不是 branchName）
    createdAt: string;
    createdBy: string;
    sourceInfo?: { branch: string; patchId: string };   // 源分支@patch
    description?: string;
    patchCount: number;
    latestPatchId: string;
    patches?: IPatchInfo[];
    drawingName?: string;
}

interface IPatchInfo {
    id: string;                      // patch ID（不是 patchId）
    parentId?: string;
    time?: string;                   // 时间字符串（不是 timestamp number）
    author?: string;                 // 作者（不是 userId）
    remark?: string;                 // 备注（不是 message）
    editAreas?: IEditArea[];         // 瓦片编辑区（tile 模式恢复用）
    editLayers?: string[];
    tileAlpha?: number;
}
```

## 6. 与时间机器的对应

把 `listBranches` + `openDrawing({ patchId })` 组合起来，可以做：

- 历史时间轴
- 任意点 checkout 出来对比
- 找到回退点 → `createBranch({ sourcePatchId })` → 在该点出新分支

## 红旗

- `deletePatch` 删除的是"该 patch 及其后全部"，删中间补丁等于把分支回退到它之前；只想保留历史又要回退，用 `createBranch({ sourcePatchId })` 从回退点出新分支
- main 分支不可删，服务端硬性拒绝；只能 `deleteDrawing` 删整图
- `listBranches` 数据量大时（年单位、几千补丁）建议后端分页或按时间过滤
- 不需要前端逐个回放补丁：`openDrawing({ branch, patchId })` 由服务端合成（带缓存）并直接返回该补丁处的完整图纸
- 如果只想撤销当前未保存改动，请用 Undo（参见 `undo/`），不要去 deletePatch

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/version/versioning.md`
- 示例：`vjcad/example/version/04versionhistory.js`
- 源码：`DrawingManagerService.listBranches/openDrawing/deletePatch/deleteBranch`、`ServiceInterface.IBranchInfo/IPatchInfo`
