nano-git 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/workdir/change-index-plan.mjs +76 -0
- package/dist/workdir/change-index.d.mts +21 -0
- package/dist/workdir/change-index.mjs +413 -0
- package/dist/workdir/core.d.mts +64 -63
- package/dist/workdir/directory-view.mjs +197 -0
- package/dist/workdir/dirty-dir-plan.mjs +73 -0
- package/dist/workdir/dirty-dir.d.mts +28 -0
- package/dist/workdir/dirty-dir.mjs +32 -0
- package/dist/workdir/file-backend.d.mts +34 -12
- package/dist/workdir/file-backend.mjs +161 -94
- package/dist/workdir/file.d.mts +2 -2
- package/dist/workdir/file.mjs +2 -2
- package/dist/workdir/ids.d.mts +1 -1
- package/dist/workdir/ids.mjs +3 -3
- package/dist/workdir/memory-backend.mjs +34 -50
- package/dist/workdir/memory.d.mts +2 -3
- package/dist/workdir/memory.mjs +2 -3
- package/dist/workdir/nodes.d.mts +7 -7
- package/dist/workdir/nodes.mjs +3 -9
- package/dist/workdir/overlay.d.mts +3 -3
- package/dist/workdir/sqlite-backend.d.mts +31 -14
- package/dist/workdir/sqlite-backend.mjs +238 -142
- package/dist/workdir/sqlite.d.mts +2 -2
- package/dist/workdir/sqlite.mjs +2 -2
- package/dist/workdir/state-store.d.mts +23 -12
- package/dist/workdir/workdir-path.mjs +348 -0
- package/dist/workdir/workdir-transaction.mjs +115 -0
- package/dist/workdir/workdir.d.mts +30 -0
- package/dist/workdir/workdir.mjs +280 -0
- package/dist/workdir/write-tree.mjs +108 -44
- package/package.json +2 -1
- package/dist/workdir/change-log.d.mts +0 -25
- package/dist/workdir/change-log.mjs +0 -69
- package/dist/workdir/memory-backend.d.mts +0 -17
- package/dist/workdir/session-id.mjs +0 -18
- package/dist/workdir/session-internal.mjs +0 -141
- package/dist/workdir/session.d.mts +0 -30
- package/dist/workdir/session.mjs +0 -407
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { VirtualNotDirectoryError } from "../core/errors.mjs";
|
|
2
|
-
import { VIRTUAL_ROOT_NODE_ID, originBackedNodeId } from "./ids.mjs";
|
|
3
|
-
import { mergeDirectoryChildren } from "./overlay.mjs";
|
|
4
|
-
import { readRepoTree, treeEntryToNodeOrigin } from "./origin.mjs";
|
|
5
|
-
import { assertValidVirtualPath, joinPath, splitPathSegments } from "./path.mjs";
|
|
6
|
-
//#region src/workdir/session-internal.ts
|
|
7
|
-
/**
|
|
8
|
-
* Virtual Workdir session 内部共享逻辑
|
|
9
|
-
*
|
|
10
|
-
* 供 session.ts 与 write-tree.ts 复用:
|
|
11
|
-
* - 路径解析(resolvePath)
|
|
12
|
-
* - 目录子项展开(listDirectoryChildren)
|
|
13
|
-
* - origin 节点懒注册(ensureNodeFromTreeEntry)
|
|
14
|
-
*/
|
|
15
|
-
function getRootNode(state) {
|
|
16
|
-
const root = state.getNode(VIRTUAL_ROOT_NODE_ID);
|
|
17
|
-
if (root === null) throw new Error("Virtual workdir session is missing root node");
|
|
18
|
-
return root;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 从根目录沿路径分段解析到目标节点
|
|
22
|
-
*/
|
|
23
|
-
function resolvePath(source, state, path) {
|
|
24
|
-
assertValidVirtualPath(path);
|
|
25
|
-
const segments = splitPathSegments(path);
|
|
26
|
-
let current = getRootNode(state);
|
|
27
|
-
let currentPath = "";
|
|
28
|
-
for (const segment of segments) {
|
|
29
|
-
if (current.state.kind !== "directory") return {
|
|
30
|
-
found: false,
|
|
31
|
-
node: null
|
|
32
|
-
};
|
|
33
|
-
const child = listDirectoryChildren(source, state, current, currentPath).find((c) => c.name === segment);
|
|
34
|
-
if (child === void 0) return {
|
|
35
|
-
found: false,
|
|
36
|
-
node: null
|
|
37
|
-
};
|
|
38
|
-
currentPath = joinPath(currentPath === "" ? null : currentPath, segment);
|
|
39
|
-
const childNode = state.getNode(child.nodeId);
|
|
40
|
-
if (childNode === null) return {
|
|
41
|
-
found: false,
|
|
42
|
-
node: null
|
|
43
|
-
};
|
|
44
|
-
current = childNode;
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
found: true,
|
|
48
|
-
node: current
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* 根据父节点与子条目名解析子节点
|
|
53
|
-
*/
|
|
54
|
-
function resolveChild(source, state, parentNode, parentPath, name) {
|
|
55
|
-
if (parentNode.state.kind !== "directory") return {
|
|
56
|
-
found: false,
|
|
57
|
-
node: null
|
|
58
|
-
};
|
|
59
|
-
const child = listDirectoryChildren(source, state, parentNode, parentPath).find((c) => c.name === name);
|
|
60
|
-
if (child === void 0) return {
|
|
61
|
-
found: false,
|
|
62
|
-
node: null
|
|
63
|
-
};
|
|
64
|
-
const childNode = state.getNode(child.nodeId);
|
|
65
|
-
if (childNode === null) return {
|
|
66
|
-
found: false,
|
|
67
|
-
node: null
|
|
68
|
-
};
|
|
69
|
-
return {
|
|
70
|
-
found: true,
|
|
71
|
-
node: childNode
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* 确保 origin 条目对应的 session 节点已懒注册
|
|
76
|
-
*/
|
|
77
|
-
function ensureNodeFromTreeEntry(state, entry) {
|
|
78
|
-
const id = originBackedNodeId(entry.hash);
|
|
79
|
-
if (state.getNode(id) === null) {
|
|
80
|
-
const origin = treeEntryToNodeOrigin(entry);
|
|
81
|
-
let nodeState;
|
|
82
|
-
if (entry.mode === "40000") nodeState = {
|
|
83
|
-
kind: "directory",
|
|
84
|
-
overlay: {
|
|
85
|
-
addedEntries: /* @__PURE__ */ new Map(),
|
|
86
|
-
deletedNames: /* @__PURE__ */ new Set()
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
else if (entry.mode === "120000") nodeState = {
|
|
90
|
-
kind: "symlink",
|
|
91
|
-
mode: "120000"
|
|
92
|
-
};
|
|
93
|
-
else nodeState = {
|
|
94
|
-
kind: "file",
|
|
95
|
-
mode: entry.mode === "100755" ? "100755" : "100644"
|
|
96
|
-
};
|
|
97
|
-
state.setNode({
|
|
98
|
-
id,
|
|
99
|
-
origin,
|
|
100
|
-
state: nodeState
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return id;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* 展开目录的完整子项列表(origin + overlay 合成)
|
|
107
|
-
*/
|
|
108
|
-
function listDirectoryChildren(source, state, dirNode, dirPath) {
|
|
109
|
-
if (dirNode.state.kind !== "directory") throw new VirtualNotDirectoryError(dirPath);
|
|
110
|
-
let originChildren = [];
|
|
111
|
-
if (dirNode.origin.kind === "repo-tree") originChildren = readRepoTree(source, dirNode.origin.hash, dirPath).entries.map((entry) => {
|
|
112
|
-
const childNodeId = ensureNodeFromTreeEntry(state, entry);
|
|
113
|
-
return {
|
|
114
|
-
name: entry.name,
|
|
115
|
-
mode: entry.mode,
|
|
116
|
-
nodeId: childNodeId
|
|
117
|
-
};
|
|
118
|
-
});
|
|
119
|
-
const addedModes = buildAddedModes(state, dirNode);
|
|
120
|
-
return mergeDirectoryChildren(originChildren, dirNode.state.overlay, addedModes);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* 构建 overlay addedEntries 对应的 mode map
|
|
124
|
-
*/
|
|
125
|
-
function buildAddedModes(state, dirNode) {
|
|
126
|
-
if (dirNode.state.kind !== "directory") return /* @__PURE__ */ new Map();
|
|
127
|
-
const addedModes = /* @__PURE__ */ new Map();
|
|
128
|
-
for (const name of dirNode.state.overlay.addedEntries.keys()) {
|
|
129
|
-
const nodeId = dirNode.state.overlay.addedEntries.get(name);
|
|
130
|
-
const node = state.getNode(nodeId);
|
|
131
|
-
if (node !== null) addedModes.set(name, sessionNodeMode(node));
|
|
132
|
-
}
|
|
133
|
-
return addedModes;
|
|
134
|
-
}
|
|
135
|
-
function sessionNodeMode(node) {
|
|
136
|
-
if (node.state.kind === "directory") return "40000";
|
|
137
|
-
if (node.state.kind === "symlink") return "120000";
|
|
138
|
-
return node.state.mode;
|
|
139
|
-
}
|
|
140
|
-
//#endregion
|
|
141
|
-
export { listDirectoryChildren, resolveChild, resolvePath };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
|
-
import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./core.mjs";
|
|
3
|
-
import { VirtualWorkdirStateStore } from "./state-store.mjs";
|
|
4
|
-
|
|
5
|
-
//#region src/workdir/session.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* 基于 ObjectDatabase 创建 VirtualWorkdirSession
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const repo = createMemoryRepository();
|
|
12
|
-
* const tree = repo.createTree([]);
|
|
13
|
-
* const session = createVirtualWorkdirSession(repo.objects, { baseTree: tree });
|
|
14
|
-
* expect(session.readdir()).toEqual([]);
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
declare function createVirtualWorkdirSession(source: ObjectDatabase, options: CreateVirtualWorkdirSessionOptions): VirtualWorkdirSession;
|
|
18
|
-
/**
|
|
19
|
-
* 基于已有状态存储打开 session
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* const store = createVirtualWorkdirMemoryStateStore(tree);
|
|
24
|
-
* const session = openVirtualWorkdirSession(repo.objects, store);
|
|
25
|
-
* expect(session.baseTree).toBe(tree);
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
declare function openVirtualWorkdirSession(source: ObjectDatabase, state: VirtualWorkdirStateStore): VirtualWorkdirSession;
|
|
29
|
-
//#endregion
|
|
30
|
-
export { createVirtualWorkdirSession, openVirtualWorkdirSession };
|
package/dist/workdir/session.mjs
DELETED
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
import { VirtualNotDirectoryError, VirtualNotFileError, VirtualNotSymlinkError, VirtualPathAlreadyExistsError, VirtualPathNotFoundError, VirtualRevertNotSupportedError } from "../core/errors.mjs";
|
|
2
|
-
import { mapInternalChangesToVirtualChanges } from "./change-log.mjs";
|
|
3
|
-
import { VIRTUAL_ROOT_NODE_ID, createNodeId } from "./ids.mjs";
|
|
4
|
-
import { overlayBindEntry, overlayRenameEntry, overlayTombstoneEntry } from "./overlay.mjs";
|
|
5
|
-
import { cloneSessionNodeForCopy, revertNodeState } from "./nodes.mjs";
|
|
6
|
-
import { modeToVirtualEntryKind, readRepoBlobContent } from "./origin.mjs";
|
|
7
|
-
import { assertValidVirtualPath, baseName, normalizeDirectoryPath, parentPath } from "./path.mjs";
|
|
8
|
-
import { listDirectoryChildren, resolveChild, resolvePath } from "./session-internal.mjs";
|
|
9
|
-
import { writeTreeFromSession } from "./write-tree.mjs";
|
|
10
|
-
import { createVirtualWorkdirMemoryStateStore } from "./memory-backend.mjs";
|
|
11
|
-
//#region src/workdir/session.ts
|
|
12
|
-
/**
|
|
13
|
-
* VirtualWorkdirSession 行为编排
|
|
14
|
-
*
|
|
15
|
-
* Phase 5:完整文件/目录 rename 与 copy 语义。
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* 基于 ObjectDatabase 创建 VirtualWorkdirSession
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const repo = createMemoryRepository();
|
|
23
|
-
* const tree = repo.createTree([]);
|
|
24
|
-
* const session = createVirtualWorkdirSession(repo.objects, { baseTree: tree });
|
|
25
|
-
* expect(session.readdir()).toEqual([]);
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
function createVirtualWorkdirSession(source, options) {
|
|
29
|
-
return openVirtualWorkdirSession(source, createVirtualWorkdirMemoryStateStore(options.baseTree));
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* 基于已有状态存储打开 session
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* const store = createVirtualWorkdirMemoryStateStore(tree);
|
|
37
|
-
* const session = openVirtualWorkdirSession(repo.objects, store);
|
|
38
|
-
* expect(session.baseTree).toBe(tree);
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
function openVirtualWorkdirSession(source, state) {
|
|
42
|
-
return {
|
|
43
|
-
get baseTree() {
|
|
44
|
-
return state.readBaseTree();
|
|
45
|
-
},
|
|
46
|
-
exists(path) {
|
|
47
|
-
if (path === "") return true;
|
|
48
|
-
return resolvePath(source, state, path).found;
|
|
49
|
-
},
|
|
50
|
-
stat(path) {
|
|
51
|
-
if (path === "") return statDirectoryNode(getRootNode(state));
|
|
52
|
-
const resolved = resolvePath(source, state, path);
|
|
53
|
-
if (!resolved.found || resolved.node === null) return null;
|
|
54
|
-
return statNode(source, resolved.node, path);
|
|
55
|
-
},
|
|
56
|
-
readdir(dirPath) {
|
|
57
|
-
const normalized = normalizeDirectoryPath(dirPath);
|
|
58
|
-
const resolved = normalized === "" ? {
|
|
59
|
-
found: true,
|
|
60
|
-
node: getRootNode(state)
|
|
61
|
-
} : resolvePath(source, state, normalized);
|
|
62
|
-
if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(normalized);
|
|
63
|
-
if (resolved.node.state.kind !== "directory") throw new VirtualNotDirectoryError(normalized);
|
|
64
|
-
return listDirectoryChildren(source, state, resolved.node, normalized).map((child) => ({
|
|
65
|
-
name: child.name,
|
|
66
|
-
kind: modeToVirtualEntryKind(child.mode),
|
|
67
|
-
mode: child.mode
|
|
68
|
-
}));
|
|
69
|
-
},
|
|
70
|
-
readFile(path) {
|
|
71
|
-
assertValidVirtualPath(path);
|
|
72
|
-
const resolved = resolvePath(source, state, path);
|
|
73
|
-
if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(path);
|
|
74
|
-
const node = resolved.node;
|
|
75
|
-
if (node.state.kind === "file") {
|
|
76
|
-
if (node.state.content !== void 0) return node.state.content;
|
|
77
|
-
if (node.origin.kind === "repo-blob") return readRepoBlobContent(source, node.origin.hash, path);
|
|
78
|
-
throw new VirtualPathNotFoundError(path);
|
|
79
|
-
}
|
|
80
|
-
if (node.state.kind === "symlink") throw new VirtualNotFileError(path);
|
|
81
|
-
throw new VirtualNotFileError(path);
|
|
82
|
-
},
|
|
83
|
-
readLink(path) {
|
|
84
|
-
assertValidVirtualPath(path);
|
|
85
|
-
const resolved = resolvePath(source, state, path);
|
|
86
|
-
if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(path);
|
|
87
|
-
const node = resolved.node;
|
|
88
|
-
if (node.state.kind !== "symlink") throw new VirtualNotSymlinkError(path);
|
|
89
|
-
if (node.state.target !== void 0) return node.state.target.toString("utf-8");
|
|
90
|
-
if (node.origin.kind === "repo-blob") return readRepoBlobContent(source, node.origin.hash, path).toString("utf-8");
|
|
91
|
-
throw new VirtualPathNotFoundError(path);
|
|
92
|
-
},
|
|
93
|
-
mkdir(path) {
|
|
94
|
-
runInWriteTransaction(state, () => {
|
|
95
|
-
assertValidVirtualPath(path);
|
|
96
|
-
const parent = parentPath(path);
|
|
97
|
-
const name = baseName(path);
|
|
98
|
-
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
99
|
-
found: true,
|
|
100
|
-
node: getRootNode(state)
|
|
101
|
-
};
|
|
102
|
-
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
103
|
-
const parentNode = parentResolved.node;
|
|
104
|
-
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
105
|
-
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
106
|
-
if (existing.found && existing.node !== null) throw new VirtualPathAlreadyExistsError(path);
|
|
107
|
-
const nodeId = createNodeId();
|
|
108
|
-
const newNode = {
|
|
109
|
-
id: nodeId,
|
|
110
|
-
origin: { kind: "none" },
|
|
111
|
-
state: {
|
|
112
|
-
kind: "directory",
|
|
113
|
-
overlay: {
|
|
114
|
-
addedEntries: /* @__PURE__ */ new Map(),
|
|
115
|
-
deletedNames: /* @__PURE__ */ new Set()
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
state.setNode(newNode);
|
|
120
|
-
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
121
|
-
state.appendChange({
|
|
122
|
-
op: "add",
|
|
123
|
-
path
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
writeFile(path, content, options) {
|
|
128
|
-
runInWriteTransaction(state, () => {
|
|
129
|
-
assertValidVirtualPath(path);
|
|
130
|
-
const mode = options?.mode ?? "100644";
|
|
131
|
-
const parent = parentPath(path);
|
|
132
|
-
const name = baseName(path);
|
|
133
|
-
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
134
|
-
found: true,
|
|
135
|
-
node: getRootNode(state)
|
|
136
|
-
};
|
|
137
|
-
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
138
|
-
const parentNode = parentResolved.node;
|
|
139
|
-
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
140
|
-
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
141
|
-
if (existing.found && existing.node !== null) {
|
|
142
|
-
if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
|
|
143
|
-
}
|
|
144
|
-
const isNew = !existing.found || existing.node === null;
|
|
145
|
-
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
146
|
-
const fileNode = {
|
|
147
|
-
id: nodeId,
|
|
148
|
-
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
149
|
-
state: {
|
|
150
|
-
kind: "file",
|
|
151
|
-
mode,
|
|
152
|
-
content
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
state.setNode(fileNode);
|
|
156
|
-
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
157
|
-
state.appendChange({
|
|
158
|
-
op: isNew ? "add" : "modify",
|
|
159
|
-
path
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
writeLink(path, target) {
|
|
164
|
-
runInWriteTransaction(state, () => {
|
|
165
|
-
assertValidVirtualPath(path);
|
|
166
|
-
const parent = parentPath(path);
|
|
167
|
-
const name = baseName(path);
|
|
168
|
-
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
169
|
-
found: true,
|
|
170
|
-
node: getRootNode(state)
|
|
171
|
-
};
|
|
172
|
-
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
173
|
-
const parentNode = parentResolved.node;
|
|
174
|
-
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
175
|
-
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
176
|
-
if (existing.found && existing.node !== null) {
|
|
177
|
-
if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
|
|
178
|
-
}
|
|
179
|
-
const isNew = !existing.found || existing.node === null;
|
|
180
|
-
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
181
|
-
const linkNode = {
|
|
182
|
-
id: nodeId,
|
|
183
|
-
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
184
|
-
state: {
|
|
185
|
-
kind: "symlink",
|
|
186
|
-
mode: "120000",
|
|
187
|
-
target: Buffer.from(target)
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
state.setNode(linkNode);
|
|
191
|
-
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
192
|
-
state.appendChange({
|
|
193
|
-
op: isNew ? "add" : "modify",
|
|
194
|
-
path
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
},
|
|
198
|
-
delete(path) {
|
|
199
|
-
runInWriteTransaction(state, () => {
|
|
200
|
-
assertValidVirtualPath(path);
|
|
201
|
-
const parent = parentPath(path);
|
|
202
|
-
const name = baseName(path);
|
|
203
|
-
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
204
|
-
found: true,
|
|
205
|
-
node: getRootNode(state)
|
|
206
|
-
};
|
|
207
|
-
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
208
|
-
const parentNode = parentResolved.node;
|
|
209
|
-
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
210
|
-
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
211
|
-
if (!existing.found || existing.node === null) throw new VirtualPathNotFoundError(path);
|
|
212
|
-
updateParentOverlay(state, parentNode.id, overlayTombstoneEntry(parentNode.state.overlay, name));
|
|
213
|
-
state.appendChange({
|
|
214
|
-
op: "delete",
|
|
215
|
-
path
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
},
|
|
219
|
-
rename(from, to) {
|
|
220
|
-
runInWriteTransaction(state, () => {
|
|
221
|
-
assertValidVirtualPath(from);
|
|
222
|
-
assertValidVirtualPath(to);
|
|
223
|
-
if (from === to) return;
|
|
224
|
-
const fromParent = parentPath(from);
|
|
225
|
-
const fromName = baseName(from);
|
|
226
|
-
const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
|
|
227
|
-
found: true,
|
|
228
|
-
node: getRootNode(state)
|
|
229
|
-
};
|
|
230
|
-
if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
|
|
231
|
-
const fromParentNode = fromParentResolved.node;
|
|
232
|
-
if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
|
|
233
|
-
const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
|
|
234
|
-
if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
|
|
235
|
-
const sourceNode = fromChild.node;
|
|
236
|
-
const toParent = parentPath(to);
|
|
237
|
-
const toName = baseName(to);
|
|
238
|
-
const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
|
|
239
|
-
found: true,
|
|
240
|
-
node: getRootNode(state)
|
|
241
|
-
};
|
|
242
|
-
if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
|
|
243
|
-
const toParentNode = toParentResolved.node;
|
|
244
|
-
if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
|
|
245
|
-
const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
|
|
246
|
-
if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
|
|
247
|
-
if (sourceNode.state.kind === "directory") {
|
|
248
|
-
const toPath = to;
|
|
249
|
-
const fromPath = from;
|
|
250
|
-
if (toPath.startsWith(fromPath + "/") || toPath === fromPath) throw new Error(`Cannot rename '${from}' to '${to}': destination is a subdirectory of source`);
|
|
251
|
-
}
|
|
252
|
-
if (fromParentNode.id === toParentNode.id) updateParentOverlay(state, fromParentNode.id, overlayRenameEntry(fromParentNode.state.overlay, fromName, toName, sourceNode.id));
|
|
253
|
-
else {
|
|
254
|
-
updateParentOverlay(state, fromParentNode.id, overlayTombstoneEntry(fromParentNode.state.overlay, fromName));
|
|
255
|
-
updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, sourceNode.id));
|
|
256
|
-
}
|
|
257
|
-
state.appendChange({
|
|
258
|
-
op: "rename",
|
|
259
|
-
from,
|
|
260
|
-
to
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
},
|
|
264
|
-
copy(from, to) {
|
|
265
|
-
runInWriteTransaction(state, () => {
|
|
266
|
-
assertValidVirtualPath(from);
|
|
267
|
-
assertValidVirtualPath(to);
|
|
268
|
-
if (from === to) throw new VirtualPathAlreadyExistsError(to);
|
|
269
|
-
const fromParent = parentPath(from);
|
|
270
|
-
const fromName = baseName(from);
|
|
271
|
-
const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
|
|
272
|
-
found: true,
|
|
273
|
-
node: getRootNode(state)
|
|
274
|
-
};
|
|
275
|
-
if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
|
|
276
|
-
const fromParentNode = fromParentResolved.node;
|
|
277
|
-
if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
|
|
278
|
-
const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
|
|
279
|
-
if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
|
|
280
|
-
const sourceNode = fromChild.node;
|
|
281
|
-
const toParent = parentPath(to);
|
|
282
|
-
const toName = baseName(to);
|
|
283
|
-
const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
|
|
284
|
-
found: true,
|
|
285
|
-
node: getRootNode(state)
|
|
286
|
-
};
|
|
287
|
-
if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
|
|
288
|
-
const toParentNode = toParentResolved.node;
|
|
289
|
-
if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
|
|
290
|
-
const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
|
|
291
|
-
if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
|
|
292
|
-
const newNodeId = cloneNodeGraphForCopy(source, state, sourceNode, from);
|
|
293
|
-
updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, newNodeId));
|
|
294
|
-
state.appendChange({
|
|
295
|
-
op: "copy",
|
|
296
|
-
from,
|
|
297
|
-
to
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
},
|
|
301
|
-
revert(path) {
|
|
302
|
-
runInWriteTransaction(state, () => {
|
|
303
|
-
assertValidVirtualPath(path);
|
|
304
|
-
const resolved = resolvePath(source, state, path);
|
|
305
|
-
if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(path);
|
|
306
|
-
const node = resolved.node;
|
|
307
|
-
const reverted = revertNodeState(node);
|
|
308
|
-
if (reverted === node) throw new VirtualRevertNotSupportedError(path);
|
|
309
|
-
state.setNode(reverted);
|
|
310
|
-
state.appendChange({
|
|
311
|
-
op: "revert",
|
|
312
|
-
path
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
},
|
|
316
|
-
writeTree() {
|
|
317
|
-
return writeTreeFromSession(source, state);
|
|
318
|
-
},
|
|
319
|
-
reset(baseTree) {
|
|
320
|
-
runInWriteTransaction(state, () => {
|
|
321
|
-
state.reset(baseTree);
|
|
322
|
-
});
|
|
323
|
-
},
|
|
324
|
-
listChanges() {
|
|
325
|
-
return mapInternalChangesToVirtualChanges(state.listChangeRecords());
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
function getRootNode(state) {
|
|
330
|
-
const root = state.getNode(VIRTUAL_ROOT_NODE_ID);
|
|
331
|
-
if (root === null) throw new Error("Virtual workdir session is missing root node");
|
|
332
|
-
return root;
|
|
333
|
-
}
|
|
334
|
-
function runInWriteTransaction(state, fn) {
|
|
335
|
-
return state.transact(fn);
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* 更新父节点的 overlay(创建新节点对象替代 Map 中的旧引用)
|
|
339
|
-
*/
|
|
340
|
-
function updateParentOverlay(state, parentId, newOverlay) {
|
|
341
|
-
const parentNode = state.getNode(parentId);
|
|
342
|
-
if (parentNode === null || parentNode.state.kind !== "directory") throw new Error("updateParentOverlay: parent is not a directory");
|
|
343
|
-
state.setNode({
|
|
344
|
-
...parentNode,
|
|
345
|
-
state: {
|
|
346
|
-
...parentNode.state,
|
|
347
|
-
overlay: newOverlay
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
function statNode(source, node, path) {
|
|
352
|
-
if (node.state.kind === "directory") return statDirectoryNode(node);
|
|
353
|
-
if (node.state.kind === "symlink") {
|
|
354
|
-
const hash = node.origin.kind === "repo-blob" ? node.origin.hash : null;
|
|
355
|
-
let size = 0;
|
|
356
|
-
if (node.state.target !== void 0) size = node.state.target.length;
|
|
357
|
-
else if (hash !== null) size = readRepoBlobContent(source, hash, path).length;
|
|
358
|
-
return {
|
|
359
|
-
kind: "symlink",
|
|
360
|
-
mode: "120000",
|
|
361
|
-
size,
|
|
362
|
-
hash
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
const hash = node.origin.kind === "repo-blob" ? node.origin.hash : null;
|
|
366
|
-
let size = 0;
|
|
367
|
-
if (node.state.content !== void 0) size = node.state.content.length;
|
|
368
|
-
else if (hash !== null) size = readRepoBlobContent(source, hash, path).length;
|
|
369
|
-
return {
|
|
370
|
-
kind: "blob",
|
|
371
|
-
mode: node.state.mode,
|
|
372
|
-
size,
|
|
373
|
-
hash
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
function statDirectoryNode(node) {
|
|
377
|
-
return {
|
|
378
|
-
kind: "tree",
|
|
379
|
-
mode: "40000",
|
|
380
|
-
size: 0,
|
|
381
|
-
hash: node.origin.kind === "repo-tree" ? node.origin.hash : null
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
function cloneNodeGraphForCopy(source, state, node, path) {
|
|
385
|
-
const newNodeId = createNodeId();
|
|
386
|
-
const cloned = cloneSessionNodeForCopy(node, newNodeId);
|
|
387
|
-
state.setNode(cloned);
|
|
388
|
-
if (node.state.kind !== "directory" || cloned.state.kind !== "directory") return newNodeId;
|
|
389
|
-
let overlay = cloned.state.overlay;
|
|
390
|
-
const children = listDirectoryChildren(source, state, node, path);
|
|
391
|
-
for (const child of children) {
|
|
392
|
-
const childNode = state.getNode(child.nodeId);
|
|
393
|
-
if (childNode === null) continue;
|
|
394
|
-
const clonedChildId = cloneNodeGraphForCopy(source, state, childNode, path === "" ? child.name : `${path}/${child.name}`);
|
|
395
|
-
overlay = overlayBindEntry(overlay, child.name, clonedChildId);
|
|
396
|
-
}
|
|
397
|
-
state.setNode({
|
|
398
|
-
...cloned,
|
|
399
|
-
state: {
|
|
400
|
-
kind: "directory",
|
|
401
|
-
overlay
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
return newNodeId;
|
|
405
|
-
}
|
|
406
|
-
//#endregion
|
|
407
|
-
export { createVirtualWorkdirSession, openVirtualWorkdirSession };
|