nano-git 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/backend/sqlite.d.mts +44 -0
- package/dist/backend/sqlite.mjs +59 -0
- package/dist/index.d.mts +2 -1
- package/dist/odb/sqlite.d.mts +23 -0
- package/dist/odb/sqlite.mjs +83 -0
- package/dist/refs/shallow/sqlite.d.mts +23 -0
- package/dist/refs/shallow/sqlite.mjs +61 -0
- package/dist/refs/sqlite.d.mts +23 -0
- package/dist/refs/sqlite.mjs +135 -0
- package/dist/remote/http.d.mts +51 -0
- package/dist/remote/http.mjs +74 -0
- package/dist/remote/types.d.mts +20 -0
- package/dist/repository/import/import-session-types.d.mts +3 -16
- package/dist/repository/ops/object-operations.mjs +0 -5
- package/dist/repository/ops/object-types.d.mts +0 -21
- package/dist/repository/sqlite.d.mts +28 -0
- package/dist/repository/sqlite.mjs +45 -0
- package/dist/transport/upload-pack.d.mts +1 -1
- package/dist/transport/upload-pack.mjs +1 -1
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
- ✅ **SHA-1 哈希计算** — 与 Git 完全兼容的对象哈希
|
|
9
9
|
- ✅ **Git 对象模型** — 支持 blob、tree、commit、tag 四种对象类型
|
|
10
10
|
- ✅ **对象序列化/反序列化** — 完整的二进制格式支持
|
|
11
|
-
- ✅ **对象存储** —
|
|
11
|
+
- ✅ **对象存储** — 文件系统存储、内存存储和 SQLite 存储三种模式
|
|
12
12
|
- ✅ **Packfile 支持** — 读取、写入、索引生成、delta 编解码
|
|
13
13
|
- ✅ **引用管理** — refs 验证、解析、存储(文件系统 + 内存)
|
|
14
14
|
- ✅ **仓库 API** — 类似 Git plumbing 命令的高层接口(init、hash-object、cat-file、commit-tree、update-ref 等)
|
|
15
15
|
- ✅ **增量 Tree Patch** — 不经暂存区直接修改目录结构(`patchTree`、`readTree`、`walkTree`)
|
|
16
16
|
- ✅ **可达性遍历与 GC** — 基于 refs 的可达对象收集、repack、gc
|
|
17
17
|
- ✅ **Smart HTTP 传输** — 基于 Bun fetch 的 Git 协议客户端,支持 `fetch()`/`push()` 与完整的 Import Session 物化流程
|
|
18
|
+
- ✅ **远端查询 API** — 将 refs 快照、`object-info` 等纯远端能力独立出 `Repository`
|
|
18
19
|
- ✅ **类型安全** — 完整的 TypeScript 类型定义
|
|
19
20
|
- ✅ **Reference Transaction** — 批量 ref 更新的原子性保障,支持 Hooks 回调与自动回滚
|
|
20
21
|
|
|
@@ -55,6 +56,22 @@ await repo.fetch("https://github.com/user/repo.git");
|
|
|
55
56
|
console.log(repo.listBranches());
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
### 查询远端 refs / object-info
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { createHttpRemote } from "nano-git/remote/http";
|
|
63
|
+
|
|
64
|
+
const remote = createHttpRemote({
|
|
65
|
+
url: "https://github.com/user/repo.git",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const snapshot = await remote.readRefAdvertisement();
|
|
69
|
+
console.log(snapshot.defaultBranch);
|
|
70
|
+
|
|
71
|
+
const info = await remote.fetchObjectInfo(["95d09f2b10159347eece71399a7e2e907ea3df4f"]);
|
|
72
|
+
console.log(info.objects[0]?.size);
|
|
73
|
+
```
|
|
74
|
+
|
|
58
75
|
### 内存仓库完整工作流
|
|
59
76
|
|
|
60
77
|
```typescript
|
|
@@ -138,6 +155,43 @@ repo.updateRef("refs/heads/main", commitHash);
|
|
|
138
155
|
|
|
139
156
|
`openRepository()` 默认会同时读取 `.git/objects/` 下的 loose objects 和 `.git/objects/pack/` 下的 packed objects,因此可以直接打开经过 `git gc` 或 `git repack` 的真实仓库(包括裸仓库)。搭配 `initRepository()` 的第二个参数可初始化为裸仓库布局。
|
|
140
157
|
|
|
158
|
+
### 使用 SQLite 仓库(Bun 运行时)
|
|
159
|
+
|
|
160
|
+
适用于需要持久化但又不想管理松散文件的场景——
|
|
161
|
+
所有对象和引用存在一个 `.sqlite` 文件中。
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { createSqliteRepository } from "nano-git/repository/sqlite";
|
|
165
|
+
|
|
166
|
+
// 创建或打开 SQLite 持久化仓库(支持 using 自动释放)
|
|
167
|
+
using repo = createSqliteRepository("/tmp/cache.sqlite");
|
|
168
|
+
|
|
169
|
+
// 与普通 repo 使用方式完全一致
|
|
170
|
+
const hash = repo.writeBlob(Buffer.from("persistent data"));
|
|
171
|
+
const treeHash = repo.createTree([{ mode: "100644", name: "data.txt", hash }]);
|
|
172
|
+
const commitHash = repo.createCommit(treeHash, [], "SQLite commit", {
|
|
173
|
+
name: "You",
|
|
174
|
+
email: "you@example.com",
|
|
175
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
176
|
+
timezone: "+0800",
|
|
177
|
+
});
|
|
178
|
+
console.log(`Committed: ${commitHash}`);
|
|
179
|
+
|
|
180
|
+
// 作用域结束时自动关闭数据库连接
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
也可拆分使用底层后端:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { createSqliteRepositoryBackend } from "nano-git/backend/sqlite";
|
|
187
|
+
import { createRepository } from "nano-git/repository/core";
|
|
188
|
+
|
|
189
|
+
const backend = createSqliteRepositoryBackend("/tmp/repo.sqlite");
|
|
190
|
+
const repo = createRepository(backend);
|
|
191
|
+
// 用完记得释放
|
|
192
|
+
backend[Symbol.dispose]();
|
|
193
|
+
```
|
|
194
|
+
|
|
141
195
|
### 生成 Packfile
|
|
142
196
|
|
|
143
197
|
```typescript
|
|
@@ -305,6 +359,8 @@ bun run examples/demo.ts
|
|
|
305
359
|
|
|
306
360
|
本库默认入口 `"nano-git"` 直接提供高频的纯计算能力:类型、错误、对象编解码、refs 工具和 SHA-1 工具。
|
|
307
361
|
带 `node:fs` / `node:zlib` 的运行时能力通过子路径显式导入,例如 `nano-git/repository/file`、`nano-git/pack`、`nano-git/transport/http`。
|
|
362
|
+
纯远端查询能力通过 `nano-git/remote/http` 导入。
|
|
363
|
+
基于 `bun:sqlite` 的存储后端通过 `nano-git/odb/sqlite`、`nano-git/refs/sqlite`、`nano-git/backend/sqlite`、`nano-git/repository/sqlite` 等子路径导入。
|
|
308
364
|
tree-shaking 主要依赖模块本身的无副作用结构,而不是把所有 API 都拆成叶子级子路径。完整入口表见 `package.json` 的 `exports` 与 `src/index.ts` 的 JSDoc。
|
|
309
365
|
|
|
310
366
|
## Git 对象模型
|
|
@@ -381,6 +437,7 @@ bun test
|
|
|
381
437
|
- [x] 可达性遍历与 GC(repack、gc)
|
|
382
438
|
- [x] **Smart HTTP 传输** — pkt-line 编解码、ref 广告解析、side-band 解复用、Fetch / Push 协议、Import Session 集成
|
|
383
439
|
- [x] **Reference Transaction** — 批量 ref 更新原子性、lock-then-rename 文件事务、生命周期 Hooks
|
|
440
|
+
- [x] **SQLite 存储后端** — 基于 `bun:sqlite` 的单文件持久化,支持对象/refs/shallow 存储、ACID 事务、`Symbol.dispose` 生命周期管理
|
|
384
441
|
|
|
385
442
|
- [x] **Smart HTTP 服务端(upload-pack)** — 类 git-http-backend、框架无关的 HTTP handler,支持 ls-refs 和 fetch 命令,协议实现与编排器解耦
|
|
386
443
|
- [x] **Smart HTTP 服务端(v1 receive-pack)** — 服务端 push 支持,基于 v1 协议,含 ref 广告、packfile 解包、ref 校验与 report-status
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RepositoryBackend } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/backend/sqlite.d.ts
|
|
4
|
+
/** 创建 SQLite 仓库后端的可选参数 */
|
|
5
|
+
interface CreateSqliteRepositoryBackendOptions {
|
|
6
|
+
/** 开启 WAL 模式,默认 true */
|
|
7
|
+
readonly walMode?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* SQLite 仓库后端(含资源释放能力)
|
|
11
|
+
*
|
|
12
|
+
* 在 RepositoryBackend 的基础上增加了 [Symbol.dispose](),
|
|
13
|
+
* 支持 `using backend = createSqliteRepositoryBackend(...)` 语法。
|
|
14
|
+
*/
|
|
15
|
+
interface SqliteRepositoryBackend extends RepositoryBackend {
|
|
16
|
+
/** 释放 SQLite 数据库连接 */
|
|
17
|
+
[Symbol.dispose](): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 创建基于 SQLite 文件的完整仓库后端
|
|
21
|
+
*
|
|
22
|
+
* 内部创建 Database 实例并组合三个子 store。
|
|
23
|
+
* 返回的 backend 实现了 [Symbol.dispose](),可使用 `using` 语法
|
|
24
|
+
* 或在不再使用时手动调用 `backend[Symbol.dispose]()`。
|
|
25
|
+
*
|
|
26
|
+
* @param dbPath - SQLite 数据库文件路径
|
|
27
|
+
* @returns 实现了 SqliteRepositoryBackend 的仓库后端(可用 `using` 管理)
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // 使用 using 自动释放(推荐)
|
|
32
|
+
* using backend = createSqliteRepositoryBackend("/tmp/repo.sqlite");
|
|
33
|
+
* const repo = createRepository(backend);
|
|
34
|
+
*
|
|
35
|
+
* // 或手动管理
|
|
36
|
+
* const backend = createSqliteRepositoryBackend("/tmp/repo.sqlite");
|
|
37
|
+
* const repo = createRepository(backend);
|
|
38
|
+
* // ... 使用完毕 ...
|
|
39
|
+
* backend[Symbol.dispose]();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createSqliteRepositoryBackend(dbPath: string, options?: CreateSqliteRepositoryBackendOptions): SqliteRepositoryBackend;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { CreateSqliteRepositoryBackendOptions, SqliteRepositoryBackend, createSqliteRepositoryBackend };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { HEADS_PREFIX, HEAD_REF } from "../core/types/refs.mjs";
|
|
2
|
+
import { createSqliteObjectStore } from "../odb/sqlite.mjs";
|
|
3
|
+
import { createSqliteRefStore } from "../refs/sqlite.mjs";
|
|
4
|
+
import { createSqliteShallowStore } from "../refs/shallow/sqlite.mjs";
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
//#region src/backend/sqlite.ts
|
|
7
|
+
/**
|
|
8
|
+
* 基于 SQLite 的仓库后端
|
|
9
|
+
*
|
|
10
|
+
* 将 SQLite 中的 objects、refs、shallow 存储
|
|
11
|
+
* 组合为统一的 RepositoryBackend。
|
|
12
|
+
*
|
|
13
|
+
* 支持 [Symbol.dispose]() 释放数据库连接,
|
|
14
|
+
* 可使用 `using` 语法管理生命周期。
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 创建基于 SQLite 文件的完整仓库后端
|
|
18
|
+
*
|
|
19
|
+
* 内部创建 Database 实例并组合三个子 store。
|
|
20
|
+
* 返回的 backend 实现了 [Symbol.dispose](),可使用 `using` 语法
|
|
21
|
+
* 或在不再使用时手动调用 `backend[Symbol.dispose]()`。
|
|
22
|
+
*
|
|
23
|
+
* @param dbPath - SQLite 数据库文件路径
|
|
24
|
+
* @returns 实现了 SqliteRepositoryBackend 的仓库后端(可用 `using` 管理)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // 使用 using 自动释放(推荐)
|
|
29
|
+
* using backend = createSqliteRepositoryBackend("/tmp/repo.sqlite");
|
|
30
|
+
* const repo = createRepository(backend);
|
|
31
|
+
*
|
|
32
|
+
* // 或手动管理
|
|
33
|
+
* const backend = createSqliteRepositoryBackend("/tmp/repo.sqlite");
|
|
34
|
+
* const repo = createRepository(backend);
|
|
35
|
+
* // ... 使用完毕 ...
|
|
36
|
+
* backend[Symbol.dispose]();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function createSqliteRepositoryBackend(dbPath, options = {}) {
|
|
40
|
+
const db = new Database(dbPath);
|
|
41
|
+
if (options.walMode !== false) db.run("PRAGMA journal_mode = WAL");
|
|
42
|
+
db.run("CREATE TABLE IF NOT EXISTS objects (hash TEXT PRIMARY KEY, type TEXT NOT NULL, content BLOB NOT NULL)");
|
|
43
|
+
db.run("CREATE TABLE IF NOT EXISTS refs (name TEXT PRIMARY KEY, target TEXT NOT NULL)");
|
|
44
|
+
db.run("CREATE TABLE IF NOT EXISTS shallow (hash TEXT PRIMARY KEY)");
|
|
45
|
+
db.run("INSERT OR IGNORE INTO refs (name, target) VALUES (?, ?)", [HEAD_REF, `ref: ${HEADS_PREFIX}main`]);
|
|
46
|
+
return {
|
|
47
|
+
gitDir: dbPath,
|
|
48
|
+
objects: createSqliteObjectStore(db),
|
|
49
|
+
refs: createSqliteRefStore(db),
|
|
50
|
+
shallow: createSqliteShallowStore(db),
|
|
51
|
+
packs: null,
|
|
52
|
+
/** 释放 SQLite 数据库连接 */
|
|
53
|
+
[Symbol.dispose]() {
|
|
54
|
+
db.close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { createSqliteRepositoryBackend };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GitAuthor, GitBlob, GitCommit, GitCommitExtraHeader, GitObject, GitTag, GitTree, ObjectType, RawGitObject, SHA1, TreeEntry, assertObjectType, sha1 } from "./core/types.mjs";
|
|
2
2
|
import { PackRepackOptions, RepositoryBackend, RepositoryGCOptions, RepositoryPackSupport, RepositoryRepackOptions } from "./backend/types.mjs";
|
|
3
3
|
import { CircularReferenceError, DeltaError, GitError, InvalidObjectError, InvalidPackError, InvalidSHA1Error, ObjectNotFoundError, PackError, PackIndexError, PreconditionCheckError, RefNotFoundError, RepositoryError, TransactionError } from "./core/errors.mjs";
|
|
4
|
+
import { RemoteSource } from "./remote/types.mjs";
|
|
4
5
|
import { FileRepository, Repository } from "./repository/types.mjs";
|
|
5
6
|
import { hashData, hashObject } from "./core/hash-digest.mjs";
|
|
6
7
|
import { isValidSHA1 } from "./core/hash-path.mjs";
|
|
@@ -13,4 +14,4 @@ import { deserialize, deserializeContent, serialize, serializeContent } from "./
|
|
|
13
14
|
import { decodeObject, encodeObject, readObject, tryReadObject, writeObject } from "./objects/raw.mjs";
|
|
14
15
|
import { branchNameToRef, normalizeShortRefName, tagNameToRef, validateRefName, validateRefPrefix } from "./refs/names.mjs";
|
|
15
16
|
import { resolveRefHash, resolveSymbolicRef, resolveTargetHash } from "./refs/resolve.mjs";
|
|
16
|
-
export { CircularReferenceError, DeltaError, type FileRepository, type GitAuthor, type GitBlob, type GitCommit, type GitCommitExtraHeader, GitError, type GitObject, type GitTag, type GitTree, InvalidObjectError, InvalidPackError, InvalidSHA1Error, ObjectNotFoundError, type ObjectType, PackError, PackIndexError, type PackRepackOptions, PreconditionCheckError, type RawGitObject, RefNotFoundError, type Repository, type RepositoryBackend, RepositoryError, type RepositoryGCOptions, type RepositoryPackSupport, type RepositoryRepackOptions, type SHA1, TransactionError, type TreeEntry, assertObjectType, branchNameToRef, decodeObject, deserialize, deserializeBlob, deserializeCommit, deserializeContent, deserializeTag, deserializeTree, encodeObject, formatAuthor, hashData, hashObject, isValidSHA1, normalizeShortRefName, parseAuthor, readObject, resolveRefHash, resolveSymbolicRef, resolveTargetHash, serialize, serializeBlob, serializeCommit, serializeContent, serializeTag, serializeTree, sha1, tagNameToRef, tryReadObject, validateRefName, validateRefPrefix, writeObject };
|
|
17
|
+
export { CircularReferenceError, DeltaError, type FileRepository, type GitAuthor, type GitBlob, type GitCommit, type GitCommitExtraHeader, GitError, type GitObject, type GitTag, type GitTree, InvalidObjectError, InvalidPackError, InvalidSHA1Error, ObjectNotFoundError, type ObjectType, PackError, PackIndexError, type PackRepackOptions, PreconditionCheckError, type RawGitObject, RefNotFoundError, type RemoteSource, type Repository, type RepositoryBackend, RepositoryError, type RepositoryGCOptions, type RepositoryPackSupport, type RepositoryRepackOptions, type SHA1, TransactionError, type TreeEntry, assertObjectType, branchNameToRef, decodeObject, deserialize, deserializeBlob, deserializeCommit, deserializeContent, deserializeTag, deserializeTree, encodeObject, formatAuthor, hashData, hashObject, isValidSHA1, normalizeShortRefName, parseAuthor, readObject, resolveRefHash, resolveSymbolicRef, resolveTargetHash, serialize, serializeBlob, serializeCommit, serializeContent, serializeTag, serializeTree, sha1, tagNameToRef, tryReadObject, validateRefName, validateRefPrefix, writeObject };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ObjectDatabase } from "../core/types/odb.mjs";
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
//#region src/odb/sqlite.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 创建基于 SQLite 的对象数据库
|
|
7
|
+
*
|
|
8
|
+
* @param db - 已打开的 bun:sqlite Database 实例
|
|
9
|
+
* @returns 符合 ObjectDatabase 接口的存储后端
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Database } from "bun:sqlite";
|
|
14
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteObjectStore(db);
|
|
16
|
+
*
|
|
17
|
+
* store.ingest(raw);
|
|
18
|
+
* const obj = store.read(hash);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function createSqliteObjectStore(db: Database): ObjectDatabase;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { createSqliteObjectStore };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ObjectNotFoundError } from "../core/errors.mjs";
|
|
2
|
+
import { sha1 } from "../core/types.mjs";
|
|
3
|
+
import { hashObject } from "../core/hash-digest.mjs";
|
|
4
|
+
//#region src/odb/sqlite.ts
|
|
5
|
+
/**
|
|
6
|
+
* 基于 SQLite 的对象数据库(raw-first)
|
|
7
|
+
*
|
|
8
|
+
* 所有对象存储在 SQLite 数据库的 objects 表中。
|
|
9
|
+
* 使用 INSERT OR IGNORE 实现幂等写入,使用 db.transaction() 实现批量原子写入。
|
|
10
|
+
*
|
|
11
|
+
* 表创建由上层 createSqliteRepositoryBackend 负责,
|
|
12
|
+
* 本模块只操作已存在的表,不负责 DDL。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 创建基于 SQLite 的对象数据库
|
|
16
|
+
*
|
|
17
|
+
* @param db - 已打开的 bun:sqlite Database 实例
|
|
18
|
+
* @returns 符合 ObjectDatabase 接口的存储后端
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { Database } from "bun:sqlite";
|
|
23
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
24
|
+
* const store = createSqliteObjectStore(db);
|
|
25
|
+
*
|
|
26
|
+
* store.ingest(raw);
|
|
27
|
+
* const obj = store.read(hash);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function createSqliteObjectStore(db) {
|
|
31
|
+
const selectStmt = db.query("SELECT hash, type, content FROM objects WHERE hash = ?");
|
|
32
|
+
const existsStmt = db.query("SELECT 1 FROM objects WHERE hash = ?");
|
|
33
|
+
const insertStmt = db.query("INSERT OR IGNORE INTO objects (hash, type, content) VALUES (?, ?, ?)");
|
|
34
|
+
const deleteStmt = db.query("DELETE FROM objects WHERE hash = ?");
|
|
35
|
+
const listStmt = db.query("SELECT hash FROM objects ORDER BY hash");
|
|
36
|
+
/** 批量插入的事务包装 */
|
|
37
|
+
const ingestManyTx = db.transaction((objects) => {
|
|
38
|
+
for (const raw of objects) {
|
|
39
|
+
const expectedHash = hashObject(raw.type, raw.content);
|
|
40
|
+
if (expectedHash !== raw.hash) throw new Error(`RawGitObject hash mismatch: expected ${expectedHash}, got ${raw.hash}`);
|
|
41
|
+
insertStmt.run(raw.hash, raw.type, raw.content);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
ingest(raw) {
|
|
46
|
+
const expectedHash = hashObject(raw.type, raw.content);
|
|
47
|
+
if (expectedHash !== raw.hash) throw new Error(`RawGitObject hash mismatch: expected ${expectedHash}, got ${raw.hash}`);
|
|
48
|
+
insertStmt.run(raw.hash, raw.type, raw.content);
|
|
49
|
+
},
|
|
50
|
+
ingestMany(objects) {
|
|
51
|
+
ingestManyTx(objects);
|
|
52
|
+
},
|
|
53
|
+
read(hash) {
|
|
54
|
+
const row = selectStmt.get(hash);
|
|
55
|
+
if (!row) throw new ObjectNotFoundError(hash);
|
|
56
|
+
return {
|
|
57
|
+
hash: sha1(row.hash),
|
|
58
|
+
type: row.type,
|
|
59
|
+
content: Buffer.from(row.content)
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
tryRead(hash) {
|
|
63
|
+
const row = selectStmt.get(hash);
|
|
64
|
+
if (!row) return;
|
|
65
|
+
return {
|
|
66
|
+
hash: sha1(row.hash),
|
|
67
|
+
type: row.type,
|
|
68
|
+
content: Buffer.from(row.content)
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
exists(hash) {
|
|
72
|
+
return existsStmt.get(hash) !== null;
|
|
73
|
+
},
|
|
74
|
+
list() {
|
|
75
|
+
return listStmt.all().map((row) => sha1(row.hash));
|
|
76
|
+
},
|
|
77
|
+
delete(hash) {
|
|
78
|
+
deleteStmt.run(hash);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { createSqliteObjectStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ShallowStore } from "../../core/types/shallow.mjs";
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
//#region src/refs/shallow/sqlite.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 创建基于 SQLite 的 shallow 边界存储
|
|
7
|
+
*
|
|
8
|
+
* @param db - 已打开的 bun:sqlite Database 实例
|
|
9
|
+
* @returns 符合 ShallowStore 接口的存储后端
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Database } from "bun:sqlite";
|
|
14
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteShallowStore(db);
|
|
16
|
+
*
|
|
17
|
+
* store.write([hashA, hashB]);
|
|
18
|
+
* console.log(store.isShallow(hashA)); // true
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function createSqliteShallowStore(db: Database): ShallowStore;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { createSqliteShallowStore };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { sha1 } from "../../core/types.mjs";
|
|
2
|
+
//#region src/refs/shallow/sqlite.ts
|
|
3
|
+
/**
|
|
4
|
+
* 基于 SQLite 的 Shallow 存储
|
|
5
|
+
*
|
|
6
|
+
* 所有 shallow 边界存储在 SQLite 数据库的 shallow 表中。
|
|
7
|
+
* write 使用 DELETE + INSERT 全量替换模式(与 memory/file 后端一致)。
|
|
8
|
+
* applyUpdate 使用 SQL 事务做增量更新。
|
|
9
|
+
*
|
|
10
|
+
* 表创建由上层 createSqliteRepositoryBackend 负责,
|
|
11
|
+
* 本模块只操作已存在的表,不负责 DDL。
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 创建基于 SQLite 的 shallow 边界存储
|
|
15
|
+
*
|
|
16
|
+
* @param db - 已打开的 bun:sqlite Database 实例
|
|
17
|
+
* @returns 符合 ShallowStore 接口的存储后端
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Database } from "bun:sqlite";
|
|
22
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
23
|
+
* const store = createSqliteShallowStore(db);
|
|
24
|
+
*
|
|
25
|
+
* store.write([hashA, hashB]);
|
|
26
|
+
* console.log(store.isShallow(hashA)); // true
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function createSqliteShallowStore(db) {
|
|
30
|
+
const selectAllStmt = db.query("SELECT hash FROM shallow ORDER BY hash");
|
|
31
|
+
const selectExistsStmt = db.query("SELECT 1 FROM shallow WHERE hash = ?");
|
|
32
|
+
const deleteAllStmt = db.query("DELETE FROM shallow");
|
|
33
|
+
const insertStmt = db.query("INSERT OR IGNORE INTO shallow (hash) VALUES (?)");
|
|
34
|
+
const deleteOneStmt = db.query("DELETE FROM shallow WHERE hash = ?");
|
|
35
|
+
/** 全量替换事务 */
|
|
36
|
+
const replaceAllTx = db.transaction((boundaries) => {
|
|
37
|
+
deleteAllStmt.run();
|
|
38
|
+
for (const hash of boundaries) insertStmt.run(hash);
|
|
39
|
+
});
|
|
40
|
+
/** 增量更新事务 */
|
|
41
|
+
const applyUpdateTx = db.transaction((update) => {
|
|
42
|
+
for (const hash of update.unshallow) deleteOneStmt.run(hash);
|
|
43
|
+
for (const hash of update.shallow) insertStmt.run(hash);
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
read() {
|
|
47
|
+
return selectAllStmt.all().map((row) => sha1(row.hash));
|
|
48
|
+
},
|
|
49
|
+
write(boundaries) {
|
|
50
|
+
replaceAllTx(boundaries);
|
|
51
|
+
},
|
|
52
|
+
applyUpdate(update) {
|
|
53
|
+
applyUpdateTx(update);
|
|
54
|
+
},
|
|
55
|
+
isShallow(hash) {
|
|
56
|
+
return selectExistsStmt.get(hash) !== null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createSqliteShallowStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RefStore } from "../core/types/refs.mjs";
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
//#region src/refs/sqlite.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 创建基于 SQLite 的 RefStore
|
|
7
|
+
*
|
|
8
|
+
* @param db - 已打开的 bun:sqlite Database 实例(生命周期由调用方管理)
|
|
9
|
+
* @returns 符合 RefStore 接口的存储后端(含事务支持)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Database } from "bun:sqlite";
|
|
14
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
15
|
+
* const store = createSqliteRefStore(db);
|
|
16
|
+
*
|
|
17
|
+
* store.write("refs/heads/main", "abc123");
|
|
18
|
+
* const content = store.read("refs/heads/main");
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function createSqliteRefStore(db: Database): RefStore;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { createSqliteRefStore };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { RefNotFoundError, TransactionError } from "../core/errors.mjs";
|
|
2
|
+
import { validateRefName, validateRefPrefix } from "./names.mjs";
|
|
3
|
+
//#region src/refs/sqlite.ts
|
|
4
|
+
/**
|
|
5
|
+
* 基于 SQLite 的 Refs 存储
|
|
6
|
+
*
|
|
7
|
+
* 所有引用存储在 SQLite 数据库的 refs 表中。
|
|
8
|
+
* 使用 INSERT OR REPLACE 实现幂等写入,使用 db.transaction() 实现事务原子性。
|
|
9
|
+
*
|
|
10
|
+
* 表创建由上层 createSqliteRepositoryBackend 负责,
|
|
11
|
+
* 本模块只操作已存在的表,不负责 DDL。
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 创建基于 SQLite 的 RefStore
|
|
15
|
+
*
|
|
16
|
+
* @param db - 已打开的 bun:sqlite Database 实例(生命周期由调用方管理)
|
|
17
|
+
* @returns 符合 RefStore 接口的存储后端(含事务支持)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Database } from "bun:sqlite";
|
|
22
|
+
* const db = new Database("/tmp/repo.sqlite");
|
|
23
|
+
* const store = createSqliteRefStore(db);
|
|
24
|
+
*
|
|
25
|
+
* store.write("refs/heads/main", "abc123");
|
|
26
|
+
* const content = store.read("refs/heads/main");
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function createSqliteRefStore(db) {
|
|
30
|
+
const selectStmt = db.query("SELECT target FROM refs WHERE name = ?");
|
|
31
|
+
const selectExistsStmt = db.query("SELECT 1 FROM refs WHERE name = ?");
|
|
32
|
+
const insertStmt = db.query("INSERT OR REPLACE INTO refs (name, target) VALUES (?, ?)");
|
|
33
|
+
const deleteStmt = db.query("DELETE FROM refs WHERE name = ?");
|
|
34
|
+
const listPrefixStmt = db.query("SELECT name FROM refs WHERE name >= ? AND name < ? ORDER BY name");
|
|
35
|
+
const listAllStmt = db.query("SELECT name FROM refs WHERE name LIKE 'refs/%' ORDER BY name");
|
|
36
|
+
/**
|
|
37
|
+
* 开启一个新的事务
|
|
38
|
+
*
|
|
39
|
+
* 所有变更暂存于 JS Map,commit() 时通过 SQLite 事务原子性写入。
|
|
40
|
+
*/
|
|
41
|
+
function beginTransaction(hooks) {
|
|
42
|
+
const pending = /* @__PURE__ */ new Map();
|
|
43
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const row of listAllStmt.all()) {
|
|
45
|
+
const val = selectStmt.get(row.name);
|
|
46
|
+
if (val !== null) snapshot.set(row.name, val.target);
|
|
47
|
+
}
|
|
48
|
+
let committed = false;
|
|
49
|
+
return {
|
|
50
|
+
get pendingCount() {
|
|
51
|
+
return pending.size;
|
|
52
|
+
},
|
|
53
|
+
write(ref, content) {
|
|
54
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
55
|
+
validateRefName(ref);
|
|
56
|
+
pending.set(ref, content.trimEnd());
|
|
57
|
+
},
|
|
58
|
+
delete(ref) {
|
|
59
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
60
|
+
validateRefName(ref);
|
|
61
|
+
const inDb = selectExistsStmt.get(ref) !== null;
|
|
62
|
+
const inPending = pending.has(ref);
|
|
63
|
+
if (!inDb && !inPending) throw new RefNotFoundError(ref);
|
|
64
|
+
pending.set(ref, null);
|
|
65
|
+
},
|
|
66
|
+
commit() {
|
|
67
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
68
|
+
committed = true;
|
|
69
|
+
const txSnapshot = freezePending(pending);
|
|
70
|
+
try {
|
|
71
|
+
for (const hook of hooks ?? []) hook.onPrepare?.(txSnapshot);
|
|
72
|
+
db.transaction(() => {
|
|
73
|
+
for (const [ref, content] of pending) if (content === null) {
|
|
74
|
+
if (!snapshot.has(ref) && !selectExistsStmt.get(ref)) throw new RefNotFoundError(ref);
|
|
75
|
+
deleteStmt.run(ref);
|
|
76
|
+
} else insertStmt.run(ref, content);
|
|
77
|
+
})();
|
|
78
|
+
for (const hook of hooks ?? []) hook.onCommitted?.(txSnapshot);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
rollback() {
|
|
85
|
+
if (committed) return;
|
|
86
|
+
committed = true;
|
|
87
|
+
const txSnapshot = freezePending(pending);
|
|
88
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
read(ref) {
|
|
94
|
+
validateRefName(ref);
|
|
95
|
+
return selectStmt.get(ref)?.target ?? null;
|
|
96
|
+
},
|
|
97
|
+
write(ref, content) {
|
|
98
|
+
validateRefName(ref);
|
|
99
|
+
insertStmt.run(ref, content.trimEnd());
|
|
100
|
+
},
|
|
101
|
+
delete(ref) {
|
|
102
|
+
validateRefName(ref);
|
|
103
|
+
if (!selectExistsStmt.get(ref)) throw new RefNotFoundError(ref);
|
|
104
|
+
deleteStmt.run(ref);
|
|
105
|
+
},
|
|
106
|
+
list(prefix) {
|
|
107
|
+
validateRefPrefix(prefix);
|
|
108
|
+
const end = prefix + "";
|
|
109
|
+
return listPrefixStmt.all(prefix, end).map((row) => row.name);
|
|
110
|
+
},
|
|
111
|
+
listAll() {
|
|
112
|
+
return listAllStmt.all().map((row) => row.name);
|
|
113
|
+
},
|
|
114
|
+
beginTransaction
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 将 pending Map 冻结为只读快照
|
|
119
|
+
*/
|
|
120
|
+
function freezePending(pending) {
|
|
121
|
+
const writes = [];
|
|
122
|
+
const deletes = [];
|
|
123
|
+
for (const [ref, content] of pending) if (content === null) deletes.push({ ref });
|
|
124
|
+
else writes.push({
|
|
125
|
+
ref,
|
|
126
|
+
content
|
|
127
|
+
});
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
pendingCount: pending.size,
|
|
130
|
+
writes: Object.freeze(writes),
|
|
131
|
+
deletes: Object.freeze(deletes)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { createSqliteRefStore };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { RemoteSource } from "./types.mjs";
|
|
2
|
+
import { RefAdvertisement } from "../transport/protocol/types.mjs";
|
|
3
|
+
import { LsRefsEntry, V2CapabilityAdvertisement } from "../transport/client/upload-pack/types.mjs";
|
|
4
|
+
import { LsRefsOptions } from "../transport/client/upload-pack/ls-refs.mjs";
|
|
5
|
+
import { ObjectInfoQueryResult } from "../transport/client/upload-pack/object-info.mjs";
|
|
6
|
+
|
|
7
|
+
//#region src/remote/http.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* 远端 HTTP 查询接口
|
|
10
|
+
*/
|
|
11
|
+
interface HttpRemote {
|
|
12
|
+
/** 远端来源配置 */
|
|
13
|
+
readonly source: Readonly<RemoteSource>;
|
|
14
|
+
/** 读取 v2 能力广告 */
|
|
15
|
+
advertise(): Promise<V2CapabilityAdvertisement>;
|
|
16
|
+
/** 原样执行 ls-refs 查询 */
|
|
17
|
+
listRefs(options?: LsRefsOptions): Promise<LsRefsEntry[]>;
|
|
18
|
+
/** 读取适合高层 API 使用的 ref 快照 */
|
|
19
|
+
readRefAdvertisement(): Promise<RefAdvertisement>;
|
|
20
|
+
/** 查询对象元数据(协议 v2 object-info) */
|
|
21
|
+
fetchObjectInfo(oids: string[]): Promise<ObjectInfoQueryResult>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 创建基于 Smart HTTP 的远端查询对象
|
|
25
|
+
*
|
|
26
|
+
* 适用于只依赖远端 URL / 认证信息的查询,
|
|
27
|
+
* 例如 refs 快照和 object-info,不需要本地 repo 上下文。
|
|
28
|
+
*
|
|
29
|
+
* @param source - 远端来源
|
|
30
|
+
* @returns 远端查询对象
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { createHttpRemote } from "nano-git/remote/http";
|
|
35
|
+
*
|
|
36
|
+
* const remote = createHttpRemote({
|
|
37
|
+
* url: "https://github.com/user/repo.git",
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* const snapshot = await remote.readRefAdvertisement();
|
|
41
|
+
* const info = await remote.fetchObjectInfo([
|
|
42
|
+
* "95d09f2b10159347eece71399a7e2e907ea3df4f",
|
|
43
|
+
* ]);
|
|
44
|
+
*
|
|
45
|
+
* console.log(snapshot.defaultBranch);
|
|
46
|
+
* console.log(info.objects[0]?.size);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function createHttpRemote(source: RemoteSource): HttpRemote;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { HttpRemote, createHttpRemote };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createV2HttpTransport } from "../transport/client/upload-pack/http.mjs";
|
|
2
|
+
import { lsRefs, lsRefsToRefAdvertisement } from "../transport/client/upload-pack/ls-refs.mjs";
|
|
3
|
+
import { objectInfo } from "../transport/client/upload-pack/object-info.mjs";
|
|
4
|
+
//#region src/remote/http.ts
|
|
5
|
+
/**
|
|
6
|
+
* 基于 HTTP 的远端 Git 查询 API
|
|
7
|
+
*
|
|
8
|
+
* 将纯远端查询能力从 Repository 中拆出:
|
|
9
|
+
* - refs 快照
|
|
10
|
+
* - v2 object-info
|
|
11
|
+
* - 协议能力广告
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 创建基于 Smart HTTP 的远端查询对象
|
|
15
|
+
*
|
|
16
|
+
* 适用于只依赖远端 URL / 认证信息的查询,
|
|
17
|
+
* 例如 refs 快照和 object-info,不需要本地 repo 上下文。
|
|
18
|
+
*
|
|
19
|
+
* @param source - 远端来源
|
|
20
|
+
* @returns 远端查询对象
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { createHttpRemote } from "nano-git/remote/http";
|
|
25
|
+
*
|
|
26
|
+
* const remote = createHttpRemote({
|
|
27
|
+
* url: "https://github.com/user/repo.git",
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* const snapshot = await remote.readRefAdvertisement();
|
|
31
|
+
* const info = await remote.fetchObjectInfo([
|
|
32
|
+
* "95d09f2b10159347eece71399a7e2e907ea3df4f",
|
|
33
|
+
* ]);
|
|
34
|
+
*
|
|
35
|
+
* console.log(snapshot.defaultBranch);
|
|
36
|
+
* console.log(info.objects[0]?.size);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function createHttpRemote(source) {
|
|
40
|
+
const frozenSource = Object.freeze({
|
|
41
|
+
url: source.url,
|
|
42
|
+
token: source.token,
|
|
43
|
+
headers: source.headers ? Object.freeze({ ...source.headers }) : void 0
|
|
44
|
+
});
|
|
45
|
+
const transport = createV2HttpTransport(frozenSource.url, {
|
|
46
|
+
token: frozenSource.token,
|
|
47
|
+
headers: frozenSource.headers
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
source: frozenSource,
|
|
51
|
+
advertise() {
|
|
52
|
+
return transport.advertise();
|
|
53
|
+
},
|
|
54
|
+
listRefs(options) {
|
|
55
|
+
return lsRefs(transport, options);
|
|
56
|
+
},
|
|
57
|
+
async readRefAdvertisement() {
|
|
58
|
+
return lsRefsToRefAdvertisement(await lsRefs(transport, {
|
|
59
|
+
symrefs: true,
|
|
60
|
+
peel: true,
|
|
61
|
+
refPrefixes: [
|
|
62
|
+
"HEAD",
|
|
63
|
+
"refs/heads/",
|
|
64
|
+
"refs/tags/"
|
|
65
|
+
]
|
|
66
|
+
}));
|
|
67
|
+
},
|
|
68
|
+
fetchObjectInfo(oids) {
|
|
69
|
+
return objectInfo(transport, oids);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { createHttpRemote };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/remote/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* 远端来源类型定义
|
|
4
|
+
*
|
|
5
|
+
* 只描述"从哪里读",不描述"写到哪里"。
|
|
6
|
+
* 可被远端查询 API 与仓库导入 API 共同复用。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 远端 Git 数据来源
|
|
10
|
+
*/
|
|
11
|
+
interface RemoteSource {
|
|
12
|
+
/** 远端仓库 URL */
|
|
13
|
+
readonly url: string;
|
|
14
|
+
/** 认证 token(用于 bearer 或 basic auth) */
|
|
15
|
+
readonly token?: string;
|
|
16
|
+
/** 自定义请求头 */
|
|
17
|
+
readonly headers?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { RemoteSource };
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { SHA1 } from "../../core/types.mjs";
|
|
2
|
+
import { RemoteSource } from "../../remote/types.mjs";
|
|
2
3
|
import { RefAdvertisement, RemoteRef } from "../../transport/protocol/types.mjs";
|
|
3
4
|
|
|
4
5
|
//#region src/repository/import/import-session-types.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* 远端 Git 数据来源
|
|
7
|
-
*
|
|
8
|
-
* 只描述"从哪里读",不描述"写到哪里"。
|
|
9
|
-
* ImportSource 不包含命名空间映射规则。
|
|
10
|
-
*/
|
|
11
|
-
interface ImportSource {
|
|
12
|
-
/** 远端仓库 URL */
|
|
13
|
-
readonly url: string;
|
|
14
|
-
/** 认证 token(用于 bearer 或 basic auth) */
|
|
15
|
-
readonly token?: string;
|
|
16
|
-
/** 自定义请求头 */
|
|
17
|
-
readonly headers?: Record<string, string>;
|
|
18
|
-
}
|
|
19
6
|
/**
|
|
20
7
|
* 远端 ref 视图
|
|
21
8
|
*
|
|
@@ -244,7 +231,7 @@ interface ImportApplyResult {
|
|
|
244
231
|
* 想刷新远端状态时,必须重新创建 session。
|
|
245
232
|
*/
|
|
246
233
|
interface ImportSession {
|
|
247
|
-
readonly source:
|
|
234
|
+
readonly source: RemoteSource;
|
|
248
235
|
readonly advertisement: RefAdvertisement;
|
|
249
236
|
select(pattern: string): ImportView;
|
|
250
237
|
selectRefs(patterns: readonly string[]): ImportView;
|
|
@@ -274,7 +261,7 @@ interface RepoImportOperations {
|
|
|
274
261
|
* const branches = session.select("refs/heads/*");
|
|
275
262
|
* ```
|
|
276
263
|
*/
|
|
277
|
-
openImportSession(source:
|
|
264
|
+
openImportSession(source: RemoteSource): Promise<ImportSession>;
|
|
278
265
|
}
|
|
279
266
|
//#endregion
|
|
280
267
|
export { RepoImportOperations };
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { hashObject } from "../../core/hash-digest.mjs";
|
|
2
2
|
import { readObject, writeObject } from "../../objects/raw.mjs";
|
|
3
|
-
import { createV2HttpTransport } from "../../transport/client/upload-pack/http.mjs";
|
|
4
|
-
import { objectInfo } from "../../transport/client/upload-pack/object-info.mjs";
|
|
5
3
|
import { patchTree } from "../tree/tree-patch.mjs";
|
|
6
4
|
//#region src/repository/ops/object-operations.ts
|
|
7
5
|
/**
|
|
@@ -55,9 +53,6 @@ function createObjectRepositoryOperations(objects) {
|
|
|
55
53
|
},
|
|
56
54
|
patchTree(rootHash, ops) {
|
|
57
55
|
return patchTree(objects, rootHash, ops);
|
|
58
|
-
},
|
|
59
|
-
async fetchObjectInfo(url, oids, token) {
|
|
60
|
-
return objectInfo(createV2HttpTransport(url, { token }), oids);
|
|
61
56
|
}
|
|
62
57
|
};
|
|
63
58
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { GitAuthor, GitObject, SHA1, TreeEntry } from "../../core/types.mjs";
|
|
2
|
-
import { ObjectInfoQueryResult } from "../../transport/client/upload-pack/object-info.mjs";
|
|
3
2
|
import { TreePatchOp, TreePatchResult } from "../tree/tree-patch.mjs";
|
|
4
3
|
|
|
5
4
|
//#region src/repository/ops/object-types.d.ts
|
|
@@ -67,26 +66,6 @@ interface RepositoryObjectOperations {
|
|
|
67
66
|
* ```
|
|
68
67
|
*/
|
|
69
68
|
patchTree(rootHash: SHA1, ops: TreePatchOp[]): TreePatchResult;
|
|
70
|
-
/**
|
|
71
|
-
* 查询远端对象信息(协议 v2 object-info)
|
|
72
|
-
*
|
|
73
|
-
* 批量查询远端对象的元数据(如 size),无需下载对象内容。
|
|
74
|
-
* 仅在远端支持 Git Wire 协议 v2 时可用。
|
|
75
|
-
*
|
|
76
|
-
* @param url - 远端仓库 URL
|
|
77
|
-
* @param oids - 要查询的 OID 列表
|
|
78
|
-
* @param token - 可选认证 token
|
|
79
|
-
* @returns 对象信息列表(含 size 等元数据)
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* const result = await repo.fetchObjectInfo("https://github.com/user/repo", [
|
|
84
|
-
* "95d09f2b10159347eece71399a7e2e907ea3df4f",
|
|
85
|
-
* ]);
|
|
86
|
-
* console.log(result.objects[0]?.size); // 文件大小
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
fetchObjectInfo(url: string, oids: string[], token?: string): Promise<ObjectInfoQueryResult>;
|
|
90
69
|
}
|
|
91
70
|
/**
|
|
92
71
|
* 文件系统对象操作扩展
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CreateSqliteRepositoryBackendOptions } from "../backend/sqlite.mjs";
|
|
2
|
+
import { Repository } from "./types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/repository/sqlite.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 创建基于 SQLite 文件的持久化仓库
|
|
7
|
+
*
|
|
8
|
+
* 相比直接使用 createSqliteRepositoryBackend + createRepository,
|
|
9
|
+
* 此函数提供了更简洁的一步到位接口。
|
|
10
|
+
*
|
|
11
|
+
* @param dbPath - SQLite 数据库文件路径
|
|
12
|
+
* @param options - 可选参数(如 walMode)
|
|
13
|
+
* @returns 仓库实例(附带 [Symbol.dispose](),可用 `using` 管理)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // 使用 using 自动释放(推荐)
|
|
18
|
+
* using repo = createSqliteRepository("/tmp/repo.sqlite");
|
|
19
|
+
* repo.createBranch("main", repo.createTree([]));
|
|
20
|
+
* repo.writeBlob(Buffer.from("data"));
|
|
21
|
+
* // 作用域结束时自动关闭数据库连接
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function createSqliteRepository(dbPath: string, options?: CreateSqliteRepositoryBackendOptions): Repository & {
|
|
25
|
+
[Symbol.dispose](): void;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { createSqliteRepository };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createSqliteRepositoryBackend } from "../backend/sqlite.mjs";
|
|
2
|
+
import { createRepository } from "./create.mjs";
|
|
3
|
+
//#region src/repository/sqlite.ts
|
|
4
|
+
/**
|
|
5
|
+
* SQLite 仓库便捷创建函数
|
|
6
|
+
*
|
|
7
|
+
* 一键创建基于 SQLite 的持久化 Git 仓库。
|
|
8
|
+
* 内部组合 createSqliteRepositoryBackend + createRepository,
|
|
9
|
+
* 返回的 repo 自带 [Symbol.dispose](),可用 `using` 管理生命周期。
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { createSqliteRepository } from "nano-git/repository/sqlite";
|
|
14
|
+
*
|
|
15
|
+
* using repo = createSqliteRepository("/tmp/cache.sqlite");
|
|
16
|
+
* const hash = repo.writeBlob(Buffer.from("hello world"));
|
|
17
|
+
* // 作用域结束时自动 db.close()
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* 创建基于 SQLite 文件的持久化仓库
|
|
22
|
+
*
|
|
23
|
+
* 相比直接使用 createSqliteRepositoryBackend + createRepository,
|
|
24
|
+
* 此函数提供了更简洁的一步到位接口。
|
|
25
|
+
*
|
|
26
|
+
* @param dbPath - SQLite 数据库文件路径
|
|
27
|
+
* @param options - 可选参数(如 walMode)
|
|
28
|
+
* @returns 仓库实例(附带 [Symbol.dispose](),可用 `using` 管理)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* // 使用 using 自动释放(推荐)
|
|
33
|
+
* using repo = createSqliteRepository("/tmp/repo.sqlite");
|
|
34
|
+
* repo.createBranch("main", repo.createTree([]));
|
|
35
|
+
* repo.writeBlob(Buffer.from("data"));
|
|
36
|
+
* // 作用域结束时自动关闭数据库连接
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function createSqliteRepository(dbPath, options) {
|
|
40
|
+
const backend = createSqliteRepositoryBackend(dbPath, options);
|
|
41
|
+
const repo = createRepository(backend);
|
|
42
|
+
return Object.assign(repo, { [Symbol.dispose]: () => backend[Symbol.dispose]() });
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { createSqliteRepository };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LsRefsEntry, ObjectInfoEntry, ObjectInfoResponse, V2CapabilityAdvertisement, V2CommandEntry, V2FetchRequest, V2FetchResponse, V2GitServiceTransport } from "./client/upload-pack/types.mjs";
|
|
2
|
+
import { LsRefsError, LsRefsOptions, lsRefs, lsRefsToRefAdvertisement, parseLsRefsResponse } from "./client/upload-pack/ls-refs.mjs";
|
|
2
3
|
import { ObjectInfoError, ObjectInfoQueryResult, ObjectInfoResult, objectInfo, parseObjectInfoResponse } from "./client/upload-pack/object-info.mjs";
|
|
3
4
|
import { V2CapabilityError, getCommandFeatures, hasCommand, parseV2CapabilityAdvertisement } from "./client/upload-pack/capability-advertisement.mjs";
|
|
4
5
|
import { V2SmartHttpError, createV2HttpTransport } from "./client/upload-pack/http.mjs";
|
|
5
|
-
import { LsRefsError, LsRefsOptions, lsRefs, lsRefsToRefAdvertisement, parseLsRefsResponse } from "./client/upload-pack/ls-refs.mjs";
|
|
6
6
|
import { V2FetchError, V2FetchParams, parseV2FetchResponse, v2Fetch, v2FetchObjects } from "./client/upload-pack/fetch.mjs";
|
|
7
7
|
export { type LsRefsEntry, LsRefsError, type LsRefsOptions, type ObjectInfoEntry, ObjectInfoError, type ObjectInfoQueryResult, type ObjectInfoResponse, type ObjectInfoResult, type V2CapabilityAdvertisement, V2CapabilityError, type V2CommandEntry, V2FetchError, type V2FetchParams, type V2FetchRequest, type V2FetchResponse, type V2GitServiceTransport, V2SmartHttpError, createV2HttpTransport, getCommandFeatures, hasCommand, lsRefs, lsRefsToRefAdvertisement, objectInfo, parseLsRefsResponse, parseObjectInfoResponse, parseV2CapabilityAdvertisement, parseV2FetchResponse, v2Fetch, v2FetchObjects };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { V2CapabilityError, getCommandFeatures, hasCommand, parseV2CapabilityAdvertisement } from "./client/upload-pack/capability-advertisement.mjs";
|
|
2
2
|
import { V2SmartHttpError, createV2HttpTransport } from "./client/upload-pack/http.mjs";
|
|
3
3
|
import { LsRefsError, lsRefs, lsRefsToRefAdvertisement, parseLsRefsResponse } from "./client/upload-pack/ls-refs.mjs";
|
|
4
|
-
import { V2FetchError, parseV2FetchResponse, v2Fetch, v2FetchObjects } from "./client/upload-pack/fetch.mjs";
|
|
5
4
|
import { ObjectInfoError, objectInfo, parseObjectInfoResponse } from "./client/upload-pack/object-info.mjs";
|
|
5
|
+
import { V2FetchError, parseV2FetchResponse, v2Fetch, v2FetchObjects } from "./client/upload-pack/fetch.mjs";
|
|
6
6
|
export { LsRefsError, ObjectInfoError, V2CapabilityError, V2FetchError, V2SmartHttpError, createV2HttpTransport, getCommandFeatures, hasCommand, lsRefs, lsRefsToRefAdvertisement, objectInfo, parseLsRefsResponse, parseObjectInfoResponse, parseV2CapabilityAdvertisement, parseV2FetchResponse, v2Fetch, v2FetchObjects };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nano-git",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
"./backend": "./src/backend/index.ts",
|
|
12
12
|
"./backend/file": "./src/backend/file.ts",
|
|
13
13
|
"./backend/memory": "./src/backend/memory.ts",
|
|
14
|
+
"./backend/sqlite": "./src/backend/sqlite.ts",
|
|
14
15
|
"./errors": "./src/errors.ts",
|
|
15
16
|
"./hash-file": "./src/hash-file.ts",
|
|
16
17
|
"./objects": "./src/objects/index.ts",
|
|
17
18
|
"./odb/file": "./src/odb/file.ts",
|
|
18
19
|
"./odb/memory": "./src/odb/memory.ts",
|
|
20
|
+
"./odb/sqlite": "./src/odb/sqlite.ts",
|
|
19
21
|
"./pack": "./src/pack/index.ts",
|
|
20
22
|
"./refs/file": "./src/refs/file.ts",
|
|
21
23
|
"./refs/memory": "./src/refs/memory.ts",
|
|
@@ -23,9 +25,13 @@
|
|
|
23
25
|
"./refs/resolve": "./src/refs/resolve.ts",
|
|
24
26
|
"./refs/shallow/file": "./src/refs/shallow/file.ts",
|
|
25
27
|
"./refs/shallow/memory": "./src/refs/shallow/memory.ts",
|
|
28
|
+
"./refs/shallow/sqlite": "./src/refs/shallow/sqlite.ts",
|
|
29
|
+
"./refs/sqlite": "./src/refs/sqlite.ts",
|
|
30
|
+
"./remote/http": "./src/remote/http.ts",
|
|
26
31
|
"./repository/core": "./src/repository/core.ts",
|
|
27
32
|
"./repository/file": "./src/repository/file.ts",
|
|
28
33
|
"./repository/memory": "./src/repository/memory.ts",
|
|
34
|
+
"./repository/sqlite": "./src/repository/sqlite.ts",
|
|
29
35
|
"./repository/tree/tree-patch": "./src/repository/tree/tree-patch.ts",
|
|
30
36
|
"./repository/tree/tree-walk": "./src/repository/tree/tree-walk.ts",
|
|
31
37
|
"./sha1": "./src/sha1.ts",
|
|
@@ -42,11 +48,13 @@
|
|
|
42
48
|
"./backend": "./dist/backend/index.mjs",
|
|
43
49
|
"./backend/file": "./dist/backend/file.mjs",
|
|
44
50
|
"./backend/memory": "./dist/backend/memory.mjs",
|
|
51
|
+
"./backend/sqlite": "./dist/backend/sqlite.mjs",
|
|
45
52
|
"./errors": "./dist/errors.mjs",
|
|
46
53
|
"./hash-file": "./dist/hash-file.mjs",
|
|
47
54
|
"./objects": "./dist/objects/index.mjs",
|
|
48
55
|
"./odb/file": "./dist/odb/file.mjs",
|
|
49
56
|
"./odb/memory": "./dist/odb/memory.mjs",
|
|
57
|
+
"./odb/sqlite": "./dist/odb/sqlite.mjs",
|
|
50
58
|
"./pack": "./dist/pack/index.mjs",
|
|
51
59
|
"./refs/file": "./dist/refs/file.mjs",
|
|
52
60
|
"./refs/memory": "./dist/refs/memory.mjs",
|
|
@@ -54,9 +62,13 @@
|
|
|
54
62
|
"./refs/resolve": "./dist/refs/resolve.mjs",
|
|
55
63
|
"./refs/shallow/file": "./dist/refs/shallow/file.mjs",
|
|
56
64
|
"./refs/shallow/memory": "./dist/refs/shallow/memory.mjs",
|
|
65
|
+
"./refs/shallow/sqlite": "./dist/refs/shallow/sqlite.mjs",
|
|
66
|
+
"./refs/sqlite": "./dist/refs/sqlite.mjs",
|
|
67
|
+
"./remote/http": "./dist/remote/http.mjs",
|
|
57
68
|
"./repository/core": "./dist/repository/core.mjs",
|
|
58
69
|
"./repository/file": "./dist/repository/file.mjs",
|
|
59
70
|
"./repository/memory": "./dist/repository/memory.mjs",
|
|
71
|
+
"./repository/sqlite": "./dist/repository/sqlite.mjs",
|
|
60
72
|
"./repository/tree/tree-patch": "./dist/repository/tree/tree-patch.mjs",
|
|
61
73
|
"./repository/tree/tree-walk": "./dist/repository/tree/tree-walk.mjs",
|
|
62
74
|
"./sha1": "./dist/sha1.mjs",
|