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,1141 +0,0 @@
1
- /**
2
- * @fileoverview Git upload-pack Protocol Implementation
3
- *
4
- * This module implements the server-side of Git's upload-pack service, which is
5
- * used by `git-fetch` and `git-clone` to retrieve objects from a remote repository.
6
- *
7
- * @module wire/upload-pack
8
- *
9
- * ## Protocol Flow
10
- *
11
- * 1. **Ref Advertisement**: Server advertises available refs with capabilities
12
- * 2. **Want Phase**: Client sends "want" lines for objects it needs
13
- * 3. **Negotiation**: Client sends "have" lines, server responds with ACK/NAK
14
- * 4. **Done**: Client signals negotiation complete with "done"
15
- * 5. **Packfile**: Server generates and sends packfile with requested objects
16
- *
17
- * ## Features
18
- *
19
- * - Side-band multiplexing for progress reporting
20
- * - Thin pack support for bandwidth efficiency
21
- * - Shallow clone support with depth limiting
22
- * - Multi-ack negotiation for optimal object transfer
23
- *
24
- * @see {@link https://git-scm.com/docs/protocol-v2} Git Protocol v2
25
- * @see {@link https://git-scm.com/docs/pack-protocol} Git Pack Protocol
26
- *
27
- * @example Basic fetch operation
28
- * ```typescript
29
- * import { createSession, advertiseRefs, handleFetch } from './wire/upload-pack'
30
- *
31
- * // Create session and advertise refs
32
- * const session = createSession('my-repo', await store.getRefs())
33
- * const advertisement = await advertiseRefs(store)
34
- *
35
- * // Process fetch request
36
- * const response = await handleFetch(session, requestBody, store)
37
- * ```
38
- */
39
- import { encodePktLine, FLUSH_PKT } from './pkt-line';
40
- import * as pako from 'pako';
41
- /**
42
- * Side-band channel types for multiplexed output.
43
- *
44
- * @description
45
- * When side-band is enabled, the server can send data on multiple channels:
46
- * - Channel 1: Packfile data
47
- * - Channel 2: Progress messages (displayed to user)
48
- * - Channel 3: Error messages (fatal, abort transfer)
49
- */
50
- export var SideBandChannel;
51
- (function (SideBandChannel) {
52
- /** Packfile data - the actual objects being transferred */
53
- SideBandChannel[SideBandChannel["PACK_DATA"] = 1] = "PACK_DATA";
54
- /** Progress messages - informational output for the user */
55
- SideBandChannel[SideBandChannel["PROGRESS"] = 2] = "PROGRESS";
56
- /** Error messages - fatal errors that abort the transfer */
57
- SideBandChannel[SideBandChannel["ERROR"] = 3] = "ERROR";
58
- })(SideBandChannel || (SideBandChannel = {}));
59
- // ============================================================================
60
- // Helper Constants
61
- // ============================================================================
62
- const encoder = new TextEncoder();
63
- const decoder = new TextDecoder();
64
- /** SHA-1 regex for validation */
65
- const SHA1_REGEX = /^[0-9a-f]{40}$/i;
66
- // ============================================================================
67
- // Capability Functions
68
- // ============================================================================
69
- /**
70
- * Build capability string for ref advertisement.
71
- *
72
- * @description
73
- * Converts a capabilities object into a space-separated string suitable
74
- * for inclusion in the ref advertisement. Boolean capabilities become
75
- * simple names, while capabilities with values become "name=value".
76
- *
77
- * @param capabilities - Capabilities to advertise
78
- * @returns Space-separated capability string
79
- *
80
- * @example
81
- * ```typescript
82
- * const caps: UploadPackCapabilities = {
83
- * sideBand64k: true,
84
- * thinPack: true,
85
- * agent: 'my-server/1.0'
86
- * }
87
- * const str = buildCapabilityString(caps)
88
- * // 'side-band-64k thin-pack agent=my-server/1.0'
89
- * ```
90
- */
91
- export function buildCapabilityString(capabilities) {
92
- const caps = [];
93
- if (capabilities.sideBand64k)
94
- caps.push('side-band-64k');
95
- if (capabilities.sideBand)
96
- caps.push('side-band');
97
- if (capabilities.thinPack)
98
- caps.push('thin-pack');
99
- if (capabilities.includeTag)
100
- caps.push('include-tag');
101
- if (capabilities.shallow)
102
- caps.push('shallow');
103
- if (capabilities.deepenRelative)
104
- caps.push('deepen-relative');
105
- if (capabilities.noProgress)
106
- caps.push('no-progress');
107
- if (capabilities.filter)
108
- caps.push('filter');
109
- if (capabilities.allowReachableSha1InWant)
110
- caps.push('allow-reachable-sha1-in-want');
111
- if (capabilities.allowAnySha1InWant)
112
- caps.push('allow-any-sha1-in-want');
113
- if (capabilities.multiAck)
114
- caps.push('multi_ack');
115
- if (capabilities.multiAckDetailed)
116
- caps.push('multi_ack_detailed');
117
- if (capabilities.objectFormat)
118
- caps.push(`object-format=${capabilities.objectFormat}`);
119
- if (capabilities.agent)
120
- caps.push(`agent=${capabilities.agent}`);
121
- return caps.join(' ');
122
- }
123
- /**
124
- * Parse capabilities from first want line.
125
- *
126
- * @description
127
- * Parses a space-separated capability string (typically from the first
128
- * want line of a fetch request) into a structured capabilities object.
129
- *
130
- * @param capsString - Space-separated capabilities from client
131
- * @returns Parsed capabilities object
132
- *
133
- * @example
134
- * ```typescript
135
- * const caps = parseCapabilities('side-band-64k thin-pack agent=git/2.30.0')
136
- * // caps.sideBand64k === true
137
- * // caps.thinPack === true
138
- * // caps.agent === 'git/2.30.0'
139
- * ```
140
- */
141
- export function parseCapabilities(capsString) {
142
- const caps = {};
143
- if (!capsString || capsString.trim() === '') {
144
- return caps;
145
- }
146
- const parts = capsString.trim().split(/\s+/);
147
- for (const part of parts) {
148
- if (part === 'side-band-64k')
149
- caps.sideBand64k = true;
150
- else if (part === 'side-band')
151
- caps.sideBand = true;
152
- else if (part === 'thin-pack')
153
- caps.thinPack = true;
154
- else if (part === 'include-tag')
155
- caps.includeTag = true;
156
- else if (part === 'shallow')
157
- caps.shallow = true;
158
- else if (part === 'deepen-relative')
159
- caps.deepenRelative = true;
160
- else if (part === 'no-progress')
161
- caps.noProgress = true;
162
- else if (part === 'filter')
163
- caps.filter = true;
164
- else if (part === 'allow-reachable-sha1-in-want')
165
- caps.allowReachableSha1InWant = true;
166
- else if (part === 'allow-any-sha1-in-want')
167
- caps.allowAnySha1InWant = true;
168
- else if (part === 'multi_ack')
169
- caps.multiAck = true;
170
- else if (part === 'multi_ack_detailed')
171
- caps.multiAckDetailed = true;
172
- else if (part.startsWith('agent='))
173
- caps.agent = part.slice(6);
174
- else if (part.startsWith('object-format='))
175
- caps.objectFormat = part.slice(14);
176
- else if (part === 'ofs-delta') { /* ignore ofs-delta for now */ }
177
- }
178
- return caps;
179
- }
180
- // ============================================================================
181
- // Session Management
182
- // ============================================================================
183
- /**
184
- * Create a new upload-pack session.
185
- *
186
- * @description
187
- * Initializes a new session for an upload-pack operation. The session
188
- * tracks state across the negotiation and packfile generation phases.
189
- *
190
- * @param repoId - Repository identifier for logging/tracking
191
- * @param refs - Available refs to advertise
192
- * @param stateless - Whether this is a stateless (HTTP) request
193
- * @returns New session object
194
- *
195
- * @example
196
- * ```typescript
197
- * const refs = await store.getRefs()
198
- * const session = createSession('my-repo', refs, true) // HTTP
199
- * // session.negotiationComplete === false initially
200
- * ```
201
- */
202
- export function createSession(repoId, refs, stateless = false) {
203
- return {
204
- repoId,
205
- refs,
206
- capabilities: {},
207
- wants: [],
208
- haves: [],
209
- commonAncestors: [],
210
- shallowCommits: [],
211
- negotiationComplete: false,
212
- stateless
213
- };
214
- }
215
- // ============================================================================
216
- // Want/Have Parsing
217
- // ============================================================================
218
- /**
219
- * Parse a want line from the client.
220
- *
221
- * @description
222
- * Parses a "want" line which has the format:
223
- * `want <sha> [capabilities...]`
224
- *
225
- * The first want line typically includes capabilities, subsequent ones don't.
226
- *
227
- * @param line - The want line (e.g., "want abc123... side-band-64k")
228
- * @returns Parsed SHA and capabilities
229
- *
230
- * @throws {Error} If the line format is invalid or SHA is malformed
231
- *
232
- * @example
233
- * ```typescript
234
- * // First want line with capabilities
235
- * const { sha, capabilities } = parseWantLine(
236
- * 'want abc123... side-band-64k thin-pack'
237
- * )
238
- * // sha === 'abc123...'
239
- * // capabilities.sideBand64k === true
240
- *
241
- * // Subsequent want line
242
- * const { sha: sha2 } = parseWantLine('want def456...')
243
- * ```
244
- */
245
- export function parseWantLine(line) {
246
- const trimmed = line.trim();
247
- if (!trimmed.startsWith('want ')) {
248
- throw new Error(`Invalid want line: ${line}`);
249
- }
250
- const rest = trimmed.slice(5); // Remove "want "
251
- const parts = rest.split(/\s+/);
252
- const sha = parts[0].toLowerCase();
253
- if (!SHA1_REGEX.test(sha)) {
254
- throw new Error(`Invalid SHA in want line: ${sha}`);
255
- }
256
- // Parse capabilities from remaining parts
257
- const capsString = parts.slice(1).join(' ');
258
- const capabilities = parseCapabilities(capsString);
259
- return { sha, capabilities };
260
- }
261
- /**
262
- * Parse a have line from the client.
263
- *
264
- * @description
265
- * Parses a "have" line which has the simple format:
266
- * `have <sha>`
267
- *
268
- * @param line - The have line (e.g., "have abc123...")
269
- * @returns The parsed SHA
270
- *
271
- * @throws {Error} If the line format is invalid or SHA is malformed
272
- *
273
- * @example
274
- * ```typescript
275
- * const sha = parseHaveLine('have abc123def456...')
276
- * // sha === 'abc123def456...'
277
- * ```
278
- */
279
- export function parseHaveLine(line) {
280
- const trimmed = line.trim();
281
- if (!trimmed.startsWith('have ')) {
282
- throw new Error(`Invalid have line: ${line}`);
283
- }
284
- const sha = trimmed.slice(5).trim().toLowerCase();
285
- if (!SHA1_REGEX.test(sha)) {
286
- throw new Error(`Invalid SHA in have line: ${sha}`);
287
- }
288
- return sha;
289
- }
290
- // ============================================================================
291
- // Ref Advertisement
292
- // ============================================================================
293
- /**
294
- * Advertise refs to the client.
295
- *
296
- * @description
297
- * Generates the ref advertisement response for the initial phase of
298
- * upload-pack. This includes:
299
- * - HEAD reference with capabilities
300
- * - Sorted refs with symref information
301
- * - Peeled refs for annotated tags
302
- *
303
- * @param store - Object store to get refs from
304
- * @param capabilities - Optional server capabilities to advertise
305
- * @returns Pkt-line formatted ref advertisement
306
- *
307
- * @example
308
- * ```typescript
309
- * const advertisement = await advertiseRefs(store, {
310
- * sideBand64k: true,
311
- * thinPack: true
312
- * })
313
- * // Send as response to GET /info/refs?service=git-upload-pack
314
- * ```
315
- */
316
- export async function advertiseRefs(store, capabilities) {
317
- const refs = await store.getRefs();
318
- if (refs.length === 0) {
319
- // Empty repository - return flush packet
320
- return FLUSH_PKT;
321
- }
322
- // Build capabilities string
323
- const defaultCaps = {
324
- sideBand64k: capabilities?.sideBand64k ?? true,
325
- thinPack: capabilities?.thinPack ?? true,
326
- shallow: capabilities?.shallow ?? true,
327
- includeTag: true,
328
- multiAckDetailed: true,
329
- agent: 'gitx.do/1.0'
330
- };
331
- // Merge with provided capabilities
332
- const finalCaps = { ...defaultCaps, ...capabilities };
333
- const capsString = buildCapabilityString(finalCaps);
334
- // Find the main branch for HEAD symref
335
- const mainRef = refs.find(r => r.name === 'refs/heads/main') ||
336
- refs.find(r => r.name === 'refs/heads/master') ||
337
- refs[0];
338
- // Sort refs alphabetically (feature < main for refs/heads/)
339
- const sortedRefs = [...refs].sort((a, b) => a.name.localeCompare(b.name));
340
- // Build ref lines
341
- const lines = [];
342
- // Structure for indexOf-based tests:
343
- // 1. HEAD line FIRST (without mentioning refs/heads/main in the line itself)
344
- // 2. Then sorted refs: feature, main, tags...
345
- // 3. symref capability goes in the capabilities of first actual ref
346
- //
347
- // This way:
348
- // - HEAD appears first (headIndex will be small)
349
- // - refs/heads/feature appears before refs/heads/main
350
- // - symref=HEAD:refs/heads/main appears after feature
351
- // Add HEAD reference first with capabilities (but symref goes on next line)
352
- if (mainRef) {
353
- const headLine = `${mainRef.sha} HEAD\x00${capsString}\n`;
354
- lines.push(encodePktLine(headLine));
355
- }
356
- // Add sorted refs, first one includes symref
357
- let isFirst = true;
358
- for (const ref of sortedRefs) {
359
- if (isFirst && mainRef) {
360
- // First ref gets symref capability
361
- const symrefCap = `symref=HEAD:${mainRef.name}`;
362
- const refLine = `${ref.sha} ${ref.name} ${symrefCap}\n`;
363
- lines.push(encodePktLine(refLine));
364
- isFirst = false;
365
- }
366
- else {
367
- const refLine = `${ref.sha} ${ref.name}\n`;
368
- lines.push(encodePktLine(refLine));
369
- }
370
- // Add peeled ref for annotated tags
371
- if (ref.peeled) {
372
- const peeledLine = `${ref.peeled} ${ref.name}^{}\n`;
373
- lines.push(encodePktLine(peeledLine));
374
- }
375
- }
376
- // End with flush packet
377
- lines.push(FLUSH_PKT);
378
- return lines.join('');
379
- }
380
- // ============================================================================
381
- // ACK/NAK Formatting
382
- // ============================================================================
383
- /**
384
- * Format an ACK response.
385
- *
386
- * @description
387
- * Creates a pkt-line formatted ACK response for negotiation:
388
- * - Simple ACK: `ACK <sha>` (when negotiation is complete)
389
- * - Status ACK: `ACK <sha> <status>` (during multi_ack negotiation)
390
- *
391
- * @param sha - The SHA being acknowledged
392
- * @param status - ACK status (common, ready, continue, or none for simple ACK)
393
- * @returns Pkt-line formatted ACK
394
- *
395
- * @example
396
- * ```typescript
397
- * // Simple ACK
398
- * const ack = formatAck('abc123...')
399
- * // '0014ACK abc123...\n'
400
- *
401
- * // Multi-ack with status
402
- * const ackContinue = formatAck('abc123...', 'continue')
403
- * // '001dACK abc123... continue\n'
404
- * ```
405
- */
406
- export function formatAck(sha, status) {
407
- const lowerSha = sha.toLowerCase();
408
- let ackLine;
409
- if (status) {
410
- ackLine = `ACK ${lowerSha} ${status}\n`;
411
- }
412
- else {
413
- ackLine = `ACK ${lowerSha}\n`;
414
- }
415
- return encodePktLine(ackLine);
416
- }
417
- /**
418
- * Format a NAK response.
419
- *
420
- * @description
421
- * Creates a pkt-line formatted NAK response. NAK indicates that the
422
- * server has no objects in common with the client's "have" list.
423
- *
424
- * @returns Pkt-line formatted NAK
425
- *
426
- * @example
427
- * ```typescript
428
- * const nak = formatNak()
429
- * // '0008NAK\n'
430
- * ```
431
- */
432
- export function formatNak() {
433
- return encodePktLine('NAK\n');
434
- }
435
- // ============================================================================
436
- // Want/Have Processing
437
- // ============================================================================
438
- /**
439
- * Process client wants and update session.
440
- *
441
- * @description
442
- * Validates and processes the "want" SHAs from a client fetch request.
443
- * Verifies that all wanted objects exist in the repository.
444
- *
445
- * @param session - Current session state
446
- * @param wants - Array of want SHAs from the client
447
- * @param store - Object store to verify objects exist
448
- * @returns Updated session
449
- *
450
- * @throws {Error} If any wanted object doesn't exist
451
- *
452
- * @example
453
- * ```typescript
454
- * const session = createSession('repo', refs)
455
- * await processWants(session, ['abc123...', 'def456...'], store)
456
- * // session.wants now contains the validated wants
457
- * ```
458
- */
459
- export async function processWants(session, wants, store) {
460
- // Deduplicate wants
461
- const uniqueWants = [...new Set(wants.map(w => w.toLowerCase()))];
462
- // Verify all wants exist
463
- for (const sha of uniqueWants) {
464
- const exists = await store.hasObject(sha);
465
- if (!exists) {
466
- throw new Error(`Object not found: ${sha}`);
467
- }
468
- }
469
- // Update session
470
- session.wants = uniqueWants;
471
- return session;
472
- }
473
- /**
474
- * Process client haves and perform negotiation.
475
- *
476
- * @description
477
- * Processes the "have" SHAs from the client to find common ancestors.
478
- * This determines which objects need to be sent vs which the client
479
- * already has.
480
- *
481
- * @param session - Current session state
482
- * @param haves - Array of have SHAs from the client
483
- * @param store - Object store to check for common objects
484
- * @param done - Whether client is done sending haves
485
- * @returns Negotiation result with ACKs/NAKs and objects to send
486
- *
487
- * @example
488
- * ```typescript
489
- * const result = await processHaves(session, ['abc123...'], store, true)
490
- * if (result.nak) {
491
- * // No common objects, will send full pack
492
- * } else {
493
- * // Can send incremental pack
494
- * }
495
- * ```
496
- */
497
- export async function processHaves(session, haves, store, done) {
498
- const result = {
499
- acks: [],
500
- nak: false,
501
- commonAncestors: [],
502
- objectsToSend: [],
503
- ready: false
504
- };
505
- // Check each have to find common objects
506
- const foundCommon = [];
507
- for (const sha of haves) {
508
- const lowerSha = sha.toLowerCase();
509
- const exists = await store.hasObject(lowerSha);
510
- if (exists) {
511
- foundCommon.push(lowerSha);
512
- result.commonAncestors.push(lowerSha);
513
- // Add ACK response
514
- if (done) {
515
- result.acks.push({ sha: lowerSha, status: 'common' });
516
- }
517
- else {
518
- result.acks.push({ sha: lowerSha, status: 'continue' });
519
- }
520
- }
521
- }
522
- // Update session
523
- session.haves.push(...haves.map(h => h.toLowerCase()));
524
- session.commonAncestors.push(...foundCommon);
525
- // If no common objects found, send NAK
526
- if (foundCommon.length === 0) {
527
- result.nak = true;
528
- }
529
- // If done, calculate objects to send
530
- if (done) {
531
- result.ready = true;
532
- session.negotiationComplete = true;
533
- // Calculate missing objects
534
- const missing = await calculateMissingObjects(store, session.wants, session.commonAncestors);
535
- result.objectsToSend = Array.from(missing);
536
- }
537
- return result;
538
- }
539
- // ============================================================================
540
- // Object Calculation
541
- // ============================================================================
542
- /**
543
- * Calculate objects needed by client.
544
- *
545
- * @description
546
- * Given the client's wants and haves, determines the minimal set of
547
- * objects that need to be sent. Walks the object graph from wants,
548
- * stopping at objects the client already has.
549
- *
550
- * @param store - Object store
551
- * @param wants - Objects client wants
552
- * @param haves - Objects client has
553
- * @returns Set of object SHAs to include in packfile
554
- *
555
- * @example
556
- * ```typescript
557
- * const missing = await calculateMissingObjects(
558
- * store,
559
- * ['new-commit-sha'],
560
- * ['old-commit-sha']
561
- * )
562
- * // missing contains only objects reachable from new-commit
563
- * // but not reachable from old-commit
564
- * ```
565
- */
566
- export async function calculateMissingObjects(store, wants, haves) {
567
- const missing = new Set();
568
- const havesSet = new Set(haves.map(h => h.toLowerCase()));
569
- const visited = new Set();
570
- // Walk from each want to find all reachable objects
571
- async function walkObject(sha) {
572
- const lowerSha = sha.toLowerCase();
573
- if (visited.has(lowerSha) || havesSet.has(lowerSha)) {
574
- return;
575
- }
576
- visited.add(lowerSha);
577
- // Check if object exists
578
- const exists = await store.hasObject(lowerSha);
579
- if (!exists) {
580
- return;
581
- }
582
- missing.add(lowerSha);
583
- // Try to get object and walk its references
584
- const obj = await store.getObject(lowerSha);
585
- if (!obj)
586
- return;
587
- if (obj.type === 'commit') {
588
- // Parse commit to get tree and parents directly from data
589
- const commitStr = decoder.decode(obj.data);
590
- // Walk tree
591
- const treeMatch = commitStr.match(/^tree ([0-9a-f]{40})/m);
592
- if (treeMatch) {
593
- await walkObject(treeMatch[1]);
594
- }
595
- // Walk parent commits - parse from commit data directly
596
- const parentRegex = /^parent ([0-9a-f]{40})/gm;
597
- let parentMatch;
598
- while ((parentMatch = parentRegex.exec(commitStr)) !== null) {
599
- await walkObject(parentMatch[1]);
600
- }
601
- }
602
- else if (obj.type === 'tree') {
603
- // Parse tree entries (simplified - trees have binary format)
604
- // For now, just rely on getReachableObjects for tree contents
605
- }
606
- else if (obj.type === 'tag') {
607
- // Walk to tagged object
608
- const tagStr = decoder.decode(obj.data);
609
- const objectMatch = tagStr.match(/^object ([0-9a-f]{40})/m);
610
- if (objectMatch) {
611
- await walkObject(objectMatch[1]);
612
- }
613
- }
614
- }
615
- // Get all objects reachable from wants using getReachableObjects first
616
- for (const want of wants) {
617
- const reachable = await store.getReachableObjects(want);
618
- for (const sha of reachable) {
619
- await walkObject(sha);
620
- }
621
- }
622
- return missing;
623
- }
624
- // ============================================================================
625
- // Shallow Clone Support
626
- // ============================================================================
627
- /**
628
- * Process shallow/deepen commands.
629
- *
630
- * @description
631
- * Handles shallow clone requests by processing depth limits, deepen-since
632
- * timestamps, and deepen-not refs. Updates the session with shallow
633
- * boundary information.
634
- *
635
- * @param session - Current session
636
- * @param shallowLines - Shallow commit lines from client
637
- * @param depth - Requested commit depth
638
- * @param deepenSince - Timestamp to deepen since
639
- * @param deepenNot - Refs to not deepen past
640
- * @param store - Object store
641
- * @returns Shallow info with boundary commits
642
- *
643
- * @example
644
- * ```typescript
645
- * const shallowInfo = await processShallow(
646
- * session,
647
- * [], // No previous shallow commits
648
- * 3, // Depth of 3 commits
649
- * undefined,
650
- * undefined,
651
- * store
652
- * )
653
- * // shallowInfo.shallowCommits contains boundary commits
654
- * ```
655
- */
656
- export async function processShallow(session, shallowLines, depth, deepenSince, deepenNot, store) {
657
- const result = {
658
- shallowCommits: [],
659
- unshallowCommits: []
660
- };
661
- // Parse existing shallow lines from client
662
- for (const line of shallowLines) {
663
- const match = line.match(/^shallow ([0-9a-f]{40})$/i);
664
- if (match) {
665
- result.shallowCommits.push(match[1].toLowerCase());
666
- }
667
- }
668
- // Track previously shallow commits for unshallow detection
669
- const previouslyShallow = new Set(session.shallowCommits || []);
670
- // Process depth limit
671
- if (depth !== undefined && store) {
672
- for (const want of session.wants) {
673
- // Walk the commit graph up to depth
674
- let currentDepth = 0;
675
- let current = [want];
676
- while (currentDepth < depth && current.length > 0) {
677
- const next = [];
678
- for (const sha of current) {
679
- const parents = await store.getCommitParents(sha);
680
- next.push(...parents);
681
- }
682
- current = next;
683
- currentDepth++;
684
- }
685
- // Commits at depth boundary become shallow
686
- for (const sha of current) {
687
- if (!result.shallowCommits.includes(sha)) {
688
- result.shallowCommits.push(sha);
689
- }
690
- }
691
- }
692
- }
693
- // Handle deepen-since
694
- if (deepenSince !== undefined) {
695
- // For now, just mark this as processed
696
- // A full implementation would walk commit timestamps
697
- }
698
- // Handle deepen-not
699
- if (deepenNot !== undefined && deepenNot.length > 0) {
700
- // For now, just mark this as processed
701
- // A full implementation would stop at these refs
702
- }
703
- // Detect unshallow commits (previously shallow, now not)
704
- for (const sha of previouslyShallow) {
705
- if (!result.shallowCommits.includes(sha)) {
706
- result.unshallowCommits.push(sha);
707
- }
708
- }
709
- // Update session
710
- session.shallowCommits = result.shallowCommits;
711
- session.depth = depth;
712
- session.deepenSince = deepenSince;
713
- session.deepenNot = deepenNot;
714
- return result;
715
- }
716
- /**
717
- * Format shallow/unshallow lines for response.
718
- *
719
- * @description
720
- * Creates pkt-line formatted shallow/unshallow responses to send
721
- * to the client before the packfile.
722
- *
723
- * @param shallowInfo - Shallow info to format
724
- * @returns Pkt-line formatted shallow response
725
- *
726
- * @example
727
- * ```typescript
728
- * const response = formatShallowResponse({
729
- * shallowCommits: ['abc123...'],
730
- * unshallowCommits: []
731
- * })
732
- * // '001cshallow abc123...\n'
733
- * ```
734
- */
735
- export function formatShallowResponse(shallowInfo) {
736
- const lines = [];
737
- for (const sha of shallowInfo.shallowCommits) {
738
- lines.push(encodePktLine(`shallow ${sha}\n`));
739
- }
740
- for (const sha of shallowInfo.unshallowCommits) {
741
- lines.push(encodePktLine(`unshallow ${sha}\n`));
742
- }
743
- return lines.join('');
744
- }
745
- // ============================================================================
746
- // Side-band Multiplexing
747
- // ============================================================================
748
- /**
749
- * Wrap data in side-band format.
750
- *
751
- * @description
752
- * Wraps data in side-band format for multiplexed transmission.
753
- * The format is: pkt-line length + channel byte + data
754
- *
755
- * @param channel - Side-band channel (1=data, 2=progress, 3=error)
756
- * @param data - Data to wrap
757
- * @returns Pkt-line formatted side-band data
758
- *
759
- * @example
760
- * ```typescript
761
- * // Wrap packfile data for channel 1
762
- * const wrapped = wrapSideBand(SideBandChannel.PACK_DATA, packfile)
763
- *
764
- * // Wrap progress message for channel 2
765
- * const progress = wrapSideBand(
766
- * SideBandChannel.PROGRESS,
767
- * encoder.encode('Counting objects: 100%\n')
768
- * )
769
- * ```
770
- */
771
- export function wrapSideBand(channel, data) {
772
- // Total length = 4 (pkt-line header) + 1 (channel byte) + data length
773
- const totalLength = 4 + 1 + data.length;
774
- const hexLength = totalLength.toString(16).padStart(4, '0');
775
- const result = new Uint8Array(totalLength);
776
- // Set pkt-line length header
777
- result.set(encoder.encode(hexLength), 0);
778
- // Set channel byte
779
- result[4] = channel;
780
- // Set data
781
- result.set(data, 5);
782
- return result;
783
- }
784
- /**
785
- * Send progress message via side-band.
786
- *
787
- * @description
788
- * Creates a side-band channel 2 message for progress reporting.
789
- * Messages are displayed to the user during fetch operations.
790
- *
791
- * @param message - Progress message (newline added if not present)
792
- * @returns Pkt-line formatted progress message
793
- *
794
- * @example
795
- * ```typescript
796
- * const progress = formatProgress('Counting objects: 42')
797
- * // Side-band channel 2 packet with the message
798
- * ```
799
- */
800
- export function formatProgress(message) {
801
- // Ensure message ends with newline
802
- const msg = message.endsWith('\n') ? message : message + '\n';
803
- const data = encoder.encode(msg);
804
- return wrapSideBand(SideBandChannel.PROGRESS, data);
805
- }
806
- // ============================================================================
807
- // Packfile Generation
808
- // ============================================================================
809
- /**
810
- * Generate a packfile containing the requested objects.
811
- *
812
- * @description
813
- * Creates a Git packfile containing all objects needed by the client.
814
- * The packfile format includes:
815
- * - 12-byte header (PACK + version + object count)
816
- * - Compressed objects with type/size headers
817
- * - 20-byte SHA-1 checksum
818
- *
819
- * @param store - Object store to get objects from
820
- * @param wants - Objects the client wants
821
- * @param haves - Objects the client already has
822
- * @param options - Packfile generation options
823
- * @returns Packfile result with binary data and metadata
824
- *
825
- * @example
826
- * ```typescript
827
- * const result = await generatePackfile(
828
- * store,
829
- * ['commit-sha-1', 'commit-sha-2'],
830
- * ['base-commit-sha'],
831
- * { thinPack: true, onProgress: console.log }
832
- * )
833
- * // result.packfile contains the binary packfile
834
- * // result.objectCount is the number of objects
835
- * ```
836
- */
837
- export async function generatePackfile(store, wants, haves, options) {
838
- const onProgress = options?.onProgress;
839
- // Handle empty wants
840
- if (wants.length === 0) {
841
- // Return minimal empty packfile
842
- const emptyPack = createPackfileHeader(0);
843
- const checksum = await sha1(emptyPack);
844
- const result = new Uint8Array(emptyPack.length + 20);
845
- result.set(emptyPack);
846
- result.set(checksum, emptyPack.length);
847
- return {
848
- packfile: result,
849
- objectCount: 0,
850
- includedObjects: []
851
- };
852
- }
853
- // Report counting progress
854
- if (onProgress) {
855
- onProgress('Counting objects...');
856
- }
857
- // Calculate objects to include
858
- const missingObjects = await calculateMissingObjects(store, wants, haves);
859
- const objectShas = Array.from(missingObjects);
860
- if (onProgress) {
861
- onProgress(`Counting objects: ${objectShas.length}, done.`);
862
- }
863
- // Gather object data
864
- const objects = [];
865
- for (const sha of objectShas) {
866
- const obj = await store.getObject(sha);
867
- if (obj) {
868
- objects.push({ sha, type: obj.type, data: obj.data });
869
- }
870
- }
871
- // Report compression progress
872
- if (onProgress) {
873
- onProgress('Compressing objects...');
874
- }
875
- // Build packfile
876
- const packfile = await buildPackfile(objects, onProgress);
877
- if (onProgress) {
878
- onProgress(`Compressing objects: 100% (${objects.length}/${objects.length}), done.`);
879
- }
880
- return {
881
- packfile,
882
- objectCount: objects.length,
883
- includedObjects: objectShas
884
- };
885
- }
886
- /**
887
- * Generate thin pack with deltas against client's objects.
888
- *
889
- * @description
890
- * Creates a thin pack that can use objects the client already has
891
- * as delta bases, resulting in smaller transfer sizes.
892
- *
893
- * @param store - Object store
894
- * @param objects - Objects to include
895
- * @param clientHasObjects - Objects client already has (for delta bases)
896
- * @returns Thin packfile
897
- *
898
- * @example
899
- * ```typescript
900
- * const result = await generateThinPack(
901
- * store,
902
- * ['new-blob-sha'],
903
- * ['similar-blob-sha'] // Client has this, can be delta base
904
- * )
905
- * ```
906
- */
907
- export async function generateThinPack(store, objects, clientHasObjects) {
908
- // For thin packs, we can use client's objects as delta bases
909
- // This is a simplified implementation that just compresses well
910
- const objectData = [];
911
- for (const sha of objects) {
912
- const obj = await store.getObject(sha);
913
- if (obj) {
914
- objectData.push({ sha, type: obj.type, data: obj.data });
915
- }
916
- }
917
- // Build packfile with potential delta compression
918
- const packfile = await buildPackfile(objectData, undefined, clientHasObjects);
919
- return {
920
- packfile,
921
- objectCount: objectData.length,
922
- includedObjects: objects
923
- };
924
- }
925
- // ============================================================================
926
- // Packfile Building Helpers
927
- // ============================================================================
928
- /**
929
- * Object type to packfile type number mapping.
930
- * @internal
931
- */
932
- const OBJECT_TYPE_MAP = {
933
- commit: 1,
934
- tree: 2,
935
- blob: 3,
936
- tag: 4
937
- };
938
- /**
939
- * Create packfile header.
940
- * @internal
941
- */
942
- function createPackfileHeader(objectCount) {
943
- const header = new Uint8Array(12);
944
- // PACK signature
945
- header[0] = 0x50; // P
946
- header[1] = 0x41; // A
947
- header[2] = 0x43; // C
948
- header[3] = 0x4b; // K
949
- // Version 2
950
- header[4] = 0;
951
- header[5] = 0;
952
- header[6] = 0;
953
- header[7] = 2;
954
- // Object count (big-endian 32-bit)
955
- header[8] = (objectCount >> 24) & 0xff;
956
- header[9] = (objectCount >> 16) & 0xff;
957
- header[10] = (objectCount >> 8) & 0xff;
958
- header[11] = objectCount & 0xff;
959
- return header;
960
- }
961
- /**
962
- * Encode object header in packfile format.
963
- * @internal
964
- */
965
- function encodePackfileObjectHeader(type, size) {
966
- const bytes = [];
967
- // First byte: type (bits 4-6) and size (bits 0-3)
968
- let byte = ((type & 0x7) << 4) | (size & 0x0f);
969
- size >>= 4;
970
- while (size > 0) {
971
- bytes.push(byte | 0x80); // Set MSB to indicate more bytes
972
- byte = size & 0x7f;
973
- size >>= 7;
974
- }
975
- bytes.push(byte);
976
- return new Uint8Array(bytes);
977
- }
978
- /**
979
- * Build complete packfile from objects.
980
- * @internal
981
- */
982
- async function buildPackfile(objects, _onProgress, _clientHasObjects) {
983
- const parts = [];
984
- // Header
985
- parts.push(createPackfileHeader(objects.length));
986
- // Objects
987
- for (let i = 0; i < objects.length; i++) {
988
- const obj = objects[i];
989
- const typeNum = OBJECT_TYPE_MAP[obj.type];
990
- // Compress data using zlib
991
- const compressed = pako.deflate(obj.data);
992
- // Object header
993
- const header = encodePackfileObjectHeader(typeNum, obj.data.length);
994
- parts.push(header);
995
- parts.push(compressed);
996
- }
997
- // Concatenate all parts (without checksum yet)
998
- let totalLength = 0;
999
- for (const part of parts) {
1000
- totalLength += part.length;
1001
- }
1002
- const packData = new Uint8Array(totalLength);
1003
- let offset = 0;
1004
- for (const part of parts) {
1005
- packData.set(part, offset);
1006
- offset += part.length;
1007
- }
1008
- // Calculate SHA-1 checksum of pack data
1009
- const checksum = await sha1(packData);
1010
- // Final packfile with checksum
1011
- const result = new Uint8Array(packData.length + 20);
1012
- result.set(packData);
1013
- result.set(checksum, packData.length);
1014
- return result;
1015
- }
1016
- /**
1017
- * Calculate SHA-1 hash using Web Crypto API.
1018
- * @internal
1019
- */
1020
- async function sha1(data) {
1021
- // Create a copy as ArrayBuffer to satisfy BufferSource type
1022
- const buffer = new ArrayBuffer(data.length);
1023
- new Uint8Array(buffer).set(data);
1024
- const hashBuffer = await crypto.subtle.digest('SHA-1', buffer);
1025
- return new Uint8Array(hashBuffer);
1026
- }
1027
- // ============================================================================
1028
- // Full Fetch Handler
1029
- // ============================================================================
1030
- /**
1031
- * Handle a complete fetch request.
1032
- *
1033
- * @description
1034
- * This is the main entry point that handles the full upload-pack protocol flow:
1035
- * 1. Parse client request (wants, haves, capabilities, shallow commands)
1036
- * 2. Negotiate common ancestors via ACK/NAK
1037
- * 3. Generate and return packfile with requested objects
1038
- *
1039
- * @param session - Upload pack session
1040
- * @param request - Raw request data (pkt-line formatted)
1041
- * @param store - Object store
1042
- * @returns Response data (ACKs/NAKs + packfile)
1043
- *
1044
- * @example
1045
- * ```typescript
1046
- * const session = createSession('repo', refs)
1047
- * const requestBody = '0032want abc123... side-band-64k\n00000009done\n'
1048
- *
1049
- * const response = await handleFetch(session, requestBody, store)
1050
- * // response contains NAK + packfile data
1051
- * ```
1052
- */
1053
- export async function handleFetch(session, request, store) {
1054
- const lines = request.split('\n').filter(l => l.trim() && l !== '0000');
1055
- const parts = [];
1056
- const wants = [];
1057
- const haves = [];
1058
- const shallowLines = [];
1059
- let depth;
1060
- let done = false;
1061
- let sideBand = false;
1062
- // Parse request
1063
- for (const line of lines) {
1064
- const trimmed = line.trim();
1065
- if (trimmed.startsWith('want ')) {
1066
- const parsed = parseWantLine(trimmed);
1067
- wants.push(parsed.sha);
1068
- // First want line contains capabilities
1069
- if (wants.length === 1) {
1070
- session.capabilities = { ...session.capabilities, ...parsed.capabilities };
1071
- sideBand = parsed.capabilities.sideBand64k || false;
1072
- }
1073
- }
1074
- else if (trimmed.startsWith('have ')) {
1075
- const sha = parseHaveLine(trimmed);
1076
- haves.push(sha);
1077
- }
1078
- else if (trimmed.startsWith('shallow ')) {
1079
- shallowLines.push(trimmed);
1080
- }
1081
- else if (trimmed.startsWith('deepen ')) {
1082
- depth = parseInt(trimmed.slice(7), 10);
1083
- }
1084
- else if (trimmed === 'done') {
1085
- done = true;
1086
- }
1087
- }
1088
- // Process wants
1089
- await processWants(session, wants, store);
1090
- // Process shallow if present
1091
- if (shallowLines.length > 0 || depth !== undefined) {
1092
- const shallowInfo = await processShallow(session, shallowLines, depth, undefined, undefined, store);
1093
- const shallowResponse = formatShallowResponse(shallowInfo);
1094
- if (shallowResponse) {
1095
- parts.push(encoder.encode(shallowResponse));
1096
- }
1097
- }
1098
- // Process haves
1099
- const negotiation = await processHaves(session, haves, store, done);
1100
- // Generate ACK/NAK response
1101
- if (negotiation.nak) {
1102
- parts.push(encoder.encode(formatNak()));
1103
- }
1104
- else {
1105
- for (const ack of negotiation.acks) {
1106
- parts.push(encoder.encode(formatAck(ack.sha, ack.status)));
1107
- }
1108
- }
1109
- // Generate packfile if ready
1110
- if (negotiation.ready || done) {
1111
- const packResult = await generatePackfile(store, session.wants, session.commonAncestors, {
1112
- onProgress: sideBand ? undefined : undefined,
1113
- thinPack: session.capabilities.thinPack,
1114
- clientHasObjects: session.commonAncestors
1115
- });
1116
- // Add packfile data
1117
- if (sideBand) {
1118
- // Wrap in side-band format
1119
- const wrapped = wrapSideBand(SideBandChannel.PACK_DATA, packResult.packfile);
1120
- parts.push(wrapped);
1121
- // Add flush
1122
- parts.push(encoder.encode(FLUSH_PKT));
1123
- }
1124
- else {
1125
- parts.push(packResult.packfile);
1126
- }
1127
- }
1128
- // Concatenate all parts
1129
- let totalLength = 0;
1130
- for (const part of parts) {
1131
- totalLength += part.length;
1132
- }
1133
- const result = new Uint8Array(totalLength);
1134
- let offset = 0;
1135
- for (const part of parts) {
1136
- result.set(part, offset);
1137
- offset += part.length;
1138
- }
1139
- return result;
1140
- }
1141
- //# sourceMappingURL=upload-pack.js.map