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.
Files changed (227) hide show
  1. package/README.md +407 -0
  2. package/dist/backend/file-backend.d.mts +26 -0
  3. package/dist/backend/file-backend.mjs +83 -0
  4. package/dist/backend/file.d.mts +2 -0
  5. package/dist/backend/file.mjs +2 -0
  6. package/dist/backend/index.d.mts +2 -0
  7. package/dist/backend/index.mjs +1 -0
  8. package/dist/backend/memory-backend.d.mts +25 -0
  9. package/dist/backend/memory-backend.mjs +33 -0
  10. package/dist/backend/memory.d.mts +2 -0
  11. package/dist/backend/memory.mjs +2 -0
  12. package/dist/backend/types.d.mts +90 -0
  13. package/dist/core/errors.d.mts +124 -0
  14. package/dist/core/errors.mjs +168 -0
  15. package/dist/core/hash-digest.d.mts +35 -0
  16. package/dist/core/hash-digest.mjs +50 -0
  17. package/dist/core/hash-file.d.mts +20 -0
  18. package/dist/core/hash-file.mjs +27 -0
  19. package/dist/core/hash-path.d.mts +17 -0
  20. package/dist/core/hash-path.mjs +35 -0
  21. package/dist/core/types/odb.d.mts +63 -0
  22. package/dist/core/types/refs.d.mts +140 -0
  23. package/dist/core/types/refs.mjs +19 -0
  24. package/dist/core/types/shallow.d.mts +52 -0
  25. package/dist/core/types.d.mts +154 -0
  26. package/dist/core/types.mjs +43 -0
  27. package/dist/errors.d.mts +2 -0
  28. package/dist/errors.mjs +2 -0
  29. package/dist/hash-file.d.mts +2 -0
  30. package/dist/hash-file.mjs +2 -0
  31. package/dist/index.d.mts +16 -0
  32. package/dist/index.mjs +14 -0
  33. package/dist/objects/author.d.mts +25 -0
  34. package/dist/objects/author.mjs +45 -0
  35. package/dist/objects/blob.d.mts +15 -0
  36. package/dist/objects/blob.mjs +20 -0
  37. package/dist/objects/codec.d.mts +46 -0
  38. package/dist/objects/codec.mjs +84 -0
  39. package/dist/objects/commit.d.mts +26 -0
  40. package/dist/objects/commit.mjs +160 -0
  41. package/dist/objects/index.d.mts +8 -0
  42. package/dist/objects/index.mjs +8 -0
  43. package/dist/objects/raw.d.mts +85 -0
  44. package/dist/objects/raw.mjs +111 -0
  45. package/dist/objects/tag.d.mts +26 -0
  46. package/dist/objects/tag.mjs +148 -0
  47. package/dist/objects/tree.d.mts +22 -0
  48. package/dist/objects/tree.mjs +66 -0
  49. package/dist/odb/file-utils.mjs +136 -0
  50. package/dist/odb/file.d.mts +22 -0
  51. package/dist/odb/file.mjs +66 -0
  52. package/dist/odb/memory.d.mts +22 -0
  53. package/dist/odb/memory.mjs +54 -0
  54. package/dist/pack/composite-store.d.mts +76 -0
  55. package/dist/pack/composite-store.mjs +133 -0
  56. package/dist/pack/constants.mjs +48 -0
  57. package/dist/pack/crc32.mjs +38 -0
  58. package/dist/pack/delta-apply.d.mts +21 -0
  59. package/dist/pack/delta-apply.mjs +71 -0
  60. package/dist/pack/delta-create.d.mts +28 -0
  61. package/dist/pack/delta-create.mjs +151 -0
  62. package/dist/pack/index.d.mts +16 -0
  63. package/dist/pack/index.mjs +13 -0
  64. package/dist/pack/object-header.d.mts +36 -0
  65. package/dist/pack/object-header.mjs +72 -0
  66. package/dist/pack/ofs-delta-offset.d.mts +35 -0
  67. package/dist/pack/ofs-delta-offset.mjs +59 -0
  68. package/dist/pack/pack-builder-types.d.mts +19 -0
  69. package/dist/pack/pack-builder.d.mts +52 -0
  70. package/dist/pack/pack-builder.mjs +80 -0
  71. package/dist/pack/pack-encoding.mjs +76 -0
  72. package/dist/pack/pack-index-reader.d.mts +57 -0
  73. package/dist/pack/pack-index-reader.mjs +90 -0
  74. package/dist/pack/pack-index-types.d.mts +16 -0
  75. package/dist/pack/pack-index-writer.d.mts +45 -0
  76. package/dist/pack/pack-index-writer.mjs +106 -0
  77. package/dist/pack/pack-reader-resolver.mjs +104 -0
  78. package/dist/pack/pack-reader-types.d.mts +31 -0
  79. package/dist/pack/pack-reader-types.mjs +22 -0
  80. package/dist/pack/pack-reader-utils.mjs +61 -0
  81. package/dist/pack/pack-reader.d.mts +81 -0
  82. package/dist/pack/pack-reader.mjs +171 -0
  83. package/dist/pack/pack-store-loader.mjs +79 -0
  84. package/dist/pack/pack-store-types.d.mts +16 -0
  85. package/dist/pack/pack-store.d.mts +55 -0
  86. package/dist/pack/pack-store.mjs +114 -0
  87. package/dist/pack/pack-writer.mjs +96 -0
  88. package/dist/pack/varint.d.mts +34 -0
  89. package/dist/pack/varint.mjs +57 -0
  90. package/dist/refs/file.d.mts +6 -0
  91. package/dist/refs/file.mjs +222 -0
  92. package/dist/refs/fs-utils.mjs +30 -0
  93. package/dist/refs/memory.d.mts +14 -0
  94. package/dist/refs/memory.mjs +104 -0
  95. package/dist/refs/names.d.mts +57 -0
  96. package/dist/refs/names.mjs +80 -0
  97. package/dist/refs/resolve.d.mts +33 -0
  98. package/dist/refs/resolve.mjs +55 -0
  99. package/dist/refs/shallow/file.d.mts +17 -0
  100. package/dist/refs/shallow/file.mjs +61 -0
  101. package/dist/refs/shallow/memory.d.mts +18 -0
  102. package/dist/refs/shallow/memory.mjs +33 -0
  103. package/dist/repository/core.d.mts +3 -0
  104. package/dist/repository/core.mjs +2 -0
  105. package/dist/repository/create.d.mts +22 -0
  106. package/dist/repository/create.mjs +43 -0
  107. package/dist/repository/file.d.mts +43 -0
  108. package/dist/repository/file.mjs +82 -0
  109. package/dist/repository/import/import-glob.mjs +44 -0
  110. package/dist/repository/import/import-plan-builder.mjs +625 -0
  111. package/dist/repository/import/import-session-types.d.mts +280 -0
  112. package/dist/repository/import/import-session.mjs +96 -0
  113. package/dist/repository/import/import-view.mjs +133 -0
  114. package/dist/repository/memory.d.mts +17 -0
  115. package/dist/repository/memory.mjs +33 -0
  116. package/dist/repository/ops/fetch-operations.mjs +20 -0
  117. package/dist/repository/ops/fetch-types.d.mts +82 -0
  118. package/dist/repository/ops/fetch-url.mjs +82 -0
  119. package/dist/repository/ops/fs-object-operations.mjs +31 -0
  120. package/dist/repository/ops/maintenance-operations.mjs +62 -0
  121. package/dist/repository/ops/maintenance-types.d.mts +42 -0
  122. package/dist/repository/ops/object-operations.mjs +65 -0
  123. package/dist/repository/ops/object-types.d.mts +109 -0
  124. package/dist/repository/ops/push-operations.mjs +16 -0
  125. package/dist/repository/ops/push-resolution.mjs +17 -0
  126. package/dist/repository/ops/push-types.d.mts +72 -0
  127. package/dist/repository/ops/push-url.mjs +45 -0
  128. package/dist/repository/ops/reachability.mjs +60 -0
  129. package/dist/repository/ops/ref-operations.mjs +86 -0
  130. package/dist/repository/ops/ref-types.d.mts +70 -0
  131. package/dist/repository/tree/tree-patch.d.mts +64 -0
  132. package/dist/repository/tree/tree-patch.mjs +268 -0
  133. package/dist/repository/tree/tree-walk.d.mts +46 -0
  134. package/dist/repository/tree/tree-walk.mjs +65 -0
  135. package/dist/repository/tree/tree-writer.mjs +68 -0
  136. package/dist/repository/types.d.mts +36 -0
  137. package/dist/sha1.d.mts +4 -0
  138. package/dist/sha1.mjs +4 -0
  139. package/dist/transport/client/receive-pack/http.d.mts +33 -0
  140. package/dist/transport/client/receive-pack/http.mjs +99 -0
  141. package/dist/transport/client/receive-pack/push-error.d.mts +23 -0
  142. package/dist/transport/client/receive-pack/push-error.mjs +32 -0
  143. package/dist/transport/client/receive-pack/push-pack-plan.d.mts +28 -0
  144. package/dist/transport/client/receive-pack/push-pack-plan.mjs +60 -0
  145. package/dist/transport/client/receive-pack/push-policy.d.mts +19 -0
  146. package/dist/transport/client/receive-pack/push-policy.mjs +64 -0
  147. package/dist/transport/client/receive-pack/push-ref-plan.d.mts +45 -0
  148. package/dist/transport/client/receive-pack/push-ref-plan.mjs +108 -0
  149. package/dist/transport/client/receive-pack/push-report.d.mts +28 -0
  150. package/dist/transport/client/receive-pack/push-report.mjs +84 -0
  151. package/dist/transport/client/receive-pack/push-request-plan.mjs +52 -0
  152. package/dist/transport/client/receive-pack/push.d.mts +32 -0
  153. package/dist/transport/client/receive-pack/push.mjs +97 -0
  154. package/dist/transport/client/receive-pack/request.d.mts +39 -0
  155. package/dist/transport/client/receive-pack/request.mjs +46 -0
  156. package/dist/transport/client/receive-pack/response.d.mts +26 -0
  157. package/dist/transport/client/receive-pack/response.mjs +52 -0
  158. package/dist/transport/client/receive-pack/result.d.mts +34 -0
  159. package/dist/transport/client/receive-pack/result.mjs +100 -0
  160. package/dist/transport/client/upload-pack/capability-advertisement.d.mts +56 -0
  161. package/dist/transport/client/upload-pack/capability-advertisement.mjs +130 -0
  162. package/dist/transport/client/upload-pack/fetch.d.mts +109 -0
  163. package/dist/transport/client/upload-pack/fetch.mjs +392 -0
  164. package/dist/transport/client/upload-pack/http.d.mts +29 -0
  165. package/dist/transport/client/upload-pack/http.mjs +79 -0
  166. package/dist/transport/client/upload-pack/ls-refs.d.mts +75 -0
  167. package/dist/transport/client/upload-pack/ls-refs.mjs +150 -0
  168. package/dist/transport/client/upload-pack/object-info.d.mts +65 -0
  169. package/dist/transport/client/upload-pack/object-info.mjs +111 -0
  170. package/dist/transport/client/upload-pack/types.d.mts +153 -0
  171. package/dist/transport/http/index.d.mts +3 -0
  172. package/dist/transport/http/index.mjs +2 -0
  173. package/dist/transport/http/smart-http.d.mts +46 -0
  174. package/dist/transport/http/smart-http.mjs +176 -0
  175. package/dist/transport/http/types.d.mts +27 -0
  176. package/dist/transport/index.d.mts +9 -0
  177. package/dist/transport/index.mjs +8 -0
  178. package/dist/transport/protocol/object-graph.d.mts +63 -0
  179. package/dist/transport/protocol/object-graph.mjs +149 -0
  180. package/dist/transport/protocol/pkt-line.d.mts +109 -0
  181. package/dist/transport/protocol/pkt-line.mjs +195 -0
  182. package/dist/transport/protocol/ref-advertisement.mjs +185 -0
  183. package/dist/transport/protocol/ref-collection.d.mts +38 -0
  184. package/dist/transport/protocol/ref-collection.mjs +63 -0
  185. package/dist/transport/protocol/ref-match.d.mts +39 -0
  186. package/dist/transport/protocol/ref-match.mjs +42 -0
  187. package/dist/transport/protocol/refspec.d.mts +44 -0
  188. package/dist/transport/protocol/refspec.mjs +79 -0
  189. package/dist/transport/protocol/side-band.d.mts +65 -0
  190. package/dist/transport/protocol/side-band.mjs +142 -0
  191. package/dist/transport/protocol/transport-capabilities.mjs +46 -0
  192. package/dist/transport/protocol/types.d.mts +148 -0
  193. package/dist/transport/protocol/update-refs.d.mts +68 -0
  194. package/dist/transport/protocol/update-refs.mjs +126 -0
  195. package/dist/transport/receive-pack.d.mts +11 -0
  196. package/dist/transport/receive-pack.mjs +11 -0
  197. package/dist/transport/server/receive-pack/advertise.d.mts +28 -0
  198. package/dist/transport/server/receive-pack/advertise.mjs +88 -0
  199. package/dist/transport/server/receive-pack/handler.d.mts +30 -0
  200. package/dist/transport/server/receive-pack/handler.mjs +156 -0
  201. package/dist/transport/server/receive-pack/index.d.mts +6 -0
  202. package/dist/transport/server/receive-pack/index.mjs +6 -0
  203. package/dist/transport/server/receive-pack/parse.d.mts +17 -0
  204. package/dist/transport/server/receive-pack/parse.mjs +80 -0
  205. package/dist/transport/server/receive-pack/report-status.mjs +64 -0
  206. package/dist/transport/server/receive-pack/service.d.mts +41 -0
  207. package/dist/transport/server/receive-pack/service.mjs +39 -0
  208. package/dist/transport/server/receive-pack/types.d.mts +56 -0
  209. package/dist/transport/server/receive-pack/types.mjs +25 -0
  210. package/dist/transport/server/receive-pack/unpack.mjs +119 -0
  211. package/dist/transport/server/upload-pack/advertise.d.mts +20 -0
  212. package/dist/transport/server/upload-pack/advertise.mjs +30 -0
  213. package/dist/transport/server/upload-pack/command.d.mts +43 -0
  214. package/dist/transport/server/upload-pack/command.mjs +56 -0
  215. package/dist/transport/server/upload-pack/fetch.d.mts +43 -0
  216. package/dist/transport/server/upload-pack/fetch.mjs +217 -0
  217. package/dist/transport/server/upload-pack/index.d.mts +7 -0
  218. package/dist/transport/server/upload-pack/index.mjs +7 -0
  219. package/dist/transport/server/upload-pack/ls-refs.d.mts +38 -0
  220. package/dist/transport/server/upload-pack/ls-refs.mjs +113 -0
  221. package/dist/transport/server/upload-pack/service.d.mts +40 -0
  222. package/dist/transport/server/upload-pack/service.mjs +51 -0
  223. package/dist/transport/server/upload-pack/types.d.mts +11 -0
  224. package/dist/transport/server/upload-pack/types.mjs +21 -0
  225. package/dist/transport/upload-pack.d.mts +7 -0
  226. package/dist/transport/upload-pack.mjs +6 -0
  227. 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 };