nano-git 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +407 -0
  2. package/dist/backend/file-backend.d.mts +26 -0
  3. package/dist/backend/file-backend.mjs +83 -0
  4. package/dist/backend/file.d.mts +2 -0
  5. package/dist/backend/file.mjs +2 -0
  6. package/dist/backend/index.d.mts +2 -0
  7. package/dist/backend/index.mjs +1 -0
  8. package/dist/backend/memory-backend.d.mts +25 -0
  9. package/dist/backend/memory-backend.mjs +33 -0
  10. package/dist/backend/memory.d.mts +2 -0
  11. package/dist/backend/memory.mjs +2 -0
  12. package/dist/backend/types.d.mts +90 -0
  13. package/dist/core/errors.d.mts +124 -0
  14. package/dist/core/errors.mjs +168 -0
  15. package/dist/core/hash-digest.d.mts +35 -0
  16. package/dist/core/hash-digest.mjs +50 -0
  17. package/dist/core/hash-file.d.mts +20 -0
  18. package/dist/core/hash-file.mjs +27 -0
  19. package/dist/core/hash-path.d.mts +17 -0
  20. package/dist/core/hash-path.mjs +35 -0
  21. package/dist/core/types/odb.d.mts +63 -0
  22. package/dist/core/types/refs.d.mts +140 -0
  23. package/dist/core/types/refs.mjs +19 -0
  24. package/dist/core/types/shallow.d.mts +52 -0
  25. package/dist/core/types.d.mts +154 -0
  26. package/dist/core/types.mjs +43 -0
  27. package/dist/errors.d.mts +2 -0
  28. package/dist/errors.mjs +2 -0
  29. package/dist/hash-file.d.mts +2 -0
  30. package/dist/hash-file.mjs +2 -0
  31. package/dist/index.d.mts +16 -0
  32. package/dist/index.mjs +14 -0
  33. package/dist/objects/author.d.mts +25 -0
  34. package/dist/objects/author.mjs +45 -0
  35. package/dist/objects/blob.d.mts +15 -0
  36. package/dist/objects/blob.mjs +20 -0
  37. package/dist/objects/codec.d.mts +46 -0
  38. package/dist/objects/codec.mjs +84 -0
  39. package/dist/objects/commit.d.mts +26 -0
  40. package/dist/objects/commit.mjs +160 -0
  41. package/dist/objects/index.d.mts +8 -0
  42. package/dist/objects/index.mjs +8 -0
  43. package/dist/objects/raw.d.mts +85 -0
  44. package/dist/objects/raw.mjs +111 -0
  45. package/dist/objects/tag.d.mts +26 -0
  46. package/dist/objects/tag.mjs +148 -0
  47. package/dist/objects/tree.d.mts +22 -0
  48. package/dist/objects/tree.mjs +66 -0
  49. package/dist/odb/file-utils.mjs +136 -0
  50. package/dist/odb/file.d.mts +22 -0
  51. package/dist/odb/file.mjs +66 -0
  52. package/dist/odb/memory.d.mts +22 -0
  53. package/dist/odb/memory.mjs +54 -0
  54. package/dist/pack/composite-store.d.mts +76 -0
  55. package/dist/pack/composite-store.mjs +133 -0
  56. package/dist/pack/constants.mjs +48 -0
  57. package/dist/pack/crc32.mjs +38 -0
  58. package/dist/pack/delta-apply.d.mts +21 -0
  59. package/dist/pack/delta-apply.mjs +71 -0
  60. package/dist/pack/delta-create.d.mts +28 -0
  61. package/dist/pack/delta-create.mjs +151 -0
  62. package/dist/pack/index.d.mts +16 -0
  63. package/dist/pack/index.mjs +13 -0
  64. package/dist/pack/object-header.d.mts +36 -0
  65. package/dist/pack/object-header.mjs +72 -0
  66. package/dist/pack/ofs-delta-offset.d.mts +35 -0
  67. package/dist/pack/ofs-delta-offset.mjs +59 -0
  68. package/dist/pack/pack-builder-types.d.mts +19 -0
  69. package/dist/pack/pack-builder.d.mts +52 -0
  70. package/dist/pack/pack-builder.mjs +80 -0
  71. package/dist/pack/pack-encoding.mjs +76 -0
  72. package/dist/pack/pack-index-reader.d.mts +57 -0
  73. package/dist/pack/pack-index-reader.mjs +90 -0
  74. package/dist/pack/pack-index-types.d.mts +16 -0
  75. package/dist/pack/pack-index-writer.d.mts +45 -0
  76. package/dist/pack/pack-index-writer.mjs +106 -0
  77. package/dist/pack/pack-reader-resolver.mjs +104 -0
  78. package/dist/pack/pack-reader-types.d.mts +31 -0
  79. package/dist/pack/pack-reader-types.mjs +22 -0
  80. package/dist/pack/pack-reader-utils.mjs +61 -0
  81. package/dist/pack/pack-reader.d.mts +81 -0
  82. package/dist/pack/pack-reader.mjs +171 -0
  83. package/dist/pack/pack-store-loader.mjs +79 -0
  84. package/dist/pack/pack-store-types.d.mts +16 -0
  85. package/dist/pack/pack-store.d.mts +55 -0
  86. package/dist/pack/pack-store.mjs +114 -0
  87. package/dist/pack/pack-writer.mjs +96 -0
  88. package/dist/pack/varint.d.mts +34 -0
  89. package/dist/pack/varint.mjs +57 -0
  90. package/dist/refs/file.d.mts +6 -0
  91. package/dist/refs/file.mjs +222 -0
  92. package/dist/refs/fs-utils.mjs +30 -0
  93. package/dist/refs/memory.d.mts +14 -0
  94. package/dist/refs/memory.mjs +104 -0
  95. package/dist/refs/names.d.mts +57 -0
  96. package/dist/refs/names.mjs +80 -0
  97. package/dist/refs/resolve.d.mts +33 -0
  98. package/dist/refs/resolve.mjs +55 -0
  99. package/dist/refs/shallow/file.d.mts +17 -0
  100. package/dist/refs/shallow/file.mjs +61 -0
  101. package/dist/refs/shallow/memory.d.mts +18 -0
  102. package/dist/refs/shallow/memory.mjs +33 -0
  103. package/dist/repository/core.d.mts +3 -0
  104. package/dist/repository/core.mjs +2 -0
  105. package/dist/repository/create.d.mts +22 -0
  106. package/dist/repository/create.mjs +43 -0
  107. package/dist/repository/file.d.mts +43 -0
  108. package/dist/repository/file.mjs +82 -0
  109. package/dist/repository/import/import-glob.mjs +44 -0
  110. package/dist/repository/import/import-plan-builder.mjs +625 -0
  111. package/dist/repository/import/import-session-types.d.mts +280 -0
  112. package/dist/repository/import/import-session.mjs +96 -0
  113. package/dist/repository/import/import-view.mjs +133 -0
  114. package/dist/repository/memory.d.mts +17 -0
  115. package/dist/repository/memory.mjs +33 -0
  116. package/dist/repository/ops/fetch-operations.mjs +20 -0
  117. package/dist/repository/ops/fetch-types.d.mts +82 -0
  118. package/dist/repository/ops/fetch-url.mjs +82 -0
  119. package/dist/repository/ops/fs-object-operations.mjs +31 -0
  120. package/dist/repository/ops/maintenance-operations.mjs +62 -0
  121. package/dist/repository/ops/maintenance-types.d.mts +42 -0
  122. package/dist/repository/ops/object-operations.mjs +65 -0
  123. package/dist/repository/ops/object-types.d.mts +109 -0
  124. package/dist/repository/ops/push-operations.mjs +16 -0
  125. package/dist/repository/ops/push-resolution.mjs +17 -0
  126. package/dist/repository/ops/push-types.d.mts +72 -0
  127. package/dist/repository/ops/push-url.mjs +45 -0
  128. package/dist/repository/ops/reachability.mjs +60 -0
  129. package/dist/repository/ops/ref-operations.mjs +86 -0
  130. package/dist/repository/ops/ref-types.d.mts +70 -0
  131. package/dist/repository/tree/tree-patch.d.mts +64 -0
  132. package/dist/repository/tree/tree-patch.mjs +268 -0
  133. package/dist/repository/tree/tree-walk.d.mts +46 -0
  134. package/dist/repository/tree/tree-walk.mjs +65 -0
  135. package/dist/repository/tree/tree-writer.mjs +68 -0
  136. package/dist/repository/types.d.mts +36 -0
  137. package/dist/sha1.d.mts +4 -0
  138. package/dist/sha1.mjs +4 -0
  139. package/dist/transport/client/receive-pack/http.d.mts +33 -0
  140. package/dist/transport/client/receive-pack/http.mjs +99 -0
  141. package/dist/transport/client/receive-pack/push-error.d.mts +23 -0
  142. package/dist/transport/client/receive-pack/push-error.mjs +32 -0
  143. package/dist/transport/client/receive-pack/push-pack-plan.d.mts +28 -0
  144. package/dist/transport/client/receive-pack/push-pack-plan.mjs +60 -0
  145. package/dist/transport/client/receive-pack/push-policy.d.mts +19 -0
  146. package/dist/transport/client/receive-pack/push-policy.mjs +64 -0
  147. package/dist/transport/client/receive-pack/push-ref-plan.d.mts +45 -0
  148. package/dist/transport/client/receive-pack/push-ref-plan.mjs +108 -0
  149. package/dist/transport/client/receive-pack/push-report.d.mts +28 -0
  150. package/dist/transport/client/receive-pack/push-report.mjs +84 -0
  151. package/dist/transport/client/receive-pack/push-request-plan.mjs +52 -0
  152. package/dist/transport/client/receive-pack/push.d.mts +32 -0
  153. package/dist/transport/client/receive-pack/push.mjs +97 -0
  154. package/dist/transport/client/receive-pack/request.d.mts +39 -0
  155. package/dist/transport/client/receive-pack/request.mjs +46 -0
  156. package/dist/transport/client/receive-pack/response.d.mts +26 -0
  157. package/dist/transport/client/receive-pack/response.mjs +52 -0
  158. package/dist/transport/client/receive-pack/result.d.mts +34 -0
  159. package/dist/transport/client/receive-pack/result.mjs +100 -0
  160. package/dist/transport/client/upload-pack/capability-advertisement.d.mts +56 -0
  161. package/dist/transport/client/upload-pack/capability-advertisement.mjs +130 -0
  162. package/dist/transport/client/upload-pack/fetch.d.mts +109 -0
  163. package/dist/transport/client/upload-pack/fetch.mjs +392 -0
  164. package/dist/transport/client/upload-pack/http.d.mts +29 -0
  165. package/dist/transport/client/upload-pack/http.mjs +79 -0
  166. package/dist/transport/client/upload-pack/ls-refs.d.mts +75 -0
  167. package/dist/transport/client/upload-pack/ls-refs.mjs +150 -0
  168. package/dist/transport/client/upload-pack/object-info.d.mts +65 -0
  169. package/dist/transport/client/upload-pack/object-info.mjs +111 -0
  170. package/dist/transport/client/upload-pack/types.d.mts +153 -0
  171. package/dist/transport/http/index.d.mts +3 -0
  172. package/dist/transport/http/index.mjs +2 -0
  173. package/dist/transport/http/smart-http.d.mts +46 -0
  174. package/dist/transport/http/smart-http.mjs +176 -0
  175. package/dist/transport/http/types.d.mts +27 -0
  176. package/dist/transport/index.d.mts +9 -0
  177. package/dist/transport/index.mjs +8 -0
  178. package/dist/transport/protocol/object-graph.d.mts +63 -0
  179. package/dist/transport/protocol/object-graph.mjs +149 -0
  180. package/dist/transport/protocol/pkt-line.d.mts +109 -0
  181. package/dist/transport/protocol/pkt-line.mjs +195 -0
  182. package/dist/transport/protocol/ref-advertisement.mjs +185 -0
  183. package/dist/transport/protocol/ref-collection.d.mts +38 -0
  184. package/dist/transport/protocol/ref-collection.mjs +63 -0
  185. package/dist/transport/protocol/ref-match.d.mts +39 -0
  186. package/dist/transport/protocol/ref-match.mjs +42 -0
  187. package/dist/transport/protocol/refspec.d.mts +44 -0
  188. package/dist/transport/protocol/refspec.mjs +79 -0
  189. package/dist/transport/protocol/side-band.d.mts +65 -0
  190. package/dist/transport/protocol/side-band.mjs +142 -0
  191. package/dist/transport/protocol/transport-capabilities.mjs +46 -0
  192. package/dist/transport/protocol/types.d.mts +148 -0
  193. package/dist/transport/protocol/update-refs.d.mts +68 -0
  194. package/dist/transport/protocol/update-refs.mjs +126 -0
  195. package/dist/transport/receive-pack.d.mts +11 -0
  196. package/dist/transport/receive-pack.mjs +11 -0
  197. package/dist/transport/server/receive-pack/advertise.d.mts +28 -0
  198. package/dist/transport/server/receive-pack/advertise.mjs +88 -0
  199. package/dist/transport/server/receive-pack/handler.d.mts +30 -0
  200. package/dist/transport/server/receive-pack/handler.mjs +156 -0
  201. package/dist/transport/server/receive-pack/index.d.mts +6 -0
  202. package/dist/transport/server/receive-pack/index.mjs +6 -0
  203. package/dist/transport/server/receive-pack/parse.d.mts +17 -0
  204. package/dist/transport/server/receive-pack/parse.mjs +80 -0
  205. package/dist/transport/server/receive-pack/report-status.mjs +64 -0
  206. package/dist/transport/server/receive-pack/service.d.mts +41 -0
  207. package/dist/transport/server/receive-pack/service.mjs +39 -0
  208. package/dist/transport/server/receive-pack/types.d.mts +56 -0
  209. package/dist/transport/server/receive-pack/types.mjs +25 -0
  210. package/dist/transport/server/receive-pack/unpack.mjs +119 -0
  211. package/dist/transport/server/upload-pack/advertise.d.mts +20 -0
  212. package/dist/transport/server/upload-pack/advertise.mjs +30 -0
  213. package/dist/transport/server/upload-pack/command.d.mts +43 -0
  214. package/dist/transport/server/upload-pack/command.mjs +56 -0
  215. package/dist/transport/server/upload-pack/fetch.d.mts +43 -0
  216. package/dist/transport/server/upload-pack/fetch.mjs +217 -0
  217. package/dist/transport/server/upload-pack/index.d.mts +7 -0
  218. package/dist/transport/server/upload-pack/index.mjs +7 -0
  219. package/dist/transport/server/upload-pack/ls-refs.d.mts +38 -0
  220. package/dist/transport/server/upload-pack/ls-refs.mjs +113 -0
  221. package/dist/transport/server/upload-pack/service.d.mts +40 -0
  222. package/dist/transport/server/upload-pack/service.mjs +51 -0
  223. package/dist/transport/server/upload-pack/types.d.mts +11 -0
  224. package/dist/transport/server/upload-pack/types.mjs +21 -0
  225. package/dist/transport/upload-pack.d.mts +7 -0
  226. package/dist/transport/upload-pack.mjs +6 -0
  227. package/package.json +98 -0
