nano-git 0.3.4 → 0.3.6
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/backend/sqlite-pool.d.mts +20 -0
- package/dist/backend/sqlite-pool.mjs +95 -0
- package/dist/backend/sqlite.d.mts +2 -1
- package/dist/backend/sqlite.mjs +16 -13
- package/dist/odb/sqlite.d.mts +6 -6
- package/dist/odb/sqlite.mjs +11 -11
- package/dist/refs/shallow/sqlite.d.mts +6 -6
- package/dist/refs/shallow/sqlite.mjs +12 -12
- package/dist/refs/sqlite.d.mts +6 -6
- package/dist/refs/sqlite.mjs +12 -11
- package/dist/repository/ops/object-operations.mjs +16 -0
- package/dist/workdir/change-index.d.mts +1 -1
- package/dist/workdir/change-index.mjs +8 -8
- package/dist/workdir/core.d.mts +10 -8
- package/dist/workdir/directory-view.mjs +2 -113
- package/dist/workdir/file-backend.mjs +12 -1
- package/dist/workdir/nodes.mjs +2 -1
- package/dist/workdir/overlay.mjs +2 -2
- package/dist/workdir/sqlite-backend.d.mts +0 -1
- package/dist/workdir/sqlite-backend.mjs +48 -45
- package/dist/workdir/workdir-path.mjs +1 -1
- package/dist/workdir/workdir.mjs +7 -21
- package/dist/workdir/write-tree.mjs +144 -104
- package/package.json +1 -1
- package/dist/workdir/dirty-dir-plan.mjs +0 -73
- package/dist/workdir/dirty-dir.mjs +0 -32
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Database, Statement } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
//#region src/backend/sqlite-pool.d.ts
|
|
4
|
+
/** 连接池获取结果 */
|
|
5
|
+
interface SqliteConnectionHandle {
|
|
6
|
+
readonly db: Database;
|
|
7
|
+
/** 释放连接(引用计数减一,归零时关闭数据库) */
|
|
8
|
+
readonly release: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* 获取缓存的 prepared statement
|
|
11
|
+
*
|
|
12
|
+
* 相同 SQL 文本返回同一实例,避免重复编译。
|
|
13
|
+
* 绑定参数类型由调用方按 SQL 保证一致,此处不做泛型约束。
|
|
14
|
+
*/
|
|
15
|
+
readonly prepare: <T>(sql: string) => Statement<T, any[]>;
|
|
16
|
+
/** 支持 `using` 语法自动释放 */
|
|
17
|
+
[Symbol.dispose](): void;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { SqliteConnectionHandle };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
//#region src/backend/sqlite-pool.ts
|
|
3
|
+
/**
|
|
4
|
+
* SQLite 连接池(引用计数)
|
|
5
|
+
*
|
|
6
|
+
* 缓存 Database 实例,相同 dbPath 复用同一连接,
|
|
7
|
+
* 防止反复打开同一数据库文件带来的开销。
|
|
8
|
+
*
|
|
9
|
+
* 每个 acquire 对应一个 release,当引用计数归零时自动关闭连接。
|
|
10
|
+
* 返回的 handle 实现了 [Symbol.dispose](),可用 `using` 语法自动释放。
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* using _conn = acquireConnection("/tmp/repo.sqlite");
|
|
15
|
+
* // 作用域结束时自动释放
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const pool = /* @__PURE__ */ new Map();
|
|
19
|
+
/** 构造连接句柄 */
|
|
20
|
+
function makeHandle(db, stmts, onRelease) {
|
|
21
|
+
return {
|
|
22
|
+
db,
|
|
23
|
+
release: onRelease,
|
|
24
|
+
prepare(sql) {
|
|
25
|
+
let stmt = stmts.get(sql);
|
|
26
|
+
if (stmt === void 0) {
|
|
27
|
+
stmt = db.query(sql);
|
|
28
|
+
stmts.set(sql, stmt);
|
|
29
|
+
}
|
|
30
|
+
return stmt;
|
|
31
|
+
},
|
|
32
|
+
[Symbol.dispose]() {
|
|
33
|
+
onRelease();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 从全局连接池获取或创建 SQLite 数据库连接
|
|
39
|
+
*
|
|
40
|
+
* 相同 dbPath 返回同一个 Database 实例,通过引用计数管理生命周期。
|
|
41
|
+
* 首次打开时根据 walMode 执行 PRAGMA journal_mode = WAL。
|
|
42
|
+
* 后续打开(缓存命中)忽略 walMode 参数。
|
|
43
|
+
*
|
|
44
|
+
* 注意:`:memory:` 和 `file:xxx?mode=memory` 等内存数据库不会被缓存,
|
|
45
|
+
* 每次调用都会创建独立连接(SQLite 的 :memory: 为每个连接独立)。
|
|
46
|
+
*/
|
|
47
|
+
function acquireConnection(dbPath, walMode = true) {
|
|
48
|
+
if (dbPath === ":memory:" || dbPath.startsWith("file:") && dbPath.includes("mode=memory")) {
|
|
49
|
+
const db = new Database(dbPath);
|
|
50
|
+
if (walMode) db.run("PRAGMA journal_mode = WAL");
|
|
51
|
+
const localCache = /* @__PURE__ */ new Map();
|
|
52
|
+
let released = false;
|
|
53
|
+
return makeHandle(db, localCache, () => {
|
|
54
|
+
if (released) return;
|
|
55
|
+
released = true;
|
|
56
|
+
db.close();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const existing = pool.get(dbPath);
|
|
60
|
+
if (existing !== void 0) {
|
|
61
|
+
existing.refCount++;
|
|
62
|
+
let released = false;
|
|
63
|
+
return makeHandle(existing.db, existing.stmts, () => {
|
|
64
|
+
if (released) return;
|
|
65
|
+
released = true;
|
|
66
|
+
releaseConnection(dbPath);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const db = new Database(dbPath);
|
|
70
|
+
if (walMode) db.run("PRAGMA journal_mode = WAL");
|
|
71
|
+
const stmts = /* @__PURE__ */ new Map();
|
|
72
|
+
pool.set(dbPath, {
|
|
73
|
+
db,
|
|
74
|
+
refCount: 1,
|
|
75
|
+
stmts
|
|
76
|
+
});
|
|
77
|
+
let released = false;
|
|
78
|
+
return makeHandle(db, stmts, () => {
|
|
79
|
+
if (released) return;
|
|
80
|
+
released = true;
|
|
81
|
+
releaseConnection(dbPath);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** 内部释放逻辑 */
|
|
85
|
+
function releaseConnection(dbPath) {
|
|
86
|
+
const entry = pool.get(dbPath);
|
|
87
|
+
if (entry === void 0) return;
|
|
88
|
+
entry.refCount--;
|
|
89
|
+
if (entry.refCount <= 0) {
|
|
90
|
+
pool.delete(dbPath);
|
|
91
|
+
entry.db.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { acquireConnection };
|
|
@@ -19,7 +19,8 @@ interface SqliteRepositoryBackend extends RepositoryBackend {
|
|
|
19
19
|
/**
|
|
20
20
|
* 创建基于 SQLite 文件的完整仓库后端
|
|
21
21
|
*
|
|
22
|
-
*
|
|
22
|
+
* 内部通过连接池获取 Database 实例并组合三个子 store。
|
|
23
|
+
* 相同 dbPath 复用同一连接,引用计数归零时自动关闭。
|
|
23
24
|
* 返回的 backend 实现了 [Symbol.dispose](),可使用 `using` 语法
|
|
24
25
|
* 或在不再使用时手动调用 `backend[Symbol.dispose]()`。
|
|
25
26
|
*
|
package/dist/backend/sqlite.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { HEADS_PREFIX, HEAD_REF } from "../core/types/refs.mjs";
|
|
|
2
2
|
import { createSqliteObjectStore } from "../odb/sqlite.mjs";
|
|
3
3
|
import { createSqliteRefStore } from "../refs/sqlite.mjs";
|
|
4
4
|
import { createSqliteShallowStore } from "../refs/shallow/sqlite.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { acquireConnection } from "./sqlite-pool.mjs";
|
|
6
6
|
//#region src/backend/sqlite.ts
|
|
7
7
|
/**
|
|
8
8
|
* 基于 SQLite 的仓库后端
|
|
@@ -12,11 +12,15 @@ import { Database } from "bun:sqlite";
|
|
|
12
12
|
*
|
|
13
13
|
* 支持 [Symbol.dispose]() 释放数据库连接,
|
|
14
14
|
* 可使用 `using` 语法管理生命周期。
|
|
15
|
+
*
|
|
16
|
+
* 数据库连接通过全局连接池管理(引用计数),
|
|
17
|
+
* 相同 dbPath 复用同一连接,避免反复打开同一个数据库文件。
|
|
15
18
|
*/
|
|
16
19
|
/**
|
|
17
20
|
* 创建基于 SQLite 文件的完整仓库后端
|
|
18
21
|
*
|
|
19
|
-
*
|
|
22
|
+
* 内部通过连接池获取 Database 实例并组合三个子 store。
|
|
23
|
+
* 相同 dbPath 复用同一连接,引用计数归零时自动关闭。
|
|
20
24
|
* 返回的 backend 实现了 [Symbol.dispose](),可使用 `using` 语法
|
|
21
25
|
* 或在不再使用时手动调用 `backend[Symbol.dispose]()`。
|
|
22
26
|
*
|
|
@@ -37,21 +41,20 @@ import { Database } from "bun:sqlite";
|
|
|
37
41
|
* ```
|
|
38
42
|
*/
|
|
39
43
|
function createSqliteRepositoryBackend(dbPath, options = {}) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
db.run("CREATE TABLE IF NOT EXISTS
|
|
43
|
-
db.run("CREATE TABLE IF NOT EXISTS
|
|
44
|
-
db.run("
|
|
45
|
-
db.run("INSERT OR IGNORE INTO refs (name, target) VALUES (?, ?)", [HEAD_REF, `ref: ${HEADS_PREFIX}main`]);
|
|
44
|
+
const conn = acquireConnection(dbPath, options.walMode !== false);
|
|
45
|
+
conn.db.run("CREATE TABLE IF NOT EXISTS objects (hash TEXT PRIMARY KEY, type TEXT NOT NULL, content BLOB NOT NULL)");
|
|
46
|
+
conn.db.run("CREATE TABLE IF NOT EXISTS refs (name TEXT PRIMARY KEY, target TEXT NOT NULL)");
|
|
47
|
+
conn.db.run("CREATE TABLE IF NOT EXISTS shallow (hash TEXT PRIMARY KEY)");
|
|
48
|
+
conn.db.run("INSERT OR IGNORE INTO refs (name, target) VALUES (?, ?)", [HEAD_REF, `ref: ${HEADS_PREFIX}main`]);
|
|
46
49
|
return {
|
|
47
50
|
gitDir: dbPath,
|
|
48
|
-
objects: createSqliteObjectStore(
|
|
49
|
-
refs: createSqliteRefStore(
|
|
50
|
-
shallow: createSqliteShallowStore(
|
|
51
|
+
objects: createSqliteObjectStore(conn),
|
|
52
|
+
refs: createSqliteRefStore(conn),
|
|
53
|
+
shallow: createSqliteShallowStore(conn),
|
|
51
54
|
packs: null,
|
|
52
|
-
/** 释放 SQLite
|
|
55
|
+
/** 释放 SQLite 数据库连接(引用计数减一) */
|
|
53
56
|
[Symbol.dispose]() {
|
|
54
|
-
|
|
57
|
+
conn.release();
|
|
55
58
|
}
|
|
56
59
|
};
|
|
57
60
|
}
|
package/dist/odb/sqlite.d.mts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { SqliteConnectionHandle } from "../backend/sqlite-pool.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/odb/sqlite.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* 创建基于 SQLite 的对象数据库
|
|
7
7
|
*
|
|
8
|
-
* @param
|
|
8
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
9
9
|
* @returns 符合 ObjectDatabase 接口的存储后端
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
|
-
* import {
|
|
14
|
-
*
|
|
15
|
-
* const store = createSqliteObjectStore(
|
|
13
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
14
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteObjectStore(conn);
|
|
16
16
|
*
|
|
17
17
|
* store.ingest(raw);
|
|
18
18
|
* const obj = store.read(hash);
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
declare function createSqliteObjectStore(
|
|
21
|
+
declare function createSqliteObjectStore(conn: SqliteConnectionHandle): ObjectDatabase;
|
|
22
22
|
//#endregion
|
|
23
23
|
export { createSqliteObjectStore };
|
package/dist/odb/sqlite.mjs
CHANGED
|
@@ -14,27 +14,27 @@ import { hashObject } from "../core/hash-digest.mjs";
|
|
|
14
14
|
/**
|
|
15
15
|
* 创建基于 SQLite 的对象数据库
|
|
16
16
|
*
|
|
17
|
-
* @param
|
|
17
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
18
18
|
* @returns 符合 ObjectDatabase 接口的存储后端
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```ts
|
|
22
|
-
* import {
|
|
23
|
-
*
|
|
24
|
-
* const store = createSqliteObjectStore(
|
|
22
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
23
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
24
|
+
* const store = createSqliteObjectStore(conn);
|
|
25
25
|
*
|
|
26
26
|
* store.ingest(raw);
|
|
27
27
|
* const obj = store.read(hash);
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
function createSqliteObjectStore(
|
|
31
|
-
const selectStmt =
|
|
32
|
-
const existsStmt =
|
|
33
|
-
const insertStmt =
|
|
34
|
-
const deleteStmt =
|
|
35
|
-
const listStmt =
|
|
30
|
+
function createSqliteObjectStore(conn) {
|
|
31
|
+
const selectStmt = conn.prepare("SELECT hash, type, content FROM objects WHERE hash = ?");
|
|
32
|
+
const existsStmt = conn.prepare("SELECT 1 FROM objects WHERE hash = ?");
|
|
33
|
+
const insertStmt = conn.prepare("INSERT OR IGNORE INTO objects (hash, type, content) VALUES (?, ?, ?)");
|
|
34
|
+
const deleteStmt = conn.prepare("DELETE FROM objects WHERE hash = ?");
|
|
35
|
+
const listStmt = conn.prepare("SELECT hash FROM objects ORDER BY hash");
|
|
36
36
|
/** 批量插入的事务包装 */
|
|
37
|
-
const ingestManyTx = db.transaction((objects) => {
|
|
37
|
+
const ingestManyTx = conn.db.transaction((objects) => {
|
|
38
38
|
for (const raw of objects) {
|
|
39
39
|
const expectedHash = hashObject(raw.type, raw.content);
|
|
40
40
|
if (expectedHash !== raw.hash) throw new Error(`RawGitObject hash mismatch: expected ${expectedHash}, got ${raw.hash}`);
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { ShallowStore } from "../../core/types/shallow.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { SqliteConnectionHandle } from "../../backend/sqlite-pool.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/refs/shallow/sqlite.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* 创建基于 SQLite 的 shallow 边界存储
|
|
7
7
|
*
|
|
8
|
-
* @param
|
|
8
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
9
9
|
* @returns 符合 ShallowStore 接口的存储后端
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
|
-
* import {
|
|
14
|
-
*
|
|
15
|
-
* const store = createSqliteShallowStore(
|
|
13
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
14
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteShallowStore(conn);
|
|
16
16
|
*
|
|
17
17
|
* store.write([hashA, hashB]);
|
|
18
18
|
* console.log(store.isShallow(hashA)); // true
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
declare function createSqliteShallowStore(
|
|
21
|
+
declare function createSqliteShallowStore(conn: SqliteConnectionHandle): ShallowStore;
|
|
22
22
|
//#endregion
|
|
23
23
|
export { createSqliteShallowStore };
|
|
@@ -13,32 +13,32 @@ import { sha1 } from "../../core/types.mjs";
|
|
|
13
13
|
/**
|
|
14
14
|
* 创建基于 SQLite 的 shallow 边界存储
|
|
15
15
|
*
|
|
16
|
-
* @param
|
|
16
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
17
17
|
* @returns 符合 ShallowStore 接口的存储后端
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
|
-
* import {
|
|
22
|
-
*
|
|
23
|
-
* const store = createSqliteShallowStore(
|
|
21
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
22
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
23
|
+
* const store = createSqliteShallowStore(conn);
|
|
24
24
|
*
|
|
25
25
|
* store.write([hashA, hashB]);
|
|
26
26
|
* console.log(store.isShallow(hashA)); // true
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
function createSqliteShallowStore(
|
|
30
|
-
const selectAllStmt =
|
|
31
|
-
const selectExistsStmt =
|
|
32
|
-
const deleteAllStmt =
|
|
33
|
-
const insertStmt =
|
|
34
|
-
const deleteOneStmt =
|
|
29
|
+
function createSqliteShallowStore(conn) {
|
|
30
|
+
const selectAllStmt = conn.prepare("SELECT hash FROM shallow ORDER BY hash");
|
|
31
|
+
const selectExistsStmt = conn.prepare("SELECT 1 FROM shallow WHERE hash = ?");
|
|
32
|
+
const deleteAllStmt = conn.prepare("DELETE FROM shallow");
|
|
33
|
+
const insertStmt = conn.prepare("INSERT OR IGNORE INTO shallow (hash) VALUES (?)");
|
|
34
|
+
const deleteOneStmt = conn.prepare("DELETE FROM shallow WHERE hash = ?");
|
|
35
35
|
/** 全量替换事务 */
|
|
36
|
-
const replaceAllTx = db.transaction((boundaries) => {
|
|
36
|
+
const replaceAllTx = conn.db.transaction((boundaries) => {
|
|
37
37
|
deleteAllStmt.run();
|
|
38
38
|
for (const hash of boundaries) insertStmt.run(hash);
|
|
39
39
|
});
|
|
40
40
|
/** 增量更新事务 */
|
|
41
|
-
const applyUpdateTx = db.transaction((update) => {
|
|
41
|
+
const applyUpdateTx = conn.db.transaction((update) => {
|
|
42
42
|
for (const hash of update.unshallow) deleteOneStmt.run(hash);
|
|
43
43
|
for (const hash of update.shallow) insertStmt.run(hash);
|
|
44
44
|
});
|
package/dist/refs/sqlite.d.mts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { RefStore } from "../core/types/refs.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { SqliteConnectionHandle } from "../backend/sqlite-pool.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/refs/sqlite.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* 创建基于 SQLite 的 RefStore
|
|
7
7
|
*
|
|
8
|
-
* @param
|
|
8
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
9
9
|
* @returns 符合 RefStore 接口的存储后端(含事务支持)
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
|
-
* import {
|
|
14
|
-
*
|
|
15
|
-
* const store = createSqliteRefStore(
|
|
13
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
14
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteRefStore(conn);
|
|
16
16
|
*
|
|
17
17
|
* store.write("refs/heads/main", "abc123");
|
|
18
18
|
* const content = store.read("refs/heads/main");
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
declare function createSqliteRefStore(
|
|
21
|
+
declare function createSqliteRefStore(conn: SqliteConnectionHandle): RefStore;
|
|
22
22
|
//#endregion
|
|
23
23
|
export { createSqliteRefStore };
|
package/dist/refs/sqlite.mjs
CHANGED
|
@@ -13,26 +13,27 @@ import { validateRefName, validateRefPrefix } from "./names.mjs";
|
|
|
13
13
|
/**
|
|
14
14
|
* 创建基于 SQLite 的 RefStore
|
|
15
15
|
*
|
|
16
|
-
* @param
|
|
16
|
+
* @param conn - SQLite 连接池句柄(含 statement 缓存)
|
|
17
17
|
* @returns 符合 RefStore 接口的存储后端(含事务支持)
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
|
-
* import {
|
|
22
|
-
*
|
|
23
|
-
* const store = createSqliteRefStore(
|
|
21
|
+
* import { acquireConnection } from "nano-git/backend/sqlite";
|
|
22
|
+
* using conn = acquireConnection("/tmp/repo.sqlite");
|
|
23
|
+
* const store = createSqliteRefStore(conn);
|
|
24
24
|
*
|
|
25
25
|
* store.write("refs/heads/main", "abc123");
|
|
26
26
|
* const content = store.read("refs/heads/main");
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
function createSqliteRefStore(
|
|
30
|
-
const selectStmt =
|
|
31
|
-
const selectExistsStmt =
|
|
32
|
-
const insertStmt =
|
|
33
|
-
const deleteStmt =
|
|
34
|
-
const listPrefixStmt =
|
|
35
|
-
const listAllStmt =
|
|
29
|
+
function createSqliteRefStore(conn) {
|
|
30
|
+
const selectStmt = conn.prepare("SELECT target FROM refs WHERE name = ?");
|
|
31
|
+
const selectExistsStmt = conn.prepare("SELECT 1 FROM refs WHERE name = ?");
|
|
32
|
+
const insertStmt = conn.prepare("INSERT OR REPLACE INTO refs (name, target) VALUES (?, ?)");
|
|
33
|
+
const deleteStmt = conn.prepare("DELETE FROM refs WHERE name = ?");
|
|
34
|
+
const listPrefixStmt = conn.prepare("SELECT name FROM refs WHERE name >= ? AND name < ? ORDER BY name");
|
|
35
|
+
const listAllStmt = conn.prepare("SELECT name FROM refs WHERE name LIKE 'refs/%' ORDER BY name");
|
|
36
|
+
const db = conn.db;
|
|
36
37
|
/**
|
|
37
38
|
* 开启一个新的事务
|
|
38
39
|
*
|
|
@@ -6,6 +6,18 @@ import { patchTree } from "../tree/tree-patch.mjs";
|
|
|
6
6
|
* 仓库对象操作组装
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
+
* 空 tree 的规范原始对象
|
|
10
|
+
*
|
|
11
|
+
* 空 tree 序列化后内容为空,header 为 "tree 0\0",
|
|
12
|
+
* 其 SHA-1 是 Git 通用已知常数。
|
|
13
|
+
* 预计算一份避免每次重复序列化和哈希。
|
|
14
|
+
*/
|
|
15
|
+
const EMPTY_TREE_RAW = {
|
|
16
|
+
hash: hashObject("tree", Buffer.alloc(0)),
|
|
17
|
+
type: "tree",
|
|
18
|
+
content: Buffer.alloc(0)
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
9
21
|
* 创建仓库对象相关操作
|
|
10
22
|
*
|
|
11
23
|
* @example
|
|
@@ -36,6 +48,10 @@ function createObjectRepositoryOperations(objects) {
|
|
|
36
48
|
return objects.list();
|
|
37
49
|
},
|
|
38
50
|
createTree(entries) {
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
objects.ingest(EMPTY_TREE_RAW);
|
|
53
|
+
return EMPTY_TREE_RAW.hash;
|
|
54
|
+
}
|
|
39
55
|
return writeObject(objects, {
|
|
40
56
|
type: "tree",
|
|
41
57
|
entries
|
|
@@ -14,7 +14,7 @@ interface NormalizedChangeRecord {
|
|
|
14
14
|
readonly previous: VirtualDiffObject | null;
|
|
15
15
|
/** 变更后对象 */
|
|
16
16
|
readonly current: VirtualDiffObject | null;
|
|
17
|
-
/**
|
|
17
|
+
/** move/copy 来源 */
|
|
18
18
|
readonly source: VirtualDiffSource | null;
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
@@ -76,7 +76,7 @@ function rebuildNormalizedChangeIndex(source, state, cache) {
|
|
|
76
76
|
previous: renameFrom.previous,
|
|
77
77
|
current: addRecord.current,
|
|
78
78
|
source: {
|
|
79
|
-
kind: "
|
|
79
|
+
kind: "move",
|
|
80
80
|
path: renameFrom.path
|
|
81
81
|
}
|
|
82
82
|
});
|
|
@@ -155,17 +155,17 @@ function refreshChangeRecordForPath(source, state, path, cache) {
|
|
|
155
155
|
state.setChangeRecord(nextRecord);
|
|
156
156
|
}
|
|
157
157
|
/**
|
|
158
|
-
* 将单一路径的变更记录折叠为
|
|
158
|
+
* 将单一路径的变更记录折叠为 move 目标路径。
|
|
159
159
|
*
|
|
160
|
-
* 仅适用于叶子节点
|
|
160
|
+
* 仅适用于叶子节点 move;
|
|
161
161
|
* 目录及无法判定来源的情况应由调用方回退到全量重建。
|
|
162
162
|
*/
|
|
163
163
|
function rewriteChangeRecordForRename(source, state, from, to, cache) {
|
|
164
164
|
const previousRecord = state.getChangeRecord(from);
|
|
165
165
|
const currentTarget = snapshotCurrentEntryAtPath(source, state, to, cache);
|
|
166
|
-
if (currentTarget === null) throw new Error(`Cannot rewrite
|
|
166
|
+
if (currentTarget === null) throw new Error(`Cannot rewrite move change record for missing path: ${to}`);
|
|
167
167
|
const nextRecord = computeRenameRecordForPath(source, state, from, to, previousRecord, currentTarget);
|
|
168
|
-
if (nextRecord === null) throw new Error(`Cannot rewrite
|
|
168
|
+
if (nextRecord === null) throw new Error(`Cannot rewrite move change record from '${from}' to '${to}'`);
|
|
169
169
|
state.deleteChangeRecord(from);
|
|
170
170
|
state.setChangeRecord(nextRecord);
|
|
171
171
|
}
|
|
@@ -279,7 +279,7 @@ function computeRenameRecordForPath(source, state, from, to, previousRecord, cur
|
|
|
279
279
|
if (derivedFromPrevious !== void 0) return derivedFromPrevious;
|
|
280
280
|
const baseEntry = baseSnapshotEntryAtPath(source, state.readBaseTree(), from);
|
|
281
281
|
if (baseEntry === null) return null;
|
|
282
|
-
return createNormalizedChangeRecord(to, baseEntry.object, currentTarget.object, createDiffSource("
|
|
282
|
+
return createNormalizedChangeRecord(to, baseEntry.object, currentTarget.object, createDiffSource("move", from));
|
|
283
283
|
}
|
|
284
284
|
function deriveCopySource(from, sourceRecord, source, state) {
|
|
285
285
|
const fromRecordSource = sourceRecord?.source;
|
|
@@ -288,7 +288,7 @@ function deriveCopySource(from, sourceRecord, source, state) {
|
|
|
288
288
|
return null;
|
|
289
289
|
}
|
|
290
290
|
function preserveLineageRecordForPath(path, previousRecord, currentEntry) {
|
|
291
|
-
if (previousRecord?.source?.kind === "
|
|
291
|
+
if (previousRecord?.source?.kind === "move" && previousRecord.previous !== null) {
|
|
292
292
|
if (currentEntry === null) return null;
|
|
293
293
|
return createNormalizedChangeRecord(path, previousRecord.previous, currentEntry.object, previousRecord.source);
|
|
294
294
|
}
|
|
@@ -302,7 +302,7 @@ function deriveRenameRecordFromPreviousRecord(from, to, previousRecord, currentT
|
|
|
302
302
|
if (previousRecord.current === null) return null;
|
|
303
303
|
if (previousRecord.source !== null) return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, previousRecord.source);
|
|
304
304
|
if (previousRecord.previous === null) return createNormalizedChangeRecord(to, null, currentTarget.object);
|
|
305
|
-
return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, createDiffSource("
|
|
305
|
+
return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, createDiffSource("move", from));
|
|
306
306
|
}
|
|
307
307
|
function snapshotCurrentEntryAtPath(source, state, path, cache) {
|
|
308
308
|
const resolved = resolveCurrentLeafAtPath(source, state, path);
|
package/dist/workdir/core.d.mts
CHANGED
|
@@ -50,11 +50,11 @@ interface VirtualDiffObject {
|
|
|
50
50
|
readonly hash: SHA1;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* move/copy 来源描述
|
|
54
54
|
*/
|
|
55
55
|
interface VirtualDiffSource {
|
|
56
56
|
/** 来源类型 */
|
|
57
|
-
readonly kind: "
|
|
57
|
+
readonly kind: "move" | "copy";
|
|
58
58
|
/** 来源路径 */
|
|
59
59
|
readonly path: string;
|
|
60
60
|
}
|
|
@@ -77,7 +77,7 @@ interface VirtualDiffChanges {
|
|
|
77
77
|
type VirtualDiffEntry = {
|
|
78
78
|
/** 新建路径 */readonly kind: "create"; /** 当前路径 */
|
|
79
79
|
readonly path: string; /** 当前对象 */
|
|
80
|
-
readonly current: VirtualDiffObject; /**
|
|
80
|
+
readonly current: VirtualDiffObject; /** move/copy 的来源 */
|
|
81
81
|
readonly source?: VirtualDiffSource;
|
|
82
82
|
} | {
|
|
83
83
|
/** 删除路径 */readonly kind: "remove"; /** 当前路径 */
|
|
@@ -88,7 +88,7 @@ type VirtualDiffEntry = {
|
|
|
88
88
|
readonly path: string; /** 更新前对象 */
|
|
89
89
|
readonly previous: VirtualDiffObject; /** 更新后对象 */
|
|
90
90
|
readonly current: VirtualDiffObject; /** 变化维度 */
|
|
91
|
-
readonly changes: VirtualDiffChanges; /**
|
|
91
|
+
readonly changes: VirtualDiffChanges; /** move/copy 的来源 */
|
|
92
92
|
readonly source?: VirtualDiffSource;
|
|
93
93
|
};
|
|
94
94
|
/**
|
|
@@ -158,17 +158,19 @@ interface VirtualWorkdir {
|
|
|
158
158
|
readonly force?: boolean;
|
|
159
159
|
}): void;
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* 移动路径(可跨目录树)
|
|
162
162
|
*
|
|
163
163
|
* 只做路径重绑定,不退化为 delete + write。
|
|
164
|
-
*
|
|
164
|
+
* 目标父目录不存在时会自动创建中间目录。
|
|
165
|
+
* 目录移动后,子项保持懒加载。
|
|
165
166
|
*/
|
|
166
|
-
|
|
167
|
+
move(from: string, to: string): void;
|
|
167
168
|
/**
|
|
168
169
|
* 复制路径
|
|
169
170
|
*
|
|
170
171
|
* 新建 workdir node,共享 origin,不共享 node 身份。
|
|
171
|
-
*
|
|
172
|
+
* 目录复制采用 CoW(写时复制):子树节点共享同一份 origin 引用,
|
|
173
|
+
* 任一副本下的子项被修改时才会真正分裂出独立副本。
|
|
172
174
|
*/
|
|
173
175
|
copy(from: string, to: string): void;
|
|
174
176
|
/**
|