nano-git 0.1.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 +407 -0
- package/dist/backend/file-backend.d.mts +26 -0
- package/dist/backend/file-backend.mjs +83 -0
- package/dist/backend/file.d.mts +2 -0
- package/dist/backend/file.mjs +2 -0
- package/dist/backend/index.d.mts +2 -0
- package/dist/backend/index.mjs +1 -0
- package/dist/backend/memory-backend.d.mts +25 -0
- package/dist/backend/memory-backend.mjs +33 -0
- package/dist/backend/memory.d.mts +2 -0
- package/dist/backend/memory.mjs +2 -0
- package/dist/backend/types.d.mts +90 -0
- package/dist/core/errors.d.mts +124 -0
- package/dist/core/errors.mjs +168 -0
- package/dist/core/hash-digest.d.mts +35 -0
- package/dist/core/hash-digest.mjs +50 -0
- package/dist/core/hash-file.d.mts +20 -0
- package/dist/core/hash-file.mjs +27 -0
- package/dist/core/hash-path.d.mts +17 -0
- package/dist/core/hash-path.mjs +35 -0
- package/dist/core/types/odb.d.mts +63 -0
- package/dist/core/types/refs.d.mts +140 -0
- package/dist/core/types/refs.mjs +19 -0
- package/dist/core/types/shallow.d.mts +52 -0
- package/dist/core/types.d.mts +154 -0
- package/dist/core/types.mjs +43 -0
- package/dist/errors.d.mts +2 -0
- package/dist/errors.mjs +2 -0
- package/dist/hash-file.d.mts +2 -0
- package/dist/hash-file.mjs +2 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +14 -0
- package/dist/objects/author.d.mts +25 -0
- package/dist/objects/author.mjs +45 -0
- package/dist/objects/blob.d.mts +15 -0
- package/dist/objects/blob.mjs +20 -0
- package/dist/objects/codec.d.mts +46 -0
- package/dist/objects/codec.mjs +84 -0
- package/dist/objects/commit.d.mts +26 -0
- package/dist/objects/commit.mjs +160 -0
- package/dist/objects/index.d.mts +8 -0
- package/dist/objects/index.mjs +8 -0
- package/dist/objects/raw.d.mts +85 -0
- package/dist/objects/raw.mjs +111 -0
- package/dist/objects/tag.d.mts +26 -0
- package/dist/objects/tag.mjs +148 -0
- package/dist/objects/tree.d.mts +22 -0
- package/dist/objects/tree.mjs +66 -0
- package/dist/odb/file-utils.mjs +136 -0
- package/dist/odb/file.d.mts +22 -0
- package/dist/odb/file.mjs +66 -0
- package/dist/odb/memory.d.mts +22 -0
- package/dist/odb/memory.mjs +54 -0
- package/dist/pack/composite-store.d.mts +76 -0
- package/dist/pack/composite-store.mjs +133 -0
- package/dist/pack/constants.mjs +48 -0
- package/dist/pack/crc32.mjs +38 -0
- package/dist/pack/delta-apply.d.mts +21 -0
- package/dist/pack/delta-apply.mjs +71 -0
- package/dist/pack/delta-create.d.mts +28 -0
- package/dist/pack/delta-create.mjs +151 -0
- package/dist/pack/index.d.mts +16 -0
- package/dist/pack/index.mjs +13 -0
- package/dist/pack/object-header.d.mts +36 -0
- package/dist/pack/object-header.mjs +72 -0
- package/dist/pack/ofs-delta-offset.d.mts +35 -0
- package/dist/pack/ofs-delta-offset.mjs +59 -0
- package/dist/pack/pack-builder-types.d.mts +19 -0
- package/dist/pack/pack-builder.d.mts +52 -0
- package/dist/pack/pack-builder.mjs +80 -0
- package/dist/pack/pack-encoding.mjs +76 -0
- package/dist/pack/pack-index-reader.d.mts +57 -0
- package/dist/pack/pack-index-reader.mjs +90 -0
- package/dist/pack/pack-index-types.d.mts +16 -0
- package/dist/pack/pack-index-writer.d.mts +45 -0
- package/dist/pack/pack-index-writer.mjs +106 -0
- package/dist/pack/pack-reader-resolver.mjs +104 -0
- package/dist/pack/pack-reader-types.d.mts +31 -0
- package/dist/pack/pack-reader-types.mjs +22 -0
- package/dist/pack/pack-reader-utils.mjs +61 -0
- package/dist/pack/pack-reader.d.mts +81 -0
- package/dist/pack/pack-reader.mjs +171 -0
- package/dist/pack/pack-store-loader.mjs +79 -0
- package/dist/pack/pack-store-types.d.mts +16 -0
- package/dist/pack/pack-store.d.mts +55 -0
- package/dist/pack/pack-store.mjs +114 -0
- package/dist/pack/pack-writer.mjs +96 -0
- package/dist/pack/varint.d.mts +34 -0
- package/dist/pack/varint.mjs +57 -0
- package/dist/refs/file.d.mts +6 -0
- package/dist/refs/file.mjs +222 -0
- package/dist/refs/fs-utils.mjs +30 -0
- package/dist/refs/memory.d.mts +14 -0
- package/dist/refs/memory.mjs +104 -0
- package/dist/refs/names.d.mts +57 -0
- package/dist/refs/names.mjs +80 -0
- package/dist/refs/resolve.d.mts +33 -0
- package/dist/refs/resolve.mjs +55 -0
- package/dist/refs/shallow/file.d.mts +17 -0
- package/dist/refs/shallow/file.mjs +61 -0
- package/dist/refs/shallow/memory.d.mts +18 -0
- package/dist/refs/shallow/memory.mjs +33 -0
- package/dist/repository/core.d.mts +3 -0
- package/dist/repository/core.mjs +2 -0
- package/dist/repository/create.d.mts +22 -0
- package/dist/repository/create.mjs +43 -0
- package/dist/repository/file.d.mts +43 -0
- package/dist/repository/file.mjs +82 -0
- package/dist/repository/import/import-glob.mjs +44 -0
- package/dist/repository/import/import-plan-builder.mjs +625 -0
- package/dist/repository/import/import-session-types.d.mts +280 -0
- package/dist/repository/import/import-session.mjs +96 -0
- package/dist/repository/import/import-view.mjs +133 -0
- package/dist/repository/memory.d.mts +17 -0
- package/dist/repository/memory.mjs +33 -0
- package/dist/repository/ops/fetch-operations.mjs +20 -0
- package/dist/repository/ops/fetch-types.d.mts +82 -0
- package/dist/repository/ops/fetch-url.mjs +82 -0
- package/dist/repository/ops/fs-object-operations.mjs +31 -0
- package/dist/repository/ops/maintenance-operations.mjs +62 -0
- package/dist/repository/ops/maintenance-types.d.mts +42 -0
- package/dist/repository/ops/object-operations.mjs +65 -0
- package/dist/repository/ops/object-types.d.mts +109 -0
- package/dist/repository/ops/push-operations.mjs +16 -0
- package/dist/repository/ops/push-resolution.mjs +17 -0
- package/dist/repository/ops/push-types.d.mts +72 -0
- package/dist/repository/ops/push-url.mjs +45 -0
- package/dist/repository/ops/reachability.mjs +60 -0
- package/dist/repository/ops/ref-operations.mjs +86 -0
- package/dist/repository/ops/ref-types.d.mts +70 -0
- package/dist/repository/tree/tree-patch.d.mts +64 -0
- package/dist/repository/tree/tree-patch.mjs +268 -0
- package/dist/repository/tree/tree-walk.d.mts +46 -0
- package/dist/repository/tree/tree-walk.mjs +65 -0
- package/dist/repository/tree/tree-writer.mjs +68 -0
- package/dist/repository/types.d.mts +36 -0
- package/dist/sha1.d.mts +4 -0
- package/dist/sha1.mjs +4 -0
- package/dist/transport/client/receive-pack/http.d.mts +33 -0
- package/dist/transport/client/receive-pack/http.mjs +99 -0
- package/dist/transport/client/receive-pack/push-error.d.mts +23 -0
- package/dist/transport/client/receive-pack/push-error.mjs +32 -0
- package/dist/transport/client/receive-pack/push-pack-plan.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-pack-plan.mjs +60 -0
- package/dist/transport/client/receive-pack/push-policy.d.mts +19 -0
- package/dist/transport/client/receive-pack/push-policy.mjs +64 -0
- package/dist/transport/client/receive-pack/push-ref-plan.d.mts +45 -0
- package/dist/transport/client/receive-pack/push-ref-plan.mjs +108 -0
- package/dist/transport/client/receive-pack/push-report.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-report.mjs +84 -0
- package/dist/transport/client/receive-pack/push-request-plan.mjs +52 -0
- package/dist/transport/client/receive-pack/push.d.mts +32 -0
- package/dist/transport/client/receive-pack/push.mjs +97 -0
- package/dist/transport/client/receive-pack/request.d.mts +39 -0
- package/dist/transport/client/receive-pack/request.mjs +46 -0
- package/dist/transport/client/receive-pack/response.d.mts +26 -0
- package/dist/transport/client/receive-pack/response.mjs +52 -0
- package/dist/transport/client/receive-pack/result.d.mts +34 -0
- package/dist/transport/client/receive-pack/result.mjs +100 -0
- package/dist/transport/client/upload-pack/capability-advertisement.d.mts +56 -0
- package/dist/transport/client/upload-pack/capability-advertisement.mjs +130 -0
- package/dist/transport/client/upload-pack/fetch.d.mts +109 -0
- package/dist/transport/client/upload-pack/fetch.mjs +392 -0
- package/dist/transport/client/upload-pack/http.d.mts +29 -0
- package/dist/transport/client/upload-pack/http.mjs +79 -0
- package/dist/transport/client/upload-pack/ls-refs.d.mts +75 -0
- package/dist/transport/client/upload-pack/ls-refs.mjs +150 -0
- package/dist/transport/client/upload-pack/object-info.d.mts +65 -0
- package/dist/transport/client/upload-pack/object-info.mjs +111 -0
- package/dist/transport/client/upload-pack/types.d.mts +153 -0
- package/dist/transport/http/index.d.mts +3 -0
- package/dist/transport/http/index.mjs +2 -0
- package/dist/transport/http/smart-http.d.mts +46 -0
- package/dist/transport/http/smart-http.mjs +176 -0
- package/dist/transport/http/types.d.mts +27 -0
- package/dist/transport/index.d.mts +9 -0
- package/dist/transport/index.mjs +8 -0
- package/dist/transport/protocol/object-graph.d.mts +63 -0
- package/dist/transport/protocol/object-graph.mjs +149 -0
- package/dist/transport/protocol/pkt-line.d.mts +109 -0
- package/dist/transport/protocol/pkt-line.mjs +195 -0
- package/dist/transport/protocol/ref-advertisement.mjs +185 -0
- package/dist/transport/protocol/ref-collection.d.mts +38 -0
- package/dist/transport/protocol/ref-collection.mjs +63 -0
- package/dist/transport/protocol/ref-match.d.mts +39 -0
- package/dist/transport/protocol/ref-match.mjs +42 -0
- package/dist/transport/protocol/refspec.d.mts +44 -0
- package/dist/transport/protocol/refspec.mjs +79 -0
- package/dist/transport/protocol/side-band.d.mts +65 -0
- package/dist/transport/protocol/side-band.mjs +142 -0
- package/dist/transport/protocol/transport-capabilities.mjs +46 -0
- package/dist/transport/protocol/types.d.mts +148 -0
- package/dist/transport/protocol/update-refs.d.mts +68 -0
- package/dist/transport/protocol/update-refs.mjs +126 -0
- package/dist/transport/receive-pack.d.mts +11 -0
- package/dist/transport/receive-pack.mjs +11 -0
- package/dist/transport/server/receive-pack/advertise.d.mts +28 -0
- package/dist/transport/server/receive-pack/advertise.mjs +88 -0
- package/dist/transport/server/receive-pack/handler.d.mts +30 -0
- package/dist/transport/server/receive-pack/handler.mjs +156 -0
- package/dist/transport/server/receive-pack/index.d.mts +6 -0
- package/dist/transport/server/receive-pack/index.mjs +6 -0
- package/dist/transport/server/receive-pack/parse.d.mts +17 -0
- package/dist/transport/server/receive-pack/parse.mjs +80 -0
- package/dist/transport/server/receive-pack/report-status.mjs +64 -0
- package/dist/transport/server/receive-pack/service.d.mts +41 -0
- package/dist/transport/server/receive-pack/service.mjs +39 -0
- package/dist/transport/server/receive-pack/types.d.mts +56 -0
- package/dist/transport/server/receive-pack/types.mjs +25 -0
- package/dist/transport/server/receive-pack/unpack.mjs +119 -0
- package/dist/transport/server/upload-pack/advertise.d.mts +20 -0
- package/dist/transport/server/upload-pack/advertise.mjs +30 -0
- package/dist/transport/server/upload-pack/command.d.mts +43 -0
- package/dist/transport/server/upload-pack/command.mjs +56 -0
- package/dist/transport/server/upload-pack/fetch.d.mts +43 -0
- package/dist/transport/server/upload-pack/fetch.mjs +217 -0
- package/dist/transport/server/upload-pack/index.d.mts +7 -0
- package/dist/transport/server/upload-pack/index.mjs +7 -0
- package/dist/transport/server/upload-pack/ls-refs.d.mts +38 -0
- package/dist/transport/server/upload-pack/ls-refs.mjs +113 -0
- package/dist/transport/server/upload-pack/service.d.mts +40 -0
- package/dist/transport/server/upload-pack/service.mjs +51 -0
- package/dist/transport/server/upload-pack/types.d.mts +11 -0
- package/dist/transport/server/upload-pack/types.mjs +21 -0
- package/dist/transport/upload-pack.d.mts +7 -0
- package/dist/transport/upload-pack.mjs +6 -0
- package/package.json +98 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { RefNotFoundError, TransactionError } from "../core/errors.mjs";
|
|
2
|
+
import { validateRefName, validateRefPrefix } from "./names.mjs";
|
|
3
|
+
import { listLooseRefsRecursive } from "./fs-utils.mjs";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
//#region src/refs/file.ts
|
|
7
|
+
/**
|
|
8
|
+
* 基于文件系统的 Refs 存储
|
|
9
|
+
*/
|
|
10
|
+
function readPackedRefs(gitDir) {
|
|
11
|
+
const packedRefsPath = join(gitDir, "packed-refs");
|
|
12
|
+
if (!existsSync(packedRefsPath)) return /* @__PURE__ */ new Map();
|
|
13
|
+
const packedRefs = /* @__PURE__ */ new Map();
|
|
14
|
+
const lines = readFileSync(packedRefsPath, "utf-8").split("\n");
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (!line || line.startsWith("#") || line.startsWith("^")) continue;
|
|
17
|
+
const spaceIndex = line.indexOf(" ");
|
|
18
|
+
if (spaceIndex === -1) continue;
|
|
19
|
+
const hash = line.slice(0, spaceIndex);
|
|
20
|
+
const ref = line.slice(spaceIndex + 1);
|
|
21
|
+
packedRefs.set(ref, hash);
|
|
22
|
+
}
|
|
23
|
+
return packedRefs;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 从 packed-refs 中删除指定引用
|
|
27
|
+
*
|
|
28
|
+
* 会同时删除该引用可能携带的 peeled 行(`^...`)。
|
|
29
|
+
*
|
|
30
|
+
* @param gitDir - Git 目录
|
|
31
|
+
* @param ref - 完整引用路径
|
|
32
|
+
* @returns 是否实际删除了 packed-refs 条目
|
|
33
|
+
*/
|
|
34
|
+
function deletePackedRef(gitDir, ref) {
|
|
35
|
+
const packedRefsPath = join(gitDir, "packed-refs");
|
|
36
|
+
if (!existsSync(packedRefsPath)) return false;
|
|
37
|
+
const lines = readFileSync(packedRefsPath, "utf-8").split("\n");
|
|
38
|
+
const keptLines = [];
|
|
39
|
+
let removed = false;
|
|
40
|
+
let skipNextPeeledLine = false;
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
if (skipNextPeeledLine && line.startsWith("^")) {
|
|
43
|
+
skipNextPeeledLine = false;
|
|
44
|
+
removed = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
skipNextPeeledLine = false;
|
|
48
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
49
|
+
keptLines.push(line);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const spaceIndex = line.indexOf(" ");
|
|
53
|
+
if (spaceIndex === -1) {
|
|
54
|
+
keptLines.push(line);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (line.slice(spaceIndex + 1) === ref) {
|
|
58
|
+
removed = true;
|
|
59
|
+
skipNextPeeledLine = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
keptLines.push(line);
|
|
63
|
+
}
|
|
64
|
+
if (!removed) return false;
|
|
65
|
+
writeFileSync(packedRefsPath, keptLines.join("\n"));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 创建基于文件系统的 Refs 存储
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const store = createFileRefStore("/path/to/repo/.git");
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* Lock 文件路径
|
|
78
|
+
*/
|
|
79
|
+
function lockPath(gitDir, ref) {
|
|
80
|
+
return join(gitDir, ref) + ".lock";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 创建 Lock 文件(占位)
|
|
84
|
+
*
|
|
85
|
+
* 如果 lock 文件已存在,说明有并发写入或残留 lock,抛出异常。
|
|
86
|
+
*/
|
|
87
|
+
function createLockFile(gitDir, ref) {
|
|
88
|
+
const lock = lockPath(gitDir, ref);
|
|
89
|
+
mkdirSync(dirname(lock), { recursive: true });
|
|
90
|
+
if (existsSync(lock)) throw new TransactionError(`Cannot lock ref "${ref}": lock file already exists. This may indicate a concurrent write or a stale lock file.`);
|
|
91
|
+
writeFileSync(lock, "");
|
|
92
|
+
return lock;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 将 pending Map 冻结为只读快照
|
|
96
|
+
*/
|
|
97
|
+
function freezePending(pending) {
|
|
98
|
+
const writes = [];
|
|
99
|
+
const deletes = [];
|
|
100
|
+
for (const [ref, content] of pending) if (content === null) deletes.push({ ref });
|
|
101
|
+
else writes.push({
|
|
102
|
+
ref,
|
|
103
|
+
content
|
|
104
|
+
});
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
pendingCount: pending.size,
|
|
107
|
+
writes: Object.freeze(writes),
|
|
108
|
+
deletes: Object.freeze(deletes)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function createFileRefStore(gitDir) {
|
|
112
|
+
function beginTransaction(hooks) {
|
|
113
|
+
const pending = /* @__PURE__ */ new Map();
|
|
114
|
+
let committed = false;
|
|
115
|
+
return {
|
|
116
|
+
get pendingCount() {
|
|
117
|
+
return pending.size;
|
|
118
|
+
},
|
|
119
|
+
write(ref, content) {
|
|
120
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
121
|
+
validateRefName(ref);
|
|
122
|
+
pending.set(ref, content.trimEnd());
|
|
123
|
+
},
|
|
124
|
+
delete(ref) {
|
|
125
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
126
|
+
validateRefName(ref);
|
|
127
|
+
const hasLooseRef = existsSync(join(gitDir, ref));
|
|
128
|
+
const hasPackedRef = readPackedRefs(gitDir).has(ref);
|
|
129
|
+
if (!hasLooseRef && !hasPackedRef && !pending.has(ref)) throw new RefNotFoundError(ref);
|
|
130
|
+
pending.set(ref, null);
|
|
131
|
+
},
|
|
132
|
+
commit() {
|
|
133
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
134
|
+
committed = true;
|
|
135
|
+
const txSnapshot = freezePending(pending);
|
|
136
|
+
const locks = [];
|
|
137
|
+
try {
|
|
138
|
+
for (const refName of pending.keys()) {
|
|
139
|
+
const lock = createLockFile(gitDir, refName);
|
|
140
|
+
locks.push(lock);
|
|
141
|
+
}
|
|
142
|
+
for (const hook of hooks ?? []) hook.onPrepare?.(txSnapshot);
|
|
143
|
+
let idx = 0;
|
|
144
|
+
for (const [, content] of pending) {
|
|
145
|
+
const lock = locks[idx];
|
|
146
|
+
idx++;
|
|
147
|
+
if (content === null) writeFileSync(lock, "");
|
|
148
|
+
else writeFileSync(lock, `${content}\n`);
|
|
149
|
+
}
|
|
150
|
+
idx = 0;
|
|
151
|
+
for (const [ref, content] of pending) {
|
|
152
|
+
const lock = locks[idx];
|
|
153
|
+
idx++;
|
|
154
|
+
const target = join(gitDir, ref);
|
|
155
|
+
if (content === null) {
|
|
156
|
+
if (existsSync(target)) unlinkSync(target);
|
|
157
|
+
deletePackedRef(gitDir, ref);
|
|
158
|
+
unlinkSync(lock);
|
|
159
|
+
} else {
|
|
160
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
161
|
+
renameSync(lock, target);
|
|
162
|
+
deletePackedRef(gitDir, ref);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const hook of hooks ?? []) hook.onCommitted?.(txSnapshot);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
for (const lock of locks) try {
|
|
168
|
+
if (existsSync(lock)) unlinkSync(lock);
|
|
169
|
+
} catch {}
|
|
170
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
rollback() {
|
|
175
|
+
if (committed) return;
|
|
176
|
+
committed = true;
|
|
177
|
+
const txSnapshot = freezePending(pending);
|
|
178
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
read(ref) {
|
|
184
|
+
validateRefName(ref);
|
|
185
|
+
const refPath = join(gitDir, ref);
|
|
186
|
+
if (existsSync(refPath)) return readFileSync(refPath, "utf-8").trimEnd();
|
|
187
|
+
return readPackedRefs(gitDir).get(ref) ?? null;
|
|
188
|
+
},
|
|
189
|
+
write(ref, content) {
|
|
190
|
+
validateRefName(ref);
|
|
191
|
+
const refPath = join(gitDir, ref);
|
|
192
|
+
mkdirSync(dirname(refPath), { recursive: true });
|
|
193
|
+
writeFileSync(refPath, `${content.trimEnd()}\n`);
|
|
194
|
+
},
|
|
195
|
+
delete(ref) {
|
|
196
|
+
validateRefName(ref);
|
|
197
|
+
const refPath = join(gitDir, ref);
|
|
198
|
+
const hasLooseRef = existsSync(refPath);
|
|
199
|
+
const removedPackedRef = deletePackedRef(gitDir, ref);
|
|
200
|
+
if (!hasLooseRef && !removedPackedRef) throw new RefNotFoundError(ref);
|
|
201
|
+
if (hasLooseRef) unlinkSync(refPath);
|
|
202
|
+
},
|
|
203
|
+
list(prefix) {
|
|
204
|
+
validateRefPrefix(prefix);
|
|
205
|
+
const baseDir = join(gitDir, prefix);
|
|
206
|
+
const refs = /* @__PURE__ */ new Set();
|
|
207
|
+
if (existsSync(baseDir)) for (const ref of listLooseRefsRecursive(baseDir, prefix)) refs.add(ref);
|
|
208
|
+
for (const ref of readPackedRefs(gitDir).keys()) if (ref.startsWith(prefix)) refs.add(ref);
|
|
209
|
+
return Array.from(refs).sort();
|
|
210
|
+
},
|
|
211
|
+
listAll() {
|
|
212
|
+
const refs = /* @__PURE__ */ new Set();
|
|
213
|
+
const refsDir = join(gitDir, "refs");
|
|
214
|
+
if (existsSync(refsDir)) for (const ref of listLooseRefsRecursive(refsDir, "refs/")) refs.add(ref);
|
|
215
|
+
for (const ref of readPackedRefs(gitDir).keys()) if (ref.startsWith("refs/")) refs.add(ref);
|
|
216
|
+
return Array.from(refs).sort();
|
|
217
|
+
},
|
|
218
|
+
beginTransaction
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { createFileRefStore };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/refs/fs-utils.ts
|
|
4
|
+
/**
|
|
5
|
+
* 文件系统 Refs 辅助函数
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 递归列出目录下的所有引用文件
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const refs = listLooseRefsRecursive("/tmp/repo/.git/refs", "refs/");
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function listLooseRefsRecursive(baseDir, prefix) {
|
|
16
|
+
const refs = [];
|
|
17
|
+
const entries = readdirSync(baseDir).sort();
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const fullPath = join(baseDir, entry);
|
|
20
|
+
const stat = statSync(fullPath);
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
refs.push(...listLooseRefsRecursive(fullPath, `${prefix}${entry}/`));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (stat.isFile()) refs.push(`${prefix}${entry}`);
|
|
26
|
+
}
|
|
27
|
+
return refs;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { listLooseRefsRecursive };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RefStore } from "../core/types/refs.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/refs/memory.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 创建基于内存的 Refs 存储
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const store = createMemoryRefStore();
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare function createMemoryRefStore(initial?: Map<string, string>): RefStore;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createMemoryRefStore };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { RefNotFoundError, TransactionError } from "../core/errors.mjs";
|
|
2
|
+
import { validateRefName, validateRefPrefix } from "./names.mjs";
|
|
3
|
+
//#region src/refs/memory.ts
|
|
4
|
+
/**
|
|
5
|
+
* 基于内存的 Refs 存储
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 创建基于内存的 Refs 存储
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const store = createMemoryRefStore();
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function createMemoryRefStore(initial) {
|
|
16
|
+
const active = new Map(initial);
|
|
17
|
+
function beginTransaction(hooks) {
|
|
18
|
+
const pending = /* @__PURE__ */ new Map();
|
|
19
|
+
const snapshot = new Map(active);
|
|
20
|
+
let committed = false;
|
|
21
|
+
return {
|
|
22
|
+
get pendingCount() {
|
|
23
|
+
return pending.size;
|
|
24
|
+
},
|
|
25
|
+
write(ref, content) {
|
|
26
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
27
|
+
validateRefName(ref);
|
|
28
|
+
pending.set(ref, content.trimEnd());
|
|
29
|
+
},
|
|
30
|
+
delete(ref) {
|
|
31
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
32
|
+
validateRefName(ref);
|
|
33
|
+
if (!active.has(ref) && !pending.has(ref)) throw new RefNotFoundError(ref);
|
|
34
|
+
pending.set(ref, null);
|
|
35
|
+
},
|
|
36
|
+
commit() {
|
|
37
|
+
if (committed) throw new TransactionError("Transaction already committed");
|
|
38
|
+
committed = true;
|
|
39
|
+
const txSnapshot = freezePending(pending);
|
|
40
|
+
try {
|
|
41
|
+
for (const hook of hooks ?? []) hook.onPrepare?.(txSnapshot);
|
|
42
|
+
for (const [ref, content] of pending) if (content === null) {
|
|
43
|
+
if (!active.has(ref)) throw new RefNotFoundError(ref);
|
|
44
|
+
active.delete(ref);
|
|
45
|
+
} else active.set(ref, content);
|
|
46
|
+
for (const hook of hooks ?? []) hook.onCommitted?.(txSnapshot);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
active.clear();
|
|
49
|
+
for (const [k, v] of snapshot) active.set(k, v);
|
|
50
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
rollback() {
|
|
55
|
+
if (committed) return;
|
|
56
|
+
committed = true;
|
|
57
|
+
const txSnapshot = freezePending(pending);
|
|
58
|
+
for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
read(ref) {
|
|
64
|
+
validateRefName(ref);
|
|
65
|
+
return active.get(ref) ?? null;
|
|
66
|
+
},
|
|
67
|
+
write(ref, content) {
|
|
68
|
+
validateRefName(ref);
|
|
69
|
+
active.set(ref, content.trimEnd());
|
|
70
|
+
},
|
|
71
|
+
delete(ref) {
|
|
72
|
+
validateRefName(ref);
|
|
73
|
+
if (!active.has(ref)) throw new RefNotFoundError(ref);
|
|
74
|
+
active.delete(ref);
|
|
75
|
+
},
|
|
76
|
+
list(prefix) {
|
|
77
|
+
validateRefPrefix(prefix);
|
|
78
|
+
return Array.from(active.keys()).filter((key) => key.startsWith(prefix)).sort();
|
|
79
|
+
},
|
|
80
|
+
listAll() {
|
|
81
|
+
return Array.from(active.keys()).filter((key) => key.startsWith("refs/")).sort();
|
|
82
|
+
},
|
|
83
|
+
beginTransaction
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 将 pending Map 冻结为只读快照
|
|
88
|
+
*/
|
|
89
|
+
function freezePending(pending) {
|
|
90
|
+
const writes = [];
|
|
91
|
+
const deletes = [];
|
|
92
|
+
for (const [ref, content] of pending) if (content === null) deletes.push({ ref });
|
|
93
|
+
else writes.push({
|
|
94
|
+
ref,
|
|
95
|
+
content
|
|
96
|
+
});
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
pendingCount: pending.size,
|
|
99
|
+
writes: Object.freeze(writes),
|
|
100
|
+
deletes: Object.freeze(deletes)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { createMemoryRefStore };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/refs/names.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Refs 名称校验与名称转换工具
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 验证引用前缀是否合法
|
|
7
|
+
*
|
|
8
|
+
* @param prefix - 引用前缀,如 "refs/heads/"
|
|
9
|
+
* @throws 如果前缀格式无效
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* validateRefPrefix("refs/heads/");
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function validateRefPrefix(prefix: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* 验证引用名称是否合法
|
|
19
|
+
*
|
|
20
|
+
* @param ref - 完整引用路径
|
|
21
|
+
* @throws 如果引用名称无效
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* validateRefName("refs/heads/main");
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function validateRefName(ref: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* 将分支短名转换为完整引用路径
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* branchNameToRef("main");
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function branchNameToRef(name: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* 将标签短名转换为完整引用路径
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* tagNameToRef("v1.0.0");
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function tagNameToRef(name: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* 规范化短引用名称
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* normalizeShortRefName("main", "branch");
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare function normalizeShortRefName(name: string, kind: "branch" | "tag"): string;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { branchNameToRef, normalizeShortRefName, tagNameToRef, validateRefName, validateRefPrefix };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { HEADS_PREFIX, TAGS_PREFIX } from "../core/types/refs.mjs";
|
|
2
|
+
//#region src/refs/names.ts
|
|
3
|
+
/**
|
|
4
|
+
* Refs 名称校验与名称转换工具
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 验证引用前缀是否合法
|
|
8
|
+
*
|
|
9
|
+
* @param prefix - 引用前缀,如 "refs/heads/"
|
|
10
|
+
* @throws 如果前缀格式无效
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* validateRefPrefix("refs/heads/");
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function validateRefPrefix(prefix) {
|
|
18
|
+
if (!prefix.startsWith("refs/") || !prefix.endsWith("/")) throw new Error(`Invalid ref prefix: ${prefix}`);
|
|
19
|
+
validateRefName(`${prefix}placeholder`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 验证引用名称是否合法
|
|
23
|
+
*
|
|
24
|
+
* @param ref - 完整引用路径
|
|
25
|
+
* @throws 如果引用名称无效
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* validateRefName("refs/heads/main");
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function validateRefName(ref) {
|
|
33
|
+
if (ref === "HEAD") return;
|
|
34
|
+
if (!ref.startsWith("refs/")) throw new Error(`Invalid ref name: ${ref}`);
|
|
35
|
+
if (ref.includes("\\") || ref.includes("..") || ref.includes("@{") || ref.includes("//") || ref.endsWith("/") || ref.endsWith(".")) throw new Error(`Invalid ref name: ${ref}`);
|
|
36
|
+
for (const char of ref) {
|
|
37
|
+
const code = char.charCodeAt(0);
|
|
38
|
+
if (code <= 31 || code === 127 || char === " " || char === "~" || char === "^" || char === ":" || char === "?" || char === "*" || char === "[") throw new Error(`Invalid ref name: ${ref}`);
|
|
39
|
+
}
|
|
40
|
+
const parts = ref.split("/");
|
|
41
|
+
if (parts.length < 3) throw new Error(`Invalid ref name: ${ref}`);
|
|
42
|
+
for (const part of parts) if (!part || part === "." || part === ".." || part.endsWith(".lock")) throw new Error(`Invalid ref name: ${ref}`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 将分支短名转换为完整引用路径
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* branchNameToRef("main");
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function branchNameToRef(name) {
|
|
53
|
+
return `${HEADS_PREFIX}${normalizeShortRefName(name, "branch")}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 将标签短名转换为完整引用路径
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* tagNameToRef("v1.0.0");
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function tagNameToRef(name) {
|
|
64
|
+
return `${TAGS_PREFIX}${normalizeShortRefName(name, "tag")}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 规范化短引用名称
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* normalizeShortRefName("main", "branch");
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
function normalizeShortRefName(name, kind) {
|
|
75
|
+
if (!name) throw new Error(`${kind} name cannot be empty`);
|
|
76
|
+
validateRefName(`refs/x/${name}`);
|
|
77
|
+
return name;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { branchNameToRef, normalizeShortRefName, tagNameToRef, validateRefName, validateRefPrefix };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SHA1 } from "../core/types.mjs";
|
|
2
|
+
import { RefStore } from "../core/types/refs.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/refs/resolve.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 解析引用为 SHA-1 哈希
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const hash = resolveRefHash(store, "HEAD");
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare function resolveRefHash(store: RefStore, ref: string, seen?: Set<string>): SHA1 | null;
|
|
14
|
+
/**
|
|
15
|
+
* 解析符号引用为目标引用路径
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const target = resolveSymbolicRef(store, "HEAD");
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function resolveSymbolicRef(store: RefStore, ref: string, seen?: Set<string>): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* 解析可选的哈希值,未提供时基于 HEAD 获取
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const hash = resolveTargetHash(store, undefined);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
declare function resolveTargetHash(store: RefStore, hash: SHA1 | undefined): SHA1;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { resolveRefHash, resolveSymbolicRef, resolveTargetHash };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CircularReferenceError } from "../core/errors.mjs";
|
|
2
|
+
import { sha1 } from "../core/types.mjs";
|
|
3
|
+
import { HEAD_REF } from "../core/types/refs.mjs";
|
|
4
|
+
//#region src/refs/resolve.ts
|
|
5
|
+
/**
|
|
6
|
+
* Refs 解析工具
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 解析引用为 SHA-1 哈希
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const hash = resolveRefHash(store, "HEAD");
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function resolveRefHash(store, ref, seen = /* @__PURE__ */ new Set()) {
|
|
17
|
+
if (seen.has(ref)) throw new CircularReferenceError(ref);
|
|
18
|
+
seen.add(ref);
|
|
19
|
+
const content = store.read(ref);
|
|
20
|
+
if (content === null) return null;
|
|
21
|
+
if (content.startsWith("ref: ")) return resolveRefHash(store, content.slice(5), seen);
|
|
22
|
+
return sha1(content);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 解析符号引用为目标引用路径
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const target = resolveSymbolicRef(store, "HEAD");
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function resolveSymbolicRef(store, ref, seen = /* @__PURE__ */ new Set()) {
|
|
33
|
+
if (seen.has(ref)) throw new CircularReferenceError(ref);
|
|
34
|
+
seen.add(ref);
|
|
35
|
+
const content = store.read(ref);
|
|
36
|
+
if (content === null || !content.startsWith("ref: ")) return null;
|
|
37
|
+
const target = content.slice(5);
|
|
38
|
+
return resolveSymbolicRef(store, target, seen) ?? target;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 解析可选的哈希值,未提供时基于 HEAD 获取
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const hash = resolveTargetHash(store, undefined);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function resolveTargetHash(store, hash) {
|
|
49
|
+
if (hash) return hash;
|
|
50
|
+
const headHash = resolveRefHash(store, HEAD_REF);
|
|
51
|
+
if (!headHash) throw new Error("Cannot resolve HEAD to create ref");
|
|
52
|
+
return headHash;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { resolveRefHash, resolveSymbolicRef, resolveTargetHash };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ShallowStore } from "../../core/types/shallow.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/refs/shallow/file.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 创建基于文件系统的 Shallow 存储
|
|
6
|
+
*
|
|
7
|
+
* @param gitDir - .git 目录的路径
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const store = createFileShallowStore("/repo/.git");
|
|
12
|
+
* console.log(store.read());
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare function createFileShallowStore(gitDir: string): ShallowStore;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { createFileShallowStore };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { sha1 } from "../../core/types.mjs";
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
//#region src/refs/shallow/file.ts
|
|
5
|
+
/**
|
|
6
|
+
* 基于文件系统的 Shallow 存储
|
|
7
|
+
*
|
|
8
|
+
* 读写 .git/shallow 文件。
|
|
9
|
+
* 格式:每行一个 40 字符 SHA1 哈希。
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const store = createFileShallowStore("/path/to/repo/.git");
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 创建基于文件系统的 Shallow 存储
|
|
18
|
+
*
|
|
19
|
+
* @param gitDir - .git 目录的路径
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const store = createFileShallowStore("/repo/.git");
|
|
24
|
+
* console.log(store.read());
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function createFileShallowStore(gitDir) {
|
|
28
|
+
const shallowFilePath = join(gitDir, "shallow");
|
|
29
|
+
function readFromFile() {
|
|
30
|
+
if (!existsSync(shallowFilePath)) return [];
|
|
31
|
+
const content = readFileSync(shallowFilePath, "utf8").trim();
|
|
32
|
+
if (content.length === 0) return [];
|
|
33
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((hash) => sha1(hash));
|
|
34
|
+
}
|
|
35
|
+
function writeToFile(boundaries) {
|
|
36
|
+
if (boundaries.length === 0) {
|
|
37
|
+
if (existsSync(shallowFilePath)) unlinkSync(shallowFilePath);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
writeFileSync(shallowFilePath, [...boundaries].sort().join("\n") + "\n");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
read() {
|
|
44
|
+
return readFromFile();
|
|
45
|
+
},
|
|
46
|
+
write(boundaries) {
|
|
47
|
+
writeToFile(boundaries);
|
|
48
|
+
},
|
|
49
|
+
applyUpdate(update) {
|
|
50
|
+
const current = new Set(readFromFile());
|
|
51
|
+
for (const hash of update.unshallow) current.delete(hash);
|
|
52
|
+
for (const hash of update.shallow) current.add(hash);
|
|
53
|
+
writeToFile(Array.from(current));
|
|
54
|
+
},
|
|
55
|
+
isShallow(hash) {
|
|
56
|
+
return readFromFile().includes(hash);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createFileShallowStore };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SHA1 } from "../../core/types.mjs";
|
|
2
|
+
import { ShallowStore } from "../../core/types/shallow.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/refs/shallow/memory.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 创建基于内存的 Shallow 存储
|
|
7
|
+
*
|
|
8
|
+
* @param initial - 初始 shallow 边界集合(可选)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const store = createMemoryShallowStore([hashA]);
|
|
13
|
+
* console.log(store.isShallow(hashA)); // true
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function createMemoryShallowStore(initial?: SHA1[]): ShallowStore;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createMemoryShallowStore };
|