gitx.do 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -415
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -174
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -131
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/commit.d.ts +0 -182
  32. package/dist/cli/commands/commit.d.ts.map +0 -1
  33. package/dist/cli/commands/commit.js +0 -437
  34. package/dist/cli/commands/commit.js.map +0 -1
  35. package/dist/cli/commands/diff.d.ts +0 -464
  36. package/dist/cli/commands/diff.d.ts.map +0 -1
  37. package/dist/cli/commands/diff.js +0 -958
  38. package/dist/cli/commands/diff.js.map +0 -1
  39. package/dist/cli/commands/log.d.ts +0 -239
  40. package/dist/cli/commands/log.d.ts.map +0 -1
  41. package/dist/cli/commands/log.js +0 -535
  42. package/dist/cli/commands/log.js.map +0 -1
  43. package/dist/cli/commands/merge.d.ts +0 -106
  44. package/dist/cli/commands/merge.d.ts.map +0 -1
  45. package/dist/cli/commands/merge.js +0 -55
  46. package/dist/cli/commands/merge.js.map +0 -1
  47. package/dist/cli/commands/review.d.ts +0 -457
  48. package/dist/cli/commands/review.d.ts.map +0 -1
  49. package/dist/cli/commands/review.js +0 -533
  50. package/dist/cli/commands/review.js.map +0 -1
  51. package/dist/cli/commands/status.d.ts +0 -269
  52. package/dist/cli/commands/status.d.ts.map +0 -1
  53. package/dist/cli/commands/status.js +0 -493
  54. package/dist/cli/commands/status.js.map +0 -1
  55. package/dist/cli/commands/web.d.ts +0 -199
  56. package/dist/cli/commands/web.d.ts.map +0 -1
  57. package/dist/cli/commands/web.js +0 -696
  58. package/dist/cli/commands/web.js.map +0 -1
  59. package/dist/cli/fs-adapter.d.ts +0 -656
  60. package/dist/cli/fs-adapter.d.ts.map +0 -1
  61. package/dist/cli/fs-adapter.js +0 -1179
  62. package/dist/cli/fs-adapter.js.map +0 -1
  63. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  64. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  65. package/dist/cli/fsx-cli-adapter.js +0 -619
  66. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  67. package/dist/cli/index.d.ts +0 -387
  68. package/dist/cli/index.d.ts.map +0 -1
  69. package/dist/cli/index.js +0 -523
  70. package/dist/cli/index.js.map +0 -1
  71. package/dist/cli/ui/components/DiffView.d.ts +0 -7
  72. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  73. package/dist/cli/ui/components/DiffView.js +0 -11
  74. package/dist/cli/ui/components/DiffView.js.map +0 -1
  75. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
  76. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  77. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  78. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  79. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
  80. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  81. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  82. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  83. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
  84. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  85. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  86. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  87. package/dist/cli/ui/components/NavigationList.d.ts +0 -9
  88. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  89. package/dist/cli/ui/components/NavigationList.js +0 -11
  90. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  91. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
  92. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  93. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  94. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  95. package/dist/cli/ui/components/index.d.ts +0 -7
  96. package/dist/cli/ui/components/index.d.ts.map +0 -1
  97. package/dist/cli/ui/components/index.js +0 -9
  98. package/dist/cli/ui/components/index.js.map +0 -1
  99. package/dist/cli/ui/terminal-ui.d.ts +0 -52
  100. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  101. package/dist/cli/ui/terminal-ui.js +0 -121
  102. package/dist/cli/ui/terminal-ui.js.map +0 -1
  103. package/dist/do/BashModule.d.ts +0 -871
  104. package/dist/do/BashModule.d.ts.map +0 -1
  105. package/dist/do/BashModule.js +0 -1143
  106. package/dist/do/BashModule.js.map +0 -1
  107. package/dist/do/FsModule.d.ts +0 -601
  108. package/dist/do/FsModule.d.ts.map +0 -1
  109. package/dist/do/FsModule.js +0 -1120
  110. package/dist/do/FsModule.js.map +0 -1
  111. package/dist/do/GitModule.d.ts +0 -635
  112. package/dist/do/GitModule.d.ts.map +0 -1
  113. package/dist/do/GitModule.js +0 -781
  114. package/dist/do/GitModule.js.map +0 -1
  115. package/dist/do/GitRepoDO.d.ts +0 -281
  116. package/dist/do/GitRepoDO.d.ts.map +0 -1
  117. package/dist/do/GitRepoDO.js +0 -479
  118. package/dist/do/GitRepoDO.js.map +0 -1
  119. package/dist/do/bash-ast.d.ts +0 -246
  120. package/dist/do/bash-ast.d.ts.map +0 -1
  121. package/dist/do/bash-ast.js +0 -888
  122. package/dist/do/bash-ast.js.map +0 -1
  123. package/dist/do/container-executor.d.ts +0 -491
  124. package/dist/do/container-executor.d.ts.map +0 -1
  125. package/dist/do/container-executor.js +0 -730
  126. package/dist/do/container-executor.js.map +0 -1
  127. package/dist/do/index.d.ts +0 -53
  128. package/dist/do/index.d.ts.map +0 -1
  129. package/dist/do/index.js +0 -91
  130. package/dist/do/index.js.map +0 -1
  131. package/dist/do/tiered-storage.d.ts +0 -403
  132. package/dist/do/tiered-storage.d.ts.map +0 -1
  133. package/dist/do/tiered-storage.js +0 -689
  134. package/dist/do/tiered-storage.js.map +0 -1
  135. package/dist/do/withBash.d.ts +0 -231
  136. package/dist/do/withBash.d.ts.map +0 -1
  137. package/dist/do/withBash.js +0 -244
  138. package/dist/do/withBash.js.map +0 -1
  139. package/dist/do/withFs.d.ts +0 -237
  140. package/dist/do/withFs.d.ts.map +0 -1
  141. package/dist/do/withFs.js +0 -387
  142. package/dist/do/withFs.js.map +0 -1
  143. package/dist/do/withGit.d.ts +0 -180
  144. package/dist/do/withGit.d.ts.map +0 -1
  145. package/dist/do/withGit.js +0 -271
  146. package/dist/do/withGit.js.map +0 -1
  147. package/dist/durable-object/object-store.d.ts +0 -633
  148. package/dist/durable-object/object-store.d.ts.map +0 -1
  149. package/dist/durable-object/object-store.js +0 -1161
  150. package/dist/durable-object/object-store.js.map +0 -1
  151. package/dist/durable-object/schema.d.ts.map +0 -1
  152. package/dist/durable-object/schema.js.map +0 -1
  153. package/dist/durable-object/wal.d.ts +0 -416
  154. package/dist/durable-object/wal.d.ts.map +0 -1
  155. package/dist/durable-object/wal.js +0 -445
  156. package/dist/durable-object/wal.js.map +0 -1
  157. package/dist/mcp/adapter.d.ts +0 -772
  158. package/dist/mcp/adapter.d.ts.map +0 -1
  159. package/dist/mcp/adapter.js +0 -895
  160. package/dist/mcp/adapter.js.map +0 -1
  161. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  162. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  163. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  164. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  165. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  166. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  167. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  168. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  169. package/dist/mcp/sandbox/template.d.ts +0 -17
  170. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/template.js +0 -71
  172. package/dist/mcp/sandbox/template.js.map +0 -1
  173. package/dist/mcp/sandbox.d.ts +0 -764
  174. package/dist/mcp/sandbox.d.ts.map +0 -1
  175. package/dist/mcp/sandbox.js +0 -1362
  176. package/dist/mcp/sandbox.js.map +0 -1
  177. package/dist/mcp/sdk-adapter.d.ts +0 -835
  178. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  179. package/dist/mcp/sdk-adapter.js +0 -974
  180. package/dist/mcp/sdk-adapter.js.map +0 -1
  181. package/dist/mcp/tools/do.d.ts +0 -32
  182. package/dist/mcp/tools/do.d.ts.map +0 -1
  183. package/dist/mcp/tools/do.js +0 -115
  184. package/dist/mcp/tools/do.js.map +0 -1
  185. package/dist/mcp/tools.d.ts +0 -548
  186. package/dist/mcp/tools.d.ts.map +0 -1
  187. package/dist/mcp/tools.js +0 -1934
  188. package/dist/mcp/tools.js.map +0 -1
  189. package/dist/ops/blame.d.ts +0 -551
  190. package/dist/ops/blame.d.ts.map +0 -1
  191. package/dist/ops/blame.js +0 -1037
  192. package/dist/ops/blame.js.map +0 -1
  193. package/dist/ops/branch.d.ts +0 -766
  194. package/dist/ops/branch.d.ts.map +0 -1
  195. package/dist/ops/branch.js +0 -950
  196. package/dist/ops/branch.js.map +0 -1
  197. package/dist/ops/commit-traversal.d.ts +0 -349
  198. package/dist/ops/commit-traversal.d.ts.map +0 -1
  199. package/dist/ops/commit-traversal.js +0 -821
  200. package/dist/ops/commit-traversal.js.map +0 -1
  201. package/dist/ops/commit.d.ts +0 -555
  202. package/dist/ops/commit.d.ts.map +0 -1
  203. package/dist/ops/commit.js +0 -826
  204. package/dist/ops/commit.js.map +0 -1
  205. package/dist/ops/merge-base.d.ts +0 -397
  206. package/dist/ops/merge-base.d.ts.map +0 -1
  207. package/dist/ops/merge-base.js +0 -691
  208. package/dist/ops/merge-base.js.map +0 -1
  209. package/dist/ops/merge.d.ts +0 -855
  210. package/dist/ops/merge.d.ts.map +0 -1
  211. package/dist/ops/merge.js +0 -1551
  212. package/dist/ops/merge.js.map +0 -1
  213. package/dist/ops/tag.d.ts +0 -247
  214. package/dist/ops/tag.d.ts.map +0 -1
  215. package/dist/ops/tag.js +0 -649
  216. package/dist/ops/tag.js.map +0 -1
  217. package/dist/ops/tree-builder.d.ts +0 -178
  218. package/dist/ops/tree-builder.d.ts.map +0 -1
  219. package/dist/ops/tree-builder.js +0 -271
  220. package/dist/ops/tree-builder.js.map +0 -1
  221. package/dist/ops/tree-diff.d.ts +0 -291
  222. package/dist/ops/tree-diff.d.ts.map +0 -1
  223. package/dist/ops/tree-diff.js +0 -705
  224. package/dist/ops/tree-diff.js.map +0 -1
  225. package/dist/pack/delta.d.ts +0 -248
  226. package/dist/pack/delta.d.ts.map +0 -1
  227. package/dist/pack/delta.js +0 -736
  228. package/dist/pack/delta.js.map +0 -1
  229. package/dist/pack/format.d.ts +0 -446
  230. package/dist/pack/format.d.ts.map +0 -1
  231. package/dist/pack/format.js +0 -572
  232. package/dist/pack/format.js.map +0 -1
  233. package/dist/pack/full-generation.d.ts +0 -612
  234. package/dist/pack/full-generation.d.ts.map +0 -1
  235. package/dist/pack/full-generation.js +0 -1378
  236. package/dist/pack/full-generation.js.map +0 -1
  237. package/dist/pack/generation.d.ts +0 -441
  238. package/dist/pack/generation.d.ts.map +0 -1
  239. package/dist/pack/generation.js +0 -707
  240. package/dist/pack/generation.js.map +0 -1
  241. package/dist/pack/index.d.ts +0 -502
  242. package/dist/pack/index.d.ts.map +0 -1
  243. package/dist/pack/index.js +0 -833
  244. package/dist/pack/index.js.map +0 -1
  245. package/dist/refs/branch.d.ts +0 -668
  246. package/dist/refs/branch.d.ts.map +0 -1
  247. package/dist/refs/branch.js +0 -897
  248. package/dist/refs/branch.js.map +0 -1
  249. package/dist/refs/storage.d.ts +0 -833
  250. package/dist/refs/storage.d.ts.map +0 -1
  251. package/dist/refs/storage.js +0 -1023
  252. package/dist/refs/storage.js.map +0 -1
  253. package/dist/refs/tag.d.ts +0 -860
  254. package/dist/refs/tag.d.ts.map +0 -1
  255. package/dist/refs/tag.js +0 -996
  256. package/dist/refs/tag.js.map +0 -1
  257. package/dist/storage/backend.d.ts +0 -425
  258. package/dist/storage/backend.d.ts.map +0 -1
  259. package/dist/storage/backend.js +0 -41
  260. package/dist/storage/backend.js.map +0 -1
  261. package/dist/storage/fsx-adapter.d.ts +0 -204
  262. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  263. package/dist/storage/fsx-adapter.js +0 -470
  264. package/dist/storage/fsx-adapter.js.map +0 -1
  265. package/dist/storage/lru-cache.d.ts +0 -691
  266. package/dist/storage/lru-cache.d.ts.map +0 -1
  267. package/dist/storage/lru-cache.js +0 -813
  268. package/dist/storage/lru-cache.js.map +0 -1
  269. package/dist/storage/object-index.d.ts +0 -585
  270. package/dist/storage/object-index.d.ts.map +0 -1
  271. package/dist/storage/object-index.js +0 -532
  272. package/dist/storage/object-index.js.map +0 -1
  273. package/dist/storage/r2-pack.d.ts +0 -1257
  274. package/dist/storage/r2-pack.d.ts.map +0 -1
  275. package/dist/storage/r2-pack.js +0 -1770
  276. package/dist/storage/r2-pack.js.map +0 -1
  277. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  278. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  279. package/dist/tiered/cdc-pipeline.js +0 -1880
  280. package/dist/tiered/cdc-pipeline.js.map +0 -1
  281. package/dist/tiered/migration.d.ts +0 -1104
  282. package/dist/tiered/migration.d.ts.map +0 -1
  283. package/dist/tiered/migration.js +0 -1214
  284. package/dist/tiered/migration.js.map +0 -1
  285. package/dist/tiered/parquet-writer.d.ts +0 -1145
  286. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  287. package/dist/tiered/parquet-writer.js +0 -1183
  288. package/dist/tiered/parquet-writer.js.map +0 -1
  289. package/dist/tiered/read-path.d.ts +0 -835
  290. package/dist/tiered/read-path.d.ts.map +0 -1
  291. package/dist/tiered/read-path.js +0 -487
  292. package/dist/tiered/read-path.js.map +0 -1
  293. package/dist/types/capability.d.ts +0 -1385
  294. package/dist/types/capability.d.ts.map +0 -1
  295. package/dist/types/capability.js +0 -36
  296. package/dist/types/capability.js.map +0 -1
  297. package/dist/types/index.d.ts +0 -13
  298. package/dist/types/index.d.ts.map +0 -1
  299. package/dist/types/index.js +0 -18
  300. package/dist/types/index.js.map +0 -1
  301. package/dist/types/objects.d.ts +0 -692
  302. package/dist/types/objects.d.ts.map +0 -1
  303. package/dist/types/objects.js +0 -837
  304. package/dist/types/objects.js.map +0 -1
  305. package/dist/types/storage.d.ts +0 -603
  306. package/dist/types/storage.d.ts.map +0 -1
  307. package/dist/types/storage.js +0 -191
  308. package/dist/types/storage.js.map +0 -1
  309. package/dist/types/worker-loader.d.ts +0 -60
  310. package/dist/types/worker-loader.d.ts.map +0 -1
  311. package/dist/types/worker-loader.js +0 -62
  312. package/dist/types/worker-loader.js.map +0 -1
  313. package/dist/utils/hash.d.ts +0 -197
  314. package/dist/utils/hash.d.ts.map +0 -1
  315. package/dist/utils/hash.js +0 -268
  316. package/dist/utils/hash.js.map +0 -1
  317. package/dist/utils/sha1.d.ts +0 -290
  318. package/dist/utils/sha1.d.ts.map +0 -1
  319. package/dist/utils/sha1.js +0 -582
  320. package/dist/utils/sha1.js.map +0 -1
  321. package/dist/wire/capabilities.d.ts +0 -1044
  322. package/dist/wire/capabilities.d.ts.map +0 -1
  323. package/dist/wire/capabilities.js +0 -941
  324. package/dist/wire/capabilities.js.map +0 -1
  325. package/dist/wire/path-security.d.ts +0 -157
  326. package/dist/wire/path-security.d.ts.map +0 -1
  327. package/dist/wire/path-security.js +0 -307
  328. package/dist/wire/path-security.js.map +0 -1
  329. package/dist/wire/pkt-line.d.ts +0 -345
  330. package/dist/wire/pkt-line.d.ts.map +0 -1
  331. package/dist/wire/pkt-line.js +0 -381
  332. package/dist/wire/pkt-line.js.map +0 -1
  333. package/dist/wire/receive-pack.d.ts +0 -1059
  334. package/dist/wire/receive-pack.d.ts.map +0 -1
  335. package/dist/wire/receive-pack.js +0 -1414
  336. package/dist/wire/receive-pack.js.map +0 -1
  337. package/dist/wire/smart-http.d.ts +0 -799
  338. package/dist/wire/smart-http.d.ts.map +0 -1
  339. package/dist/wire/smart-http.js +0 -945
  340. package/dist/wire/smart-http.js.map +0 -1
  341. package/dist/wire/upload-pack.d.ts +0 -727
  342. package/dist/wire/upload-pack.d.ts.map +0 -1
  343. package/dist/wire/upload-pack.js +0 -1138
  344. package/dist/wire/upload-pack.js.map +0 -1
