nano-git 0.2.1 → 0.2.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/dist/workdir/change-log.d.mts +25 -0
- package/dist/workdir/change-log.mjs +13 -7
- package/dist/workdir/core.d.mts +23 -1
- package/dist/workdir/file-backend.d.mts +22 -0
- package/dist/workdir/file-backend.mjs +366 -0
- package/dist/workdir/file.d.mts +2 -0
- package/dist/workdir/file.mjs +2 -0
- package/dist/workdir/ids.d.mts +7 -0
- package/dist/workdir/memory-backend.d.mts +17 -0
- package/dist/workdir/memory-backend.mjs +146 -8
- package/dist/workdir/memory.d.mts +3 -2
- package/dist/workdir/memory.mjs +3 -2
- package/dist/workdir/nodes.d.mts +57 -0
- package/dist/workdir/overlay.d.mts +14 -0
- package/dist/workdir/session-id.mjs +18 -0
- package/dist/workdir/session-internal.mjs +10 -10
- package/dist/workdir/session.d.mts +13 -1
- package/dist/workdir/session.mjs +232 -205
- package/dist/workdir/sqlite-backend.d.mts +30 -0
- package/dist/workdir/sqlite-backend.mjs +363 -0
- package/dist/workdir/sqlite.d.mts +2 -0
- package/dist/workdir/sqlite.mjs +2 -0
- package/dist/workdir/state-store.d.mts +44 -0
- package/dist/workdir/write-tree.mjs +4 -4
- package/package.json +160 -39
|
@@ -1,21 +1,159 @@
|
|
|
1
|
-
import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
|
|
2
1
|
import { createVirtualChangeLog } from "./change-log.mjs";
|
|
2
|
+
import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
|
|
3
3
|
import { createRootDirectoryNode } from "./nodes.mjs";
|
|
4
|
+
import { createVirtualWorkdirSessionId } from "./session-id.mjs";
|
|
5
|
+
import { openVirtualWorkdirSession } from "./session.mjs";
|
|
4
6
|
//#region src/workdir/memory-backend.ts
|
|
5
7
|
/**
|
|
6
|
-
* Virtual Workdir
|
|
8
|
+
* Virtual Workdir 内存状态存储
|
|
7
9
|
*/
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* 创建内存版状态存储
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const store = createVirtualWorkdirMemoryStateStore(tree);
|
|
16
|
+
* expect(store.readBaseTree()).toBe(tree);
|
|
17
|
+
* ```
|
|
10
18
|
*/
|
|
11
|
-
function
|
|
19
|
+
function createVirtualWorkdirMemoryStateStore(baseTree) {
|
|
20
|
+
const state = {
|
|
21
|
+
baseTree,
|
|
22
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
23
|
+
changeLog: createVirtualChangeLog()
|
|
24
|
+
};
|
|
25
|
+
resetState(state, baseTree);
|
|
26
|
+
return {
|
|
27
|
+
kind: "memory",
|
|
28
|
+
transact(fn) {
|
|
29
|
+
const snapshot = snapshotState(state);
|
|
30
|
+
try {
|
|
31
|
+
return fn();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
restoreState(state, snapshot);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
readBaseTree() {
|
|
38
|
+
return state.baseTree;
|
|
39
|
+
},
|
|
40
|
+
writeBaseTree(nextBaseTree) {
|
|
41
|
+
state.baseTree = nextBaseTree;
|
|
42
|
+
},
|
|
43
|
+
getNode(id) {
|
|
44
|
+
return state.nodes.get(id) ?? null;
|
|
45
|
+
},
|
|
46
|
+
setNode(node) {
|
|
47
|
+
state.nodes.set(node.id, node);
|
|
48
|
+
},
|
|
49
|
+
deleteNode(id) {
|
|
50
|
+
state.nodes.delete(id);
|
|
51
|
+
},
|
|
52
|
+
appendChange(record) {
|
|
53
|
+
state.changeLog.append(record);
|
|
54
|
+
},
|
|
55
|
+
listChangeRecords() {
|
|
56
|
+
return state.changeLog.snapshot();
|
|
57
|
+
},
|
|
58
|
+
clearChanges() {
|
|
59
|
+
state.changeLog.clear();
|
|
60
|
+
},
|
|
61
|
+
reset(nextBaseTree) {
|
|
62
|
+
resetState(state, nextBaseTree);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 创建内存版 Virtual Workdir backend
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const backend = createMemoryVirtualWorkdirBackend();
|
|
72
|
+
* const sessionId = backend.createSession({ baseTree: tree });
|
|
73
|
+
* const session = backend.openSession(repo.objects, sessionId);
|
|
74
|
+
* expect(session.baseTree).toBe(tree);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
function createMemoryVirtualWorkdirBackend() {
|
|
78
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
79
|
+
return {
|
|
80
|
+
kind: "memory",
|
|
81
|
+
createSession(options) {
|
|
82
|
+
const sessionId = createVirtualWorkdirSessionId();
|
|
83
|
+
sessions.set(sessionId, createVirtualWorkdirMemoryStateStore(options.baseTree));
|
|
84
|
+
return sessionId;
|
|
85
|
+
},
|
|
86
|
+
openSession(source, sessionId) {
|
|
87
|
+
const store = sessions.get(sessionId);
|
|
88
|
+
if (store === void 0) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
89
|
+
return openVirtualWorkdirSession(source, store);
|
|
90
|
+
},
|
|
91
|
+
deleteSession(sessionId) {
|
|
92
|
+
if (!sessions.delete(sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
93
|
+
},
|
|
94
|
+
listSessions() {
|
|
95
|
+
return Array.from(sessions.keys());
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function resetState(state, baseTree) {
|
|
100
|
+
state.baseTree = baseTree;
|
|
101
|
+
state.nodes.clear();
|
|
102
|
+
state.nodes.set(VIRTUAL_ROOT_NODE_ID, createRootDirectoryNode(baseTree));
|
|
103
|
+
state.changeLog.clear();
|
|
104
|
+
}
|
|
105
|
+
function snapshotState(state) {
|
|
12
106
|
const nodes = /* @__PURE__ */ new Map();
|
|
13
|
-
nodes.set(
|
|
107
|
+
for (const [nodeId, node] of state.nodes) nodes.set(nodeId, cloneSessionNode(node));
|
|
14
108
|
return {
|
|
15
|
-
baseTree,
|
|
109
|
+
baseTree: state.baseTree,
|
|
16
110
|
nodes,
|
|
17
|
-
|
|
111
|
+
changes: state.changeLog.snapshot()
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function restoreState(state, snapshot) {
|
|
115
|
+
state.baseTree = snapshot.baseTree;
|
|
116
|
+
state.nodes.clear();
|
|
117
|
+
for (const [nodeId, node] of snapshot.nodes) state.nodes.set(nodeId, cloneSessionNode(node));
|
|
118
|
+
state.changeLog.clear();
|
|
119
|
+
for (const record of snapshot.changes) state.changeLog.append(record);
|
|
120
|
+
}
|
|
121
|
+
function cloneSessionNode(node) {
|
|
122
|
+
if (node.state.kind === "directory") return {
|
|
123
|
+
id: node.id,
|
|
124
|
+
origin: node.origin,
|
|
125
|
+
state: {
|
|
126
|
+
kind: "directory",
|
|
127
|
+
overlay: {
|
|
128
|
+
addedEntries: new Map(node.state.overlay.addedEntries),
|
|
129
|
+
deletedNames: new Set(node.state.overlay.deletedNames)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
if (node.state.kind === "file") return {
|
|
134
|
+
id: node.id,
|
|
135
|
+
origin: node.origin,
|
|
136
|
+
state: node.state.content === void 0 ? {
|
|
137
|
+
kind: "file",
|
|
138
|
+
mode: node.state.mode
|
|
139
|
+
} : {
|
|
140
|
+
kind: "file",
|
|
141
|
+
mode: node.state.mode,
|
|
142
|
+
content: Buffer.from(node.state.content)
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
id: node.id,
|
|
147
|
+
origin: node.origin,
|
|
148
|
+
state: node.state.target === void 0 ? {
|
|
149
|
+
kind: "symlink",
|
|
150
|
+
mode: "120000"
|
|
151
|
+
} : {
|
|
152
|
+
kind: "symlink",
|
|
153
|
+
mode: "120000",
|
|
154
|
+
target: Buffer.from(node.state.target)
|
|
155
|
+
}
|
|
18
156
|
};
|
|
19
157
|
}
|
|
20
158
|
//#endregion
|
|
21
|
-
export {
|
|
159
|
+
export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirMemoryStateStore };
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { createMemoryVirtualWorkdirBackend } from "./memory-backend.mjs";
|
|
2
|
+
import { createVirtualWorkdirSession, openVirtualWorkdirSession } from "./session.mjs";
|
|
3
|
+
export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
|
package/dist/workdir/memory.mjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { createVirtualWorkdirSession } from "./session.mjs";
|
|
2
|
-
|
|
1
|
+
import { createVirtualWorkdirSession, openVirtualWorkdirSession } from "./session.mjs";
|
|
2
|
+
import { createMemoryVirtualWorkdirBackend } from "./memory-backend.mjs";
|
|
3
|
+
export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SHA1 } from "../core/types.mjs";
|
|
2
|
+
import { NodeId } from "./ids.mjs";
|
|
3
|
+
import { DirectoryOverlay } from "./overlay.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/workdir/nodes.d.ts
|
|
6
|
+
/** Blob / 符号链接在 origin 与 state 中使用的 mode */
|
|
7
|
+
type BlobObjectMode = "100644" | "100755" | "120000";
|
|
8
|
+
/**
|
|
9
|
+
* 节点来源(repo 对象或纯 session 新建)
|
|
10
|
+
*/
|
|
11
|
+
type NodeOrigin = {
|
|
12
|
+
readonly kind: "none";
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "repo-tree";
|
|
15
|
+
readonly hash: SHA1;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "repo-blob";
|
|
18
|
+
readonly mode: BlobObjectMode;
|
|
19
|
+
readonly hash: SHA1;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 目录节点当前状态
|
|
23
|
+
*
|
|
24
|
+
* `overlay` 表达 session 层增删改;子项 nodeId 通过 overlay 合成与懒加载解析。
|
|
25
|
+
*/
|
|
26
|
+
interface DirectoryNodeState {
|
|
27
|
+
readonly kind: "directory";
|
|
28
|
+
readonly overlay: DirectoryOverlay;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 文件节点当前状态(raw content overlay)
|
|
32
|
+
*/
|
|
33
|
+
interface FileNodeState {
|
|
34
|
+
readonly kind: "file";
|
|
35
|
+
readonly mode: "100644" | "100755";
|
|
36
|
+
/** 未设置时表示未 materialize,可读 origin */
|
|
37
|
+
readonly content?: Buffer;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 符号链接节点当前状态
|
|
41
|
+
*/
|
|
42
|
+
interface SymlinkNodeState {
|
|
43
|
+
readonly kind: "symlink";
|
|
44
|
+
readonly mode: "120000";
|
|
45
|
+
readonly target?: Buffer;
|
|
46
|
+
}
|
|
47
|
+
type SessionNodeState = DirectoryNodeState | FileNodeState | SymlinkNodeState;
|
|
48
|
+
/**
|
|
49
|
+
* 完整的会话节点记录
|
|
50
|
+
*/
|
|
51
|
+
interface SessionNode {
|
|
52
|
+
readonly id: NodeId;
|
|
53
|
+
readonly origin: NodeOrigin;
|
|
54
|
+
readonly state: SessionNodeState;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { SessionNode };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NodeId } from "./ids.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/workdir/overlay.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 目录 overlay 状态(挂在目录 SessionNode 上)
|
|
6
|
+
*/
|
|
7
|
+
interface DirectoryOverlay {
|
|
8
|
+
/** session 新增或覆盖:条目名 -> 绑定的 nodeId */
|
|
9
|
+
readonly addedEntries: Map<string, NodeId>;
|
|
10
|
+
/** session 删除的 origin/先前条目名(tombstone) */
|
|
11
|
+
readonly deletedNames: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { DirectoryOverlay };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/workdir/session-id.ts
|
|
2
|
+
let nextSessionCounter = 1;
|
|
3
|
+
/**
|
|
4
|
+
* 分配新的 session ID
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const sessionId = createVirtualWorkdirSessionId();
|
|
9
|
+
* expect(String(sessionId).startsWith("session:")).toBe(true);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
function createVirtualWorkdirSessionId() {
|
|
13
|
+
const id = `session:${nextSessionCounter}`;
|
|
14
|
+
nextSessionCounter += 1;
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createVirtualWorkdirSessionId };
|
|
@@ -13,8 +13,8 @@ import { assertValidVirtualPath, joinPath, splitPathSegments } from "./path.mjs"
|
|
|
13
13
|
* - origin 节点懒注册(ensureNodeFromTreeEntry)
|
|
14
14
|
*/
|
|
15
15
|
function getRootNode(state) {
|
|
16
|
-
const root = state.
|
|
17
|
-
if (root ===
|
|
16
|
+
const root = state.getNode(VIRTUAL_ROOT_NODE_ID);
|
|
17
|
+
if (root === null) throw new Error("Virtual workdir session is missing root node");
|
|
18
18
|
return root;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -36,8 +36,8 @@ function resolvePath(source, state, path) {
|
|
|
36
36
|
node: null
|
|
37
37
|
};
|
|
38
38
|
currentPath = joinPath(currentPath === "" ? null : currentPath, segment);
|
|
39
|
-
const childNode = state.
|
|
40
|
-
if (childNode ===
|
|
39
|
+
const childNode = state.getNode(child.nodeId);
|
|
40
|
+
if (childNode === null) return {
|
|
41
41
|
found: false,
|
|
42
42
|
node: null
|
|
43
43
|
};
|
|
@@ -61,8 +61,8 @@ function resolveChild(source, state, parentNode, parentPath, name) {
|
|
|
61
61
|
found: false,
|
|
62
62
|
node: null
|
|
63
63
|
};
|
|
64
|
-
const childNode = state.
|
|
65
|
-
if (childNode ===
|
|
64
|
+
const childNode = state.getNode(child.nodeId);
|
|
65
|
+
if (childNode === null) return {
|
|
66
66
|
found: false,
|
|
67
67
|
node: null
|
|
68
68
|
};
|
|
@@ -76,7 +76,7 @@ function resolveChild(source, state, parentNode, parentPath, name) {
|
|
|
76
76
|
*/
|
|
77
77
|
function ensureNodeFromTreeEntry(state, entry) {
|
|
78
78
|
const id = originBackedNodeId(entry.hash);
|
|
79
|
-
if (
|
|
79
|
+
if (state.getNode(id) === null) {
|
|
80
80
|
const origin = treeEntryToNodeOrigin(entry);
|
|
81
81
|
let nodeState;
|
|
82
82
|
if (entry.mode === "40000") nodeState = {
|
|
@@ -94,7 +94,7 @@ function ensureNodeFromTreeEntry(state, entry) {
|
|
|
94
94
|
kind: "file",
|
|
95
95
|
mode: entry.mode === "100755" ? "100755" : "100644"
|
|
96
96
|
};
|
|
97
|
-
state.
|
|
97
|
+
state.setNode({
|
|
98
98
|
id,
|
|
99
99
|
origin,
|
|
100
100
|
state: nodeState
|
|
@@ -127,8 +127,8 @@ function buildAddedModes(state, dirNode) {
|
|
|
127
127
|
const addedModes = /* @__PURE__ */ new Map();
|
|
128
128
|
for (const name of dirNode.state.overlay.addedEntries.keys()) {
|
|
129
129
|
const nodeId = dirNode.state.overlay.addedEntries.get(name);
|
|
130
|
-
const node = state.
|
|
131
|
-
if (node !==
|
|
130
|
+
const node = state.getNode(nodeId);
|
|
131
|
+
if (node !== null) addedModes.set(name, sessionNodeMode(node));
|
|
132
132
|
}
|
|
133
133
|
return addedModes;
|
|
134
134
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
2
|
import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./core.mjs";
|
|
3
|
+
import { VirtualWorkdirStateStore } from "./state-store.mjs";
|
|
3
4
|
|
|
4
5
|
//#region src/workdir/session.d.ts
|
|
5
6
|
/**
|
|
@@ -14,5 +15,16 @@ import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./cor
|
|
|
14
15
|
* ```
|
|
15
16
|
*/
|
|
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;
|
|
17
29
|
//#endregion
|
|
18
|
-
export { createVirtualWorkdirSession };
|
|
30
|
+
export { createVirtualWorkdirSession, openVirtualWorkdirSession };
|