gitx.do 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -415
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -174
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -131
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/commit.d.ts +0 -182
  32. package/dist/cli/commands/commit.d.ts.map +0 -1
  33. package/dist/cli/commands/commit.js +0 -437
  34. package/dist/cli/commands/commit.js.map +0 -1
  35. package/dist/cli/commands/diff.d.ts +0 -464
  36. package/dist/cli/commands/diff.d.ts.map +0 -1
  37. package/dist/cli/commands/diff.js +0 -958
  38. package/dist/cli/commands/diff.js.map +0 -1
  39. package/dist/cli/commands/log.d.ts +0 -239
  40. package/dist/cli/commands/log.d.ts.map +0 -1
  41. package/dist/cli/commands/log.js +0 -535
  42. package/dist/cli/commands/log.js.map +0 -1
  43. package/dist/cli/commands/merge.d.ts +0 -106
  44. package/dist/cli/commands/merge.d.ts.map +0 -1
  45. package/dist/cli/commands/merge.js +0 -55
  46. package/dist/cli/commands/merge.js.map +0 -1
  47. package/dist/cli/commands/review.d.ts +0 -457
  48. package/dist/cli/commands/review.d.ts.map +0 -1
  49. package/dist/cli/commands/review.js +0 -533
  50. package/dist/cli/commands/review.js.map +0 -1
  51. package/dist/cli/commands/status.d.ts +0 -269
  52. package/dist/cli/commands/status.d.ts.map +0 -1
  53. package/dist/cli/commands/status.js +0 -493
  54. package/dist/cli/commands/status.js.map +0 -1
  55. package/dist/cli/commands/web.d.ts +0 -199
  56. package/dist/cli/commands/web.d.ts.map +0 -1
  57. package/dist/cli/commands/web.js +0 -696
  58. package/dist/cli/commands/web.js.map +0 -1
  59. package/dist/cli/fs-adapter.d.ts +0 -656
  60. package/dist/cli/fs-adapter.d.ts.map +0 -1
  61. package/dist/cli/fs-adapter.js +0 -1179
  62. package/dist/cli/fs-adapter.js.map +0 -1
  63. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  64. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  65. package/dist/cli/fsx-cli-adapter.js +0 -619
  66. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  67. package/dist/cli/index.d.ts +0 -387
  68. package/dist/cli/index.d.ts.map +0 -1
  69. package/dist/cli/index.js +0 -523
  70. package/dist/cli/index.js.map +0 -1
  71. package/dist/cli/ui/components/DiffView.d.ts +0 -7
  72. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  73. package/dist/cli/ui/components/DiffView.js +0 -11
  74. package/dist/cli/ui/components/DiffView.js.map +0 -1
  75. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
  76. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  77. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  78. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  79. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
  80. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  81. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  82. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  83. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
  84. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  85. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  86. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  87. package/dist/cli/ui/components/NavigationList.d.ts +0 -9
  88. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  89. package/dist/cli/ui/components/NavigationList.js +0 -11
  90. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  91. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
  92. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  93. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  94. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  95. package/dist/cli/ui/components/index.d.ts +0 -7
  96. package/dist/cli/ui/components/index.d.ts.map +0 -1
  97. package/dist/cli/ui/components/index.js +0 -9
  98. package/dist/cli/ui/components/index.js.map +0 -1
  99. package/dist/cli/ui/terminal-ui.d.ts +0 -52
  100. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  101. package/dist/cli/ui/terminal-ui.js +0 -121
  102. package/dist/cli/ui/terminal-ui.js.map +0 -1
  103. package/dist/do/BashModule.d.ts +0 -871
  104. package/dist/do/BashModule.d.ts.map +0 -1
  105. package/dist/do/BashModule.js +0 -1143
  106. package/dist/do/BashModule.js.map +0 -1
  107. package/dist/do/FsModule.d.ts +0 -601
  108. package/dist/do/FsModule.d.ts.map +0 -1
  109. package/dist/do/FsModule.js +0 -1120
  110. package/dist/do/FsModule.js.map +0 -1
  111. package/dist/do/GitModule.d.ts +0 -635
  112. package/dist/do/GitModule.d.ts.map +0 -1
  113. package/dist/do/GitModule.js +0 -781
  114. package/dist/do/GitModule.js.map +0 -1
  115. package/dist/do/GitRepoDO.d.ts +0 -281
  116. package/dist/do/GitRepoDO.d.ts.map +0 -1
  117. package/dist/do/GitRepoDO.js +0 -479
  118. package/dist/do/GitRepoDO.js.map +0 -1
  119. package/dist/do/bash-ast.d.ts +0 -246
  120. package/dist/do/bash-ast.d.ts.map +0 -1
  121. package/dist/do/bash-ast.js +0 -888
  122. package/dist/do/bash-ast.js.map +0 -1
  123. package/dist/do/container-executor.d.ts +0 -491
  124. package/dist/do/container-executor.d.ts.map +0 -1
  125. package/dist/do/container-executor.js +0 -730
  126. package/dist/do/container-executor.js.map +0 -1
  127. package/dist/do/index.d.ts +0 -53
  128. package/dist/do/index.d.ts.map +0 -1
  129. package/dist/do/index.js +0 -91
  130. package/dist/do/index.js.map +0 -1
  131. package/dist/do/tiered-storage.d.ts +0 -403
  132. package/dist/do/tiered-storage.d.ts.map +0 -1
  133. package/dist/do/tiered-storage.js +0 -689
  134. package/dist/do/tiered-storage.js.map +0 -1
  135. package/dist/do/withBash.d.ts +0 -231
  136. package/dist/do/withBash.d.ts.map +0 -1
  137. package/dist/do/withBash.js +0 -244
  138. package/dist/do/withBash.js.map +0 -1
  139. package/dist/do/withFs.d.ts +0 -237
  140. package/dist/do/withFs.d.ts.map +0 -1
  141. package/dist/do/withFs.js +0 -387
  142. package/dist/do/withFs.js.map +0 -1
  143. package/dist/do/withGit.d.ts +0 -180
  144. package/dist/do/withGit.d.ts.map +0 -1
  145. package/dist/do/withGit.js +0 -271
  146. package/dist/do/withGit.js.map +0 -1
  147. package/dist/durable-object/object-store.d.ts +0 -633
  148. package/dist/durable-object/object-store.d.ts.map +0 -1
  149. package/dist/durable-object/object-store.js +0 -1161
  150. package/dist/durable-object/object-store.js.map +0 -1
  151. package/dist/durable-object/schema.d.ts.map +0 -1
  152. package/dist/durable-object/schema.js.map +0 -1
  153. package/dist/durable-object/wal.d.ts +0 -416
  154. package/dist/durable-object/wal.d.ts.map +0 -1
  155. package/dist/durable-object/wal.js +0 -445
  156. package/dist/durable-object/wal.js.map +0 -1
  157. package/dist/mcp/adapter.d.ts +0 -772
  158. package/dist/mcp/adapter.d.ts.map +0 -1
  159. package/dist/mcp/adapter.js +0 -895
  160. package/dist/mcp/adapter.js.map +0 -1
  161. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  162. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  163. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  164. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  165. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  166. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  167. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  168. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  169. package/dist/mcp/sandbox/template.d.ts +0 -17
  170. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/template.js +0 -71
  172. package/dist/mcp/sandbox/template.js.map +0 -1
  173. package/dist/mcp/sandbox.d.ts +0 -764
  174. package/dist/mcp/sandbox.d.ts.map +0 -1
  175. package/dist/mcp/sandbox.js +0 -1362
  176. package/dist/mcp/sandbox.js.map +0 -1
  177. package/dist/mcp/sdk-adapter.d.ts +0 -835
  178. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  179. package/dist/mcp/sdk-adapter.js +0 -974
  180. package/dist/mcp/sdk-adapter.js.map +0 -1
  181. package/dist/mcp/tools/do.d.ts +0 -32
  182. package/dist/mcp/tools/do.d.ts.map +0 -1
  183. package/dist/mcp/tools/do.js +0 -115
  184. package/dist/mcp/tools/do.js.map +0 -1
  185. package/dist/mcp/tools.d.ts +0 -548
  186. package/dist/mcp/tools.d.ts.map +0 -1
  187. package/dist/mcp/tools.js +0 -1934
  188. package/dist/mcp/tools.js.map +0 -1
  189. package/dist/ops/blame.d.ts +0 -551
  190. package/dist/ops/blame.d.ts.map +0 -1
  191. package/dist/ops/blame.js +0 -1037
  192. package/dist/ops/blame.js.map +0 -1
  193. package/dist/ops/branch.d.ts +0 -766
  194. package/dist/ops/branch.d.ts.map +0 -1
  195. package/dist/ops/branch.js +0 -950
  196. package/dist/ops/branch.js.map +0 -1
  197. package/dist/ops/commit-traversal.d.ts +0 -349
  198. package/dist/ops/commit-traversal.d.ts.map +0 -1
  199. package/dist/ops/commit-traversal.js +0 -821
  200. package/dist/ops/commit-traversal.js.map +0 -1
  201. package/dist/ops/commit.d.ts +0 -555
  202. package/dist/ops/commit.d.ts.map +0 -1
  203. package/dist/ops/commit.js +0 -826
  204. package/dist/ops/commit.js.map +0 -1
  205. package/dist/ops/merge-base.d.ts +0 -397
  206. package/dist/ops/merge-base.d.ts.map +0 -1
  207. package/dist/ops/merge-base.js +0 -691
  208. package/dist/ops/merge-base.js.map +0 -1
  209. package/dist/ops/merge.d.ts +0 -855
  210. package/dist/ops/merge.d.ts.map +0 -1
  211. package/dist/ops/merge.js +0 -1551
  212. package/dist/ops/merge.js.map +0 -1
  213. package/dist/ops/tag.d.ts +0 -247
  214. package/dist/ops/tag.d.ts.map +0 -1
  215. package/dist/ops/tag.js +0 -649
  216. package/dist/ops/tag.js.map +0 -1
  217. package/dist/ops/tree-builder.d.ts +0 -178
  218. package/dist/ops/tree-builder.d.ts.map +0 -1
  219. package/dist/ops/tree-builder.js +0 -271
  220. package/dist/ops/tree-builder.js.map +0 -1
  221. package/dist/ops/tree-diff.d.ts +0 -291
  222. package/dist/ops/tree-diff.d.ts.map +0 -1
  223. package/dist/ops/tree-diff.js +0 -705
  224. package/dist/ops/tree-diff.js.map +0 -1
  225. package/dist/pack/delta.d.ts +0 -248
  226. package/dist/pack/delta.d.ts.map +0 -1
  227. package/dist/pack/delta.js +0 -736
  228. package/dist/pack/delta.js.map +0 -1
  229. package/dist/pack/format.d.ts +0 -446
  230. package/dist/pack/format.d.ts.map +0 -1
  231. package/dist/pack/format.js +0 -572
  232. package/dist/pack/format.js.map +0 -1
  233. package/dist/pack/full-generation.d.ts +0 -612
  234. package/dist/pack/full-generation.d.ts.map +0 -1
  235. package/dist/pack/full-generation.js +0 -1378
  236. package/dist/pack/full-generation.js.map +0 -1
  237. package/dist/pack/generation.d.ts +0 -441
  238. package/dist/pack/generation.d.ts.map +0 -1
  239. package/dist/pack/generation.js +0 -707
  240. package/dist/pack/generation.js.map +0 -1
  241. package/dist/pack/index.d.ts +0 -502
  242. package/dist/pack/index.d.ts.map +0 -1
  243. package/dist/pack/index.js +0 -833
  244. package/dist/pack/index.js.map +0 -1
  245. package/dist/refs/branch.d.ts +0 -668
  246. package/dist/refs/branch.d.ts.map +0 -1
  247. package/dist/refs/branch.js +0 -897
  248. package/dist/refs/branch.js.map +0 -1
  249. package/dist/refs/storage.d.ts +0 -833
  250. package/dist/refs/storage.d.ts.map +0 -1
  251. package/dist/refs/storage.js +0 -1023
  252. package/dist/refs/storage.js.map +0 -1
  253. package/dist/refs/tag.d.ts +0 -860
  254. package/dist/refs/tag.d.ts.map +0 -1
  255. package/dist/refs/tag.js +0 -996
  256. package/dist/refs/tag.js.map +0 -1
  257. package/dist/storage/backend.d.ts +0 -425
  258. package/dist/storage/backend.d.ts.map +0 -1
  259. package/dist/storage/backend.js +0 -41
  260. package/dist/storage/backend.js.map +0 -1
  261. package/dist/storage/fsx-adapter.d.ts +0 -204
  262. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  263. package/dist/storage/fsx-adapter.js +0 -470
  264. package/dist/storage/fsx-adapter.js.map +0 -1
  265. package/dist/storage/lru-cache.d.ts +0 -691
  266. package/dist/storage/lru-cache.d.ts.map +0 -1
  267. package/dist/storage/lru-cache.js +0 -813
  268. package/dist/storage/lru-cache.js.map +0 -1
  269. package/dist/storage/object-index.d.ts +0 -585
  270. package/dist/storage/object-index.d.ts.map +0 -1
  271. package/dist/storage/object-index.js +0 -532
  272. package/dist/storage/object-index.js.map +0 -1
  273. package/dist/storage/r2-pack.d.ts +0 -1257
  274. package/dist/storage/r2-pack.d.ts.map +0 -1
  275. package/dist/storage/r2-pack.js +0 -1770
  276. package/dist/storage/r2-pack.js.map +0 -1
  277. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  278. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  279. package/dist/tiered/cdc-pipeline.js +0 -1880
  280. package/dist/tiered/cdc-pipeline.js.map +0 -1
  281. package/dist/tiered/migration.d.ts +0 -1104
  282. package/dist/tiered/migration.d.ts.map +0 -1
  283. package/dist/tiered/migration.js +0 -1214
  284. package/dist/tiered/migration.js.map +0 -1
  285. package/dist/tiered/parquet-writer.d.ts +0 -1145
  286. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  287. package/dist/tiered/parquet-writer.js +0 -1183
  288. package/dist/tiered/parquet-writer.js.map +0 -1
  289. package/dist/tiered/read-path.d.ts +0 -835
  290. package/dist/tiered/read-path.d.ts.map +0 -1
  291. package/dist/tiered/read-path.js +0 -487
  292. package/dist/tiered/read-path.js.map +0 -1
  293. package/dist/types/capability.d.ts +0 -1385
  294. package/dist/types/capability.d.ts.map +0 -1
  295. package/dist/types/capability.js +0 -36
  296. package/dist/types/capability.js.map +0 -1
  297. package/dist/types/index.d.ts +0 -13
  298. package/dist/types/index.d.ts.map +0 -1
  299. package/dist/types/index.js +0 -18
  300. package/dist/types/index.js.map +0 -1
  301. package/dist/types/objects.d.ts +0 -692
  302. package/dist/types/objects.d.ts.map +0 -1
  303. package/dist/types/objects.js +0 -837
  304. package/dist/types/objects.js.map +0 -1
  305. package/dist/types/storage.d.ts +0 -603
  306. package/dist/types/storage.d.ts.map +0 -1
  307. package/dist/types/storage.js +0 -191
  308. package/dist/types/storage.js.map +0 -1
  309. package/dist/types/worker-loader.d.ts +0 -60
  310. package/dist/types/worker-loader.d.ts.map +0 -1
  311. package/dist/types/worker-loader.js +0 -62
  312. package/dist/types/worker-loader.js.map +0 -1
  313. package/dist/utils/hash.d.ts +0 -197
  314. package/dist/utils/hash.d.ts.map +0 -1
  315. package/dist/utils/hash.js +0 -268
  316. package/dist/utils/hash.js.map +0 -1
  317. package/dist/utils/sha1.d.ts +0 -290
  318. package/dist/utils/sha1.d.ts.map +0 -1
  319. package/dist/utils/sha1.js +0 -582
  320. package/dist/utils/sha1.js.map +0 -1
  321. package/dist/wire/capabilities.d.ts +0 -1044
  322. package/dist/wire/capabilities.d.ts.map +0 -1
  323. package/dist/wire/capabilities.js +0 -941
  324. package/dist/wire/capabilities.js.map +0 -1
  325. package/dist/wire/path-security.d.ts +0 -157
  326. package/dist/wire/path-security.d.ts.map +0 -1
  327. package/dist/wire/path-security.js +0 -307
  328. package/dist/wire/path-security.js.map +0 -1
  329. package/dist/wire/pkt-line.d.ts +0 -345
  330. package/dist/wire/pkt-line.d.ts.map +0 -1
  331. package/dist/wire/pkt-line.js +0 -381
  332. package/dist/wire/pkt-line.js.map +0 -1
  333. package/dist/wire/receive-pack.d.ts +0 -1059
  334. package/dist/wire/receive-pack.d.ts.map +0 -1
  335. package/dist/wire/receive-pack.js +0 -1414
  336. package/dist/wire/receive-pack.js.map +0 -1
  337. package/dist/wire/smart-http.d.ts +0 -799
  338. package/dist/wire/smart-http.d.ts.map +0 -1
  339. package/dist/wire/smart-http.js +0 -945
  340. package/dist/wire/smart-http.js.map +0 -1
  341. package/dist/wire/upload-pack.d.ts +0 -727
  342. package/dist/wire/upload-pack.d.ts.map +0 -1
  343. package/dist/wire/upload-pack.js +0 -1138
  344. package/dist/wire/upload-pack.js.map +0 -1
