nano-git 0.3.1 → 0.3.3
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 +1 -1
- package/dist/core/types.d.mts +1 -1
- package/dist/objects/tree.d.mts +4 -0
- package/dist/objects/tree.mjs +53 -2
- package/dist/repository/tree/tree-patch.mjs +4 -4
- package/dist/repository/tree/tree-walk.mjs +1 -1
- package/dist/repository/tree/tree-writer.mjs +1 -1
- package/dist/workdir/change-index-plan.mjs +2 -2
- package/dist/workdir/change-index.mjs +6 -6
- package/dist/workdir/core.d.mts +13 -44
- package/dist/workdir/directory-view.mjs +2 -2
- package/dist/workdir/dirty-dir-plan.mjs +3 -3
- package/dist/workdir/file-backend.d.mts +34 -12
- package/dist/workdir/file-backend.mjs +67 -86
- 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 +4 -39
- 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 -3
- package/dist/workdir/origin.mjs +2 -2
- package/dist/workdir/overlay.d.mts +3 -3
- package/dist/workdir/sqlite-backend.d.mts +31 -14
- package/dist/workdir/sqlite-backend.mjs +113 -118
- package/dist/workdir/sqlite.d.mts +2 -2
- package/dist/workdir/sqlite.mjs +2 -2
- package/dist/workdir/state-store.d.mts +4 -4
- package/dist/workdir/{session-internal.mjs → workdir-path.mjs} +10 -10
- package/dist/workdir/{session-transaction.mjs → workdir-transaction.mjs} +10 -10
- package/dist/workdir/workdir.d.mts +30 -0
- package/dist/workdir/{session.mjs → workdir.mjs} +17 -17
- package/dist/workdir/write-tree.mjs +5 -5
- package/package.json +1 -1
- package/dist/workdir/memory-backend.d.mts +0 -17
- package/dist/workdir/session-id.mjs +0 -18
- package/dist/workdir/session.d.mts +0 -30
|
@@ -1,83 +1,71 @@
|
|
|
1
1
|
import { sha1 } from "../core/types.mjs";
|
|
2
2
|
import { createRootDirectoryNode } from "./nodes.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { openVirtualWorkdirSession } from "./session.mjs";
|
|
3
|
+
import { openVirtualWorkdir } from "./workdir.mjs";
|
|
5
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { dirname, join } from "node:path";
|
|
7
6
|
//#region src/workdir/file-backend.ts
|
|
8
7
|
/**
|
|
9
|
-
* Virtual Workdir
|
|
8
|
+
* Virtual Workdir 文件系统持久化实现
|
|
10
9
|
*/
|
|
11
10
|
const FILE_WORKDIR_MANIFEST_VERSION = 5;
|
|
12
11
|
const FILE_WORKDIR_TRANSACTION_SNAPSHOT_SUFFIX = ".txn-snapshot";
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* 打开基于目录持久化的 VirtualWorkdir
|
|
15
14
|
*
|
|
16
15
|
* @example
|
|
17
16
|
* ```ts
|
|
18
|
-
* const
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* const workdir = openFileVirtualWorkdir(repo.objects, "/tmp/workdir", {
|
|
18
|
+
* baseTree: tree,
|
|
19
|
+
* create: true,
|
|
20
|
+
* });
|
|
21
|
+
* expect(workdir.baseTree).toBe(tree);
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!existsSync(sessionsRoot)) return [];
|
|
48
|
-
return readdirSync(sessionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => !entry.name.endsWith(FILE_WORKDIR_TRANSACTION_SNAPSHOT_SUFFIX)).filter((entry) => existsSync(getManifestPath(join(sessionsRoot, entry.name)))).map((entry) => decodePathToken(entry.name)).filter((sessionId) => {
|
|
49
|
-
try {
|
|
50
|
-
validateSessionIntegrity(sessionsRoot, sessionId);
|
|
51
|
-
return true;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}).sort();
|
|
56
|
-
}
|
|
57
|
-
};
|
|
24
|
+
function openFileVirtualWorkdir(source, workdirDir, options) {
|
|
25
|
+
const store = createFileVirtualWorkdirStateStore(workdirDir);
|
|
26
|
+
if (!hasFileVirtualWorkdir(workdirDir)) {
|
|
27
|
+
if (options.create !== true) throw new Error(`Virtual workdir not found: ${workdirDir}`);
|
|
28
|
+
store.reset(options.baseTree);
|
|
29
|
+
}
|
|
30
|
+
validateFileVirtualWorkdirIntegrity(workdirDir);
|
|
31
|
+
return openVirtualWorkdir(source, store);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 删除指定目录上的持久化 VirtualWorkdir
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* deleteFileVirtualWorkdir("/tmp/workdir");
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function deleteFileVirtualWorkdir(workdirDir) {
|
|
42
|
+
if (!hasFileVirtualWorkdir(workdirDir)) throw new Error(`Virtual workdir not found: ${workdirDir}`);
|
|
43
|
+
rmSync(workdirDir, {
|
|
44
|
+
recursive: true,
|
|
45
|
+
force: true
|
|
46
|
+
});
|
|
58
47
|
}
|
|
59
48
|
/**
|
|
60
|
-
*
|
|
49
|
+
* 创建单个文件系统 VirtualWorkdir 的状态存储
|
|
61
50
|
*
|
|
62
51
|
* @example
|
|
63
52
|
* ```ts
|
|
64
|
-
* const store = createFileVirtualWorkdirStateStore("/tmp/
|
|
53
|
+
* const store = createFileVirtualWorkdirStateStore("/tmp/workdir");
|
|
65
54
|
* expect(store.kind).toBe("file");
|
|
66
55
|
* ```
|
|
67
56
|
*/
|
|
68
|
-
function createFileVirtualWorkdirStateStore(
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const contentDir = getContentDir(sessionDir);
|
|
57
|
+
function createFileVirtualWorkdirStateStore(workdirDir) {
|
|
58
|
+
const manifestPath = getManifestPath(workdirDir);
|
|
59
|
+
const contentDir = getContentDir(workdirDir);
|
|
72
60
|
return {
|
|
73
61
|
kind: "file",
|
|
74
62
|
transact(fn) {
|
|
75
|
-
const snapshotDir = `${
|
|
63
|
+
const snapshotDir = `${workdirDir}${FILE_WORKDIR_TRANSACTION_SNAPSHOT_SUFFIX}`;
|
|
76
64
|
rmSync(snapshotDir, {
|
|
77
65
|
recursive: true,
|
|
78
66
|
force: true
|
|
79
67
|
});
|
|
80
|
-
if (existsSync(
|
|
68
|
+
if (existsSync(workdirDir)) copyDirectoryRecursive(workdirDir, snapshotDir);
|
|
81
69
|
try {
|
|
82
70
|
const result = fn();
|
|
83
71
|
rmSync(snapshotDir, {
|
|
@@ -86,11 +74,11 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
86
74
|
});
|
|
87
75
|
return result;
|
|
88
76
|
} catch (error) {
|
|
89
|
-
rmSync(
|
|
77
|
+
rmSync(workdirDir, {
|
|
90
78
|
recursive: true,
|
|
91
79
|
force: true
|
|
92
80
|
});
|
|
93
|
-
if (existsSync(snapshotDir)) renameSync(snapshotDir,
|
|
81
|
+
if (existsSync(snapshotDir)) renameSync(snapshotDir, workdirDir);
|
|
94
82
|
throw error;
|
|
95
83
|
}
|
|
96
84
|
},
|
|
@@ -98,7 +86,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
98
86
|
return readManifest(manifestPath).baseTree;
|
|
99
87
|
},
|
|
100
88
|
writeBaseTree(baseTree) {
|
|
101
|
-
updateManifest(
|
|
89
|
+
updateManifest(workdirDir, (manifest) => ({
|
|
102
90
|
...manifest,
|
|
103
91
|
baseTree
|
|
104
92
|
}));
|
|
@@ -109,7 +97,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
109
97
|
return restoreNode(record, contentDir);
|
|
110
98
|
},
|
|
111
99
|
setNode(node) {
|
|
112
|
-
updateManifest(
|
|
100
|
+
updateManifest(workdirDir, (manifest) => {
|
|
113
101
|
const record = persistNode(contentDir, node);
|
|
114
102
|
return {
|
|
115
103
|
...manifest,
|
|
@@ -121,7 +109,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
121
109
|
});
|
|
122
110
|
},
|
|
123
111
|
deleteNode(id) {
|
|
124
|
-
updateManifest(
|
|
112
|
+
updateManifest(workdirDir, (manifest) => {
|
|
125
113
|
if (manifest.nodes[id] === void 0) return manifest;
|
|
126
114
|
const { [id]: _deleted, ...rest } = manifest.nodes;
|
|
127
115
|
return {
|
|
@@ -137,7 +125,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
137
125
|
return readManifest(manifestPath).changeRecords.map(restoreChangeRecord).find((record) => record.path === path) ?? null;
|
|
138
126
|
},
|
|
139
127
|
setChangeRecord(record) {
|
|
140
|
-
updateManifest(
|
|
128
|
+
updateManifest(workdirDir, (manifest) => {
|
|
141
129
|
const others = manifest.changeRecords.filter((item) => item.path !== record.path);
|
|
142
130
|
return {
|
|
143
131
|
...manifest,
|
|
@@ -146,7 +134,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
146
134
|
});
|
|
147
135
|
},
|
|
148
136
|
deleteChangeRecord(path) {
|
|
149
|
-
updateManifest(
|
|
137
|
+
updateManifest(workdirDir, (manifest) => ({
|
|
150
138
|
...manifest,
|
|
151
139
|
changeRecords: manifest.changeRecords.filter((item) => item.path !== path)
|
|
152
140
|
}));
|
|
@@ -158,7 +146,7 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
158
146
|
return readManifest(manifestPath).dirtyDirSummaries.map(restoreDirtyDirSummary).find((summary) => summary.path === path) ?? null;
|
|
159
147
|
},
|
|
160
148
|
setDirtyDirSummary(summary) {
|
|
161
|
-
updateManifest(
|
|
149
|
+
updateManifest(workdirDir, (manifest) => {
|
|
162
150
|
const others = manifest.dirtyDirSummaries.filter((item) => item.path !== summary.path);
|
|
163
151
|
return {
|
|
164
152
|
...manifest,
|
|
@@ -167,46 +155,42 @@ function createFileVirtualWorkdirStateStore(sessionsRoot, sessionId) {
|
|
|
167
155
|
});
|
|
168
156
|
},
|
|
169
157
|
deleteDirtyDirSummary(path) {
|
|
170
|
-
updateManifest(
|
|
158
|
+
updateManifest(workdirDir, (manifest) => ({
|
|
171
159
|
...manifest,
|
|
172
160
|
dirtyDirSummaries: manifest.dirtyDirSummaries.filter((item) => item.path !== path)
|
|
173
161
|
}));
|
|
174
162
|
},
|
|
175
163
|
reset(baseTree) {
|
|
176
|
-
rmSync(
|
|
164
|
+
rmSync(workdirDir, {
|
|
177
165
|
recursive: true,
|
|
178
166
|
force: true
|
|
179
167
|
});
|
|
180
|
-
|
|
168
|
+
ensureWorkdirDirs(workdirDir, contentDir);
|
|
181
169
|
writeManifestAtomic(manifestPath, createManifest(baseTree, { [createRootDirectoryNode(baseTree).id]: serializeDirectoryNode(createRootDirectoryNode(baseTree)) }));
|
|
182
170
|
}
|
|
183
171
|
};
|
|
184
172
|
}
|
|
185
|
-
function
|
|
186
|
-
return existsSync(getManifestPath(
|
|
173
|
+
function hasFileVirtualWorkdir(workdirDir) {
|
|
174
|
+
return existsSync(getManifestPath(workdirDir));
|
|
187
175
|
}
|
|
188
|
-
function
|
|
189
|
-
const
|
|
190
|
-
const manifest = readManifest(getManifestPath(sessionDir));
|
|
176
|
+
function validateFileVirtualWorkdirIntegrity(workdirDir) {
|
|
177
|
+
const manifest = readManifest(getManifestPath(workdirDir));
|
|
191
178
|
const root = manifest.nodes.root;
|
|
192
|
-
if (root === void 0) throw new Error(`Virtual workdir
|
|
193
|
-
if (restoreNode(root, getContentDir(
|
|
194
|
-
for (const record of Object.values(manifest.nodes)) restoreNode(record, getContentDir(
|
|
179
|
+
if (root === void 0) throw new Error(`Virtual workdir is corrupted: missing root node for ${workdirDir}`);
|
|
180
|
+
if (restoreNode(root, getContentDir(workdirDir)).state.kind !== "directory") throw new Error(`Virtual workdir is corrupted: root node is not a directory for ${workdirDir}`);
|
|
181
|
+
for (const record of Object.values(manifest.nodes)) restoreNode(record, getContentDir(workdirDir));
|
|
195
182
|
}
|
|
196
|
-
function
|
|
197
|
-
return join(
|
|
183
|
+
function getManifestPath(workdirDir) {
|
|
184
|
+
return join(workdirDir, "manifest.json");
|
|
198
185
|
}
|
|
199
|
-
function
|
|
200
|
-
return join(
|
|
201
|
-
}
|
|
202
|
-
function getContentDir(sessionDir) {
|
|
203
|
-
return join(sessionDir, "content");
|
|
186
|
+
function getContentDir(workdirDir) {
|
|
187
|
+
return join(workdirDir, "content");
|
|
204
188
|
}
|
|
205
189
|
function getContentPath(contentDir, payloadRef) {
|
|
206
190
|
return join(contentDir, `${encodePathToken(payloadRef)}.bin`);
|
|
207
191
|
}
|
|
208
|
-
function
|
|
209
|
-
mkdirSync(
|
|
192
|
+
function ensureWorkdirDirs(workdirDir, contentDir) {
|
|
193
|
+
mkdirSync(workdirDir, { recursive: true });
|
|
210
194
|
mkdirSync(contentDir, { recursive: true });
|
|
211
195
|
}
|
|
212
196
|
function copyDirectoryRecursive(sourceDir, targetDir) {
|
|
@@ -230,16 +214,16 @@ function createManifest(baseTree, nodes) {
|
|
|
230
214
|
dirtyDirSummaries: []
|
|
231
215
|
};
|
|
232
216
|
}
|
|
233
|
-
function updateManifest(
|
|
234
|
-
const manifestPath = getManifestPath(
|
|
235
|
-
|
|
217
|
+
function updateManifest(workdirDir, updater) {
|
|
218
|
+
const manifestPath = getManifestPath(workdirDir);
|
|
219
|
+
ensureWorkdirDirs(workdirDir, getContentDir(workdirDir));
|
|
236
220
|
writeManifestAtomic(manifestPath, updater(readManifest(manifestPath)));
|
|
237
221
|
}
|
|
238
222
|
function writeManifestAtomic(path, manifest) {
|
|
239
223
|
writeJsonAtomic(path, manifest);
|
|
240
224
|
}
|
|
241
225
|
function readManifest(manifestPath) {
|
|
242
|
-
if (!existsSync(manifestPath)) throw new Error(`Virtual workdir
|
|
226
|
+
if (!existsSync(manifestPath)) throw new Error(`Virtual workdir manifest not found: ${manifestPath}`);
|
|
243
227
|
const manifest = readJson(manifestPath);
|
|
244
228
|
validateManifest(manifest);
|
|
245
229
|
return manifest;
|
|
@@ -445,8 +429,5 @@ function writeBufferAtomic(path, value) {
|
|
|
445
429
|
function encodePathToken(value) {
|
|
446
430
|
return encodeURIComponent(value);
|
|
447
431
|
}
|
|
448
|
-
function decodePathToken(value) {
|
|
449
|
-
return decodeURIComponent(value);
|
|
450
|
-
}
|
|
451
432
|
//#endregion
|
|
452
|
-
export {
|
|
433
|
+
export { createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir };
|
package/dist/workdir/file.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type
|
|
1
|
+
import { OpenFileVirtualWorkdirOptions, createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir } from "./file-backend.mjs";
|
|
2
|
+
export { type OpenFileVirtualWorkdirOptions, createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir };
|
package/dist/workdir/file.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir } from "./file-backend.mjs";
|
|
2
|
+
export { createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir };
|
package/dist/workdir/ids.d.mts
CHANGED
package/dist/workdir/ids.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//#region src/workdir/ids.ts
|
|
2
|
-
/** 根目录节点的固定 ID(每个
|
|
2
|
+
/** 根目录节点的固定 ID(每个 workdir 一致) */
|
|
3
3
|
const VIRTUAL_ROOT_NODE_ID = "root";
|
|
4
4
|
let nextNodeCounter = 1;
|
|
5
5
|
/**
|
|
6
|
-
* 分配新的
|
|
6
|
+
* 分配新的 workdir 节点 ID
|
|
7
7
|
*/
|
|
8
8
|
function createNodeId() {
|
|
9
9
|
const id = `node:${nextNodeCounter}`;
|
|
@@ -11,7 +11,7 @@ function createNodeId() {
|
|
|
11
11
|
return id;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* 为仓库对象哈希派生稳定的
|
|
14
|
+
* 为仓库对象哈希派生稳定的 workdir 节点 ID(懒加载、未 copy 前复用)
|
|
15
15
|
*/
|
|
16
16
|
function originBackedNodeId(hash) {
|
|
17
17
|
return `origin:${hash}`;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
|
|
2
2
|
import { createRootDirectoryNode } from "./nodes.mjs";
|
|
3
|
-
import { createVirtualWorkdirSessionId } from "./session-id.mjs";
|
|
4
|
-
import { openVirtualWorkdirSession } from "./session.mjs";
|
|
5
3
|
//#region src/workdir/memory-backend.ts
|
|
6
4
|
/**
|
|
7
5
|
* Virtual Workdir 内存状态存储
|
|
@@ -78,39 +76,6 @@ function createVirtualWorkdirMemoryStateStore(baseTree) {
|
|
|
78
76
|
}
|
|
79
77
|
};
|
|
80
78
|
}
|
|
81
|
-
/**
|
|
82
|
-
* 创建内存版 Virtual Workdir backend
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* const backend = createMemoryVirtualWorkdirBackend();
|
|
87
|
-
* const sessionId = backend.createSession({ baseTree: tree });
|
|
88
|
-
* const session = backend.openSession(repo.objects, sessionId);
|
|
89
|
-
* expect(session.baseTree).toBe(tree);
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
function createMemoryVirtualWorkdirBackend() {
|
|
93
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
94
|
-
return {
|
|
95
|
-
kind: "memory",
|
|
96
|
-
createSession(options) {
|
|
97
|
-
const sessionId = createVirtualWorkdirSessionId();
|
|
98
|
-
sessions.set(sessionId, createVirtualWorkdirMemoryStateStore(options.baseTree));
|
|
99
|
-
return sessionId;
|
|
100
|
-
},
|
|
101
|
-
openSession(source, sessionId) {
|
|
102
|
-
const store = sessions.get(sessionId);
|
|
103
|
-
if (store === void 0) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
104
|
-
return openVirtualWorkdirSession(source, store);
|
|
105
|
-
},
|
|
106
|
-
deleteSession(sessionId) {
|
|
107
|
-
if (!sessions.delete(sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
108
|
-
},
|
|
109
|
-
listSessions() {
|
|
110
|
-
return Array.from(sessions.keys());
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
79
|
function resetState(state, baseTree) {
|
|
115
80
|
state.baseTree = baseTree;
|
|
116
81
|
state.nodes.clear();
|
|
@@ -120,7 +85,7 @@ function resetState(state, baseTree) {
|
|
|
120
85
|
}
|
|
121
86
|
function snapshotState(state) {
|
|
122
87
|
const nodes = /* @__PURE__ */ new Map();
|
|
123
|
-
for (const [nodeId, node] of state.nodes) nodes.set(nodeId,
|
|
88
|
+
for (const [nodeId, node] of state.nodes) nodes.set(nodeId, cloneWorkdirNode(node));
|
|
124
89
|
return {
|
|
125
90
|
baseTree: state.baseTree,
|
|
126
91
|
nodes,
|
|
@@ -133,11 +98,11 @@ function restoreState(state, snapshot) {
|
|
|
133
98
|
state.nodes.clear();
|
|
134
99
|
state.changeRecords.clear();
|
|
135
100
|
state.dirtyDirSummaries.clear();
|
|
136
|
-
for (const [nodeId, node] of snapshot.nodes) state.nodes.set(nodeId,
|
|
101
|
+
for (const [nodeId, node] of snapshot.nodes) state.nodes.set(nodeId, cloneWorkdirNode(node));
|
|
137
102
|
for (const [path, record] of snapshot.changeRecords) state.changeRecords.set(path, record);
|
|
138
103
|
for (const [path, summary] of snapshot.dirtyDirSummaries) state.dirtyDirSummaries.set(path, summary);
|
|
139
104
|
}
|
|
140
|
-
function
|
|
105
|
+
function cloneWorkdirNode(node) {
|
|
141
106
|
if (node.state.kind === "directory") return {
|
|
142
107
|
id: node.id,
|
|
143
108
|
origin: node.origin,
|
|
@@ -175,4 +140,4 @@ function cloneSessionNode(node) {
|
|
|
175
140
|
};
|
|
176
141
|
}
|
|
177
142
|
//#endregion
|
|
178
|
-
export {
|
|
143
|
+
export { createVirtualWorkdirMemoryStateStore };
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
|
|
1
|
+
import { createVirtualWorkdir, openVirtualWorkdir } from "./workdir.mjs";
|
|
2
|
+
export { createVirtualWorkdir, openVirtualWorkdir };
|
package/dist/workdir/memory.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
|
|
1
|
+
import { createVirtualWorkdir, openVirtualWorkdir } from "./workdir.mjs";
|
|
2
|
+
export { createVirtualWorkdir, openVirtualWorkdir };
|
package/dist/workdir/nodes.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ import { DirectoryOverlay } from "./overlay.mjs";
|
|
|
6
6
|
/** Blob / 符号链接在 origin 与 state 中使用的 mode */
|
|
7
7
|
type BlobObjectMode = "100644" | "100755" | "120000";
|
|
8
8
|
/**
|
|
9
|
-
* 节点来源(repo 对象或纯
|
|
9
|
+
* 节点来源(repo 对象或纯 workdir 新建)
|
|
10
10
|
*/
|
|
11
11
|
type NodeOrigin = {
|
|
12
12
|
readonly kind: "none";
|
|
@@ -21,7 +21,7 @@ type NodeOrigin = {
|
|
|
21
21
|
/**
|
|
22
22
|
* 目录节点当前状态
|
|
23
23
|
*
|
|
24
|
-
* `overlay` 表达
|
|
24
|
+
* `overlay` 表达 workdir 层增删改;子项 nodeId 通过 overlay 合成与懒加载解析。
|
|
25
25
|
*/
|
|
26
26
|
interface DirectoryNodeState {
|
|
27
27
|
readonly kind: "directory";
|
|
@@ -44,14 +44,14 @@ interface SymlinkNodeState {
|
|
|
44
44
|
readonly mode: "120000";
|
|
45
45
|
readonly target?: Buffer;
|
|
46
46
|
}
|
|
47
|
-
type
|
|
47
|
+
type WorkdirNodeState = DirectoryNodeState | FileNodeState | SymlinkNodeState;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* 完整的 workdir 节点记录
|
|
50
50
|
*/
|
|
51
|
-
interface
|
|
51
|
+
interface WorkdirNode {
|
|
52
52
|
readonly id: NodeId;
|
|
53
53
|
readonly origin: NodeOrigin;
|
|
54
|
-
readonly state:
|
|
54
|
+
readonly state: WorkdirNodeState;
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
|
-
export {
|
|
57
|
+
export { WorkdirNode };
|
package/dist/workdir/nodes.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
|
|
|
2
2
|
import { cloneDirectoryOverlay, createEmptyDirectoryOverlay } from "./overlay.mjs";
|
|
3
3
|
//#region src/workdir/nodes.ts
|
|
4
4
|
/**
|
|
5
|
-
* Virtual Workdir
|
|
5
|
+
* Virtual Workdir 节点状态模型
|
|
6
6
|
*
|
|
7
7
|
* 节点身份(nodeId)与目录路径绑定分离;origin 描述 repo-backed 来源。
|
|
8
8
|
*/
|
|
@@ -63,7 +63,7 @@ function revertNodeState(node) {
|
|
|
63
63
|
/**
|
|
64
64
|
* 为 `copy` 创建新节点:共享 origin,目录为浅复制(子项绑定保留,但 nodeId 为新)
|
|
65
65
|
*/
|
|
66
|
-
function
|
|
66
|
+
function cloneWorkdirNodeForCopy(source, newId) {
|
|
67
67
|
const origin = source.origin;
|
|
68
68
|
if (source.state.kind === "directory") return {
|
|
69
69
|
id: newId,
|
|
@@ -103,4 +103,4 @@ function cloneSessionNodeForCopy(source, newId) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
|
-
export {
|
|
106
|
+
export { cloneWorkdirNodeForCopy, createRootDirectoryNode, revertNodeState };
|
package/dist/workdir/origin.mjs
CHANGED
|
@@ -28,7 +28,7 @@ function readRepoBlobContent(source, hash, path) {
|
|
|
28
28
|
* 根据 tree 条目构造节点 origin
|
|
29
29
|
*/
|
|
30
30
|
function treeEntryToNodeOrigin(entry) {
|
|
31
|
-
if (entry.mode === "
|
|
31
|
+
if (entry.mode === "040000") return {
|
|
32
32
|
kind: "repo-tree",
|
|
33
33
|
hash: entry.hash
|
|
34
34
|
};
|
|
@@ -47,7 +47,7 @@ function treeEntryToNodeOrigin(entry) {
|
|
|
47
47
|
* Git mode 转为 VirtualEntryKind
|
|
48
48
|
*/
|
|
49
49
|
function modeToVirtualEntryKind(mode) {
|
|
50
|
-
if (mode === "
|
|
50
|
+
if (mode === "040000") return "tree";
|
|
51
51
|
if (mode === "120000") return "symlink";
|
|
52
52
|
return "blob";
|
|
53
53
|
}
|
|
@@ -2,12 +2,12 @@ import { NodeId } from "./ids.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/workdir/overlay.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* 目录 overlay 状态(挂在目录
|
|
5
|
+
* 目录 overlay 状态(挂在目录 WorkdirNode 上)
|
|
6
6
|
*/
|
|
7
7
|
interface DirectoryOverlay {
|
|
8
|
-
/**
|
|
8
|
+
/** workdir 新增或覆盖:条目名 -> 绑定的 nodeId */
|
|
9
9
|
readonly addedEntries: Map<string, NodeId>;
|
|
10
|
-
/**
|
|
10
|
+
/** workdir 删除的 origin/先前条目名(tombstone) */
|
|
11
11
|
readonly deletedNames: Set<string>;
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
|
+
import { CreateVirtualWorkdirOptions, VirtualWorkdir } from "./core.mjs";
|
|
2
3
|
import { Database } from "bun:sqlite";
|
|
3
4
|
|
|
4
5
|
//#region src/workdir/sqlite-backend.d.ts
|
|
5
|
-
/**
|
|
6
|
-
interface
|
|
6
|
+
/** SQLite 连接层的可选参数 */
|
|
7
|
+
interface SqliteVirtualWorkdirConnectionOptions {
|
|
7
8
|
/** 开启 WAL 模式,默认 true */
|
|
8
9
|
readonly walMode?: boolean;
|
|
9
10
|
}
|
|
11
|
+
/** 打开 SQLite VirtualWorkdir 的可选参数 */
|
|
12
|
+
interface OpenSqliteVirtualWorkdirOptions extends CreateVirtualWorkdirOptions, SqliteVirtualWorkdirConnectionOptions {
|
|
13
|
+
/** 不存在时按 baseTree 初始化 */
|
|
14
|
+
readonly create?: boolean;
|
|
15
|
+
}
|
|
10
16
|
/**
|
|
11
|
-
* SQLite
|
|
17
|
+
* 基于 SQLite 的 VirtualWorkdir
|
|
18
|
+
*
|
|
19
|
+
* 返回值附带 `[Symbol.dispose]()`,用于释放内部数据库连接。
|
|
12
20
|
*/
|
|
13
|
-
|
|
14
|
-
/** 释放 SQLite 数据库连接 */
|
|
21
|
+
type SqliteVirtualWorkdir = VirtualWorkdir & {
|
|
15
22
|
[Symbol.dispose](): void;
|
|
16
|
-
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 打开基于 SQLite 的持久化 VirtualWorkdir
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* using workdir = openSqliteVirtualWorkdir(repo.objects, ":memory:", "demo", {
|
|
30
|
+
* baseTree: tree,
|
|
31
|
+
* create: true,
|
|
32
|
+
* });
|
|
33
|
+
* expect(workdir.baseTree).toBe(tree);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function openSqliteVirtualWorkdir(source: ObjectDatabase, dbPath: string, workdirKey: string, options: OpenSqliteVirtualWorkdirOptions): SqliteVirtualWorkdir;
|
|
17
37
|
/**
|
|
18
|
-
*
|
|
38
|
+
* 删除指定 key 上的 SQLite VirtualWorkdir
|
|
19
39
|
*
|
|
20
40
|
* @example
|
|
21
41
|
* ```ts
|
|
22
|
-
*
|
|
23
|
-
* const sessionId = backend.createSession({ baseTree: tree });
|
|
24
|
-
* const session = backend.openSession(repo.objects, sessionId);
|
|
25
|
-
* expect(session.baseTree).toBe(tree);
|
|
42
|
+
* deleteSqliteVirtualWorkdir("/tmp/workdir.sqlite", "demo");
|
|
26
43
|
* ```
|
|
27
44
|
*/
|
|
28
|
-
declare function
|
|
45
|
+
declare function deleteSqliteVirtualWorkdir(dbPath: string, workdirKey: string, options?: SqliteVirtualWorkdirConnectionOptions): void;
|
|
29
46
|
//#endregion
|
|
30
|
-
export {
|
|
47
|
+
export { OpenSqliteVirtualWorkdirOptions, SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
|