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,99 @@
1
+ import { GitError } from "../../../core/errors.mjs";
2
+ import { RefAdvertisementError, parseRefAdvertisement } from "../../protocol/ref-advertisement.mjs";
3
+ //#region src/transport/client/receive-pack/http.ts
4
+ /**
5
+ * Smart HTTP 传输层 — Receive-Pack 客户端
6
+ *
7
+ * 基于 Bun 内置 fetch() 的 Git Smart HTTP 协议 HTTP 适配器。
8
+ * 仅提供 receive-pack(push)客户端,upload-pack(fetch)请使用 v2 协议。
9
+ *
10
+ * @see https://git-scm.com/docs/http-protocol
11
+ */
12
+ /**
13
+ * Smart HTTP 传输错误
14
+ *
15
+ * 当 HTTP 层面的传输出错时抛出(网络错误、非预期状态码等)。
16
+ */
17
+ var SmartHttpError = class extends GitError {
18
+ statusCode;
19
+ constructor(message, statusCode) {
20
+ super(`Smart HTTP error: ${message}${statusCode !== void 0 ? ` (status ${statusCode})` : ""}`);
21
+ this.statusCode = statusCode;
22
+ this.name = "SmartHttpError";
23
+ }
24
+ };
25
+ function applyAuthHeaders(base, auth) {
26
+ const result = { ...auth?.headers };
27
+ if (auth?.token) result["Authorization"] = `Bearer ${auth.token}`;
28
+ return {
29
+ ...base,
30
+ ...result
31
+ };
32
+ }
33
+ async function readResponseBody(response, context) {
34
+ try {
35
+ return Buffer.from(await response.arrayBuffer());
36
+ } catch (err) {
37
+ throw new SmartHttpError(`Failed to read response body (${context}): ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ }
40
+ function assertContentType(actual, expected, context) {
41
+ if (!actual.includes(expected)) throw new SmartHttpError(`Unexpected content type: ${actual} (expected ${expected}) (${context})`);
42
+ }
43
+ /** receive-pack HTTP 端点配置 */
44
+ const RECEIVE_PACK_ADVERTISE_SERVICE = "git-receive-pack";
45
+ const RECEIVE_PACK_ADVERTISE_CONTENT_TYPE = "application/x-git-receive-pack-advertisement";
46
+ const RECEIVE_PACK_RPC_PATH = "/git-receive-pack";
47
+ const RECEIVE_PACK_RPC_CONTENT_TYPE = "application/x-git-receive-pack-request";
48
+ const RECEIVE_PACK_RESULT_CONTENT_TYPE = "application/x-git-receive-pack-result";
49
+ /**
50
+ * 创建 receive-pack HTTP 客户端
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const client = createReceivePackHttpClient("https://github.com/user/repo");
55
+ * const adv = await client.advertise();
56
+ * const raw = await client.request(body);
57
+ * ```
58
+ */
59
+ function createReceivePackHttpClient(baseUrl, auth) {
60
+ const normalizedUrl = baseUrl.replace(/\/+$/, "");
61
+ return {
62
+ async advertise() {
63
+ const url = `${normalizedUrl}/info/refs?service=${RECEIVE_PACK_ADVERTISE_SERVICE}`;
64
+ let response;
65
+ try {
66
+ response = await fetch(url, { headers: applyAuthHeaders({}, auth) });
67
+ } catch (err) {
68
+ throw new SmartHttpError(`Failed to fetch ref advertisement from ${url}: ${err instanceof Error ? err.message : String(err)}`);
69
+ }
70
+ if (!response.ok) throw new SmartHttpError(`Failed to fetch ref advertisement from ${url}`, response.status);
71
+ assertContentType(response.headers.get("content-type") ?? "", RECEIVE_PACK_ADVERTISE_CONTENT_TYPE, url);
72
+ const data = await readResponseBody(response, url);
73
+ try {
74
+ return parseRefAdvertisement(data, RECEIVE_PACK_ADVERTISE_SERVICE);
75
+ } catch (err) {
76
+ if (err instanceof RefAdvertisementError) throw err;
77
+ throw new SmartHttpError(`Failed to parse ref advertisement: ${err instanceof Error ? err.message : String(err)}`);
78
+ }
79
+ },
80
+ async request(body) {
81
+ const url = `${normalizedUrl}${RECEIVE_PACK_RPC_PATH}`;
82
+ let response;
83
+ try {
84
+ response = await fetch(url, {
85
+ method: "POST",
86
+ headers: applyAuthHeaders({ "Content-Type": RECEIVE_PACK_RPC_CONTENT_TYPE }, auth),
87
+ body
88
+ });
89
+ } catch (err) {
90
+ throw new SmartHttpError(`Failed to POST RPC request to ${url}: ${err instanceof Error ? err.message : String(err)}`);
91
+ }
92
+ if (!response.ok) throw new SmartHttpError(`RPC request to ${url} failed`, response.status);
93
+ assertContentType(response.headers.get("content-type") ?? "", RECEIVE_PACK_RESULT_CONTENT_TYPE, url);
94
+ return readResponseBody(response, url);
95
+ }
96
+ };
97
+ }
98
+ //#endregion
99
+ export { SmartHttpError, createReceivePackHttpClient };
@@ -0,0 +1,23 @@
1
+ import { GitError } from "../../../core/errors.mjs";
2
+ import { PushRefUpdate } from "../../protocol/types.mjs";
3
+
4
+ //#region src/transport/client/receive-pack/push-error.d.ts
5
+ /**
6
+ * Push 操作错误
7
+ *
8
+ * 当服务端部分或全部拒绝更新时抛出。
9
+ * 即使抛出异常,`refUpdates` 属性仍会保留服务端返回的所有 ref 状态
10
+ * (包含成功和失败的),以便调用方在部分成功场景下做出相应处理。
11
+ */
12
+ declare class PushError extends GitError {
13
+ /** 服务端返回的所有 ref 更新结果(包含成功和失败) */
14
+ refUpdates?: PushRefUpdate[];
15
+ /** 服务端返回的进度消息 */
16
+ progress?: string[];
17
+ constructor(message: string, extra?: {
18
+ refUpdates?: PushRefUpdate[];
19
+ progress?: string[];
20
+ });
21
+ }
22
+ //#endregion
23
+ export { PushError };
@@ -0,0 +1,32 @@
1
+ import { GitError } from "../../../core/errors.mjs";
2
+ //#region src/transport/client/receive-pack/push-error.ts
3
+ /**
4
+ * Push 操作错误类型
5
+ *
6
+ * 当服务端部分或全部拒绝更新时抛出。
7
+ * 即使抛出异常,`refUpdates` 属性仍会保留服务端返回的所有 ref 状态
8
+ * (包含成功和失败的),以便调用方在部分成功场景下做出相应处理。
9
+ */
10
+ /**
11
+ * Push 操作错误
12
+ *
13
+ * 当服务端部分或全部拒绝更新时抛出。
14
+ * 即使抛出异常,`refUpdates` 属性仍会保留服务端返回的所有 ref 状态
15
+ * (包含成功和失败的),以便调用方在部分成功场景下做出相应处理。
16
+ */
17
+ var PushError = class extends GitError {
18
+ /** 服务端返回的所有 ref 更新结果(包含成功和失败) */
19
+ refUpdates;
20
+ /** 服务端返回的进度消息 */
21
+ progress;
22
+ constructor(message, extra) {
23
+ super(`Push error: ${message}`);
24
+ this.name = "PushError";
25
+ if (extra) {
26
+ this.refUpdates = extra.refUpdates;
27
+ this.progress = extra.progress;
28
+ }
29
+ }
30
+ };
31
+ //#endregion
32
+ export { PushError };
@@ -0,0 +1,28 @@
1
+ import { SHA1 } from "../../../core/types.mjs";
2
+ import { ObjectDatabase } from "../../../core/types/odb.mjs";
3
+ import { PushRefItem } from "./push-ref-plan.mjs";
4
+
5
+ //#region src/transport/client/receive-pack/push-pack-plan.d.ts
6
+ /**
7
+ * 合并 shallow 边界与各推送项的远端当前 tip,供预检与本地可达性遍历使用
8
+ */
9
+ declare function mergePushBoundaries(shallowSet: Set<SHA1> | undefined, pushRefs: PushRefItem[]): Set<SHA1> | undefined;
10
+ /**
11
+ * 计算需要发送到远端的对象集合
12
+ *
13
+ * 需要推送的对象 = 从推送 refs 可达的对象 - 从远程已有 refs 可达的对象
14
+ * 删除操作(localHash === null)跳过对象收集。
15
+ *
16
+ * 本地可达性使用 "skip-commit-parents" 模式让缺失的 commit parent 不阻断遍历,
17
+ * 但 tree/blob/tag 等非 commit-parent 边的缺失仍会抛出 PushError。
18
+ *
19
+ * @param store - 本地对象存储
20
+ * @param pushRefs - 推送引用项列表
21
+ * @param remoteRefs - 远程 ref → hash 映射
22
+ * @param pushBoundaries - 合并后的边界集(可选)
23
+ * @returns 需要发送的对象哈希列表
24
+ * @throws PushError 当本地对象损坏导致无法遍历时
25
+ */
26
+ declare function computeObjectsToSend(store: ObjectDatabase, pushRefs: PushRefItem[], remoteRefs: Map<string, SHA1>, pushBoundaries: Set<SHA1> | undefined): SHA1[];
27
+ //#endregion
28
+ export { computeObjectsToSend, mergePushBoundaries };
@@ -0,0 +1,60 @@
1
+ import { collectReachable } from "../../protocol/object-graph.mjs";
2
+ import { PushError } from "./push-error.mjs";
3
+ //#region src/transport/client/receive-pack/push-pack-plan.ts
4
+ /**
5
+ * Push Pack 规划
6
+ *
7
+ * 处理"我要发送哪些对象"的问题:
8
+ * - 合并 shallow 边界与远端 tip 边界
9
+ * - 计算本地推送 ref 可达且远程缺失的对象差集
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { mergePushBoundaries, computeObjectsToSend } from "./push-pack-plan.ts";
14
+ * const boundaries = mergePushBoundaries(shallowSet, pushRefs);
15
+ * const objectsToSend = computeObjectsToSend(store, pushRefs, remoteRefs, boundaries);
16
+ * ```
17
+ */
18
+ /**
19
+ * 合并 shallow 边界与各推送项的远端当前 tip,供预检与本地可达性遍历使用
20
+ */
21
+ function mergePushBoundaries(shallowSet, pushRefs) {
22
+ const remoteTips = pushRefs.map((item) => item.remoteHash).filter((hash) => hash !== null);
23
+ if (!shallowSet && remoteTips.length === 0) return;
24
+ const merged = new Set(shallowSet);
25
+ for (const hash of remoteTips) merged.add(hash);
26
+ return merged;
27
+ }
28
+ /**
29
+ * 计算需要发送到远端的对象集合
30
+ *
31
+ * 需要推送的对象 = 从推送 refs 可达的对象 - 从远程已有 refs 可达的对象
32
+ * 删除操作(localHash === null)跳过对象收集。
33
+ *
34
+ * 本地可达性使用 "skip-commit-parents" 模式让缺失的 commit parent 不阻断遍历,
35
+ * 但 tree/blob/tag 等非 commit-parent 边的缺失仍会抛出 PushError。
36
+ *
37
+ * @param store - 本地对象存储
38
+ * @param pushRefs - 推送引用项列表
39
+ * @param remoteRefs - 远程 ref → hash 映射
40
+ * @param pushBoundaries - 合并后的边界集(可选)
41
+ * @returns 需要发送的对象哈希列表
42
+ * @throws PushError 当本地对象损坏导致无法遍历时
43
+ */
44
+ function computeObjectsToSend(store, pushRefs, remoteRefs, pushBoundaries) {
45
+ const localRoots = pushRefs.filter((r) => r.localHash !== null).map((r) => r.localHash);
46
+ let reachableLocal;
47
+ try {
48
+ reachableLocal = collectReachable(store, localRoots, "skip-commit-parents", pushBoundaries);
49
+ } catch (err) {
50
+ throw new PushError(err instanceof Error ? err.message : String(err));
51
+ }
52
+ const remoteRoots = [];
53
+ for (const [, hash] of remoteRefs) remoteRoots.push(hash);
54
+ const reachableRemote = collectReachable(store, remoteRoots);
55
+ const objectsToSend = [];
56
+ for (const hash of reachableLocal) if (!reachableRemote.has(hash)) objectsToSend.push(hash);
57
+ return objectsToSend;
58
+ }
59
+ //#endregion
60
+ export { computeObjectsToSend, mergePushBoundaries };
@@ -0,0 +1,19 @@
1
+ import { SHA1 } from "../../../core/types.mjs";
2
+ import { ObjectDatabase } from "../../../core/types/odb.mjs";
3
+ import { PushRefItem } from "./push-ref-plan.mjs";
4
+
5
+ //#region src/transport/client/receive-pack/push-policy.d.ts
6
+ /**
7
+ * 预检所有推送项是否为 fast-forward,不通过的(且未设 force)立即报错
8
+ *
9
+ * @param store - 对象存储
10
+ * @param items - 推送引用项列表
11
+ * @param shallowBoundaries - 已知 shallow 边界集合(可选)
12
+ * 提供后,isAncestor 会优先判断缺失 parent 是否为已知 shallow boundary,
13
+ * 避免在 shallow 仓库中将正常边界缺失误判为损坏。
14
+ *
15
+ * @throws PushError 如果存在 non-fast-forward 更新且未设 force
16
+ */
17
+ declare function checkFastForward(store: ObjectDatabase, items: PushRefItem[], shallowBoundaries?: Set<SHA1>): void;
18
+ //#endregion
19
+ export { checkFastForward };
@@ -0,0 +1,64 @@
1
+ import { isAncestor, peelTagChain } from "../../protocol/object-graph.mjs";
2
+ import { PushError } from "./push-error.mjs";
3
+ //#region src/transport/client/receive-pack/push-policy.ts
4
+ /**
5
+ * Push 策略规则
6
+ *
7
+ * 处理"这些 ref 更新是否合法"的问题:
8
+ * - fast-forward 预检
9
+ * - refs/tags/* 更新限制
10
+ * - non-commit 对象推送限制
11
+ *
12
+ * 纯规则层,不依赖协议细节。
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { checkFastForward } from "./push-policy.ts";
17
+ * const items = [{
18
+ * localRef: "refs/heads/main",
19
+ * remoteRef: "refs/heads/main",
20
+ * localHash: sha1("new"),
21
+ * remoteHash: sha1("old"),
22
+ * force: false,
23
+ * }];
24
+ * checkFastForward(store, items);
25
+ * ```
26
+ */
27
+ /**
28
+ * 预检所有推送项是否为 fast-forward,不通过的(且未设 force)立即报错
29
+ *
30
+ * @param store - 对象存储
31
+ * @param items - 推送引用项列表
32
+ * @param shallowBoundaries - 已知 shallow 边界集合(可选)
33
+ * 提供后,isAncestor 会优先判断缺失 parent 是否为已知 shallow boundary,
34
+ * 避免在 shallow 仓库中将正常边界缺失误判为损坏。
35
+ *
36
+ * @throws PushError 如果存在 non-fast-forward 更新且未设 force
37
+ */
38
+ function checkFastForward(store, items, shallowBoundaries) {
39
+ for (const item of items) {
40
+ if (item.localHash === null || item.remoteHash === null) continue;
41
+ if (item.force) continue;
42
+ if (item.localHash === item.remoteHash) continue;
43
+ if (item.remoteRef.startsWith("refs/tags/")) throw new PushError(`Tag update rejected for "${item.remoteRef}": tag already exists, cannot replace without force (--force or +refspec).`);
44
+ const peeledRemote = peelTagChain(store, item.remoteHash, shallowBoundaries);
45
+ const peeledLocal = peelTagChain(store, item.localHash, shallowBoundaries);
46
+ {
47
+ const remoteObj = store.tryRead(peeledRemote);
48
+ if (remoteObj !== void 0 && remoteObj.type !== "commit") throw new PushError(`Update rejected for "${item.remoteRef}": remote object is a ${remoteObj.type}, expected commit. Use --force or +refspec to override.`);
49
+ }
50
+ {
51
+ const localObj = store.tryRead(peeledLocal);
52
+ if (localObj !== void 0 && localObj.type !== "commit") throw new PushError(`Update rejected for "${item.remoteRef}": local object is a ${localObj.type}, expected commit. Use --force or +refspec to override.`);
53
+ }
54
+ const refBoundaries = /* @__PURE__ */ new Set();
55
+ if (item.remoteHash) refBoundaries.add(item.remoteHash);
56
+ if (!isAncestor(store, item.remoteHash, item.localHash, refBoundaries)) {
57
+ const shortRemote = item.remoteHash.slice(0, 8);
58
+ const shortLocal = item.localHash.slice(0, 8);
59
+ throw new PushError(`Non-fast-forward update rejected for "${item.remoteRef}": remote ${shortRemote} is not an ancestor of local ${shortLocal}. Use force (--force or +refspec) to override.`);
60
+ }
61
+ }
62
+ }
63
+ //#endregion
64
+ export { checkFastForward };
@@ -0,0 +1,45 @@
1
+ import { SHA1 } from "../../../core/types.mjs";
2
+ import { RefStore } from "../../../core/types/refs.mjs";
3
+ import { ParsedRefSpec } from "../../protocol/refspec.mjs";
4
+
5
+ //#region src/transport/client/receive-pack/push-ref-plan.d.ts
6
+ /**
7
+ * 要推送的引用项
8
+ */
9
+ interface PushRefItem {
10
+ /** 本地引用名称(删除操作时为空字符串) */
11
+ localRef: string;
12
+ /** 远程目标引用名称 */
13
+ remoteRef: string;
14
+ /** 本地 ref 当前指向的哈希(null 表示删除远程引用) */
15
+ localHash: SHA1 | null;
16
+ /** 远程 ref 当前指向的哈希(null 表示新建) */
17
+ remoteHash: SHA1 | null;
18
+ /** 是否强制推送 */
19
+ force: boolean;
20
+ }
21
+ /**
22
+ * 生成默认 refspec
23
+ *
24
+ * 等价于 `git push <url>` 的默认行为:将当前分支推送到远端同名分支。
25
+ * - HEAD 指向 `refs/heads/<name>` 时,返回 `"HEAD:refs/heads/<name>"`
26
+ * - HEAD 为 detached 状态时,抛出 PushError
27
+ *
28
+ * @param refs - 本地引用存储
29
+ * @returns 形如 `"HEAD:refs/heads/<branch>"` 的 refspec
30
+ * @throws PushError 当 HEAD 处于 detached 状态时
31
+ */
32
+ declare function resolveDefaultRefSpec(refs: RefStore): string;
33
+ /**
34
+ * 解析 refspec 并确定要推送的引用列表
35
+ *
36
+ * 根据 refspec 匹配本地引用,并与远程引用对照。
37
+ *
38
+ * @param localRefs - 本地 ref → hash 映射
39
+ * @param remoteRefs - 远程 ref → hash 映射
40
+ * @param specs - 解析后的 refspec 列表
41
+ * @returns 要推送的引用项列表
42
+ */
43
+ declare function determinePushRefs(localRefs: Map<string, SHA1>, remoteRefs: Map<string, SHA1>, specs: ParsedRefSpec[]): PushRefItem[];
44
+ //#endregion
45
+ export { PushRefItem, determinePushRefs, resolveDefaultRefSpec };
@@ -0,0 +1,108 @@
1
+ import { HEAD_REF } from "../../../core/types/refs.mjs";
2
+ import { resolveSymbolicRef } from "../../../refs/resolve.mjs";
3
+ import { PushError } from "./push-error.mjs";
4
+ //#region src/transport/client/receive-pack/push-ref-plan.ts
5
+ /**
6
+ * Push 引用规划
7
+ *
8
+ * 处理"我要推哪些 ref"的问题:
9
+ * - 解析 refspec 为本地→远程的映射
10
+ * - 确定要推送的引用项列表
11
+ * - 处理通配符、精确匹配、删除操作和去重
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { determinePushRefs } from "./push-ref-plan.ts";
16
+ * import { parseRefSpec } from "../../protocol/refspec.ts";
17
+ *
18
+ * const localRefs = new Map([["refs/heads/main", "abc123..."]]);
19
+ * const remoteRefs = new Map();
20
+ * const specs = [parseRefSpec("refs/heads/main:refs/heads/main")];
21
+ * const items = determinePushRefs(localRefs, remoteRefs, specs);
22
+ * ```
23
+ */
24
+ /**
25
+ * 生成默认 refspec
26
+ *
27
+ * 等价于 `git push <url>` 的默认行为:将当前分支推送到远端同名分支。
28
+ * - HEAD 指向 `refs/heads/<name>` 时,返回 `"HEAD:refs/heads/<name>"`
29
+ * - HEAD 为 detached 状态时,抛出 PushError
30
+ *
31
+ * @param refs - 本地引用存储
32
+ * @returns 形如 `"HEAD:refs/heads/<branch>"` 的 refspec
33
+ * @throws PushError 当 HEAD 处于 detached 状态时
34
+ */
35
+ function resolveDefaultRefSpec(refs) {
36
+ const target = resolveSymbolicRef(refs, HEAD_REF);
37
+ if (target === null) throw new PushError("HEAD is detached — cannot determine current branch. Specify a refspec explicitly (e.g. { refSpecs: [\"HEAD:refs/heads/main\"] })");
38
+ if (!target.startsWith("refs/heads/")) throw new PushError(`HEAD points to "${target}" which is not a branch. Specify a refspec explicitly when pushing from a non-branch ref.`);
39
+ return `HEAD:${target}`;
40
+ }
41
+ /**
42
+ * 解析 refspec 并确定要推送的引用列表
43
+ *
44
+ * 根据 refspec 匹配本地引用,并与远程引用对照。
45
+ *
46
+ * @param localRefs - 本地 ref → hash 映射
47
+ * @param remoteRefs - 远程 ref → hash 映射
48
+ * @param specs - 解析后的 refspec 列表
49
+ * @returns 要推送的引用项列表
50
+ */
51
+ function determinePushRefs(localRefs, remoteRefs, specs) {
52
+ const items = [];
53
+ const seen = /* @__PURE__ */ new Map();
54
+ let hasUnmatchedWildcard = false;
55
+ for (const spec of specs) if (spec.isWildcard) {
56
+ let matchedAny = false;
57
+ for (const [localRef, localHash] of localRefs) {
58
+ if (!localRef.startsWith(spec.srcPattern)) continue;
59
+ matchedAny = true;
60
+ const suffix = localRef.slice(spec.srcPattern.length);
61
+ const remoteRef = `${spec.dstPattern}${suffix}`;
62
+ const existingSpec = seen.get(remoteRef);
63
+ if (existingSpec !== void 0) throw new PushError(`Conflicting push refspec: "${spec.srcPattern}*:${spec.dstPattern}*" maps to "${remoteRef}" which is also mapped by "${existingSpec}".`);
64
+ seen.set(remoteRef, `${spec.srcPattern}*:${spec.dstPattern}*`);
65
+ const remoteHash = remoteRefs.get(remoteRef) ?? null;
66
+ items.push({
67
+ localRef,
68
+ remoteRef,
69
+ localHash,
70
+ remoteHash,
71
+ force: spec.force
72
+ });
73
+ }
74
+ if (!matchedAny) hasUnmatchedWildcard = true;
75
+ } else if (spec.srcPattern === "") {
76
+ const remoteRef = spec.dstPattern;
77
+ const existingSpec = seen.get(remoteRef);
78
+ if (existingSpec !== void 0) throw new PushError(`Conflicting push refspec: ":${spec.dstPattern}" maps to "${remoteRef}" which is also mapped by "${existingSpec}".`);
79
+ seen.set(remoteRef, `:${spec.dstPattern}`);
80
+ const remoteHash = remoteRefs.get(remoteRef) ?? null;
81
+ items.push({
82
+ localRef: "",
83
+ remoteRef: spec.dstPattern,
84
+ localHash: null,
85
+ remoteHash,
86
+ force: spec.force
87
+ });
88
+ } else {
89
+ const remoteRef = spec.dstPattern;
90
+ const existingSpec = seen.get(remoteRef);
91
+ if (existingSpec !== void 0) throw new PushError(`Conflicting push refspec: "${spec.srcPattern}:${spec.dstPattern}" maps to "${remoteRef}" which is also mapped by "${existingSpec}".`);
92
+ seen.set(remoteRef, `${spec.srcPattern}:${spec.dstPattern}`);
93
+ const localHash = localRefs.get(spec.srcPattern) ?? null;
94
+ if (!localHash) throw new PushError(`Local ref not found: "${spec.srcPattern}" (specified in refspec "${spec.srcPattern}:${spec.dstPattern}")`);
95
+ const remoteHash = remoteRefs.get(remoteRef) ?? null;
96
+ items.push({
97
+ localRef: spec.srcPattern,
98
+ remoteRef,
99
+ localHash,
100
+ remoteHash,
101
+ force: spec.force
102
+ });
103
+ }
104
+ if (items.length === 0 && hasUnmatchedWildcard) throw new PushError("src refspec does not match any local ref");
105
+ return items;
106
+ }
107
+ //#endregion
108
+ export { determinePushRefs, resolveDefaultRefSpec };
@@ -0,0 +1,28 @@
1
+ import { PushRefUpdate } from "../../protocol/types.mjs";
2
+ import { PushRefItem } from "./push-ref-plan.mjs";
3
+ import { ReceivePackCommand } from "./request.mjs";
4
+
5
+ //#region src/transport/client/receive-pack/push-report.d.ts
6
+ /**
7
+ * 校验并富化服务端返回的 report-status 结果
8
+ *
9
+ * 执行以下校验:
10
+ * 1. 空 refUpdates 检测(协议异常)
11
+ * 2. 状态行数量一致性校验
12
+ * 3. ref 名称集合完全匹配校验
13
+ * 4. 构建富化 refUpdates(补充 oldHash/newHash/forced)
14
+ * 5. 检查被拒绝的更新
15
+ *
16
+ * @param commands - 发送给服务端的 receive-pack 命令列表
17
+ * @param refUpdates - 服务端解析后的 ref 更新结果
18
+ * @param pushRefs - 推送引用项列表(用于关联命令与元信息)
19
+ * @param progress - 服务端返回的进度消息
20
+ * @returns 富化后的完整 push 结果
21
+ * @throws PushError 当校验失败或存在被拒绝的更新时
22
+ */
23
+ declare function processPushReport(commands: ReceivePackCommand[], refUpdates: PushRefUpdate[], pushRefs: PushRefItem[], progress: string[]): {
24
+ refUpdates: PushRefUpdate[];
25
+ progress: string[];
26
+ };
27
+ //#endregion
28
+ export { processPushReport };
@@ -0,0 +1,84 @@
1
+ import { PushError } from "./push-error.mjs";
2
+ //#region src/transport/client/receive-pack/push-report.ts
3
+ /**
4
+ * Push 响应报告校验
5
+ *
6
+ * 处理"服务端回复是否完整可信"的问题:
7
+ * - receive-pack 返回结果的完整性校验
8
+ * - 命令和 refUpdates 的对应关系校验
9
+ * - enrichedUpdates 构建
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { processPushReport } from "./push-report.ts";
14
+ * import { PushError } from "./push-error.ts";
15
+ *
16
+ * try {
17
+ * const result = processPushReport(commands, refUpdates, pushRefs, progress);
18
+ * return result;
19
+ * } catch (err) {
20
+ * if (err instanceof PushError && err.refUpdates) {
21
+ * // 部分成功场景
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ /**
27
+ * 校验并富化服务端返回的 report-status 结果
28
+ *
29
+ * 执行以下校验:
30
+ * 1. 空 refUpdates 检测(协议异常)
31
+ * 2. 状态行数量一致性校验
32
+ * 3. ref 名称集合完全匹配校验
33
+ * 4. 构建富化 refUpdates(补充 oldHash/newHash/forced)
34
+ * 5. 检查被拒绝的更新
35
+ *
36
+ * @param commands - 发送给服务端的 receive-pack 命令列表
37
+ * @param refUpdates - 服务端解析后的 ref 更新结果
38
+ * @param pushRefs - 推送引用项列表(用于关联命令与元信息)
39
+ * @param progress - 服务端返回的进度消息
40
+ * @returns 富化后的完整 push 结果
41
+ * @throws PushError 当校验失败或存在被拒绝的更新时
42
+ */
43
+ function processPushReport(commands, refUpdates, pushRefs, progress) {
44
+ if (commands.length > 0 && refUpdates.length === 0) throw new PushError("Server returned no status updates for the push commands. This may indicate a protocol compatibility issue or a server-side parsing error.");
45
+ if (commands.length !== refUpdates.length) {
46
+ const receivedRefNames = new Set(refUpdates.map((u) => u.refName));
47
+ const missingRefs = commands.filter((c) => !receivedRefNames.has(c.refName));
48
+ throw new PushError(`Server returned incomplete status: expected ${commands.length} status line(s) but got ${refUpdates.length}. Missing status for: ${missingRefs.map((r) => r.refName).join(", ")}`);
49
+ }
50
+ const commandRefNames = new Set(commands.map((c) => c.refName));
51
+ const updateRefNames = new Set(refUpdates.map((u) => u.refName));
52
+ const unexpectedRefs = [...updateRefNames].filter((n) => !commandRefNames.has(n));
53
+ const missingRefs = [...commandRefNames].filter((n) => !updateRefNames.has(n));
54
+ if (unexpectedRefs.length > 0 || missingRefs.length > 0) {
55
+ const parts = [];
56
+ if (unexpectedRefs.length > 0) parts.push(`unexpected ref(s): ${unexpectedRefs.join(", ")}`);
57
+ if (missingRefs.length > 0) parts.push(`missing ref(s): ${missingRefs.join(", ")}`);
58
+ throw new PushError(`Server returned mismatched ref status: ${parts.join("; ")}`);
59
+ }
60
+ const pushRefMap = /* @__PURE__ */ new Map();
61
+ for (const item of pushRefs) pushRefMap.set(item.remoteRef, item);
62
+ const enrichedUpdates = refUpdates.map((u) => {
63
+ const matched = pushRefMap.get(u.refName);
64
+ return {
65
+ refName: u.refName,
66
+ oldHash: matched?.remoteHash ?? null,
67
+ newHash: matched?.localHash ?? null,
68
+ success: u.success,
69
+ error: u.error,
70
+ forced: matched?.force ?? false
71
+ };
72
+ });
73
+ const rejectedUpdates = enrichedUpdates.filter((u) => !u.success);
74
+ if (rejectedUpdates.length > 0) throw new PushError(`Remote server rejected the push: ${rejectedUpdates.map((u) => `${u.refName}: ${u.error ?? "unknown error"}`).join("; ")}`, {
75
+ refUpdates: enrichedUpdates,
76
+ progress
77
+ });
78
+ return {
79
+ refUpdates: enrichedUpdates,
80
+ progress
81
+ };
82
+ }
83
+ //#endregion
84
+ export { processPushReport };
@@ -0,0 +1,52 @@
1
+ import { sha1 } from "../../../core/types.mjs";
2
+ import { parseRefSpec } from "../../protocol/refspec.mjs";
3
+ import { PushError } from "./push-error.mjs";
4
+ import { resolveDefaultRefSpec } from "./push-ref-plan.mjs";
5
+ import { PUSH_CAPABILITIES, extractCapabilities } from "../../protocol/transport-capabilities.mjs";
6
+ import { buildReceivePackRequest } from "./request.mjs";
7
+ //#region src/transport/client/receive-pack/push-request-plan.ts
8
+ /**
9
+ * push 请求规划
10
+ *
11
+ * refspec 归一化、能力校验、receive-pack 命令与请求 body 构造。
12
+ */
13
+ /** 零哈希(表示新建引用或删除引用) */
14
+ const ZERO_HASH = sha1("0000000000000000000000000000000000000000");
15
+ /**
16
+ * 解析并归一化 push refspec
17
+ */
18
+ function resolvePushParsedSpecs(refs, options) {
19
+ const parsedSpecs = (options?.refSpecs ?? [resolveDefaultRefSpec(refs)]).map(parseRefSpec);
20
+ if (options?.force) return parsedSpecs.map((s) => ({
21
+ ...s,
22
+ force: true
23
+ }));
24
+ return parsedSpecs;
25
+ }
26
+ /**
27
+ * 校验 push 所需远端能力
28
+ */
29
+ function validatePushCapabilities(advertisement, pushRefs) {
30
+ const caps = extractCapabilities(advertisement.capabilities, PUSH_CAPABILITIES);
31
+ if (!caps.includes("report-status")) throw new PushError("Remote server does not advertise 'report-status' capability. This client requires report-status to reliably determine push results. Please use a Git server that supports report-status.");
32
+ if (pushRefs.some((r) => r.localHash === null) && !caps.includes("delete-refs")) throw new PushError("Remote server does not advertise 'delete-refs' capability, but the push includes a delete ref operation.");
33
+ return caps;
34
+ }
35
+ /**
36
+ * 由 push 引用项构造 receive-pack 命令
37
+ */
38
+ function buildPushCommands(pushRefs) {
39
+ return pushRefs.map((r) => ({
40
+ oldHash: r.remoteHash ?? ZERO_HASH,
41
+ newHash: r.localHash ?? ZERO_HASH,
42
+ refName: r.remoteRef
43
+ }));
44
+ }
45
+ /**
46
+ * 构造 receive-pack 请求 body
47
+ */
48
+ function buildPushRequestBody(commands, packfile, capabilities) {
49
+ return buildReceivePackRequest(commands, packfile, capabilities);
50
+ }
51
+ //#endregion
52
+ export { buildPushCommands, buildPushRequestBody, resolvePushParsedSpecs, validatePushCapabilities };