# version / 版本管理总览

> 文档：`vjcad/doc/version/index.md`、`vjcad/doc/version/versioning.md`、`vjcad/doc/version/user-auth.md`
> 示例：`vjcad/example/version/01createbranch.js` ~ `06userauthcallback.js`

## 1. 是什么

vjcad 的服务端版本管理是一个 **Git 风格 + DWG 语义** 的版本系统：

- 每张图纸是一棵 **分支树**（main / dev / release / feature-xxx）
- 每次保存生成一个 **patch（补丁）**，存储 **diff** 而非完整图纸
- 分支可以 **merge**，冲突时返回结构化冲突信息，前端可弹框解决
- 支持 **删除补丁 / 删除分支**；main 分支不可删（服务端硬性拒绝）

## 2. 整体心智图

```mermaid
flowchart LR
    subgraph base[base 版本]
        B[初始图纸]
    end
    subgraph main
        B --> P1[patch1]
        P1 --> P2[patch2]
    end
    subgraph dev
        P1 --> D1[patch dev1]
        D1 --> D2[patch dev2]
    end
    subgraph feat
        P2 --> F1[patch feat1]
    end
    D2 -. merge .-> P2
```

- **base**：分支链的起点。main 及从 `main@base` 建的分支共用图纸原始 `base.vcad`；从其它分支或其它补丁分叉的分支，base 是创建时生成的分叉点快照（`branchBase.vcad`）
- **patch**：前端 WASM（`createWebcadDiff`）在 `originalJson` ↔ `currentJson` 之间算出的 **RFC 6902 JSON Patch**（`[{op, path, value}]` 数组），只上传 patch，不是 `{added/modified/erased}` 结构；补丁 id 形如 `p1`、`p2`
- **branch**：一条线性 patch 链
- **merge**：服务端计算"共同祖先 → 源分支最新"的整体 diff，作为 **一个新 patch** 追加到目标分支末尾（不是逐个重放源分支补丁）

## 3. 核心 API（DrawingManagerService）

```js
const drawingManager = new DrawingManagerService();

// 分支（listBranches 收对象参数；IBranchInfo.name 是分支名）
await drawingManager.listBranches({ type: 'imports', mapid, version });
await drawingManager.createBranch({
    type: 'imports', mapid, version,              // type 必填
    branchName, sourceBranch, sourcePatchId       // 注意：sourceBranch/sourcePatchId，不是 parentXxx
});
await drawingManager.deleteBranch({ type: 'imports', mapid, version, branchName });

// 补丁
await drawingManager.saveDrawing({
    type: ss.type,                       // 必填（ss = Engine.currentDoc.serverSource）
    mapid, version, branchName,
    parentId, originalJson, currentJson  // diff 由前端 WASM 计算，只上传 patch
});
await drawingManager.deletePatch({ type: 'imports', mapid, version, branch: branchName, patchId });  // 字段是 branch 不是 branchName

// 合并
await drawingManager.mergeBranch({
    type: 'imports', mapid, version,          // type 必填
    sourceBranch, targetBranch,
    /* conflictResolution? */
});
```

## 4. 命令对照

| 命令 | 作用 |
|---|---|
| `OPENFROMSERVER` | 图纸浏览器：选分支 / 补丁打开（普通 / 只读 / 瓦片），并内置分支创建 / 删除、合并（含冲突对话框）、删补丁、删图纸 |
| `SAVESERVER` | 把当前 doc 保存为新 patch（冲突时弹 `ConflictResolutionDialog`；瓦片模式下同时记录编辑区与瓦片透明度） |
| 自定义命令 | 需要业务定制流程 / UI 时再用 `DrawingManagerService` 自己实现 |

## 5. 子文档

- 分支创建 / 切换 → [branching](branching.md)
- 保存补丁（生成 diff） → [save-patch](save-patch.md)
- 合并 / 冲突解决 → [merge-resolve](merge-resolve.md)
- 历史浏览 / 删除补丁 → [history](history.md)
- 用户认证（鉴权回调） → [user-authentication](user-authentication.md)

## 6. 与 Git 的对照

| Git | vjcad |
|---|---|
| commit | patch |
| branch | branch |
| merge | mergeBranch |
| conflict | `mergeResult.conflict` |
| HEAD | `serverSource.lastPatchId` |
| working tree | `Engine.currentDoc` |
| `git diff` | 保存时 WASM `createWebcadDiff` 自动计算（`DiffUtils.diff` 只用于 `DIFF` 面板展示） |

## 红旗

- 保存不必先同步到最新 patch：服务端只检查"`parentId` 之后的补丁是否也改过本次修改的实体 / 图层"，无交集就直接追加到分支末尾；有交集返回 `conflict`，带 `conflictResolution` 并以 `parentId = conflict.latestPatchId` 重存。`parentId` 填了分支里不存在的 id 时冲突检测会被 **静默跳过**
- `serverSource.lastPatchId` 是当前文档"知道"自己处于哪个补丁的依据；切分支后要更新
- `originalJson` 必须是 **打开时的 baseline**，`currentJson` 是当前实时状态；它们一起生成 diff
- 删除分支 / 删除 patch **不可逆**，UI 上加二次确认；`deletePatch` 会删除该 patch **及其后全部** patch，`base` 不能删
- main 分支不能用 `deleteBranch` 删（服务端拒绝）；只能 `deleteDrawing` 删除整张图纸
- `OPENFROMSERVER` 的图纸浏览器已内置完整版本管理：只读打开 / 瓦片打开、分支创建与删除、合并（含冲突对话框）、删补丁、删图纸——不一定要自己写 UI

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/version/index.md`、`vjcad/doc/version/versioning.md`、`vjcad/doc/version/user-auth.md`
- 示例：`vjcad/example/version/01createbranch.js` ~ `06userauthcallback.js`
