Version Management
About 3 min
Version Management
Complete guide to branch management, version saving, branch merging, and conflict resolution.
Concepts
Version Structure
Drawing (mapid/version)
├── main branch
│ ├── base (initial version)
│ ├── patch-001
│ ├── patch-002
│ └── patch-003 (current)
└── feature-xxx branch
├── base (branched from main)
└── patch-001Patch Version Chain
base → patch-001 → patch-002 → patch-003Each Patch records incremental changes relative to its parent version:
- Added entities
- Modified entities
- Deleted entity IDs
- Layer changes
- Metadata (author, timestamp, remarks, etc.)
Create Branch
Command Mode
// Open drawing browser
await Engine.editor.executerWithOp('OPENFROMSERVER');
// Right-click a version and choose "Create Branch"API Mode
const { DrawingManagerService, BranchCreateDialog, message } = vjcad;
const drawingManager = new DrawingManagerService();
// Method 1: create through dialog
const dialog = new BranchCreateDialog();
const result = await dialog.showDialog({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
fromBranchName: 'main',
fromPatchId: 'base'
});
if (result && result.action === 'create') {
const createResult = await drawingManager.createBranch({
type: result.type,
mapid: result.mapid,
version: result.version,
sourceBranch: result.fromBranchName,
sourcePatchId: result.fromPatchId,
branchName: result.newBranchName
});
if (createResult.status) {
message.info(`Branch "${result.newBranchName}" created successfully!`);
}
}
// Method 2: call API directly
const createResult = await drawingManager.createBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'main',
sourcePatchId: 'base',
branchName: 'feature-new-layer'
});Save Version (Patch)
Command Mode
await Engine.editor.executerWithOp('SAVESERVER');API Mode
const { DrawingManagerService, Engine } = vjcad;
const drawingManager = new DrawingManagerService();
// 1. Open drawing from server (get original data)
const openResult = await drawingManager.openDrawing({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branch: 'main',
patchId: 'base'
});
// 2. Load into editor
const virtualFile = new File([openResult.webcadJson], 'doc', { type: 'application/json' });
await Engine.view.openDbDoc(virtualFile, openResult.webcadData);
// Save original data (for diff calculation)
await Engine.currentDoc.setOriginalJson(openResult.webcadJson);
// 3. Edit drawing...
// (Add, modify, delete entities)
// 4. Save (calculate diff to generate patch)
const currentDoc = Engine.currentDoc;
const currentJson = JSON.stringify(currentDoc.toDb());
const originalJson = await currentDoc.getOriginalJson();
const saveResult = await drawingManager.saveDrawing({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branchName: 'main',
originalJson: originalJson,
currentJson: currentJson,
parentId: openResult.latestPatchId || 'base',
drawingName: 'My Changes',
author: 'User Name',
remark: 'Added a new layer'
});
if (saveResult.status) {
if (saveResult.patchId === 'no_change') {
console.log('No changes to save');
} else {
console.log(`Saved successfully! Patch ID: ${saveResult.patchId}`);
// Update local original data
await currentDoc.setOriginalJson(currentJson);
}
} else if (saveResult.conflict && saveResult.conflict.hasConflict) {
console.warn('Conflict detected with changes from other users');
} else {
console.error(`Save failed: ${saveResult.error}`);
}Merge Branches
API Mode
const { DrawingManagerService, message } = vjcad;
const drawingManager = new DrawingManagerService();
// Merge feature branch into main
const mergeResult = await drawingManager.mergeBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'feature-new-layer',
targetBranch: 'main',
remark: 'Merge new layer feature'
});
if (mergeResult.status) {
message.info(`Merged successfully! Patch ID: ${mergeResult.patchId}`);
} else if (mergeResult.conflict && mergeResult.conflict.hasConflict) {
message.warn('Conflict detected, manual resolution required');
// Handle conflicts...
} else {
message.error(`Merge failed: ${mergeResult.error}`);
}View Version History
API Mode
const { DrawingManagerService } = vjcad;
const drawingManager = new DrawingManagerService();
// Get branch list (including patch information for each branch)
const branches = await drawingManager.listBranches({
type: 'imports',
mapid: 'drawing-123',
version: 'v1'
});
console.log('Branch list:', branches.map(b => b.name));
// Get patch list of main branch
const mainBranch = branches.find(b => b.name === 'main');
const patches = mainBranch ? mainBranch.patches : [];
console.log(`Total ${patches.length} versions`);
// Show version details
for (const patch of patches) {
console.log(`- ${patch.id}: ${patch.remark || '(no remark)'} by ${patch.author}`);
}Open a Specific Version
// Open a specified Patch version
const result = await drawingManager.openDrawing({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branch: 'main',
patchId: 'patch-001' // Specific version
});Delete Version/Branch
Delete Patch
const deleteResult = await drawingManager.deletePatch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branch: 'main',
patchId: 'patch-003'
});
if (deleteResult.status) {
console.log('Patch deleted');
}Delete Branch
const deleteResult = await drawingManager.deleteBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branchName: 'feature-old'
});
if (deleteResult.status) {
console.log('Branch deleted');
}Conflict Handling
When two branches modify the same entity, a conflict occurs during merge.
Detect Conflicts
const mergeResult = await drawingManager.mergeBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'feature-a',
targetBranch: 'main'
});
if (mergeResult.conflict && mergeResult.conflict.hasConflict) {
// Conflicts exist
const conflicts = mergeResult.conflict.entities;
console.log(`Conflicting entities: ${conflicts.length}`);
for (const conflict of conflicts) {
console.log(`Entity ${conflict.id}:`);
console.log(` - Source branch version: ${JSON.stringify(conflict.source)}`);
console.log(` - Target branch version: ${JSON.stringify(conflict.target)}`);
}
}Resolve Conflicts
Conflict resolution strategies:
- Choose source branch version - use the feature branch changes
- Choose target branch version - keep the main branch changes
- Manual merge - decide the final result manually
// Resolve conflict using source branch version
const resolveResult = await drawingManager.resolveConflict({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'feature-a',
targetBranch: 'main',
resolution: 'source' // 'source' | 'target' | 'manual'
});Complete Workflow Example
const { DrawingManagerService, Engine, LineEnt, CircleEnt, message } = vjcad;
const drawingManager = new DrawingManagerService();
// === 1. Create feature branch ===
const createResult = await drawingManager.createBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'main',
sourcePatchId: 'base',
branchName: 'feature-new-layer'
});
if (!createResult.status) {
throw new Error(`Failed to create branch: ${createResult.error}`);
}
message.info('Feature branch created successfully');
// === 2. Edit on the feature branch ===
const openResult = await drawingManager.openDrawing({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branch: 'feature-new-layer',
patchId: 'base'
});
const virtualFile = new File([openResult.webcadJson], 'doc', { type: 'application/json' });
await Engine.view.openDbDoc(virtualFile, openResult.webcadData);
await Engine.currentDoc.setOriginalJson(openResult.webcadJson);
// Add new graphics
const circle = new CircleEnt([100, 100], 50);
circle.setDefaults();
circle.color = 1;
Engine.addEntities(circle);
// === 3. Save changes ===
const currentJson = JSON.stringify(Engine.currentDoc.toDb());
const saveResult = await drawingManager.saveDrawing({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branchName: 'feature-new-layer',
originalJson: openResult.webcadJson,
currentJson: currentJson,
parentId: openResult.latestPatchId || 'base',
drawingName: 'New Feature',
author: 'Developer',
remark: 'Added red circle'
});
message.info(`Saved successfully: ${saveResult.patchId}`);
// === 4. Merge into main ===
const mergeResult = await drawingManager.mergeBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
sourceBranch: 'feature-new-layer',
targetBranch: 'main',
remark: 'Merge new feature'
});
if (mergeResult.status) {
message.info('Merge successful!');
// === 5. Clean up feature branch (optional) ===
await drawingManager.deleteBranch({
type: 'imports',
mapid: 'drawing-123',
version: 'v1',
branchName: 'feature-new-layer'
});
message.info('Feature branch deleted');
} else if (mergeResult.conflict?.hasConflict) {
message.warn('Conflicts exist and need manual resolution');
}API Reference
DrawingManagerService
| Method | Description |
|---|---|
createBranch(params) | Create branch |
deleteBranch(params) | Delete branch |
listBranches(params) | Get branch list |
mergeBranch(params) | Merge branches |
saveDrawing(params) | Save version (Patch) |
deletePatch(params) | Delete version |
openDrawing(params) | Open drawing |
Branch Operation Parameters
| Parameter | Description |
|---|---|
type | Drawing type: 'imports' | 'designs' |
mapid | Drawing ID |
version | Version number (such as 'v1') |
branchName | Branch name |
sourceBranch | Source branch |
targetBranch | Target branch |
sourcePatchId | Source Patch ID |