@@ -1,1138 +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
- const hashBuffer = await crypto.subtle.digest('SHA-1', data);
1022
- return new Uint8Array(hashBuffer);
1023
- }
1024
- // ============================================================================
1025
- // Full Fetch Handler
1026
- // ============================================================================
1027
- /**
1028
- * Handle a complete fetch request.
1029
- *
1030
- * @description
1031
- * This is the main entry point that handles the full upload-pack protocol flow:
1032
- * 1. Parse client request (wants, haves, capabilities, shallow commands)
1033
- * 2. Negotiate common ancestors via ACK/NAK
1034
- * 3. Generate and return packfile with requested objects
1035
- *
1036
- * @param session - Upload pack session
1037
- * @param request - Raw request data (pkt-line formatted)
1038
- * @param store - Object store
1039
- * @returns Response data (ACKs/NAKs + packfile)
1040
- *
1041
- * @example
1042
- * ```typescript
1043
- * const session = createSession('repo', refs)
1044
- * const requestBody = '0032want abc123... side-band-64k\n00000009done\n'
1045
- *
1046
- * const response = await handleFetch(session, requestBody, store)
1047
- * // response contains NAK + packfile data
1048
- * ```
1049
- */
1050
- export async function handleFetch(session, request, store) {
1051
- const lines = request.split('\n').filter(l => l.trim() && l !== '0000');
1052
- const parts = [];
1053
- const wants = [];
1054
- const haves = [];
1055
- const shallowLines = [];
1056
- let depth;
1057
- let done = false;
1058
- let sideBand = false;
1059
- // Parse request
1060
- for (const line of lines) {
1061
- const trimmed = line.trim();
1062
- if (trimmed.startsWith('want ')) {
1063
- const parsed = parseWantLine(trimmed);
1064
- wants.push(parsed.sha);
1065
- // First want line contains capabilities
1066
- if (wants.length === 1) {
1067
- session.capabilities = { ...session.capabilities, ...parsed.capabilities };
1068
- sideBand = parsed.capabilities.sideBand64k || false;
1069
- }
1070
- }
1071
- else if (trimmed.startsWith('have ')) {
1072
- const sha = parseHaveLine(trimmed);
1073
- haves.push(sha);
1074
- }
1075
- else if (trimmed.startsWith('shallow ')) {
1076
- shallowLines.push(trimmed);
1077
- }
1078
- else if (trimmed.startsWith('deepen ')) {
1079
- depth = parseInt(trimmed.slice(7), 10);
1080
- }
1081
- else if (trimmed === 'done') {
1082
- done = true;
1083
- }
1084
- }
1085
- // Process wants
1086
- await processWants(session, wants, store);
1087
- // Process shallow if present
1088
- if (shallowLines.length > 0 || depth !== undefined) {
1089
- const shallowInfo = await processShallow(session, shallowLines, depth, undefined, undefined, store);
1090
- const shallowResponse = formatShallowResponse(shallowInfo);
1091
- if (shallowResponse) {
1092
- parts.push(encoder.encode(shallowResponse));
1093
- }
1094
- }
1095
- // Process haves
1096
- const negotiation = await processHaves(session, haves, store, done);
1097
- // Generate ACK/NAK response
1098
- if (negotiation.nak) {
1099
- parts.push(encoder.encode(formatNak()));
1100
- }
1101
- else {
1102
- for (const ack of negotiation.acks) {
1103
- parts.push(encoder.encode(formatAck(ack.sha, ack.status)));
1104
- }
1105
- }
1106
- // Generate packfile if ready
1107
- if (negotiation.ready || done) {
1108
- const packResult = await generatePackfile(store, session.wants, session.commonAncestors, {
1109
- onProgress: sideBand ? undefined : undefined,
1110
- thinPack: session.capabilities.thinPack,
1111
- clientHasObjects: session.commonAncestors
1112
- });
1113
- // Add packfile data
1114
- if (sideBand) {
1115
- // Wrap in side-band format
1116
- const wrapped = wrapSideBand(SideBandChannel.PACK_DATA, packResult.packfile);
1117
- parts.push(wrapped);
1118
- // Add flush
1119
- parts.push(encoder.encode(FLUSH_PKT));
1120
- }
1121
- else {
1122
- parts.push(packResult.packfile);
1123
- }
1124
- }
1125
- // Concatenate all parts
1126
- let totalLength = 0;
1127
- for (const part of parts) {
1128
- totalLength += part.length;
1129
- }
1130
- const result = new Uint8Array(totalLength);
1131
- let offset = 0;
1132
- for (const part of parts) {
1133
- result.set(part, offset);
1134
- offset += part.length;
1135
- }
1136
- return result;
1137
- }
1138
- //# sourceMappingURL=upload-pack.js.map