@@ -0,0 +1,109 @@
1
+ import { ObjectDatabase } from "../../../core/types/odb.mjs";
2
+ import { GitError } from "../../../core/errors.mjs";
3
+ import { V2FetchResponse, V2GitServiceTransport } from "./types.mjs";
4
+
5
+ //#region src/transport/client/upload-pack/fetch.d.ts
6
+ /**
7
+ * v2 fetch 命令错误
8
+ */
9
+ declare class V2FetchError extends GitError {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * v2 fetch 参数
14
+ *
15
+ * 对应 git protocol v2 fetch 命令的 arguments 段参数。
16
+ */
17
+ interface V2FetchParams {
18
+ /** want 列表(对象哈希) */
19
+ readonly wants: string[];
20
+ /** have 列表(对象哈希) */
21
+ readonly haves?: string[];
22
+ /** 是否发送 done(结束协商,直接要求 packfile) */
23
+ readonly done?: boolean;
24
+ /** want-ref 列表(按 ref 名请求) */
25
+ readonly wantRefs?: string[];
26
+ /** 是否请求 thin-pack */
27
+ readonly thinPack?: boolean;
28
+ /** 是否禁用进度消息 */
29
+ readonly noProgress?: boolean;
30
+ /** 是否请求 include-tag */
31
+ readonly includeTag?: boolean;
32
+ /** 是否支持 ofs-delta */
33
+ readonly ofsDelta?: boolean;
34
+ /** shallow 边界 */
35
+ readonly shallow?: string[];
36
+ /** deepen 深度 */
37
+ readonly deepen?: number;
38
+ /** deepen-relative 标志 */
39
+ readonly deepenRelative?: boolean;
40
+ /** deepen-since 时间戳 */
41
+ readonly deepenSince?: number;
42
+ /** deepen-not 排除 */
43
+ readonly deepenNot?: string[];
44
+ /** filter 表达式 */
45
+ readonly filter?: string;
46
+ /** sideband-all 标志 */
47
+ readonly sidebandAll?: boolean;
48
+ /** wait-for-done 标志 */
49
+ readonly waitForDone?: boolean;
50
+ }
51
+ /**
52
+ * 执行 v2 fetch 命令
53
+ *
54
+ * 构建并发送 fetch 请求,返回解析后的完整响应。
55
+ *
56
+ * @param transport - v2 传输接口
57
+ * @param params - fetch 参数
58
+ * @param features - 服务端 fetch 命令支持的附加特性
59
+ * @returns 解析后的 fetch 响应
60
+ * @throws {V2FetchError} 当 wants 为空时
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const result = await v2Fetch(transport, {
65
+ * wants: [hash1, hash2],
66
+ * ofsDelta: true,
67
+ * done: true,
68
+ * });
69
+ * console.log(result.packfile?.length); // packfile 数据长度
70
+ * ```
71
+ */
72
+ declare function v2Fetch(transport: V2GitServiceTransport, params: V2FetchParams, features?: string[]): Promise<V2FetchResponse>;
73
+ /**
74
+ * 解析 v2 fetch 响应
75
+ *
76
+ * v2 fetch 响应由多个节组成,节之间由 delimiter (0001) 分隔。
77
+ * 每个节以节头(如 "acknowledgments")开始。
78
+ *
79
+ * @param data - 原始响应数据
80
+ * @param hasDone - 请求中是否包含 done
81
+ * @param sidebandAll - 是否协商了 sideband-all
82
+ * @returns 解析后的 fetch 响应
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const result = parseV2FetchResponse(responseData, true, false);
87
+ * if (result.packfile) {
88
+ * // 处理 packfile
89
+ * }
90
+ * ```
91
+ */
92
+ declare function parseV2FetchResponse(data: Buffer, _hasDone: boolean, sidebandAll: boolean): V2FetchResponse;
93
+ /**
94
+ * 使用 v2 fetch 获取对象并写入对象存储
95
+ *
96
+ * 模拟 v1 fetchPack() 的语义,使 import-plan-builder 可无缝切换。
97
+ *
98
+ * @param store - 对象存储(用于写入 packfile 中的对象)
99
+ * @param v2Trans - v2 传输接口
100
+ * @param wants - want 对象哈希列表
101
+ * @param haves - have 对象哈希列表
102
+ * @param features - 服务端 fetch 命令特性
103
+ * @returns 导入的对象数量
104
+ */
105
+ declare function v2FetchObjects(db: ObjectDatabase, v2Trans: V2GitServiceTransport, wants: string[], haves?: string[], features?: string[]): Promise<{
106
+ objectCount: number;
107
+ }>;
108
+ //#endregion
109
+ export { V2FetchError, V2FetchParams, parseV2FetchResponse, v2Fetch, v2FetchObjects };
@@ -0,0 +1,392 @@
1
+ import { GitError } from "../../../core/errors.mjs";
2
+ import { packObjectToRaw } from "../../../pack/pack-reader-types.mjs";
3
+ import { createPackReader } from "../../../pack/pack-reader.mjs";
4
+ import { splitPktLinesFromBuffer } from "../../protocol/pkt-line.mjs";
5
+ //#region src/transport/client/upload-pack/fetch.ts
6
+ /**
7
+ * v2 fetch 命令
8
+ *
9
+ * 在 Git Wire 协议 v2 中,fetch 替代了 v1 的 upload-pack 协商 + packfile 传输。
10
+ * 支持 want/have/done、want-ref、shallow、thin-pack 等参数。
11
+ *
12
+ * 请求格式:
13
+ * ```
14
+ * command=fetch\n
15
+ * agent=nano-git/0.1\n
16
+ * ofs-delta\n
17
+ * include-tag\n
18
+ * 0001
19
+ * want <oid>\n
20
+ * have <oid>\n
21
+ * done\n
22
+ * 0000
23
+ * ```
24
+ *
25
+ * 响应格式(节之间由 0001 分隔):
26
+ * ```
27
+ * acknowledgments\n
28
+ * NAK\n (或 ACK <oid>\n ... ready\n)
29
+ * 0001
30
+ * shallow-info\n
31
+ * shallow <oid>\n
32
+ * 0001
33
+ * wanted-refs\n
34
+ * <oid> <refname>\n
35
+ * 0001
36
+ * packfile\n
37
+ * [side-band 多路复用数据]
38
+ * 0000
39
+ * ```
40
+ *
41
+ * @see https://git-scm.com/docs/protocol-v2#_fetch
42
+ */
43
+ /**
44
+ * v2 fetch 命令错误
45
+ */
46
+ var V2FetchError = class extends GitError {
47
+ constructor(message) {
48
+ super(`v2 fetch error: ${message}`);
49
+ this.name = "V2FetchError";
50
+ }
51
+ };
52
+ /** 单轮最多发送的 have 数量 */
53
+ const MAX_HAVES_PER_ROUND = 32;
54
+ /** 侧信道通道编号 */
55
+ const CHANNEL_PACKFILE = 1;
56
+ /**
57
+ * 执行 v2 fetch 命令
58
+ *
59
+ * 构建并发送 fetch 请求,返回解析后的完整响应。
60
+ *
61
+ * @param transport - v2 传输接口
62
+ * @param params - fetch 参数
63
+ * @param features - 服务端 fetch 命令支持的附加特性
64
+ * @returns 解析后的 fetch 响应
65
+ * @throws {V2FetchError} 当 wants 为空时
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const result = await v2Fetch(transport, {
70
+ * wants: [hash1, hash2],
71
+ * ofsDelta: true,
72
+ * done: true,
73
+ * });
74
+ * console.log(result.packfile?.length); // packfile 数据长度
75
+ * ```
76
+ */
77
+ async function v2Fetch(transport, params, features) {
78
+ if (params.wants.length === 0) throw new V2FetchError("No wants specified for fetch");
79
+ const hasFeature = (name) => features !== void 0 && features.includes(name);
80
+ const args = [];
81
+ if (params.thinPack) args.push("thin-pack");
82
+ if (params.noProgress) args.push("no-progress");
83
+ if (params.includeTag) args.push("include-tag");
84
+ if (params.ofsDelta) args.push("ofs-delta");
85
+ if (params.sidebandAll) args.push("sideband-all");
86
+ if (params.waitForDone) args.push("wait-for-done");
87
+ for (const oid of params.wants) args.push(`want ${oid}`);
88
+ if (params.wantRefs && hasFeature("ref-in-want")) for (const ref of params.wantRefs) args.push(`want-ref ${ref}`);
89
+ if (params.done) args.push("done");
90
+ if (!params.done && params.haves) for (const oid of params.haves) args.push(`have ${oid}`);
91
+ if (params.shallow && hasFeature("shallow")) for (const oid of params.shallow) args.push(`shallow ${oid}`);
92
+ if (params.deepen !== void 0 && hasFeature("shallow")) args.push(`deepen ${params.deepen}`);
93
+ if (params.deepenRelative && hasFeature("shallow")) args.push("deepen-relative");
94
+ if (params.deepenSince !== void 0 && hasFeature("shallow")) args.push(`deepen-since ${params.deepenSince}`);
95
+ if (params.deepenNot && hasFeature("shallow")) for (const rev of params.deepenNot) args.push(`deepen-not ${rev}`);
96
+ if (params.filter && hasFeature("filter")) args.push(`filter ${params.filter}`);
97
+ return parseV2FetchResponse(await transport.command("fetch", args, []), params.done ?? false, hasFeature("sideband-all"));
98
+ }
99
+ /**
100
+ * 解析 v2 fetch 响应
101
+ *
102
+ * v2 fetch 响应由多个节组成,节之间由 delimiter (0001) 分隔。
103
+ * 每个节以节头(如 "acknowledgments")开始。
104
+ *
105
+ * @param data - 原始响应数据
106
+ * @param hasDone - 请求中是否包含 done
107
+ * @param sidebandAll - 是否协商了 sideband-all
108
+ * @returns 解析后的 fetch 响应
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const result = parseV2FetchResponse(responseData, true, false);
113
+ * if (result.packfile) {
114
+ * // 处理 packfile
115
+ * }
116
+ * ```
117
+ */
118
+ function parseV2FetchResponse(data, _hasDone, sidebandAll) {
119
+ const { lines: pktLines, trailing } = splitPktLinesFromBuffer(data);
120
+ if (sidebandAll && pktLines.length > 0) {
121
+ const first = pktLines[0];
122
+ if (first?.type === "data" && first.payload.length > 1) {
123
+ const withoutChannel = Buffer.concat(pktLines.filter((p) => p.type === "data").map((p) => p.payload.subarray(1)));
124
+ return parseV2FetchResponse(Buffer.concat([withoutChannel, trailing]), false, false);
125
+ }
126
+ }
127
+ const sections = [];
128
+ let currentSection = null;
129
+ const packfileFrames = [];
130
+ let inPackfile = false;
131
+ for (const pkt of pktLines) {
132
+ if (pkt.type === "flush") break;
133
+ if (pkt.type === "delimiter") {
134
+ currentSection = null;
135
+ inPackfile = false;
136
+ continue;
137
+ }
138
+ if (pkt.type !== "data") continue;
139
+ const payload = pkt.payload;
140
+ const text = payload.toString("utf-8");
141
+ const trimmed = text.replace(/\n$/, "");
142
+ if (currentSection === null && !inPackfile) {
143
+ currentSection = {
144
+ header: trimmed,
145
+ lines: []
146
+ };
147
+ sections.push(currentSection);
148
+ if (trimmed === "packfile") {
149
+ inPackfile = true;
150
+ const headerEndIndex = text.indexOf("\n") + 1;
151
+ if (headerEndIndex > 0 && headerEndIndex < payload.length) packfileFrames.push(payload.subarray(headerEndIndex));
152
+ }
153
+ } else if (inPackfile) packfileFrames.push(payload);
154
+ else if (currentSection) currentSection.lines.push(payload);
155
+ }
156
+ if (trailing.length > 0) packfileFrames.push(trailing);
157
+ const result = {};
158
+ for (const section of sections) switch (section.header) {
159
+ case "acknowledgments":
160
+ result.acknowledgments = parseAcknowledgments(section.lines);
161
+ break;
162
+ case "shallow-info":
163
+ result.shallowInfo = parseShallowInfo(section.lines);
164
+ break;
165
+ case "wanted-refs":
166
+ result.wantedRefs = parseWantedRefs(section.lines);
167
+ break;
168
+ case "packfile-uris":
169
+ result.packfileUris = parsePackfileUris(section.lines);
170
+ break;
171
+ case "packfile":
172
+ if (packfileFrames.length > 0) result.packfile = extractPackfileFromFrames(packfileFrames);
173
+ break;
174
+ }
175
+ return result;
176
+ }
177
+ /**
178
+ * 解析 acknowledgments 节
179
+ *
180
+ * ```
181
+ * acknowledgments\n
182
+ * NAK\n
183
+ * --- 或 ---
184
+ * ACK <oid>\n
185
+ * ACK <oid>\n
186
+ * ready\n
187
+ * ```
188
+ */
189
+ function parseAcknowledgments(lines) {
190
+ const acks = [];
191
+ let nak = false;
192
+ let ready = false;
193
+ for (const line of lines) {
194
+ const text = line.toString("utf-8").trim();
195
+ if (text === "NAK") nak = true;
196
+ else if (text === "ready") ready = true;
197
+ else if (text.startsWith("ACK ")) acks.push(text.substring(4).trim());
198
+ }
199
+ return {
200
+ nak,
201
+ acks,
202
+ ready
203
+ };
204
+ }
205
+ /**
206
+ * 解析 shallow-info 节
207
+ *
208
+ * ```
209
+ * shallow-info\n
210
+ * shallow <oid>\n
211
+ * unshallow <oid>\n
212
+ * ```
213
+ */
214
+ function parseShallowInfo(lines) {
215
+ const shallow = [];
216
+ const unshallow = [];
217
+ for (const line of lines) {
218
+ const text = line.toString("utf-8").trim();
219
+ if (text.startsWith("shallow ")) shallow.push(text.substring(8).trim());
220
+ else if (text.startsWith("unshallow ")) unshallow.push(text.substring(10).trim());
221
+ }
222
+ return {
223
+ shallow,
224
+ unshallow
225
+ };
226
+ }
227
+ /**
228
+ * 解析 wanted-refs 节
229
+ *
230
+ * ```
231
+ * wanted-refs\n
232
+ * <oid> <refname>\n
233
+ * ```
234
+ */
235
+ function parseWantedRefs(lines) {
236
+ const refs = [];
237
+ for (const line of lines) {
238
+ const text = line.toString("utf-8").trim();
239
+ if (text.length === 0) continue;
240
+ const spaceIdx = text.indexOf(" ");
241
+ if (spaceIdx === -1) continue;
242
+ refs.push({
243
+ oid: text.substring(0, spaceIdx),
244
+ refname: text.substring(spaceIdx + 1).trim()
245
+ });
246
+ }
247
+ return refs;
248
+ }
249
+ /**
250
+ * 解析 packfile-uris 节
251
+ *
252
+ * ```
253
+ * packfile-uris\n
254
+ * <oid> <uri>\n
255
+ * ```
256
+ */
257
+ function parsePackfileUris(lines) {
258
+ const uris = [];
259
+ for (const line of lines) {
260
+ const text = line.toString("utf-8").trim();
261
+ if (text.length === 0) continue;
262
+ const spaceIdx = text.indexOf(" ");
263
+ if (spaceIdx === -1) continue;
264
+ uris.push({
265
+ oid: text.substring(0, spaceIdx),
266
+ uri: text.substring(spaceIdx + 1).trim()
267
+ });
268
+ }
269
+ return uris;
270
+ }
271
+ /**
272
+ * 从 side-band pkt-line payload 帧中提取 packfile 数据
273
+ *
274
+ * v2 fetch 响应中 packfile 节的每个 pkt-line payload 格式为:
275
+ * <1-byte-channel><data>
276
+ * channel 1 = packfile 数据
277
+ * channel 2 = 进度消息
278
+ * channel 3 = 致命错误
279
+ *
280
+ * @param frames - pkt-line payload 数组(不含长度前缀,含 channel 字节)
281
+ * @returns 拼接后的完整 packfile buffer
282
+ */
283
+ function extractPackfileFromFrames(frames) {
284
+ const chunks = [];
285
+ for (const frame of frames) {
286
+ if (frame.length < 1) continue;
287
+ if (frame[0] === CHANNEL_PACKFILE) chunks.push(frame.subarray(1));
288
+ }
289
+ if (chunks.length === 0) throw new V2FetchError("No packfile data found in fetch response");
290
+ return Buffer.concat(chunks);
291
+ }
292
+ /**
293
+ * 执行 v2 多轮 fetch 协商
294
+ *
295
+ * v2 的 fetch 协商与 v1 类似,但使用不同的请求/响应格式。
296
+ * 多轮协商中,中间轮以 flush 结尾(不含 done),
297
+ * 最终轮以 done 结尾。
298
+ *
299
+ * @param transport - v2 传输接口
300
+ * @param wants - want 列表
301
+ * @param haveCandidates - have 候选列表(按时间从旧到新排序)
302
+ * @param features - 服务端 fetch 命令特性
303
+ * @returns fetch 响应(含 packfile)
304
+ */
305
+ async function negotiateV2Fetch(transport, wants, haveCandidates, features) {
306
+ if (wants.length === 0) throw new V2FetchError("No wants specified for fetch");
307
+ if (haveCandidates.length === 0) return v2Fetch(transport, {
308
+ wants,
309
+ ofsDelta: true,
310
+ done: true
311
+ }, features);
312
+ const state = {
313
+ sent: /* @__PURE__ */ new Set(),
314
+ candidates: haveCandidates,
315
+ offset: 0,
316
+ common: []
317
+ };
318
+ const MAX_ROUNDS = 10;
319
+ for (let round = 0; round < MAX_ROUNDS; round++) {
320
+ const roundHaves = [];
321
+ for (const c of state.common) if (!state.sent.has(c)) {
322
+ roundHaves.push(c);
323
+ state.sent.add(c);
324
+ }
325
+ let remaining = MAX_HAVES_PER_ROUND - roundHaves.length;
326
+ while (remaining > 0 && state.offset < state.candidates.length) {
327
+ const candidate = state.candidates[state.offset];
328
+ state.offset++;
329
+ if (!state.sent.has(candidate)) {
330
+ roundHaves.push(candidate);
331
+ state.sent.add(candidate);
332
+ remaining--;
333
+ }
334
+ }
335
+ const isFinalRound = roundHaves.length === 0 || round === MAX_ROUNDS - 1;
336
+ const response = await v2Fetch(transport, {
337
+ wants,
338
+ haves: roundHaves,
339
+ ofsDelta: true
340
+ }, features);
341
+ const ack = response.acknowledgments;
342
+ if (!ack) {
343
+ if (response.packfile) return response;
344
+ continue;
345
+ }
346
+ if (ack.acks.length > 0) {
347
+ for (const ackOid of ack.acks) if (!state.common.includes(ackOid)) state.common.push(ackOid);
348
+ }
349
+ if (ack.ready) return v2Fetch(transport, {
350
+ wants,
351
+ haves: roundHaves,
352
+ ofsDelta: true,
353
+ done: true
354
+ }, features);
355
+ if (isFinalRound) return v2Fetch(transport, {
356
+ wants,
357
+ haves: roundHaves,
358
+ ofsDelta: true,
359
+ done: true
360
+ }, features);
361
+ }
362
+ return v2Fetch(transport, {
363
+ wants,
364
+ ofsDelta: true,
365
+ done: true
366
+ }, features);
367
+ }
368
+ /**
369
+ * 使用 v2 fetch 获取对象并写入对象存储
370
+ *
371
+ * 模拟 v1 fetchPack() 的语义,使 import-plan-builder 可无缝切换。
372
+ *
373
+ * @param store - 对象存储(用于写入 packfile 中的对象)
374
+ * @param v2Trans - v2 传输接口
375
+ * @param wants - want 对象哈希列表
376
+ * @param haves - have 对象哈希列表
377
+ * @param features - 服务端 fetch 命令特性
378
+ * @returns 导入的对象数量
379
+ */
380
+ async function v2FetchObjects(db, v2Trans, wants, haves, features) {
381
+ const result = await negotiateV2Fetch(v2Trans, wants, haves ?? [], features);
382
+ if (!result.packfile || result.packfile.length === 0) return { objectCount: 0 };
383
+ const reader = createPackReader(result.packfile);
384
+ let count = 0;
385
+ for (const packObj of reader.objects()) {
386
+ db.ingest(packObjectToRaw(packObj));
387
+ count++;
388
+ }
389
+ return { objectCount: count };
390
+ }
391
+ //#endregion
392
+ export { V2FetchError, parseV2FetchResponse, v2Fetch, v2FetchObjects };
@@ -0,0 +1,29 @@
1
+ import { V2GitServiceTransport } from "./types.mjs";
2
+
3
+ //#region src/transport/client/upload-pack/http.d.ts
4
+ /**
5
+ * v2 HTTP 传输错误
6
+ */
7
+ declare class V2SmartHttpError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * 创建 v2 HTTP 传输适配器
12
+ *
13
+ * @param url - 远端仓库 URL
14
+ * @param options - 可选认证选项
15
+ * @returns v2 传输接口
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const transport = createV2HttpTransport("https://github.com/user/repo");
20
+ * const caps = await transport.advertise();
21
+ * const refs = await transport.command("ls-refs", ["symrefs", "peel"]);
22
+ * ```
23
+ */
24
+ declare function createV2HttpTransport(url: string, options?: {
25
+ token?: string;
26
+ headers?: Record<string, string>;
27
+ }): V2GitServiceTransport;
28
+ //#endregion
29
+ export { V2SmartHttpError, createV2HttpTransport };
@@ -0,0 +1,79 @@
1
+ import { encodeDelimiterPkt, encodeFlushPkt, encodePktLine } from "../../protocol/pkt-line.mjs";
2
+ import { parseV2CapabilityAdvertisement } from "./capability-advertisement.mjs";
3
+ //#region src/transport/client/upload-pack/http.ts
4
+ /**
5
+ * v2 HTTP 传输适配器
6
+ *
7
+ * Git Wire 协议 v2 的 HTTP 传输层。
8
+ * 负责构建 v2 命令式请求并发送到远端。
9
+ *
10
+ * v2 HTTP 传输流程:
11
+ * 1. advertise() — 获取能力广告(含版本声明)
12
+ * 2. command() — 执行单个命令(ls-refs / fetch / push / object-info)
13
+ *
14
+ * @see https://git-scm.com/docs/protocol-v2#_initial_client_request
15
+ */
16
+ /**
17
+ * v2 HTTP 传输错误
18
+ */
19
+ var V2SmartHttpError = class extends Error {
20
+ constructor(message) {
21
+ super(`v2 smart-http error: ${message}`);
22
+ this.name = "V2SmartHttpError";
23
+ }
24
+ };
25
+ /** v2 advertise 路径(与 v1 相同,但增加 Git-Protocol 头) */
26
+ const ADVERTISE_PATH = "/info/refs";
27
+ /** v2 命令执行路径 */
28
+ const COMMAND_PATH = "/git-upload-pack";
29
+ /**
30
+ * 创建 v2 HTTP 传输适配器
31
+ *
32
+ * @param url - 远端仓库 URL
33
+ * @param options - 可选认证选项
34
+ * @returns v2 传输接口
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const transport = createV2HttpTransport("https://github.com/user/repo");
39
+ * const caps = await transport.advertise();
40
+ * const refs = await transport.command("ls-refs", ["symrefs", "peel"]);
41
+ * ```
42
+ */
43
+ function createV2HttpTransport(url, options) {
44
+ const baseUrl = url.replace(/\/$/, "");
45
+ const baseHeaders = {
46
+ "User-Agent": "nano-git/0.1",
47
+ "Content-Type": "application/x-git-upload-pack-request",
48
+ Accept: "application/x-git-upload-pack-result",
49
+ "Git-Protocol": "version=2",
50
+ ...options?.headers
51
+ };
52
+ if (options?.token) baseHeaders.Authorization = `Bearer ${options.token}`;
53
+ return {
54
+ async advertise() {
55
+ const response = await fetch(`${baseUrl}${ADVERTISE_PATH}?service=git-upload-pack`, { headers: baseHeaders });
56
+ if (!response.ok) throw new V2SmartHttpError(`advertise failed: ${response.status} ${response.statusText}`);
57
+ return parseV2CapabilityAdvertisement(Buffer.from(await response.arrayBuffer()));
58
+ },
59
+ async command(command, args, capabilities, body) {
60
+ const lines = [];
61
+ lines.push(encodePktLine(`command=${command}\n`));
62
+ if (capabilities) for (const cap of capabilities) lines.push(encodePktLine(`${cap}\n`));
63
+ lines.push(encodeDelimiterPkt());
64
+ if (args) for (const arg of args) lines.push(encodePktLine(`${arg}\n`));
65
+ lines.push(encodeFlushPkt());
66
+ if (body) lines.push(body);
67
+ const requestBody = Buffer.concat(lines);
68
+ const response = await fetch(`${baseUrl}${COMMAND_PATH}`, {
69
+ method: "POST",
70
+ headers: baseHeaders,
71
+ body: requestBody
72
+ });
73
+ if (!response.ok) throw new V2SmartHttpError(`command "${command}" failed: ${response.status} ${response.statusText}`);
74
+ return Buffer.from(await response.arrayBuffer());
75
+ }
76
+ };
77
+ }
78
+ //#endregion
79
+ export { V2SmartHttpError, createV2HttpTransport };
@@ -0,0 +1,75 @@
1
+ import { GitError } from "../../../core/errors.mjs";
2
+ import { RefAdvertisement } from "../../protocol/types.mjs";
3
+ import { LsRefsEntry, V2GitServiceTransport } from "./types.mjs";
4
+
5
+ //#region src/transport/client/upload-pack/ls-refs.d.ts
6
+ /**
7
+ * ls-refs 命令错误
8
+ */
9
+ declare class LsRefsError extends GitError {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * ls-refs 请求参数
14
+ */
15
+ interface LsRefsOptions {
16
+ /** 请求符号引用信息 */
17
+ readonly symrefs?: boolean;
18
+ /** 请求 peeled tag 信息 */
19
+ readonly peel?: boolean;
20
+ /** 按前缀过滤 refs,如 ["refs/heads/", "refs/tags/"] */
21
+ readonly refPrefixes?: string[];
22
+ /** 请求 unborn HEAD 信息 */
23
+ readonly unborn?: boolean;
24
+ }
25
+ /**
26
+ * 执行 ls-refs 命令
27
+ *
28
+ * @param transport - v2 传输接口
29
+ * @param options - ls-refs 请求选项
30
+ * @returns 解析后的 ref 条目列表
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const entries = await lsRefs(transport, {
35
+ * symrefs: true,
36
+ * peel: true,
37
+ * refPrefixes: ["refs/heads/"],
38
+ * });
39
+ * ```
40
+ */
41
+ declare function lsRefs(transport: V2GitServiceTransport, options?: LsRefsOptions): Promise<LsRefsEntry[]>;
42
+ /**
43
+ * 解析 ls-refs 响应
44
+ *
45
+ * 从原始 Buffer 中解析 ls-refs 条目。
46
+ *
47
+ * @param data - ls-refs 命令的原始响应数据
48
+ * @returns 解析后的 ref 条目列表
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const entries = parseLsRefsResponse(response);
53
+ * console.log(entries[0].refname); // "refs/heads/main"
54
+ * ```
55
+ */
56
+ declare function parseLsRefsResponse(data: Buffer): LsRefsEntry[];
57
+ /**
58
+ * 将 v2 ls-refs 结果转换为 v1 兼容的 RefAdvertisement
59
+ *
60
+ * 用于 ImportSession 的透明升级:v2 获取 refs 后,
61
+ * 包装为 v1 的 RefAdvertisement 格式,使上游代码无需改动。
62
+ *
63
+ * @param entries - ls-refs 返回的 ref 条目
64
+ * @returns v1 兼容的 RefAdvertisement
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const entries = await lsRefs(transport);
69
+ * const adv = lsRefsToRefAdvertisement(entries);
70
+ * // 可传递给 ImportSession
71
+ * ```
72
+ */
73
+ declare function lsRefsToRefAdvertisement(entries: LsRefsEntry[]): RefAdvertisement;
74
+ //#endregion
75
+ export { LsRefsError, LsRefsOptions, lsRefs, lsRefsToRefAdvertisement, parseLsRefsResponse };