nano-git 0.3.1 → 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/dist/workdir/change-index-plan.mjs +2 -2
- package/dist/workdir/change-index.mjs +5 -5
- package/dist/workdir/core.d.mts +12 -43
- 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/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} +8 -8
- package/dist/workdir/{session-transaction.mjs → workdir-transaction.mjs} +9 -9
- package/dist/workdir/workdir.d.mts +30 -0
- package/dist/workdir/{session.mjs → workdir.mjs} +17 -17
- package/dist/workdir/write-tree.mjs +4 -4
- 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,9 +1,9 @@
|
|
|
1
|
-
import { resolvePath } from "./
|
|
1
|
+
import { resolvePath } from "./workdir-path.mjs";
|
|
2
2
|
//#region src/workdir/change-index-plan.ts
|
|
3
3
|
/**
|
|
4
4
|
* Virtual Workdir change-index 刷新策略
|
|
5
5
|
*
|
|
6
|
-
* 把
|
|
6
|
+
* 把 workdir 写路径里的“是否允许增量刷新”判断与
|
|
7
7
|
* “应该执行哪种 change-index 更新动作”集中到单独模块。
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hashObject } from "../core/hash-digest.mjs";
|
|
2
2
|
import { readRepoBlobContent, readRepoTree } from "./origin.mjs";
|
|
3
|
-
import { getDirectoryChildrenView, joinChildPath, resolveCurrentLeafAtPath } from "./
|
|
3
|
+
import { getDirectoryChildrenView, joinChildPath, resolveCurrentLeafAtPath } from "./workdir-path.mjs";
|
|
4
4
|
import { observeListedDirectoryChild } from "./directory-view.mjs";
|
|
5
5
|
//#region src/workdir/change-index.ts
|
|
6
6
|
/**
|
|
@@ -15,7 +15,7 @@ import { observeListedDirectoryChild } from "./directory-view.mjs";
|
|
|
15
15
|
*/
|
|
16
16
|
const baseSnapshotCache = /* @__PURE__ */ new WeakMap();
|
|
17
17
|
/**
|
|
18
|
-
* 重建当前
|
|
18
|
+
* 重建当前 workdir 的规范化变更索引。
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```ts
|
|
@@ -173,7 +173,7 @@ function rewriteChangeRecordForRename(source, state, from, to, cache) {
|
|
|
173
173
|
* 为 copy 目标路径写入折叠后的变更记录。
|
|
174
174
|
*
|
|
175
175
|
* 仅适用于叶子节点 copy;
|
|
176
|
-
*
|
|
176
|
+
* workdir-only 来源允许退化为普通 create。
|
|
177
177
|
*/
|
|
178
178
|
function writeChangeRecordForCopy(source, state, from, to, cache) {
|
|
179
179
|
const sourceRecord = state.getChangeRecord(from);
|
|
@@ -218,7 +218,7 @@ function baseSnapshotViewForTree(source, treeHash) {
|
|
|
218
218
|
}
|
|
219
219
|
function snapshotCurrentTree(source, state, cache) {
|
|
220
220
|
const root = state.getNode("root");
|
|
221
|
-
if (root === null) throw new Error("Virtual workdir
|
|
221
|
+
if (root === null) throw new Error("Virtual workdir is missing root node");
|
|
222
222
|
return snapshotCurrentNode(source, state, root, "", cache);
|
|
223
223
|
}
|
|
224
224
|
function snapshotCurrentNode(source, state, node, path, cache) {
|
|
@@ -398,7 +398,7 @@ function modeKind(mode) {
|
|
|
398
398
|
return mode === "120000" ? "symlink" : "blob";
|
|
399
399
|
}
|
|
400
400
|
/**
|
|
401
|
-
* 从规范化变更索引导出当前
|
|
401
|
+
* 从规范化变更索引导出当前 workdir 的最终 diff。
|
|
402
402
|
*
|
|
403
403
|
* @example
|
|
404
404
|
* ```ts
|
package/dist/workdir/core.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SHA1 } from "../core/types.mjs";
|
|
2
|
-
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
3
2
|
import { VirtualNotDirectoryError, VirtualNotFileError, VirtualNotSymlinkError, VirtualOriginUnavailableError, VirtualPathAlreadyExistsError, VirtualPathNotFoundError, VirtualRevertNotSupportedError } from "../core/errors.mjs";
|
|
4
3
|
|
|
5
4
|
//#region src/workdir/core.d.ts
|
|
@@ -93,42 +92,36 @@ type VirtualDiffEntry = {
|
|
|
93
92
|
readonly source?: VirtualDiffSource;
|
|
94
93
|
};
|
|
95
94
|
/**
|
|
96
|
-
* 创建
|
|
95
|
+
* 创建 VirtualWorkdir 的选项
|
|
97
96
|
*/
|
|
98
|
-
interface
|
|
97
|
+
interface CreateVirtualWorkdirOptions {
|
|
99
98
|
/** 基线 tree 的 SHA-1 哈希 */
|
|
100
99
|
readonly baseTree: SHA1;
|
|
101
100
|
}
|
|
102
101
|
/**
|
|
103
|
-
*
|
|
104
|
-
*/
|
|
105
|
-
type VirtualWorkdirSessionId = string & {
|
|
106
|
-
readonly __brand: "VirtualWorkdirSessionId";
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* VirtualWorkdirSession(虚拟工作目录会话)
|
|
102
|
+
* VirtualWorkdir(虚拟工作目录实例)
|
|
110
103
|
*
|
|
111
104
|
* 提供独立生命周期的可变 tree 视图,基于 `baseTree + CoW overlay` 模型。
|
|
112
105
|
* 不绑定 commit,不涉及 Git index / 真实工作目录。
|
|
113
106
|
*
|
|
114
|
-
*
|
|
107
|
+
* 当前实例对 origin 仓库对象采用弱保证:
|
|
115
108
|
* 如果 base tree / origin blob 在后续被移除、损坏或不可读取,
|
|
116
109
|
* 相关读取、`revert()`、`writeTree()` 等操作会抛出 `VirtualOriginUnavailableError`。
|
|
117
110
|
*
|
|
118
111
|
* @example
|
|
119
112
|
* ```ts
|
|
120
113
|
* import { createMemoryRepository } from "nano-git/repository/memory";
|
|
121
|
-
* import {
|
|
114
|
+
* import { createVirtualWorkdir } from "nano-git/workdir/memory";
|
|
122
115
|
*
|
|
123
116
|
* const repo = createMemoryRepository();
|
|
124
117
|
* const tree = repo.writeTree(); // 初始空 tree
|
|
125
|
-
* const
|
|
118
|
+
* const workdir = createVirtualWorkdir(repo.objects, { baseTree: tree });
|
|
126
119
|
*
|
|
127
|
-
*
|
|
128
|
-
* const newTree =
|
|
120
|
+
* workdir.writeFile("hello.txt", Buffer.from("world"));
|
|
121
|
+
* const newTree = workdir.writeTree();
|
|
129
122
|
* ```
|
|
130
123
|
*/
|
|
131
|
-
interface
|
|
124
|
+
interface VirtualWorkdir {
|
|
132
125
|
/** 当前基线 tree 的 SHA-1 哈希 */
|
|
133
126
|
readonly baseTree: SHA1;
|
|
134
127
|
/** 路径是否存在 */
|
|
@@ -161,7 +154,7 @@ interface VirtualWorkdirSession {
|
|
|
161
154
|
/**
|
|
162
155
|
* 复制路径
|
|
163
156
|
*
|
|
164
|
-
* 新建
|
|
157
|
+
* 新建 workdir node,共享 origin,不共享 node 身份。
|
|
165
158
|
* 目录复制为浅复制,子项保持懒加载。
|
|
166
159
|
*/
|
|
167
160
|
copy(from: string, to: string): void;
|
|
@@ -186,35 +179,11 @@ interface VirtualWorkdirSession {
|
|
|
186
179
|
*/
|
|
187
180
|
writeTree(): SHA1;
|
|
188
181
|
/**
|
|
189
|
-
*
|
|
182
|
+
* 重置当前实例到指定基线 tree
|
|
190
183
|
*
|
|
191
184
|
* 丢弃全部 overlay 与变更历史。
|
|
192
185
|
*/
|
|
193
186
|
reset(baseTree: SHA1): void;
|
|
194
187
|
}
|
|
195
|
-
/**
|
|
196
|
-
* VirtualWorkdirBackend
|
|
197
|
-
*
|
|
198
|
-
* session 内部状态存储的抽象接口。
|
|
199
|
-
* memory / file / sqlite 后端通过实现此接口来提供不同的持久化策略。
|
|
200
|
-
*
|
|
201
|
-
* `file` / `sqlite` 持久化 backend 当前都按单进程、单写者场景收口;
|
|
202
|
-
* 不承诺跨进程并发写安全,也不提供多写者协调协议。
|
|
203
|
-
*
|
|
204
|
-
* 本接口在后续 Phase 中会逐步补充完整方法签名。
|
|
205
|
-
* 当前仅为命名冻结与角色声明。
|
|
206
|
-
*/
|
|
207
|
-
interface VirtualWorkdirBackend {
|
|
208
|
-
/** 后端类型标识 */
|
|
209
|
-
readonly kind: "memory" | "file" | "sqlite";
|
|
210
|
-
/** 创建新 session 并返回其标识 */
|
|
211
|
-
createSession(options: CreateVirtualWorkdirSessionOptions): VirtualWorkdirSessionId;
|
|
212
|
-
/** 打开已存在的 session */
|
|
213
|
-
openSession(source: ObjectDatabase, sessionId: VirtualWorkdirSessionId): VirtualWorkdirSession;
|
|
214
|
-
/** 删除 session */
|
|
215
|
-
deleteSession(sessionId: VirtualWorkdirSessionId): void;
|
|
216
|
-
/** 列出当前后端中可完整恢复的 session */
|
|
217
|
-
listSessions(): VirtualWorkdirSessionId[];
|
|
218
|
-
}
|
|
219
188
|
//#endregion
|
|
220
|
-
export {
|
|
189
|
+
export { CreateVirtualWorkdirOptions, VirtualDiffChanges, VirtualDiffEntry, VirtualDiffObject, VirtualDiffSource, VirtualDirEntry, VirtualEntryKind, VirtualEntryStat, VirtualNotDirectoryError, VirtualNotFileError, VirtualNotSymlinkError, VirtualOriginUnavailableError, VirtualPathAlreadyExistsError, VirtualPathNotFoundError, VirtualRevertNotSupportedError, VirtualWorkdir };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { VirtualNotDirectoryError } from "../core/errors.mjs";
|
|
2
|
-
import { ensureNodeFromTreeEntry, joinChildPath, listDirectoryChildren } from "./
|
|
2
|
+
import { ensureNodeFromTreeEntry, joinChildPath, listDirectoryChildren } from "./workdir-path.mjs";
|
|
3
3
|
//#region src/workdir/directory-view.ts
|
|
4
4
|
/**
|
|
5
5
|
* Virtual Workdir 目录展开、观察与编译计划
|
|
6
6
|
*
|
|
7
|
-
* 从
|
|
7
|
+
* 从 workdir-path.ts 拆分,聚焦以下职责:
|
|
8
8
|
* - 目录子项观察(observeDirectoryChildren / observeListedDirectoryChild / observeNamedDirectoryChild)
|
|
9
9
|
* - origin 按名查询视图(createNamedOriginChildLookup)
|
|
10
10
|
* - 受影响子项编译计划(planAffectedDirectoryChildren)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { getRootNode } from "./
|
|
1
|
+
import { getRootNode } from "./workdir-path.mjs";
|
|
2
2
|
import { observeDirectoryChildren } from "./directory-view.mjs";
|
|
3
3
|
import { createDirtyDirSummary } from "./dirty-dir.mjs";
|
|
4
4
|
//#region src/workdir/dirty-dir-plan.ts
|
|
5
5
|
/**
|
|
6
6
|
* Virtual Workdir dirty-dir summary 重建策略
|
|
7
7
|
*
|
|
8
|
-
* 把
|
|
9
|
-
* 让
|
|
8
|
+
* 把 workdir 写路径里的脏目录摘要清理与重建逻辑集中到单独模块,
|
|
9
|
+
* 让 workdir.ts 更接近纯编排层。
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
12
|
* 创建 dirty-dir summary 策略器。
|
|
@@ -1,22 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
|
+
import { CreateVirtualWorkdirOptions, VirtualWorkdir } from "./core.mjs";
|
|
3
|
+
import { VirtualWorkdirStateStore } from "./state-store.mjs";
|
|
2
4
|
|
|
3
5
|
//#region src/workdir/file-backend.d.ts
|
|
4
|
-
/**
|
|
5
|
-
interface
|
|
6
|
-
/**
|
|
7
|
-
readonly
|
|
6
|
+
/** 打开文件系统 VirtualWorkdir 的可选参数 */
|
|
7
|
+
interface OpenFileVirtualWorkdirOptions extends CreateVirtualWorkdirOptions {
|
|
8
|
+
/** 不存在时按 baseTree 初始化 */
|
|
9
|
+
readonly create?: boolean;
|
|
8
10
|
}
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* 打开基于目录持久化的 VirtualWorkdir
|
|
11
13
|
*
|
|
12
14
|
* @example
|
|
13
15
|
* ```ts
|
|
14
|
-
* const
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* const workdir = openFileVirtualWorkdir(repo.objects, "/tmp/workdir", {
|
|
17
|
+
* baseTree: tree,
|
|
18
|
+
* create: true,
|
|
19
|
+
* });
|
|
20
|
+
* expect(workdir.baseTree).toBe(tree);
|
|
18
21
|
* ```
|
|
19
22
|
*/
|
|
20
|
-
declare function
|
|
23
|
+
declare function openFileVirtualWorkdir(source: ObjectDatabase, workdirDir: string, options: OpenFileVirtualWorkdirOptions): VirtualWorkdir;
|
|
24
|
+
/**
|
|
25
|
+
* 删除指定目录上的持久化 VirtualWorkdir
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* deleteFileVirtualWorkdir("/tmp/workdir");
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function deleteFileVirtualWorkdir(workdirDir: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* 创建单个文件系统 VirtualWorkdir 的状态存储
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const store = createFileVirtualWorkdirStateStore("/tmp/workdir");
|
|
39
|
+
* expect(store.kind).toBe("file");
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createFileVirtualWorkdirStateStore(workdirDir: string): VirtualWorkdirStateStore;
|
|
21
43
|
//#endregion
|
|
22
|
-
export {
|
|
44
|
+
export { OpenFileVirtualWorkdirOptions, createFileVirtualWorkdirStateStore, deleteFileVirtualWorkdir, openFileVirtualWorkdir };
|
|
@@ -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 };
|