@@ -1,1023 +0,0 @@
1
- /**
2
- * @fileoverview Git Reference Storage System
3
- *
4
- * This module provides a complete implementation of Git reference management,
5
- * including branches, tags, HEAD, and symbolic refs. It supports both loose refs
6
- * (individual files) and packed refs (consolidated file).
7
- *
8
- * **Key Concepts**:
9
- * - **Direct refs**: Point directly to a SHA-1 hash (e.g., branch pointing to commit)
10
- * - **Symbolic refs**: Point to another ref (e.g., HEAD -> refs/heads/main)
11
- * - **Loose refs**: Individual ref files in .git/refs/
12
- * - **Packed refs**: Consolidated refs in .git/packed-refs for efficiency
13
- *
14
- * **Backend Support**:
15
- * - `RefStorageBackend`: Full-featured backend with locking and packed refs
16
- * - `StorageBackend`: Simpler backend interface (optional, from storage/backend.ts)
17
- *
18
- * @module refs/storage
19
- *
20
- * @example
21
- * ```typescript
22
- * import { RefStorage, isValidRefName, isValidSha } from './refs/storage'
23
- *
24
- * // Create storage with RefStorageBackend (full features)
25
- * const storage = new RefStorage(backend)
26
- *
27
- * // Or create with StorageBackend (simpler, optional)
28
- * const storage = new RefStorage({ storageBackend: myStorageBackend })
29
- *
30
- * // Resolve HEAD to get current commit
31
- * const resolved = await storage.resolveRef('HEAD')
32
- * console.log(`Current commit: ${resolved.sha}`)
33
- *
34
- * // Update a branch
35
- * await storage.updateRef('refs/heads/feature', newCommitSha, { create: true })
36
- *
37
- * // List all branches
38
- * const branches = await storage.listBranches()
39
- * ```
40
- */
41
- /**
42
- * Error thrown when a ref operation fails.
43
- *
44
- * @description
45
- * Provides structured error information including error code
46
- * and the ref name that caused the error.
47
- *
48
- * @example
49
- * ```typescript
50
- * try {
51
- * await storage.updateRef('refs/heads/main', sha)
52
- * } catch (e) {
53
- * if (e instanceof RefError) {
54
- * switch (e.code) {
55
- * case 'NOT_FOUND': // Ref doesn't exist
56
- * case 'CONFLICT': // CAS failed
57
- * case 'LOCKED': // Ref is locked
58
- * }
59
- * }
60
- * }
61
- * ```
62
- */
63
- export class RefError extends Error {
64
- code;
65
- refName;
66
- /**
67
- * Create a new RefError.
68
- *
69
- * @param message - Human-readable error message
70
- * @param code - Error code for programmatic handling
71
- * @param refName - The ref that caused the error (optional)
72
- */
73
- constructor(message, code, refName) {
74
- super(message);
75
- this.code = code;
76
- this.refName = refName;
77
- this.name = 'RefError';
78
- }
79
- }
80
- // ============================================================================
81
- // Validation Functions
82
- // ============================================================================
83
- /**
84
- * Validate a ref name according to Git rules.
85
- *
86
- * @description
87
- * Git has specific rules for valid ref names. This function implements
88
- * the validation from `git check-ref-format`.
89
- *
90
- * **Rules**:
91
- * - Cannot be empty or just '@'
92
- * - Cannot end with '/' or '.lock'
93
- * - Cannot contain '..', '@{', control chars, space, ~, ^, :, ?, *, [, \
94
- * - Components cannot start or end with '.'
95
- * - HEAD is always valid
96
- *
97
- * @param name - Ref name to validate
98
- * @returns True if the name is valid
99
- *
100
- * @see https://git-scm.com/docs/git-check-ref-format
101
- *
102
- * @example
103
- * ```typescript
104
- * isValidRefName('refs/heads/main') // true
105
- * isValidRefName('refs/heads/feature/x') // true
106
- * isValidRefName('HEAD') // true
107
- * isValidRefName('refs/heads/../main') // false (contains ..)
108
- * isValidRefName('refs/heads/.hidden') // false (component starts with .)
109
- * isValidRefName('refs/heads/foo.lock') // false (ends with .lock)
110
- * ```
111
- */
112
- export function isValidRefName(name) {
113
- // HEAD is always valid
114
- if (name === 'HEAD') {
115
- return true;
116
- }
117
- // Just @ is invalid
118
- if (name === '@') {
119
- return false;
120
- }
121
- // Cannot be empty
122
- if (!name || name.length === 0) {
123
- return false;
124
- }
125
- // Cannot end with /
126
- if (name.endsWith('/')) {
127
- return false;
128
- }
129
- // Cannot end with .lock
130
- if (name.endsWith('.lock')) {
131
- return false;
132
- }
133
- // Cannot contain @{
134
- if (name.includes('@{')) {
135
- return false;
136
- }
137
- // Cannot contain ..
138
- if (name.includes('..')) {
139
- return false;
140
- }
141
- // Cannot contain control characters (ASCII 0-31), space, ~, ^, :, ?, *, [, \
142
- const invalidChars = /[\x00-\x1f\x7f ~^:?*[\]\\]/;
143
- if (invalidChars.test(name)) {
144
- return false;
145
- }
146
- // Split into components and check each
147
- const components = name.split('/');
148
- for (const component of components) {
149
- // Cannot have empty components (// in path)
150
- if (component.length === 0) {
151
- return false;
152
- }
153
- // Cannot start with .
154
- if (component.startsWith('.')) {
155
- return false;
156
- }
157
- // Cannot end with .
158
- if (component.endsWith('.')) {
159
- return false;
160
- }
161
- }
162
- return true;
163
- }
164
- /**
165
- * Validate a SHA-1 hash string.
166
- *
167
- * @description
168
- * SHA-1 hashes must be exactly 40 hexadecimal characters.
169
- * This validates the format, not whether the object exists.
170
- *
171
- * @param sha - SHA string to validate
172
- * @returns True if the string is a valid SHA-1 format
173
- *
174
- * @example
175
- * ```typescript
176
- * isValidSha('abc123def456789...') // true (if 40 hex chars)
177
- * isValidSha('abc123') // false (too short)
178
- * isValidSha('xyz...') // false (invalid hex)
179
- * isValidSha(null) // false
180
- * ```
181
- */
182
- export function isValidSha(sha) {
183
- // Must be exactly 40 characters
184
- if (!sha || sha.length !== 40) {
185
- return false;
186
- }
187
- // Must be valid hex
188
- return /^[0-9a-fA-F]{40}$/.test(sha);
189
- }
190
- // ============================================================================
191
- // Parsing and Serialization Functions
192
- // ============================================================================
193
- /**
194
- * Parse ref file content into type and target.
195
- *
196
- * @description
197
- * Ref files either contain a SHA directly or "ref: <target>" for symbolic refs.
198
- *
199
- * @param content - Raw ref file content
200
- * @returns Parsed type and target
201
- *
202
- * @example
203
- * ```typescript
204
- * // Direct ref
205
- * parseRefContent('abc123def456...\n')
206
- * // => { type: 'direct', target: 'abc123def456...' }
207
- *
208
- * // Symbolic ref
209
- * parseRefContent('ref: refs/heads/main\n')
210
- * // => { type: 'symbolic', target: 'refs/heads/main' }
211
- * ```
212
- */
213
- export function parseRefContent(content) {
214
- const trimmed = content.trim();
215
- // Check for symbolic ref (starts with "ref:")
216
- if (trimmed.startsWith('ref:')) {
217
- const target = trimmed.slice(4).trim();
218
- return { type: 'symbolic', target };
219
- }
220
- // Otherwise it's a direct ref (SHA)
221
- return { type: 'direct', target: trimmed };
222
- }
223
- /**
224
- * Serialize a ref to file content format.
225
- *
226
- * @description
227
- * Converts a Ref object to the string format stored in ref files.
228
- *
229
- * @param ref - Ref to serialize
230
- * @returns File content string (with trailing newline)
231
- *
232
- * @example
233
- * ```typescript
234
- * serializeRefContent({ name: 'HEAD', target: 'refs/heads/main', type: 'symbolic' })
235
- * // => 'ref: refs/heads/main\n'
236
- *
237
- * serializeRefContent({ name: 'refs/heads/main', target: 'abc123...', type: 'direct' })
238
- * // => 'abc123...\n'
239
- * ```
240
- */
241
- export function serializeRefContent(ref) {
242
- if (ref.type === 'symbolic') {
243
- return `ref: ${ref.target}\n`;
244
- }
245
- return `${ref.target}\n`;
246
- }
247
- /**
248
- * Parse packed-refs file content.
249
- *
250
- * @description
251
- * The packed-refs file contains multiple refs in a space-efficient format.
252
- * Format: "<sha> <refname>" on each line, with optional comments (#) and
253
- * peeled entries (^sha for annotated tags).
254
- *
255
- * @param content - Raw packed-refs file content
256
- * @returns Map of ref names to SHA values
257
- *
258
- * @example
259
- * ```typescript
260
- * const content = `# pack-refs with: peeled fully-peeled sorted
261
- * abc123 refs/heads/main
262
- * def456 refs/tags/v1.0.0
263
- * ^aaa111
264
- * `
265
- * const refs = parsePackedRefs(content)
266
- * // Map { 'refs/heads/main' => 'abc123', 'refs/tags/v1.0.0' => 'def456' }
267
- * ```
268
- */
269
- export function parsePackedRefs(content) {
270
- const refs = new Map();
271
- if (!content) {
272
- return refs;
273
- }
274
- const lines = content.split(/\r?\n/);
275
- for (const line of lines) {
276
- const trimmed = line.trim();
277
- // Skip empty lines
278
- if (!trimmed) {
279
- continue;
280
- }
281
- // Skip comment lines
282
- if (trimmed.startsWith('#')) {
283
- continue;
284
- }
285
- // Skip peeled entries (lines starting with ^)
286
- if (trimmed.startsWith('^')) {
287
- continue;
288
- }
289
- // Parse "sha refname" format
290
- const spaceIndex = trimmed.indexOf(' ');
291
- if (spaceIndex > 0) {
292
- const sha = trimmed.slice(0, spaceIndex);
293
- const refName = trimmed.slice(spaceIndex + 1);
294
- refs.set(refName, sha);
295
- }
296
- }
297
- return refs;
298
- }
299
- /**
300
- * Serialize refs to packed-refs file format.
301
- *
302
- * @description
303
- * Creates the content for a packed-refs file from a map of refs.
304
- * Refs are sorted alphabetically for consistency.
305
- *
306
- * @param refs - Map of ref names to SHA values
307
- * @returns Packed-refs file content
308
- *
309
- * @example
310
- * ```typescript
311
- * const refs = new Map([
312
- * ['refs/heads/main', 'abc123...'],
313
- * ['refs/tags/v1.0.0', 'def456...']
314
- * ])
315
- * const content = serializePackedRefs(refs)
316
- * // '# pack-refs with: peeled fully-peeled sorted\nabc123... refs/heads/main\n...'
317
- * ```
318
- */
319
- export function serializePackedRefs(refs) {
320
- const lines = ['# pack-refs with: peeled fully-peeled sorted'];
321
- // Sort refs alphabetically
322
- const sortedRefs = Array.from(refs.entries()).sort((a, b) => a[0].localeCompare(b[0]));
323
- for (const [refName, sha] of sortedRefs) {
324
- lines.push(`${sha} ${refName}`);
325
- }
326
- return lines.join('\n') + '\n';
327
- }
328
- // ============================================================================
329
- // RefStorage Class
330
- // ============================================================================
331
- /**
332
- * Reference storage manager.
333
- *
334
- * @description
335
- * Provides a high-level API for managing Git references. Handles ref
336
- * resolution, updates with locking, symbolic refs, and packed refs.
337
- *
338
- * Supports two backend modes:
339
- * 1. RefStorageBackend - Full features including locking and packed refs
340
- * 2. StorageBackend - Simpler interface with basic ref operations
341
- *
342
- * @example
343
- * ```typescript
344
- * // With RefStorageBackend (full features)
345
- * const storage = new RefStorage(myBackend)
346
- *
347
- * // With StorageBackend (simpler)
348
- * const storage = new RefStorage({ storageBackend: myStorageBackend })
349
- *
350
- * // Get current branch
351
- * const head = await storage.getHead()
352
- * if (head.type === 'symbolic') {
353
- * console.log(`On branch: ${head.target}`)
354
- * }
355
- *
356
- * // Resolve to SHA
357
- * const resolved = await storage.resolveRef('HEAD')
358
- * console.log(`Current commit: ${resolved.sha}`)
359
- *
360
- * // Create a branch
361
- * await storage.updateRef('refs/heads/feature', commitSha, { create: true })
362
- * ```
363
- */
364
- export class RefStorage {
365
- backend;
366
- storageBackend;
367
- /**
368
- * Create a new RefStorage instance.
369
- *
370
- * @param backendOrOptions - Either a RefStorageBackend directly, or options with storageBackend
371
- *
372
- * @example
373
- * ```typescript
374
- * // Direct backend
375
- * const storage = new RefStorage(myRefStorageBackend)
376
- *
377
- * // Options with StorageBackend
378
- * const storage = new RefStorage({ storageBackend: myStorageBackend })
379
- * ```
380
- */
381
- constructor(backendOrOptions) {
382
- // Type guard: RefStorageOptions has storageBackend, RefStorageBackend has readRef
383
- if ('storageBackend' in backendOrOptions && !('readRef' in backendOrOptions)) {
384
- // Options object with StorageBackend
385
- this.storageBackend = backendOrOptions.storageBackend;
386
- }
387
- else {
388
- // Direct RefStorageBackend
389
- this.backend = backendOrOptions;
390
- }
391
- }
392
- /**
393
- * Internal helper to get a lock (or no-op lock for StorageBackend).
394
- *
395
- * @description
396
- * When using StorageBackend, returns a no-op lock since StorageBackend
397
- * doesn't support locking. Callers should still use try/finally to release.
398
- */
399
- async getLock(name, _timeout) {
400
- if (this.storageBackend) {
401
- // No-op lock for StorageBackend
402
- return {
403
- refName: name,
404
- release: async () => { },
405
- isHeld: () => true
406
- };
407
- }
408
- if (!this.backend) {
409
- throw new Error('No backend configured');
410
- }
411
- return this.backend.acquireLock(name, _timeout);
412
- }
413
- /**
414
- * Internal helper to write a ref.
415
- */
416
- async writeRef(ref) {
417
- if (this.storageBackend) {
418
- await this.storageBackend.setRef(ref.name, ref);
419
- return;
420
- }
421
- if (!this.backend) {
422
- throw new Error('No backend configured');
423
- }
424
- await this.backend.writeRef(ref);
425
- }
426
- /**
427
- * Internal helper to delete a ref.
428
- */
429
- async removeRef(name) {
430
- if (this.storageBackend) {
431
- await this.storageBackend.deleteRef(name);
432
- return true;
433
- }
434
- if (!this.backend) {
435
- throw new Error('No backend configured');
436
- }
437
- return this.backend.deleteRef(name);
438
- }
439
- /**
440
- * Internal helper to list refs.
441
- */
442
- async getAllRefs(pattern) {
443
- if (this.storageBackend) {
444
- return this.storageBackend.listRefs(pattern);
445
- }
446
- if (!this.backend) {
447
- throw new Error('No backend configured');
448
- }
449
- return this.backend.listRefs(pattern);
450
- }
451
- /**
452
- * Get a ref by name.
453
- *
454
- * @description
455
- * Retrieves a ref without resolving symbolic refs.
456
- * Use `resolveRef` to follow symbolic refs to their final target.
457
- *
458
- * @param name - Full ref name
459
- * @returns The ref or null if not found
460
- * @throws Error if no backend is configured
461
- *
462
- * @example
463
- * ```typescript
464
- * const head = await storage.getRef('HEAD')
465
- * if (head && head.type === 'symbolic') {
466
- * console.log(`HEAD points to ${head.target}`)
467
- * }
468
- * ```
469
- */
470
- async getRef(name) {
471
- // Use StorageBackend if available
472
- if (this.storageBackend) {
473
- return this.storageBackend.getRef(name);
474
- }
475
- // Fall back to RefStorageBackend
476
- if (!this.backend?.readRef) {
477
- throw new Error('No backend configured or backend does not support readRef');
478
- }
479
- return this.backend.readRef(name);
480
- }
481
- /**
482
- * Resolve a ref to its final SHA target.
483
- *
484
- * @description
485
- * Follows symbolic refs until reaching a direct ref, then returns
486
- * the SHA and the chain of refs followed.
487
- *
488
- * @param name - Ref name to resolve
489
- * @param options - Resolution options (maxDepth)
490
- * @returns Resolved ref with SHA and chain
491
- * @throws RefError with code 'NOT_FOUND' if ref doesn't exist
492
- * @throws RefError with code 'CIRCULAR_REF' if circular reference detected
493
- * @throws RefError with code 'MAX_DEPTH_EXCEEDED' if too many redirects
494
- *
495
- * @example
496
- * ```typescript
497
- * const resolved = await storage.resolveRef('HEAD')
498
- * console.log(`SHA: ${resolved.sha}`)
499
- * console.log(`Chain: ${resolved.chain.map(r => r.name).join(' -> ')}`)
500
- * // Chain: HEAD -> refs/heads/main
501
- * ```
502
- */
503
- async resolveRef(name, options) {
504
- const maxDepth = options?.maxDepth ?? 10;
505
- const chain = [];
506
- const visited = new Set();
507
- let currentName = name;
508
- let ref = null;
509
- for (let depth = 0; depth < maxDepth; depth++) {
510
- // Check for circular refs
511
- if (visited.has(currentName)) {
512
- throw new RefError(`Circular reference detected: ${currentName}`, 'CIRCULAR_REF', currentName);
513
- }
514
- visited.add(currentName);
515
- ref = await this.getRef(currentName);
516
- if (!ref) {
517
- throw new RefError(`Ref not found: ${currentName}`, 'NOT_FOUND', currentName);
518
- }
519
- chain.push(ref);
520
- // If it's a direct ref, we're done
521
- if (ref.type === 'direct') {
522
- return {
523
- ref: chain[0],
524
- sha: ref.target,
525
- chain
526
- };
527
- }
528
- // Follow symbolic ref
529
- currentName = ref.target;
530
- }
531
- // Max depth exceeded
532
- throw new RefError(`Max ref resolution depth exceeded: ${maxDepth}`, 'MAX_DEPTH_EXCEEDED', name);
533
- }
534
- /**
535
- * Update or create a ref.
536
- *
537
- * @description
538
- * Creates a new ref or updates an existing one. Supports atomic
539
- * compare-and-swap operations via oldValue option.
540
- *
541
- * Note: For atomic operations, callers can acquire a lock via acquireLock()
542
- * and pass it via options.lock to avoid double-locking.
543
- *
544
- * @param name - Full ref name
545
- * @param target - SHA-1 hash to point to
546
- * @param options - Update options (create, oldValue, force, lock)
547
- * @returns The updated/created ref
548
- * @throws RefError with code 'INVALID_NAME' if ref name is invalid
549
- * @throws RefError with code 'INVALID_SHA' if SHA format is invalid
550
- * @throws RefError with code 'ALREADY_EXISTS' if creating and ref exists
551
- * @throws RefError with code 'CONFLICT' if oldValue doesn't match
552
- * @throws RefError with code 'NOT_FOUND' if ref doesn't exist and not creating
553
- *
554
- * @example
555
- * ```typescript
556
- * // Create a new branch
557
- * await storage.updateRef('refs/heads/feature', sha, { create: true })
558
- *
559
- * // Atomic update (fails if someone else modified)
560
- * await storage.updateRef('refs/heads/main', newSha, { oldValue: currentSha })
561
- *
562
- * // Force update (skips fast-forward check)
563
- * await storage.updateRef('refs/heads/main', sha, { force: true })
564
- * ```
565
- */
566
- async updateRef(name, target, options) {
567
- // Validate ref name
568
- if (!isValidRefName(name)) {
569
- throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
570
- }
571
- // Validate SHA
572
- if (!isValidSha(target)) {
573
- throw new RefError(`Invalid SHA: ${target}`, 'INVALID_SHA', name);
574
- }
575
- // Use provided lock or acquire a new one
576
- const externalLock = options?.lock;
577
- const lock = externalLock ?? await this.getLock(name);
578
- try {
579
- const existingRef = await this.getRef(name);
580
- // Handle oldValue check (CAS - compare and swap)
581
- if (options?.oldValue !== undefined) {
582
- if (options.oldValue === null) {
583
- // Expect ref to NOT exist
584
- if (existingRef) {
585
- throw new RefError(`Ref already exists: ${name}`, 'ALREADY_EXISTS', name);
586
- }
587
- }
588
- else {
589
- // Expect ref to have specific value
590
- if (!existingRef || existingRef.target !== options.oldValue) {
591
- throw new RefError(`Ref value mismatch: ${name}`, 'CONFLICT', name);
592
- }
593
- }
594
- }
595
- else if (!options?.force && !options?.create && !existingRef) {
596
- // If not forcing and not creating, ref must exist
597
- throw new RefError(`Ref not found: ${name}`, 'NOT_FOUND', name);
598
- }
599
- const ref = {
600
- name,
601
- target,
602
- type: 'direct'
603
- };
604
- await this.writeRef(ref);
605
- return ref;
606
- }
607
- finally {
608
- // Only release lock if we acquired it ourselves
609
- if (!externalLock) {
610
- await lock.release();
611
- }
612
- }
613
- }
614
- /**
615
- * Delete a ref.
616
- *
617
- * @description
618
- * Removes a ref from storage. HEAD cannot be deleted.
619
- * Uses locking for atomic compare-and-swap operations when oldValue is specified.
620
- *
621
- * @param name - Full ref name to delete
622
- * @param options - Delete options (oldValue for CAS)
623
- * @returns True if deleted, false if ref didn't exist
624
- * @throws RefError with code 'INVALID_NAME' for HEAD or invalid names
625
- * @throws RefError with code 'CONFLICT' if oldValue doesn't match
626
- *
627
- * @example
628
- * ```typescript
629
- * // Simple delete
630
- * const deleted = await storage.deleteRef('refs/heads/old-branch')
631
- *
632
- * // Atomic delete (only if value matches)
633
- * await storage.deleteRef('refs/heads/feature', { oldValue: expectedSha })
634
- * ```
635
- */
636
- async deleteRef(name, options) {
637
- // Cannot delete HEAD
638
- if (name === 'HEAD') {
639
- throw new RefError('Cannot delete HEAD', 'INVALID_NAME', name);
640
- }
641
- // Validate ref name
642
- if (!isValidRefName(name)) {
643
- throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
644
- }
645
- // Acquire lock for atomic operation
646
- const lock = await this.getLock(name);
647
- try {
648
- const existingRef = await this.getRef(name);
649
- // Check oldValue if provided (compare-and-swap pattern)
650
- if (options?.oldValue !== undefined && options.oldValue !== null) {
651
- if (!existingRef || existingRef.target !== options.oldValue) {
652
- throw new RefError(`Ref value mismatch: ${name}`, 'CONFLICT', name);
653
- }
654
- }
655
- if (!existingRef) {
656
- return false;
657
- }
658
- return this.removeRef(name);
659
- }
660
- finally {
661
- await lock.release();
662
- }
663
- }
664
- /**
665
- * List refs matching a pattern.
666
- *
667
- * @description
668
- * Returns refs filtered by pattern and options.
669
- * By default, excludes HEAD and symbolic refs.
670
- *
671
- * @param options - Listing options (pattern, includeHead, includeSymbolic)
672
- * @returns Array of matching refs
673
- *
674
- * @example
675
- * ```typescript
676
- * // List all refs
677
- * const all = await storage.listRefs()
678
- *
679
- * // List branches only
680
- * const branches = await storage.listRefs({ pattern: 'refs/heads/*' })
681
- *
682
- * // Include HEAD
683
- * const withHead = await storage.listRefs({ includeHead: true })
684
- * ```
685
- */
686
- async listRefs(options) {
687
- let refs = await this.getAllRefs(options?.pattern);
688
- // Filter out HEAD unless explicitly requested
689
- if (!options?.includeHead) {
690
- refs = refs.filter(r => r.name !== 'HEAD');
691
- }
692
- else {
693
- // If includeHead is true, make sure HEAD is in the list
694
- const hasHead = refs.some(r => r.name === 'HEAD');
695
- if (!hasHead) {
696
- const head = await this.getRef('HEAD');
697
- if (head) {
698
- refs = [head, ...refs];
699
- }
700
- }
701
- }
702
- // Filter symbolic refs unless requested
703
- // Note: Always keep HEAD if includeHead is true, regardless of includeSymbolic
704
- if (!options?.includeSymbolic) {
705
- refs = refs.filter(r => r.type !== 'symbolic' || (options?.includeHead && r.name === 'HEAD'));
706
- }
707
- return refs;
708
- }
709
- /**
710
- * List all branches.
711
- *
712
- * @description
713
- * Convenience method to list refs under refs/heads/.
714
- *
715
- * @returns Array of branch refs
716
- *
717
- * @example
718
- * ```typescript
719
- * const branches = await storage.listBranches()
720
- * for (const branch of branches) {
721
- * console.log(branch.name.replace('refs/heads/', ''))
722
- * }
723
- * ```
724
- */
725
- async listBranches() {
726
- return this.listRefs({ pattern: 'refs/heads/*' });
727
- }
728
- /**
729
- * List all tags.
730
- *
731
- * @description
732
- * Convenience method to list refs under refs/tags/.
733
- *
734
- * @returns Array of tag refs
735
- *
736
- * @example
737
- * ```typescript
738
- * const tags = await storage.listTags()
739
- * for (const tag of tags) {
740
- * console.log(tag.name.replace('refs/tags/', ''))
741
- * }
742
- * ```
743
- */
744
- async listTags() {
745
- return this.listRefs({ pattern: 'refs/tags/*' });
746
- }
747
- /**
748
- * Get HEAD ref.
749
- *
750
- * @description
751
- * Returns the HEAD ref. Every repository should have HEAD.
752
- *
753
- * @returns The HEAD ref
754
- * @throws RefError with code 'NOT_FOUND' if HEAD doesn't exist
755
- *
756
- * @example
757
- * ```typescript
758
- * const head = await storage.getHead()
759
- * if (head.type === 'symbolic') {
760
- * console.log(`On branch: ${head.target}`)
761
- * } else {
762
- * console.log(`Detached at: ${head.target}`)
763
- * }
764
- * ```
765
- */
766
- async getHead() {
767
- const head = await this.getRef('HEAD');
768
- if (!head) {
769
- throw new RefError('HEAD not found', 'NOT_FOUND', 'HEAD');
770
- }
771
- return head;
772
- }
773
- /**
774
- * Update HEAD (can be symbolic or detached).
775
- *
776
- * @description
777
- * Sets HEAD to point to a branch (symbolic) or commit (detached).
778
- * Uses locking to ensure atomic updates to HEAD.
779
- *
780
- * @param target - Branch ref name (symbolic) or SHA (detached)
781
- * @param symbolic - If true, create symbolic ref; if false, direct ref
782
- * @returns The updated HEAD ref
783
- *
784
- * @example
785
- * ```typescript
786
- * // Switch to branch
787
- * await storage.updateHead('refs/heads/main', true)
788
- *
789
- * // Detach HEAD at commit
790
- * await storage.updateHead(commitSha, false)
791
- * ```
792
- */
793
- async updateHead(target, symbolic) {
794
- // Acquire lock for atomic HEAD update
795
- const lock = await this.getLock('HEAD');
796
- try {
797
- const ref = {
798
- name: 'HEAD',
799
- target,
800
- type: symbolic ? 'symbolic' : 'direct'
801
- };
802
- await this.writeRef(ref);
803
- return ref;
804
- }
805
- finally {
806
- await lock.release();
807
- }
808
- }
809
- /**
810
- * Check if HEAD is detached.
811
- *
812
- * @description
813
- * HEAD is detached when it points directly to a commit SHA
814
- * rather than symbolically to a branch.
815
- *
816
- * @returns True if HEAD is detached (points to SHA directly)
817
- *
818
- * @example
819
- * ```typescript
820
- * if (await storage.isHeadDetached()) {
821
- * console.log('You are in detached HEAD state')
822
- * }
823
- * ```
824
- */
825
- async isHeadDetached() {
826
- const head = await this.getHead();
827
- return head.type === 'direct';
828
- }
829
- /**
830
- * Create a symbolic ref.
831
- *
832
- * @description
833
- * Creates a ref that points to another ref name (not a SHA).
834
- * Used primarily for HEAD pointing to a branch.
835
- * Uses locking to ensure atomic creation.
836
- *
837
- * @param name - Name for the new symbolic ref
838
- * @param target - Target ref name (not SHA)
839
- * @returns The created symbolic ref
840
- * @throws RefError with code 'INVALID_NAME' if name is invalid
841
- * @throws RefError with code 'CIRCULAR_REF' if name equals target
842
- *
843
- * @example
844
- * ```typescript
845
- * // Make HEAD point to main branch
846
- * await storage.createSymbolicRef('HEAD', 'refs/heads/main')
847
- * ```
848
- */
849
- async createSymbolicRef(name, target) {
850
- // Validate ref name
851
- if (!isValidRefName(name)) {
852
- throw new RefError(`Invalid ref name: ${name}`, 'INVALID_NAME', name);
853
- }
854
- // Cannot point to itself
855
- if (name === target) {
856
- throw new RefError(`Symbolic ref cannot point to itself: ${name}`, 'CIRCULAR_REF', name);
857
- }
858
- // Acquire lock for atomic symbolic ref creation
859
- const lock = await this.getLock(name);
860
- try {
861
- const ref = {
862
- name,
863
- target,
864
- type: 'symbolic'
865
- };
866
- await this.writeRef(ref);
867
- return ref;
868
- }
869
- finally {
870
- await lock.release();
871
- }
872
- }
873
- /**
874
- * Acquire a lock for updating a ref.
875
- *
876
- * @description
877
- * Acquires an exclusive lock on a ref. Use this for complex operations
878
- * that need to read-modify-write atomically.
879
- *
880
- * @param name - Full ref name to lock
881
- * @param timeout - Lock acquisition timeout in milliseconds
882
- * @returns Lock handle - must be released when done
883
- *
884
- * @example
885
- * ```typescript
886
- * const lock = await storage.acquireLock('refs/heads/main', 5000)
887
- * try {
888
- * // Perform atomic operations
889
- * await storage.updateRef('refs/heads/main', sha, { lock })
890
- * } finally {
891
- * await lock.release()
892
- * }
893
- * ```
894
- */
895
- async acquireLock(name, timeout) {
896
- return this.getLock(name, timeout);
897
- }
898
- /**
899
- * Pack loose refs into packed-refs file.
900
- *
901
- * @description
902
- * Consolidates loose ref files into a single packed-refs file.
903
- * This improves performance for repositories with many refs.
904
- * HEAD and symbolic refs are not packed.
905
- *
906
- * Uses a transactional approach by acquiring locks on all refs being packed
907
- * to ensure consistency during the packing operation.
908
- *
909
- * @example
910
- * ```typescript
911
- * // After creating many branches/tags
912
- * await storage.packRefs()
913
- * ```
914
- */
915
- async packRefs() {
916
- // StorageBackend doesn't support packed refs - no-op
917
- if (this.storageBackend) {
918
- return;
919
- }
920
- if (!this.backend) {
921
- throw new Error('No backend configured');
922
- }
923
- const allRefs = await this.getAllRefs();
924
- const packed = new Map();
925
- const locks = [];
926
- // Filter refs that can be packed (not HEAD, not symbolic)
927
- const packableRefs = allRefs.filter(ref => {
928
- if (ref.name === 'HEAD')
929
- return false;
930
- if (ref.type === 'symbolic')
931
- return false;
932
- return true;
933
- });
934
- // Acquire locks on all refs being packed for transactional consistency
935
- try {
936
- for (const ref of packableRefs) {
937
- const lock = await this.getLock(ref.name);
938
- locks.push(lock);
939
- }
940
- // Re-read refs while holding locks to ensure consistency
941
- for (const ref of packableRefs) {
942
- const currentRef = await this.getRef(ref.name);
943
- if (currentRef && currentRef.type === 'direct') {
944
- packed.set(currentRef.name, currentRef.target);
945
- }
946
- }
947
- // Write packed refs atomically
948
- await this.backend.writePackedRefs(packed);
949
- }
950
- finally {
951
- // Release all locks
952
- for (const lock of locks) {
953
- await lock.release();
954
- }
955
- }
956
- }
957
- }
958
- // ============================================================================
959
- // Convenience Functions
960
- // ============================================================================
961
- /**
962
- * Resolve a ref to its final SHA target.
963
- *
964
- * @description
965
- * Convenience function that wraps RefStorage.resolveRef.
966
- *
967
- * @param storage - RefStorage instance
968
- * @param name - Ref name to resolve
969
- * @param options - Resolution options
970
- * @returns The final SHA target
971
- *
972
- * @example
973
- * ```typescript
974
- * const sha = await resolveRef(storage, 'HEAD')
975
- * ```
976
- */
977
- export async function resolveRef(storage, name, options) {
978
- const resolved = await storage.resolveRef(name, options);
979
- return resolved.sha;
980
- }
981
- /**
982
- * Update a ref.
983
- *
984
- * @description
985
- * Convenience function that wraps RefStorage.updateRef.
986
- *
987
- * @param storage - RefStorage instance
988
- * @param name - Full ref name
989
- * @param target - SHA target
990
- * @param options - Update options
991
- * @returns The updated ref
992
- */
993
- export async function updateRef(storage, name, target, options) {
994
- return storage.updateRef(name, target, options);
995
- }
996
- /**
997
- * Delete a ref.
998
- *
999
- * @description
1000
- * Convenience function that wraps RefStorage.deleteRef.
1001
- *
1002
- * @param storage - RefStorage instance
1003
- * @param name - Full ref name to delete
1004
- * @param options - Delete options
1005
- * @returns True if deleted
1006
- */
1007
- export async function deleteRef(storage, name, options) {
1008
- return storage.deleteRef(name, options);
1009
- }
1010
- /**
1011
- * List refs.
1012
- *
1013
- * @description
1014
- * Convenience function that wraps RefStorage.listRefs.
1015
- *
1016
- * @param storage - RefStorage instance
1017
- * @param options - Listing options
1018
- * @returns Array of refs
1019
- */
1020
- export async function listRefs(storage, options) {
1021
- return storage.listRefs(options);
1022
- }
1023
- //# sourceMappingURL=storage.js.map