gitx.do 0.1.1 → 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 (356) 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 +14 -469
  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 -176
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -979
  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/checkout.d.ts +0 -73
  32. package/dist/cli/commands/checkout.d.ts.map +0 -1
  33. package/dist/cli/commands/checkout.js +0 -725
  34. package/dist/cli/commands/checkout.js.map +0 -1
  35. package/dist/cli/commands/commit.d.ts +0 -182
  36. package/dist/cli/commands/commit.d.ts.map +0 -1
  37. package/dist/cli/commands/commit.js +0 -457
  38. package/dist/cli/commands/commit.js.map +0 -1
  39. package/dist/cli/commands/diff.d.ts +0 -464
  40. package/dist/cli/commands/diff.d.ts.map +0 -1
  41. package/dist/cli/commands/diff.js +0 -959
  42. package/dist/cli/commands/diff.js.map +0 -1
  43. package/dist/cli/commands/log.d.ts +0 -239
  44. package/dist/cli/commands/log.d.ts.map +0 -1
  45. package/dist/cli/commands/log.js +0 -535
  46. package/dist/cli/commands/log.js.map +0 -1
  47. package/dist/cli/commands/merge.d.ts +0 -106
  48. package/dist/cli/commands/merge.d.ts.map +0 -1
  49. package/dist/cli/commands/merge.js +0 -852
  50. package/dist/cli/commands/merge.js.map +0 -1
  51. package/dist/cli/commands/review.d.ts +0 -457
  52. package/dist/cli/commands/review.d.ts.map +0 -1
  53. package/dist/cli/commands/review.js +0 -558
  54. package/dist/cli/commands/review.js.map +0 -1
  55. package/dist/cli/commands/stash.d.ts +0 -157
  56. package/dist/cli/commands/stash.d.ts.map +0 -1
  57. package/dist/cli/commands/stash.js +0 -655
  58. package/dist/cli/commands/stash.js.map +0 -1
  59. package/dist/cli/commands/status.d.ts +0 -269
  60. package/dist/cli/commands/status.d.ts.map +0 -1
  61. package/dist/cli/commands/status.js +0 -492
  62. package/dist/cli/commands/status.js.map +0 -1
  63. package/dist/cli/commands/web.d.ts +0 -199
  64. package/dist/cli/commands/web.d.ts.map +0 -1
  65. package/dist/cli/commands/web.js +0 -697
  66. package/dist/cli/commands/web.js.map +0 -1
  67. package/dist/cli/fs-adapter.d.ts +0 -656
  68. package/dist/cli/fs-adapter.d.ts.map +0 -1
  69. package/dist/cli/fs-adapter.js +0 -1177
  70. package/dist/cli/fs-adapter.js.map +0 -1
  71. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  72. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  73. package/dist/cli/fsx-cli-adapter.js +0 -619
  74. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  75. package/dist/cli/index.d.ts +0 -387
  76. package/dist/cli/index.d.ts.map +0 -1
  77. package/dist/cli/index.js +0 -579
  78. package/dist/cli/index.js.map +0 -1
  79. package/dist/cli/ui/components/DiffView.d.ts +0 -12
  80. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  81. package/dist/cli/ui/components/DiffView.js +0 -11
  82. package/dist/cli/ui/components/DiffView.js.map +0 -1
  83. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -10
  84. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  85. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  86. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  87. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -15
  88. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  89. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  90. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  91. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -10
  92. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  93. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  94. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  95. package/dist/cli/ui/components/NavigationList.d.ts +0 -14
  96. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  97. package/dist/cli/ui/components/NavigationList.js +0 -11
  98. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  99. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -13
  100. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  101. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  102. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  103. package/dist/cli/ui/components/index.d.ts +0 -7
  104. package/dist/cli/ui/components/index.d.ts.map +0 -1
  105. package/dist/cli/ui/components/index.js +0 -9
  106. package/dist/cli/ui/components/index.js.map +0 -1
  107. package/dist/cli/ui/terminal-ui.d.ts +0 -85
  108. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  109. package/dist/cli/ui/terminal-ui.js +0 -121
  110. package/dist/cli/ui/terminal-ui.js.map +0 -1
  111. package/dist/do/BashModule.d.ts +0 -871
  112. package/dist/do/BashModule.d.ts.map +0 -1
  113. package/dist/do/BashModule.js +0 -1143
  114. package/dist/do/BashModule.js.map +0 -1
  115. package/dist/do/FsModule.d.ts +0 -612
  116. package/dist/do/FsModule.d.ts.map +0 -1
  117. package/dist/do/FsModule.js +0 -1120
  118. package/dist/do/FsModule.js.map +0 -1
  119. package/dist/do/GitModule.d.ts +0 -635
  120. package/dist/do/GitModule.d.ts.map +0 -1
  121. package/dist/do/GitModule.js +0 -784
  122. package/dist/do/GitModule.js.map +0 -1
  123. package/dist/do/GitRepoDO.d.ts +0 -281
  124. package/dist/do/GitRepoDO.d.ts.map +0 -1
  125. package/dist/do/GitRepoDO.js +0 -479
  126. package/dist/do/GitRepoDO.js.map +0 -1
  127. package/dist/do/bash-ast.d.ts +0 -246
  128. package/dist/do/bash-ast.d.ts.map +0 -1
  129. package/dist/do/bash-ast.js +0 -888
  130. package/dist/do/bash-ast.js.map +0 -1
  131. package/dist/do/container-executor.d.ts +0 -491
  132. package/dist/do/container-executor.d.ts.map +0 -1
  133. package/dist/do/container-executor.js +0 -731
  134. package/dist/do/container-executor.js.map +0 -1
  135. package/dist/do/index.d.ts +0 -53
  136. package/dist/do/index.d.ts.map +0 -1
  137. package/dist/do/index.js +0 -91
  138. package/dist/do/index.js.map +0 -1
  139. package/dist/do/tiered-storage.d.ts +0 -403
  140. package/dist/do/tiered-storage.d.ts.map +0 -1
  141. package/dist/do/tiered-storage.js +0 -689
  142. package/dist/do/tiered-storage.js.map +0 -1
  143. package/dist/do/withBash.d.ts +0 -231
  144. package/dist/do/withBash.d.ts.map +0 -1
  145. package/dist/do/withBash.js +0 -244
  146. package/dist/do/withBash.js.map +0 -1
  147. package/dist/do/withFs.d.ts +0 -237
  148. package/dist/do/withFs.d.ts.map +0 -1
  149. package/dist/do/withFs.js +0 -387
  150. package/dist/do/withFs.js.map +0 -1
  151. package/dist/do/withGit.d.ts +0 -180
  152. package/dist/do/withGit.d.ts.map +0 -1
  153. package/dist/do/withGit.js +0 -271
  154. package/dist/do/withGit.js.map +0 -1
  155. package/dist/durable-object/object-store.d.ts +0 -633
  156. package/dist/durable-object/object-store.d.ts.map +0 -1
  157. package/dist/durable-object/object-store.js +0 -1164
  158. package/dist/durable-object/object-store.js.map +0 -1
  159. package/dist/durable-object/schema.d.ts.map +0 -1
  160. package/dist/durable-object/schema.js.map +0 -1
  161. package/dist/durable-object/wal.d.ts +0 -416
  162. package/dist/durable-object/wal.d.ts.map +0 -1
  163. package/dist/durable-object/wal.js +0 -445
  164. package/dist/durable-object/wal.js.map +0 -1
  165. package/dist/mcp/adapter.d.ts +0 -772
  166. package/dist/mcp/adapter.d.ts.map +0 -1
  167. package/dist/mcp/adapter.js +0 -895
  168. package/dist/mcp/adapter.js.map +0 -1
  169. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  170. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  172. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  173. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  174. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  175. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  176. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  177. package/dist/mcp/sandbox/template.d.ts +0 -17
  178. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  179. package/dist/mcp/sandbox/template.js +0 -71
  180. package/dist/mcp/sandbox/template.js.map +0 -1
  181. package/dist/mcp/sandbox.d.ts +0 -764
  182. package/dist/mcp/sandbox.d.ts.map +0 -1
  183. package/dist/mcp/sandbox.js +0 -1362
  184. package/dist/mcp/sandbox.js.map +0 -1
  185. package/dist/mcp/sdk-adapter.d.ts +0 -835
  186. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  187. package/dist/mcp/sdk-adapter.js +0 -974
  188. package/dist/mcp/sdk-adapter.js.map +0 -1
  189. package/dist/mcp/tools/do.d.ts +0 -32
  190. package/dist/mcp/tools/do.d.ts.map +0 -1
  191. package/dist/mcp/tools/do.js +0 -117
  192. package/dist/mcp/tools/do.js.map +0 -1
  193. package/dist/mcp/tools.d.ts +0 -548
  194. package/dist/mcp/tools.d.ts.map +0 -1
  195. package/dist/mcp/tools.js +0 -3170
  196. package/dist/mcp/tools.js.map +0 -1
  197. package/dist/ops/blame.d.ts +0 -551
  198. package/dist/ops/blame.d.ts.map +0 -1
  199. package/dist/ops/blame.js +0 -1037
  200. package/dist/ops/blame.js.map +0 -1
  201. package/dist/ops/branch.d.ts +0 -766
  202. package/dist/ops/branch.d.ts.map +0 -1
  203. package/dist/ops/branch.js +0 -950
  204. package/dist/ops/branch.js.map +0 -1
  205. package/dist/ops/commit-traversal.d.ts +0 -349
  206. package/dist/ops/commit-traversal.d.ts.map +0 -1
  207. package/dist/ops/commit-traversal.js +0 -821
  208. package/dist/ops/commit-traversal.js.map +0 -1
  209. package/dist/ops/commit.d.ts +0 -555
  210. package/dist/ops/commit.d.ts.map +0 -1
  211. package/dist/ops/commit.js +0 -826
  212. package/dist/ops/commit.js.map +0 -1
  213. package/dist/ops/merge-base.d.ts +0 -397
  214. package/dist/ops/merge-base.d.ts.map +0 -1
  215. package/dist/ops/merge-base.js +0 -691
  216. package/dist/ops/merge-base.js.map +0 -1
  217. package/dist/ops/merge.d.ts +0 -855
  218. package/dist/ops/merge.d.ts.map +0 -1
  219. package/dist/ops/merge.js +0 -1551
  220. package/dist/ops/merge.js.map +0 -1
  221. package/dist/ops/tag.d.ts +0 -247
  222. package/dist/ops/tag.d.ts.map +0 -1
  223. package/dist/ops/tag.js +0 -649
  224. package/dist/ops/tag.js.map +0 -1
  225. package/dist/ops/tree-builder.d.ts +0 -178
  226. package/dist/ops/tree-builder.d.ts.map +0 -1
  227. package/dist/ops/tree-builder.js +0 -271
  228. package/dist/ops/tree-builder.js.map +0 -1
  229. package/dist/ops/tree-diff.d.ts +0 -291
  230. package/dist/ops/tree-diff.d.ts.map +0 -1
  231. package/dist/ops/tree-diff.js +0 -705
  232. package/dist/ops/tree-diff.js.map +0 -1
  233. package/dist/pack/delta.d.ts +0 -248
  234. package/dist/pack/delta.d.ts.map +0 -1
  235. package/dist/pack/delta.js +0 -740
  236. package/dist/pack/delta.js.map +0 -1
  237. package/dist/pack/format.d.ts +0 -446
  238. package/dist/pack/format.d.ts.map +0 -1
  239. package/dist/pack/format.js +0 -572
  240. package/dist/pack/format.js.map +0 -1
  241. package/dist/pack/full-generation.d.ts +0 -612
  242. package/dist/pack/full-generation.d.ts.map +0 -1
  243. package/dist/pack/full-generation.js +0 -1378
  244. package/dist/pack/full-generation.js.map +0 -1
  245. package/dist/pack/generation.d.ts +0 -441
  246. package/dist/pack/generation.d.ts.map +0 -1
  247. package/dist/pack/generation.js +0 -707
  248. package/dist/pack/generation.js.map +0 -1
  249. package/dist/pack/index.d.ts +0 -502
  250. package/dist/pack/index.d.ts.map +0 -1
  251. package/dist/pack/index.js +0 -833
  252. package/dist/pack/index.js.map +0 -1
  253. package/dist/refs/branch.d.ts +0 -683
  254. package/dist/refs/branch.d.ts.map +0 -1
  255. package/dist/refs/branch.js +0 -881
  256. package/dist/refs/branch.js.map +0 -1
  257. package/dist/refs/storage.d.ts +0 -833
  258. package/dist/refs/storage.d.ts.map +0 -1
  259. package/dist/refs/storage.js +0 -1023
  260. package/dist/refs/storage.js.map +0 -1
  261. package/dist/refs/tag.d.ts +0 -860
  262. package/dist/refs/tag.d.ts.map +0 -1
  263. package/dist/refs/tag.js +0 -996
  264. package/dist/refs/tag.js.map +0 -1
  265. package/dist/storage/backend.d.ts +0 -425
  266. package/dist/storage/backend.d.ts.map +0 -1
  267. package/dist/storage/backend.js +0 -41
  268. package/dist/storage/backend.js.map +0 -1
  269. package/dist/storage/fsx-adapter.d.ts +0 -204
  270. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  271. package/dist/storage/fsx-adapter.js +0 -518
  272. package/dist/storage/fsx-adapter.js.map +0 -1
  273. package/dist/storage/lru-cache.d.ts +0 -691
  274. package/dist/storage/lru-cache.d.ts.map +0 -1
  275. package/dist/storage/lru-cache.js +0 -813
  276. package/dist/storage/lru-cache.js.map +0 -1
  277. package/dist/storage/object-index.d.ts +0 -585
  278. package/dist/storage/object-index.d.ts.map +0 -1
  279. package/dist/storage/object-index.js +0 -532
  280. package/dist/storage/object-index.js.map +0 -1
  281. package/dist/storage/r2-pack.d.ts +0 -1257
  282. package/dist/storage/r2-pack.d.ts.map +0 -1
  283. package/dist/storage/r2-pack.js +0 -1773
  284. package/dist/storage/r2-pack.js.map +0 -1
  285. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  286. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  287. package/dist/tiered/cdc-pipeline.js +0 -1880
  288. package/dist/tiered/cdc-pipeline.js.map +0 -1
  289. package/dist/tiered/migration.d.ts +0 -1104
  290. package/dist/tiered/migration.d.ts.map +0 -1
  291. package/dist/tiered/migration.js +0 -1217
  292. package/dist/tiered/migration.js.map +0 -1
  293. package/dist/tiered/parquet-writer.d.ts +0 -1145
  294. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  295. package/dist/tiered/parquet-writer.js +0 -1183
  296. package/dist/tiered/parquet-writer.js.map +0 -1
  297. package/dist/tiered/read-path.d.ts +0 -835
  298. package/dist/tiered/read-path.d.ts.map +0 -1
  299. package/dist/tiered/read-path.js +0 -487
  300. package/dist/tiered/read-path.js.map +0 -1
  301. package/dist/types/capability.d.ts +0 -1385
  302. package/dist/types/capability.d.ts.map +0 -1
  303. package/dist/types/capability.js +0 -36
  304. package/dist/types/capability.js.map +0 -1
  305. package/dist/types/index.d.ts +0 -13
  306. package/dist/types/index.d.ts.map +0 -1
  307. package/dist/types/index.js +0 -18
  308. package/dist/types/index.js.map +0 -1
  309. package/dist/types/interfaces.d.ts +0 -673
  310. package/dist/types/interfaces.d.ts.map +0 -1
  311. package/dist/types/interfaces.js +0 -26
  312. package/dist/types/interfaces.js.map +0 -1
  313. package/dist/types/objects.d.ts +0 -692
  314. package/dist/types/objects.d.ts.map +0 -1
  315. package/dist/types/objects.js +0 -837
  316. package/dist/types/objects.js.map +0 -1
  317. package/dist/types/storage.d.ts +0 -603
  318. package/dist/types/storage.d.ts.map +0 -1
  319. package/dist/types/storage.js +0 -191
  320. package/dist/types/storage.js.map +0 -1
  321. package/dist/types/worker-loader.d.ts +0 -60
  322. package/dist/types/worker-loader.d.ts.map +0 -1
  323. package/dist/types/worker-loader.js +0 -62
  324. package/dist/types/worker-loader.js.map +0 -1
  325. package/dist/utils/hash.d.ts +0 -198
  326. package/dist/utils/hash.d.ts.map +0 -1
  327. package/dist/utils/hash.js +0 -272
  328. package/dist/utils/hash.js.map +0 -1
  329. package/dist/utils/sha1.d.ts +0 -325
  330. package/dist/utils/sha1.d.ts.map +0 -1
  331. package/dist/utils/sha1.js +0 -635
  332. package/dist/utils/sha1.js.map +0 -1
  333. package/dist/wire/capabilities.d.ts +0 -1044
  334. package/dist/wire/capabilities.d.ts.map +0 -1
  335. package/dist/wire/capabilities.js +0 -941
  336. package/dist/wire/capabilities.js.map +0 -1
  337. package/dist/wire/path-security.d.ts +0 -157
  338. package/dist/wire/path-security.d.ts.map +0 -1
  339. package/dist/wire/path-security.js +0 -307
  340. package/dist/wire/path-security.js.map +0 -1
  341. package/dist/wire/pkt-line.d.ts +0 -345
  342. package/dist/wire/pkt-line.d.ts.map +0 -1
  343. package/dist/wire/pkt-line.js +0 -381
  344. package/dist/wire/pkt-line.js.map +0 -1
  345. package/dist/wire/receive-pack.d.ts +0 -1059
  346. package/dist/wire/receive-pack.d.ts.map +0 -1
  347. package/dist/wire/receive-pack.js +0 -1414
  348. package/dist/wire/receive-pack.js.map +0 -1
  349. package/dist/wire/smart-http.d.ts +0 -799
  350. package/dist/wire/smart-http.d.ts.map +0 -1
  351. package/dist/wire/smart-http.js +0 -945
  352. package/dist/wire/smart-http.js.map +0 -1
  353. package/dist/wire/upload-pack.d.ts +0 -727
  354. package/dist/wire/upload-pack.d.ts.map +0 -1
  355. package/dist/wire/upload-pack.js +0 -1141
  356. 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