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
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 };
|
|
@@ -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 };
|
|
@@ -1,90 +1,85 @@
|
|
|
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 { Database } from "bun:sqlite";
|
|
6
5
|
//#region src/workdir/sqlite-backend.ts
|
|
7
6
|
/**
|
|
8
7
|
* Virtual Workdir SQLite backend
|
|
9
8
|
*/
|
|
10
|
-
const WORKDIR_SQLITE_SCHEMA_VERSION =
|
|
9
|
+
const WORKDIR_SQLITE_SCHEMA_VERSION = 6;
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* 打开基于 SQLite 的持久化 VirtualWorkdir
|
|
13
12
|
*
|
|
14
13
|
* @example
|
|
15
14
|
* ```ts
|
|
16
|
-
* using
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* using workdir = openSqliteVirtualWorkdir(repo.objects, ":memory:", "demo", {
|
|
16
|
+
* baseTree: tree,
|
|
17
|
+
* create: true,
|
|
18
|
+
* });
|
|
19
|
+
* expect(workdir.baseTree).toBe(tree);
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
function
|
|
22
|
+
function openSqliteVirtualWorkdir(source, dbPath, workdirKey, options) {
|
|
23
23
|
const db = new Database(dbPath);
|
|
24
|
-
let disposed = false;
|
|
25
24
|
if (options.walMode !== false) db.run("PRAGMA journal_mode = WAL");
|
|
26
25
|
ensureSchema(db);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
assertBackendAvailable(disposed);
|
|
31
|
-
const sessionId = createVirtualWorkdirSessionId();
|
|
32
|
-
createSqliteVirtualWorkdirStateStore(db, sessionId).reset(options.baseTree);
|
|
33
|
-
return sessionId;
|
|
34
|
-
},
|
|
35
|
-
openSession(source, sessionId) {
|
|
36
|
-
assertBackendAvailable(disposed);
|
|
37
|
-
if (!hasSession(db, sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
38
|
-
validateSessionIntegrity(db, sessionId);
|
|
39
|
-
return openVirtualWorkdirSession(source, createSqliteVirtualWorkdirStateStore(db, sessionId));
|
|
40
|
-
},
|
|
41
|
-
deleteSession(sessionId) {
|
|
42
|
-
assertBackendAvailable(disposed);
|
|
43
|
-
if (!hasSession(db, sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
|
|
44
|
-
deleteSessionRows(db, sessionId);
|
|
45
|
-
},
|
|
46
|
-
listSessions() {
|
|
47
|
-
assertBackendAvailable(disposed);
|
|
48
|
-
return db.query("SELECT session_id FROM workdir_sessions ORDER BY session_id").all().map((row) => row.session_id).filter((sessionId) => {
|
|
49
|
-
try {
|
|
50
|
-
validateSessionIntegrity(db, sessionId);
|
|
51
|
-
return true;
|
|
52
|
-
} catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
|
-
[Symbol.dispose]() {
|
|
58
|
-
if (disposed) return;
|
|
59
|
-
disposed = true;
|
|
26
|
+
const store = createSqliteVirtualWorkdirStateStore(db, workdirKey);
|
|
27
|
+
if (!hasWorkdir(db, workdirKey)) {
|
|
28
|
+
if (options.create !== true) {
|
|
60
29
|
db.close();
|
|
30
|
+
throw new Error(`Virtual workdir not found: ${workdirKey}`);
|
|
61
31
|
}
|
|
62
|
-
|
|
32
|
+
store.reset(options.baseTree);
|
|
33
|
+
}
|
|
34
|
+
validateWorkdirIntegrity(db, workdirKey);
|
|
35
|
+
let disposed = false;
|
|
36
|
+
const workdir = openVirtualWorkdir(source, store);
|
|
37
|
+
return Object.assign(workdir, { [Symbol.dispose]() {
|
|
38
|
+
if (disposed) return;
|
|
39
|
+
disposed = true;
|
|
40
|
+
db.close();
|
|
41
|
+
} });
|
|
63
42
|
}
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
/**
|
|
44
|
+
* 删除指定 key 上的 SQLite VirtualWorkdir
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* deleteSqliteVirtualWorkdir("/tmp/workdir.sqlite", "demo");
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function deleteSqliteVirtualWorkdir(dbPath, workdirKey, options = {}) {
|
|
52
|
+
const db = new Database(dbPath);
|
|
53
|
+
try {
|
|
54
|
+
if (options.walMode !== false) db.run("PRAGMA journal_mode = WAL");
|
|
55
|
+
ensureSchema(db);
|
|
56
|
+
if (!hasWorkdir(db, workdirKey)) throw new Error(`Virtual workdir not found: ${workdirKey}`);
|
|
57
|
+
deleteWorkdirRows(db, workdirKey);
|
|
58
|
+
} finally {
|
|
59
|
+
db.close();
|
|
60
|
+
}
|
|
66
61
|
}
|
|
67
62
|
/**
|
|
68
|
-
* 创建单个
|
|
63
|
+
* 创建单个 SQLite VirtualWorkdir 的状态存储
|
|
69
64
|
*
|
|
70
65
|
* @example
|
|
71
66
|
* ```ts
|
|
72
|
-
* const store = createSqliteVirtualWorkdirStateStore(db,
|
|
67
|
+
* const store = createSqliteVirtualWorkdirStateStore(db, "demo");
|
|
73
68
|
* expect(store.kind).toBe("sqlite");
|
|
74
69
|
* ```
|
|
75
70
|
*/
|
|
76
|
-
function createSqliteVirtualWorkdirStateStore(db,
|
|
71
|
+
function createSqliteVirtualWorkdirStateStore(db, workdirKey) {
|
|
77
72
|
const transactImpl = db.transaction((fn) => fn());
|
|
78
|
-
const readBaseTreeStmt = db.query("SELECT base_tree FROM
|
|
79
|
-
const
|
|
73
|
+
const readBaseTreeStmt = db.query("SELECT base_tree FROM workdirs WHERE workdir_key = ?");
|
|
74
|
+
const upsertWorkdirStmt = db.query("INSERT INTO workdirs (workdir_key, base_tree) VALUES (?, ?) ON CONFLICT(workdir_key) DO UPDATE SET base_tree = excluded.base_tree");
|
|
80
75
|
const getNodeStmt = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
|
|
81
76
|
FROM workdir_nodes
|
|
82
|
-
WHERE
|
|
77
|
+
WHERE workdir_key = ? AND node_id = ?`);
|
|
83
78
|
const setNodeStmt = db.query(`INSERT INTO workdir_nodes (
|
|
84
|
-
|
|
79
|
+
workdir_key, node_id, origin_kind, origin_hash, origin_mode,
|
|
85
80
|
state_kind, state_mode, content, target, directory_overlay
|
|
86
81
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
87
|
-
ON CONFLICT(
|
|
82
|
+
ON CONFLICT(workdir_key, node_id) DO UPDATE SET
|
|
88
83
|
origin_kind = excluded.origin_kind,
|
|
89
84
|
origin_hash = excluded.origin_hash,
|
|
90
85
|
origin_mode = excluded.origin_mode,
|
|
@@ -93,20 +88,20 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
|
|
|
93
88
|
content = excluded.content,
|
|
94
89
|
target = excluded.target,
|
|
95
90
|
directory_overlay = excluded.directory_overlay`);
|
|
96
|
-
const deleteNodeStmt = db.query("DELETE FROM workdir_nodes WHERE
|
|
97
|
-
const clearNodesStmt = db.query("DELETE FROM workdir_nodes WHERE
|
|
91
|
+
const deleteNodeStmt = db.query("DELETE FROM workdir_nodes WHERE workdir_key = ? AND node_id = ?");
|
|
92
|
+
const clearNodesStmt = db.query("DELETE FROM workdir_nodes WHERE workdir_key = ?");
|
|
98
93
|
const listChangesStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
|
|
99
94
|
FROM workdir_changes
|
|
100
|
-
WHERE
|
|
95
|
+
WHERE workdir_key = ?
|
|
101
96
|
ORDER BY path`);
|
|
102
97
|
const getChangeStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
|
|
103
98
|
FROM workdir_changes
|
|
104
|
-
WHERE
|
|
99
|
+
WHERE workdir_key = ? AND path = ?`);
|
|
105
100
|
const upsertChangeStmt = db.query(`INSERT INTO workdir_changes (
|
|
106
|
-
|
|
101
|
+
workdir_key, path, previous_kind, previous_mode, previous_hash,
|
|
107
102
|
current_kind, current_mode, current_hash, source_kind, source_path
|
|
108
103
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
109
|
-
ON CONFLICT(
|
|
104
|
+
ON CONFLICT(workdir_key, path) DO UPDATE SET
|
|
110
105
|
previous_kind = excluded.previous_kind,
|
|
111
106
|
previous_mode = excluded.previous_mode,
|
|
112
107
|
previous_hash = excluded.previous_hash,
|
|
@@ -115,35 +110,35 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
|
|
|
115
110
|
current_hash = excluded.current_hash,
|
|
116
111
|
source_kind = excluded.source_kind,
|
|
117
112
|
source_path = excluded.source_path`);
|
|
118
|
-
const deleteChangeStmt = db.query("DELETE FROM workdir_changes WHERE
|
|
119
|
-
const clearChangesStmt = db.query("DELETE FROM workdir_changes WHERE
|
|
113
|
+
const deleteChangeStmt = db.query("DELETE FROM workdir_changes WHERE workdir_key = ? AND path = ?");
|
|
114
|
+
const clearChangesStmt = db.query("DELETE FROM workdir_changes WHERE workdir_key = ?");
|
|
120
115
|
const listDirtyDirsStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
|
|
121
116
|
FROM workdir_dirty_dirs
|
|
122
|
-
WHERE
|
|
117
|
+
WHERE workdir_key = ?
|
|
123
118
|
ORDER BY path`);
|
|
124
119
|
const getDirtyDirStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
|
|
125
120
|
FROM workdir_dirty_dirs
|
|
126
|
-
WHERE
|
|
121
|
+
WHERE workdir_key = ? AND path = ?`);
|
|
127
122
|
const upsertDirtyDirStmt = db.query(`INSERT INTO workdir_dirty_dirs (
|
|
128
|
-
|
|
123
|
+
workdir_key, path, is_dirty, dirty_entry_count, dirty_descendant_count,
|
|
129
124
|
affected_names, current_tree_hash, hash_state
|
|
130
125
|
)
|
|
131
126
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
132
|
-
ON CONFLICT(
|
|
127
|
+
ON CONFLICT(workdir_key, path) DO UPDATE SET
|
|
133
128
|
is_dirty = excluded.is_dirty,
|
|
134
129
|
dirty_entry_count = excluded.dirty_entry_count,
|
|
135
130
|
dirty_descendant_count = excluded.dirty_descendant_count,
|
|
136
131
|
affected_names = excluded.affected_names,
|
|
137
132
|
current_tree_hash = excluded.current_tree_hash,
|
|
138
133
|
hash_state = excluded.hash_state`);
|
|
139
|
-
const deleteDirtyDirStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE
|
|
140
|
-
const clearDirtyDirsStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE
|
|
134
|
+
const deleteDirtyDirStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ? AND path = ?");
|
|
135
|
+
const clearDirtyDirsStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ?");
|
|
141
136
|
const resetTx = db.transaction((baseTree) => {
|
|
142
|
-
|
|
143
|
-
clearNodesStmt.run(
|
|
144
|
-
clearChangesStmt.run(
|
|
145
|
-
clearDirtyDirsStmt.run(
|
|
146
|
-
writeNode(setNodeStmt,
|
|
137
|
+
upsertWorkdirStmt.run(workdirKey, baseTree);
|
|
138
|
+
clearNodesStmt.run(workdirKey);
|
|
139
|
+
clearChangesStmt.run(workdirKey);
|
|
140
|
+
clearDirtyDirsStmt.run(workdirKey);
|
|
141
|
+
writeNode(setNodeStmt, workdirKey, createRootDirectoryNode(baseTree));
|
|
147
142
|
});
|
|
148
143
|
return {
|
|
149
144
|
kind: "sqlite",
|
|
@@ -151,49 +146,49 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
|
|
|
151
146
|
return transactImpl(fn);
|
|
152
147
|
},
|
|
153
148
|
readBaseTree() {
|
|
154
|
-
const row = readBaseTreeStmt.get(
|
|
155
|
-
if (row === null) throw new Error(`Virtual workdir
|
|
149
|
+
const row = readBaseTreeStmt.get(workdirKey);
|
|
150
|
+
if (row === null) throw new Error(`Virtual workdir not found: ${workdirKey}`);
|
|
156
151
|
return readBaseTreeValue(row.base_tree);
|
|
157
152
|
},
|
|
158
153
|
writeBaseTree(baseTree) {
|
|
159
|
-
|
|
154
|
+
upsertWorkdirStmt.run(workdirKey, baseTree);
|
|
160
155
|
},
|
|
161
156
|
getNode(id) {
|
|
162
|
-
const row = getNodeStmt.get(
|
|
157
|
+
const row = getNodeStmt.get(workdirKey, id);
|
|
163
158
|
if (row === null) return null;
|
|
164
159
|
return readNode(row);
|
|
165
160
|
},
|
|
166
161
|
setNode(node) {
|
|
167
|
-
writeNode(setNodeStmt,
|
|
162
|
+
writeNode(setNodeStmt, workdirKey, node);
|
|
168
163
|
},
|
|
169
164
|
deleteNode(id) {
|
|
170
|
-
deleteNodeStmt.run(
|
|
165
|
+
deleteNodeStmt.run(workdirKey, id);
|
|
171
166
|
},
|
|
172
167
|
listChangeRecords() {
|
|
173
|
-
return listChangesStmt.all(
|
|
168
|
+
return listChangesStmt.all(workdirKey).map(readChangeRecord);
|
|
174
169
|
},
|
|
175
170
|
getChangeRecord(path) {
|
|
176
|
-
const row = getChangeStmt.get(
|
|
171
|
+
const row = getChangeStmt.get(workdirKey, path);
|
|
177
172
|
return row === null ? null : readChangeRecord(row);
|
|
178
173
|
},
|
|
179
174
|
setChangeRecord(record) {
|
|
180
|
-
upsertChangeStmt.run(
|
|
175
|
+
upsertChangeStmt.run(workdirKey, record.path, record.previous?.kind ?? null, record.previous?.mode ?? null, record.previous?.hash ?? null, record.current?.kind ?? null, record.current?.mode ?? null, record.current?.hash ?? null, record.source?.kind ?? null, record.source?.path ?? null);
|
|
181
176
|
},
|
|
182
177
|
deleteChangeRecord(path) {
|
|
183
|
-
deleteChangeStmt.run(
|
|
178
|
+
deleteChangeStmt.run(workdirKey, path);
|
|
184
179
|
},
|
|
185
180
|
listDirtyDirSummaries() {
|
|
186
|
-
return listDirtyDirsStmt.all(
|
|
181
|
+
return listDirtyDirsStmt.all(workdirKey).map(readDirtyDirSummary);
|
|
187
182
|
},
|
|
188
183
|
getDirtyDirSummary(path) {
|
|
189
|
-
const row = getDirtyDirStmt.get(
|
|
184
|
+
const row = getDirtyDirStmt.get(workdirKey, path);
|
|
190
185
|
return row === null ? null : readDirtyDirSummary(row);
|
|
191
186
|
},
|
|
192
187
|
setDirtyDirSummary(summary) {
|
|
193
|
-
upsertDirtyDirStmt.run(
|
|
188
|
+
upsertDirtyDirStmt.run(workdirKey, summary.path, summary.isDirty ? 1 : 0, summary.dirtyEntryCount, summary.dirtyDescendantCount, JSON.stringify(summary.affectedNames), summary.currentTreeHash, summary.hashState);
|
|
194
189
|
},
|
|
195
190
|
deleteDirtyDirSummary(path) {
|
|
196
|
-
deleteDirtyDirStmt.run(
|
|
191
|
+
deleteDirtyDirStmt.run(workdirKey, path);
|
|
197
192
|
},
|
|
198
193
|
reset(baseTree) {
|
|
199
194
|
resetTx(baseTree);
|
|
@@ -204,14 +199,14 @@ function ensureSchema(db) {
|
|
|
204
199
|
const currentVersion = readSchemaVersion(db);
|
|
205
200
|
if (currentVersion !== 0 && currentVersion !== WORKDIR_SQLITE_SCHEMA_VERSION) throw new Error(`Unsupported virtual workdir SQLite schema version: expected ${WORKDIR_SQLITE_SCHEMA_VERSION}, got ${currentVersion}`);
|
|
206
201
|
db.run(`
|
|
207
|
-
CREATE TABLE IF NOT EXISTS
|
|
208
|
-
|
|
202
|
+
CREATE TABLE IF NOT EXISTS workdirs (
|
|
203
|
+
workdir_key TEXT PRIMARY KEY,
|
|
209
204
|
base_tree TEXT NOT NULL
|
|
210
205
|
)
|
|
211
206
|
`);
|
|
212
207
|
db.run(`
|
|
213
208
|
CREATE TABLE IF NOT EXISTS workdir_nodes (
|
|
214
|
-
|
|
209
|
+
workdir_key TEXT NOT NULL,
|
|
215
210
|
node_id TEXT NOT NULL,
|
|
216
211
|
origin_kind TEXT NOT NULL,
|
|
217
212
|
origin_hash TEXT,
|
|
@@ -221,12 +216,12 @@ function ensureSchema(db) {
|
|
|
221
216
|
content BLOB,
|
|
222
217
|
target BLOB,
|
|
223
218
|
directory_overlay TEXT,
|
|
224
|
-
PRIMARY KEY (
|
|
219
|
+
PRIMARY KEY (workdir_key, node_id)
|
|
225
220
|
)
|
|
226
221
|
`);
|
|
227
222
|
db.run(`
|
|
228
223
|
CREATE TABLE IF NOT EXISTS workdir_changes (
|
|
229
|
-
|
|
224
|
+
workdir_key TEXT NOT NULL,
|
|
230
225
|
path TEXT NOT NULL,
|
|
231
226
|
previous_kind TEXT,
|
|
232
227
|
previous_mode TEXT,
|
|
@@ -236,12 +231,12 @@ function ensureSchema(db) {
|
|
|
236
231
|
current_hash TEXT,
|
|
237
232
|
source_kind TEXT,
|
|
238
233
|
source_path TEXT,
|
|
239
|
-
PRIMARY KEY (
|
|
234
|
+
PRIMARY KEY (workdir_key, path)
|
|
240
235
|
)
|
|
241
236
|
`);
|
|
242
237
|
db.run(`
|
|
243
238
|
CREATE TABLE IF NOT EXISTS workdir_dirty_dirs (
|
|
244
|
-
|
|
239
|
+
workdir_key TEXT NOT NULL,
|
|
245
240
|
path TEXT NOT NULL,
|
|
246
241
|
is_dirty INTEGER NOT NULL,
|
|
247
242
|
dirty_entry_count INTEGER NOT NULL,
|
|
@@ -249,20 +244,20 @@ function ensureSchema(db) {
|
|
|
249
244
|
affected_names TEXT NOT NULL,
|
|
250
245
|
current_tree_hash TEXT,
|
|
251
246
|
hash_state TEXT NOT NULL,
|
|
252
|
-
PRIMARY KEY (
|
|
247
|
+
PRIMARY KEY (workdir_key, path)
|
|
253
248
|
)
|
|
254
249
|
`);
|
|
255
250
|
writeSchemaVersion(db, WORKDIR_SQLITE_SCHEMA_VERSION);
|
|
256
251
|
}
|
|
257
|
-
function
|
|
258
|
-
return db.query("SELECT 1 FROM
|
|
252
|
+
function hasWorkdir(db, workdirKey) {
|
|
253
|
+
return db.query("SELECT 1 FROM workdirs WHERE workdir_key = ?").get(workdirKey) !== null;
|
|
259
254
|
}
|
|
260
|
-
function
|
|
255
|
+
function deleteWorkdirRows(db, workdirKey) {
|
|
261
256
|
db.transaction(() => {
|
|
262
|
-
db.query("DELETE FROM workdir_dirty_dirs WHERE
|
|
263
|
-
db.query("DELETE FROM workdir_changes WHERE
|
|
264
|
-
db.query("DELETE FROM workdir_nodes WHERE
|
|
265
|
-
db.query("DELETE FROM
|
|
257
|
+
db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ?").run(workdirKey);
|
|
258
|
+
db.query("DELETE FROM workdir_changes WHERE workdir_key = ?").run(workdirKey);
|
|
259
|
+
db.query("DELETE FROM workdir_nodes WHERE workdir_key = ?").run(workdirKey);
|
|
260
|
+
db.query("DELETE FROM workdirs WHERE workdir_key = ?").run(workdirKey);
|
|
266
261
|
})();
|
|
267
262
|
}
|
|
268
263
|
function readSchemaVersion(db) {
|
|
@@ -271,33 +266,33 @@ function readSchemaVersion(db) {
|
|
|
271
266
|
function writeSchemaVersion(db, version) {
|
|
272
267
|
db.run(`PRAGMA user_version = ${version}`);
|
|
273
268
|
}
|
|
274
|
-
function
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
277
|
-
readBaseTreeValue(
|
|
269
|
+
function validateWorkdirIntegrity(db, workdirKey) {
|
|
270
|
+
const workdirRow = db.query("SELECT base_tree FROM workdirs WHERE workdir_key = ?").get(workdirKey);
|
|
271
|
+
if (workdirRow === null) throw new Error(`Virtual workdir not found: ${workdirKey}`);
|
|
272
|
+
readBaseTreeValue(workdirRow.base_tree);
|
|
278
273
|
const rootRow = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
|
|
279
274
|
FROM workdir_nodes
|
|
280
|
-
WHERE
|
|
281
|
-
if (rootRow === null) throw new Error(`Virtual workdir
|
|
282
|
-
if (readNode(rootRow).state.kind !== "directory") throw new Error(`Virtual workdir
|
|
275
|
+
WHERE workdir_key = ? AND node_id = ?`).get(workdirKey, "root");
|
|
276
|
+
if (rootRow === null) throw new Error(`Virtual workdir is corrupted: missing root node for ${workdirKey}`);
|
|
277
|
+
if (readNode(rootRow).state.kind !== "directory") throw new Error(`Virtual workdir is corrupted: root node is not a directory for ${workdirKey}`);
|
|
283
278
|
const allNodesStmt = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
|
|
284
279
|
FROM workdir_nodes
|
|
285
|
-
WHERE
|
|
286
|
-
for (const row of allNodesStmt.all(
|
|
280
|
+
WHERE workdir_key = ?`);
|
|
281
|
+
for (const row of allNodesStmt.all(workdirKey)) readNode(row);
|
|
287
282
|
}
|
|
288
|
-
function writeNode(stmt,
|
|
283
|
+
function writeNode(stmt, workdirKey, node) {
|
|
289
284
|
if (node.state.kind === "directory") {
|
|
290
|
-
stmt.run(
|
|
285
|
+
stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "directory", null, null, null, JSON.stringify({
|
|
291
286
|
addedEntries: Array.from(node.state.overlay.addedEntries.entries()),
|
|
292
287
|
deletedNames: Array.from(node.state.overlay.deletedNames.values())
|
|
293
288
|
}));
|
|
294
289
|
return;
|
|
295
290
|
}
|
|
296
291
|
if (node.state.kind === "file") {
|
|
297
|
-
stmt.run(
|
|
292
|
+
stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "file", node.state.mode, node.state.content ?? null, null, null);
|
|
298
293
|
return;
|
|
299
294
|
}
|
|
300
|
-
stmt.run(
|
|
295
|
+
stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "symlink", "120000", null, node.state.target ?? null, null);
|
|
301
296
|
}
|
|
302
297
|
function readNode(row) {
|
|
303
298
|
const origin = readNodeOrigin(row);
|
|
@@ -445,11 +440,11 @@ function readBlobColumn(raw, column) {
|
|
|
445
440
|
return Buffer.from(raw);
|
|
446
441
|
}
|
|
447
442
|
function readBaseTreeValue(raw) {
|
|
448
|
-
if (typeof raw !== "string") throw new Error("Invalid SQLite workdir
|
|
443
|
+
if (typeof raw !== "string") throw new Error("Invalid SQLite workdir base_tree");
|
|
449
444
|
try {
|
|
450
445
|
return sha1(raw);
|
|
451
446
|
} catch {
|
|
452
|
-
throw new Error("Invalid SQLite workdir
|
|
447
|
+
throw new Error("Invalid SQLite workdir base_tree");
|
|
453
448
|
}
|
|
454
449
|
}
|
|
455
450
|
function isDirectoryOverlayPayload(value) {
|
|
@@ -461,4 +456,4 @@ function isDirectoryOverlayPayload(value) {
|
|
|
461
456
|
return hasValidAddedEntries && hasValidDeletedNames;
|
|
462
457
|
}
|
|
463
458
|
//#endregion
|
|
464
|
-
export {
|
|
459
|
+
export { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { type
|
|
1
|
+
import { OpenSqliteVirtualWorkdirOptions, SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir } from "./sqlite-backend.mjs";
|
|
2
|
+
export { type OpenSqliteVirtualWorkdirOptions, type SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
|
package/dist/workdir/sqlite.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir } from "./sqlite-backend.mjs";
|
|
2
|
+
export { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
|
|
@@ -2,7 +2,7 @@ import { SHA1 } from "../core/types.mjs";
|
|
|
2
2
|
import { NodeId } from "./ids.mjs";
|
|
3
3
|
import { NormalizedChangeRecord } from "./change-index.mjs";
|
|
4
4
|
import { DirtyDirSummary } from "./dirty-dir.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { WorkdirNode } from "./nodes.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/workdir/state-store.d.ts
|
|
8
8
|
/**
|
|
@@ -14,7 +14,7 @@ interface VirtualWorkdirStateStore {
|
|
|
14
14
|
/**
|
|
15
15
|
* 在单次提交边界内执行状态变更
|
|
16
16
|
*
|
|
17
|
-
* 用于把一次
|
|
17
|
+
* 用于把一次 workdir 写操作封装为单个内部事务。
|
|
18
18
|
* 若回调抛错,store 应尽力恢复到调用前状态。
|
|
19
19
|
*/
|
|
20
20
|
transact<T>(fn: () => T): T;
|
|
@@ -23,9 +23,9 @@ interface VirtualWorkdirStateStore {
|
|
|
23
23
|
/** 覆盖当前基线 tree */
|
|
24
24
|
writeBaseTree(baseTree: SHA1): void;
|
|
25
25
|
/** 读取节点,不存在时返回 null */
|
|
26
|
-
getNode(id: NodeId):
|
|
26
|
+
getNode(id: NodeId): WorkdirNode | null;
|
|
27
27
|
/** 写入或覆盖节点 */
|
|
28
|
-
setNode(node:
|
|
28
|
+
setNode(node: WorkdirNode): void;
|
|
29
29
|
/** 删除节点 */
|
|
30
30
|
deleteNode(id: NodeId): void;
|
|
31
31
|
/** 列出全部规范化变更记录 */
|