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,625 @@
1
+ import { PreconditionCheckError } from "../../core/errors.mjs";
2
+ import { globToRegex, matchRefGlob } from "./import-glob.mjs";
3
+ import { v2FetchObjects } from "../../transport/client/upload-pack/fetch.mjs";
4
+ import { isAncestor } from "../../transport/protocol/object-graph.mjs";
5
+ import { getLocalRefs } from "../../transport/protocol/ref-collection.mjs";
6
+ import { resolveBranchTargetHash } from "../../transport/protocol/update-refs.mjs";
7
+ import { getNamespacePatternPrefix, isSameRemoteRef, resolveNamespaceTargets } from "./import-view.mjs";
8
+ //#region src/repository/import/import-plan-builder.ts
9
+ /**
10
+ * Import Plan Builder 实现
11
+ *
12
+ * 负责 preview/apply 的完整执行语义,包括命名空间映射、分支/tag/HEAD 物化、
13
+ * 前置条件校验、prune 清理以及实际的 ref 写入和对象导入。
14
+ */
15
+ function clonePolicy(policy) {
16
+ return { ...policy };
17
+ }
18
+ function getViewLabel(view) {
19
+ const candidate = view;
20
+ return typeof candidate.label === "string" ? candidate.label : void 0;
21
+ }
22
+ function describeView(viewLabel) {
23
+ return viewLabel ? `命名视图 "${viewLabel}"` : "当前视图";
24
+ }
25
+ function deepFreeze(value) {
26
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) return value;
27
+ const target = value;
28
+ for (const key of Reflect.ownKeys(target)) deepFreeze(target[key]);
29
+ return Object.freeze(value);
30
+ }
31
+ function freezePreviewResult(preview, advertisement) {
32
+ return deepFreeze({
33
+ remoteSnapshot: advertisement,
34
+ selectedRefs: preview.selectedRefs,
35
+ objectRoots: preview.objectRoots,
36
+ prefetchedObjects: preview.prefetchedObjects,
37
+ localPreconditions: preview.localPreconditions,
38
+ refOperations: preview.refOperations,
39
+ headOperation: preview.headOperation,
40
+ pruneOperations: preview.pruneOperations,
41
+ diagnostics: preview.diagnostics,
42
+ canApply: preview.canApply
43
+ });
44
+ }
45
+ /**
46
+ * 快照某个 ownership 模式当前覆盖的全部 refs
47
+ *
48
+ * @param backend - 仓库后端
49
+ * @param pattern - ownership 目标模式
50
+ * @returns 已排序的原始 ref 值快照
51
+ */
52
+ function snapshotOwnedRefs(backend, pattern) {
53
+ return backend.refs.listAll().filter((refName) => matchRefGlob(pattern, refName)).sort((left, right) => left.localeCompare(right)).map((refName) => ({
54
+ refName,
55
+ expectedValue: backend.refs.read(refName)
56
+ }));
57
+ }
58
+ /**
59
+ * 校验 preview 冻结的本地前置条件仍然成立
60
+ *
61
+ * @param backend - 仓库后端
62
+ * @param preconditions - preview 记录的前置条件
63
+ * @returns 当前本地 hash 快照
64
+ */
65
+ function validateLocalPreconditions(backend, preconditions) {
66
+ const currentLocalRefs = getLocalRefs(backend.refs);
67
+ for (const pc of preconditions) {
68
+ if (pc.namespacePattern !== void 0 || pc.namespacePrefix !== void 0) {
69
+ const pattern = pc.namespacePattern ?? `${pc.namespacePrefix}*`;
70
+ const currentRefs = snapshotOwnedRefs(backend, pattern);
71
+ const expectedRefs = pc.expectedRefs ?? [];
72
+ if (!(currentRefs.length === expectedRefs.length && currentRefs.every((entry, idx) => {
73
+ const expected = expectedRefs[idx];
74
+ return expected !== void 0 && entry.refName === expected.refName && entry.expectedValue === expected.expectedValue;
75
+ }))) throw new PreconditionCheckError(`前置条件校验失败:命名空间 "${pattern}" 在 preview() 后已变化。`);
76
+ continue;
77
+ }
78
+ if (pc.expectedValue !== void 0) {
79
+ const currentValue = backend.refs.read(pc.refName);
80
+ if (currentValue !== pc.expectedValue) throw new PreconditionCheckError(`前置条件校验失败:ref "${pc.refName}" 在 preview() 后已变化。期望 ${pc.expectedValue ?? "(不存在)"},实际 ${currentValue ?? "(不存在)"}。`);
81
+ continue;
82
+ }
83
+ const currentHash = currentLocalRefs.get(pc.refName) ?? null;
84
+ if (currentHash !== pc.expectedHash) throw new PreconditionCheckError(`前置条件校验失败:ref "${pc.refName}" 在 preview() 后已变化。期望 ${pc.expectedHash ?? "(不存在)"},实际 ${currentHash ?? "(不存在)"}。`);
85
+ }
86
+ return currentLocalRefs;
87
+ }
88
+ /**
89
+ * 创建 ImportPlanBuilder
90
+ *
91
+ * 该 builder 负责 preview/apply 的完整执行语义。
92
+ */
93
+ function createPlanBuilder(backend, advertisement, source, transportFactory, v2Transport) {
94
+ const actions = [];
95
+ let planVersion = 0;
96
+ let lastPreview = null;
97
+ /**
98
+ * 计划动作变更后必须丢弃旧 preview,
99
+ * 否则 apply() 可能基于过期计划执行。
100
+ */
101
+ function invalidatePreview() {
102
+ planVersion += 1;
103
+ lastPreview = null;
104
+ }
105
+ function inferNamespaceDefaultPolicy(targetPattern) {
106
+ const headRegex = globToRegex("refs/heads/*");
107
+ if (targetPattern === "refs/heads/*" || headRegex.test(targetPattern)) return { mode: "fast-forward" };
108
+ const tagRegex = globToRegex("refs/tags/*");
109
+ if (targetPattern === "refs/tags/*" || tagRegex.test(targetPattern)) return { mode: "create-only" };
110
+ }
111
+ function captureLocalPreconditions(resolvedMappings, headRequests, namespaceOwnerships) {
112
+ const affectedRefNames = /* @__PURE__ */ new Set();
113
+ for (const mapping of resolvedMappings) affectedRefNames.add(mapping.localRef);
114
+ if (headRequests.length > 0) affectedRefNames.add("HEAD");
115
+ const localRefs = getLocalRefs(backend.refs);
116
+ const localPreconditions = [];
117
+ for (const refName of affectedRefNames) {
118
+ const expectedValue = backend.refs.read(refName);
119
+ localPreconditions.push({
120
+ refName,
121
+ expectedHash: localRefs.get(refName) ?? null,
122
+ expectedValue
123
+ });
124
+ }
125
+ for (const ownership of namespaceOwnerships.values()) {
126
+ if (!ownership.prune) continue;
127
+ localPreconditions.push({
128
+ refName: ownership.pattern,
129
+ expectedHash: null,
130
+ namespacePrefix: ownership.prefix,
131
+ namespacePattern: ownership.pattern,
132
+ expectedRefs: snapshotOwnedRefs(backend, ownership.pattern)
133
+ });
134
+ }
135
+ return localPreconditions;
136
+ }
137
+ function createPreviewResult(params) {
138
+ const canApply = !params.diagnostics.some((diagnostic) => diagnostic.level === "error");
139
+ return freezePreviewResult({
140
+ remoteSnapshot: advertisement,
141
+ selectedRefs: params.selectedRefs,
142
+ objectRoots: params.objectRoots,
143
+ prefetchedObjects: params.prefetchedObjects,
144
+ localPreconditions: params.localPreconditions,
145
+ refOperations: params.refOperations,
146
+ headOperation: params.headOperation,
147
+ pruneOperations: params.pruneOperations,
148
+ diagnostics: params.diagnostics,
149
+ canApply
150
+ }, advertisement);
151
+ }
152
+ function buildPreviewSkeleton() {
153
+ const resolvedMappings = [];
154
+ const headRequests = [];
155
+ const namespaceOwnerships = /* @__PURE__ */ new Map();
156
+ const diagnostics = [];
157
+ for (const act of actions) {
158
+ let effectivePolicy = act.policy;
159
+ if (act.action === "namespace" && effectivePolicy === void 0) {
160
+ effectivePolicy = inferNamespaceDefaultPolicy(act.target);
161
+ if (effectivePolicy === void 0) {
162
+ diagnostics.push({
163
+ level: "error",
164
+ message: `${describeView(act.viewLabel)}:命名空间 "${act.target}" 需要显式指定 policy 参数。refs/heads/* 和 refs/tags/* 之外的命名空间必须显式声明 RefUpdatePolicy。`
165
+ });
166
+ continue;
167
+ }
168
+ diagnostics.push({
169
+ level: "info",
170
+ message: `${describeView(act.viewLabel)}:命名空间 "${act.target}" 使用默认策略 ${effectivePolicy.mode}。`
171
+ });
172
+ }
173
+ if (effectivePolicy === void 0) continue;
174
+ switch (act.action) {
175
+ case "namespace": {
176
+ if (act.prune && !act.target.includes("*")) {
177
+ diagnostics.push({
178
+ level: "error",
179
+ message: `${describeView(act.viewLabel)}:toNamespace("${act.target}"):prune 只允许用于带 * 的命名空间投影。`
180
+ });
181
+ break;
182
+ }
183
+ const targets = resolveNamespaceTargets(act.viewRefs, act.target);
184
+ for (const target of targets) resolvedMappings.push({
185
+ remoteRef: target.remoteRef,
186
+ localRef: target.localRef,
187
+ policy: effectivePolicy,
188
+ viewLabel: act.viewLabel
189
+ });
190
+ const namespacePrefix = getNamespacePatternPrefix(act.target);
191
+ if (namespacePrefix !== null) {
192
+ const ownership = namespaceOwnerships.get(act.target) ?? {
193
+ pattern: act.target,
194
+ prefix: namespacePrefix,
195
+ currentRefs: /* @__PURE__ */ new Set(),
196
+ prune: false,
197
+ viewLabel: act.viewLabel
198
+ };
199
+ for (const target of targets) ownership.currentRefs.add(target.localRef);
200
+ ownership.prune = ownership.prune || (act.prune ?? false);
201
+ ownership.viewLabel = act.viewLabel ?? ownership.viewLabel;
202
+ namespaceOwnerships.set(act.target, ownership);
203
+ }
204
+ break;
205
+ }
206
+ case "branch":
207
+ if (act.viewRefs.length === 0) {
208
+ diagnostics.push({
209
+ level: "warn",
210
+ message: `${describeView(act.viewLabel)}:toBranch("${act.target}"):view 为空,不会创建分支。`
211
+ });
212
+ break;
213
+ }
214
+ if (act.viewRefs.length > 1) {
215
+ diagnostics.push({
216
+ level: "error",
217
+ message: `${describeView(act.viewLabel)}:toBranch("${act.target}") 需要单一 ref 视图,当前收到 ${act.viewRefs.length} 个 refs。`
218
+ });
219
+ break;
220
+ }
221
+ resolvedMappings.push({
222
+ remoteRef: act.viewRefs[0],
223
+ localRef: act.target.startsWith("refs/heads/") ? act.target : `refs/heads/${act.target}`,
224
+ policy: effectivePolicy,
225
+ viewLabel: act.viewLabel
226
+ });
227
+ break;
228
+ case "tag":
229
+ if (act.viewRefs.length === 0) {
230
+ diagnostics.push({
231
+ level: "warn",
232
+ message: `${describeView(act.viewLabel)}:toTag("${act.target}"):view 为空,不会创建 tag。`
233
+ });
234
+ break;
235
+ }
236
+ if (act.viewRefs.length > 1) {
237
+ diagnostics.push({
238
+ level: "error",
239
+ message: `${describeView(act.viewLabel)}:toTag("${act.target}") 需要单一 ref 视图,当前收到 ${act.viewRefs.length} 个 refs。`
240
+ });
241
+ break;
242
+ }
243
+ resolvedMappings.push({
244
+ remoteRef: act.viewRefs[0],
245
+ localRef: act.target.startsWith("refs/tags/") ? act.target : `refs/tags/${act.target}`,
246
+ policy: effectivePolicy,
247
+ viewLabel: act.viewLabel
248
+ });
249
+ break;
250
+ case "head": {
251
+ if (act.viewRefs.length === 0) {
252
+ diagnostics.push({
253
+ level: "warn",
254
+ message: `${describeView(act.viewLabel)}:setHead() 的 view 为空,HEAD 将被跳过。`
255
+ });
256
+ break;
257
+ }
258
+ if (act.viewRefs.length > 1) {
259
+ diagnostics.push({
260
+ level: "error",
261
+ message: `${describeView(act.viewLabel)}:setHead() 需要单一 ref 视图,当前收到 ${act.viewRefs.length} 个 refs。`
262
+ });
263
+ break;
264
+ }
265
+ const targetRemoteRef = act.viewRefs[0];
266
+ const lastMapping = [...resolvedMappings].reverse().find((mapping) => isSameRemoteRef(mapping.remoteRef, targetRemoteRef));
267
+ if (!lastMapping) {
268
+ diagnostics.push({
269
+ level: "warn",
270
+ message: `${describeView(act.viewLabel)}:setHead() 找不到 view "${targetRemoteRef.name}" 对应的前置物化结果,HEAD 将被跳过。`
271
+ });
272
+ break;
273
+ }
274
+ if (!lastMapping.localRef.startsWith("refs/heads/")) {
275
+ diagnostics.push({
276
+ level: "error",
277
+ message: `${describeView(act.viewLabel)}:setHead() 只能指向 refs/heads/*。当前目标为 "${lastMapping.localRef}"。`,
278
+ refName: lastMapping.localRef
279
+ });
280
+ break;
281
+ }
282
+ headRequests.push({
283
+ localRef: lastMapping.localRef,
284
+ detach: act.detach ?? false,
285
+ viewLabel: act.viewLabel
286
+ });
287
+ break;
288
+ }
289
+ }
290
+ }
291
+ const conflictedTargets = /* @__PURE__ */ new Set();
292
+ const mappingsByLocalRef = /* @__PURE__ */ new Map();
293
+ for (const mapping of resolvedMappings) {
294
+ const existing = mappingsByLocalRef.get(mapping.localRef) ?? [];
295
+ existing.push(mapping);
296
+ mappingsByLocalRef.set(mapping.localRef, existing);
297
+ }
298
+ for (const [localRef, mappings] of mappingsByLocalRef) {
299
+ if (mappings.length <= 1) continue;
300
+ conflictedTargets.add(localRef);
301
+ diagnostics.push({
302
+ level: "error",
303
+ message: `本地 ref "${localRef}" 被多个物化动作同时写入:${mappings.map((mapping) => mapping.viewLabel ? `${mapping.remoteRef.name}(${mapping.viewLabel})` : mapping.remoteRef.name).join(", ")}。`,
304
+ refName: localRef
305
+ });
306
+ }
307
+ return {
308
+ resolvedMappings,
309
+ headRequests,
310
+ namespaceOwnerships,
311
+ selectedRefs: resolvedMappings.map((mapping) => ({
312
+ remoteRef: mapping.remoteRef,
313
+ localTarget: mapping.localRef,
314
+ policy: mapping.policy,
315
+ viewLabel: mapping.viewLabel
316
+ })),
317
+ objectRoots: [...new Set(resolvedMappings.filter((mapping) => !conflictedTargets.has(mapping.localRef)).map((mapping) => mapping.remoteRef.hash).filter((hash) => !backend.objects.exists(hash)))],
318
+ localPreconditions: captureLocalPreconditions(resolvedMappings, headRequests, namespaceOwnerships),
319
+ diagnostics,
320
+ conflictedTargets
321
+ };
322
+ }
323
+ async function fetchPreviewObjects(objectRoots, localPreconditions) {
324
+ if (objectRoots.length === 0) return 0;
325
+ const currentLocalRefs = getLocalRefs(backend.refs);
326
+ const localHaveTips = [];
327
+ for (const [, hash] of currentLocalRefs) if (!localHaveTips.some((existingHash) => existingHash === hash)) localHaveTips.push(hash);
328
+ if (v2Transport) {
329
+ const v2Wants = objectRoots.map((h) => h);
330
+ const v2Haves = localHaveTips.length > 0 ? localHaveTips.map((h) => h) : void 0;
331
+ const { objectCount } = await v2FetchObjects(backend.objects, v2Transport, v2Wants, v2Haves);
332
+ validateLocalPreconditions(backend, localPreconditions);
333
+ return objectCount;
334
+ }
335
+ throw new PreconditionCheckError("v1 fetch is not supported. Use v2 Git Wire Protocol.");
336
+ }
337
+ function finalizePreview(skeleton, prefetchedObjects) {
338
+ const diagnostics = [...skeleton.diagnostics];
339
+ const refOperations = [];
340
+ const validHeadTargets = /* @__PURE__ */ new Set();
341
+ const localRefs = getLocalRefs(backend.refs);
342
+ for (const mapping of skeleton.resolvedMappings) {
343
+ if (skeleton.conflictedTargets.has(mapping.localRef)) continue;
344
+ const existingValue = backend.refs.read(mapping.localRef);
345
+ const existingHash = localRefs.get(mapping.localRef) ?? null;
346
+ const refExists = existingValue !== null;
347
+ if (!backend.objects.exists(mapping.remoteRef.hash)) {
348
+ diagnostics.push({
349
+ level: "error",
350
+ message: `${describeView(mapping.viewLabel)}:对象 "${mapping.remoteRef.hash}" 在 preview() 预取后仍不存在。`,
351
+ refName: mapping.localRef
352
+ });
353
+ continue;
354
+ }
355
+ let targetHash = mapping.remoteRef.hash;
356
+ if (mapping.localRef.startsWith("refs/heads/")) try {
357
+ targetHash = resolveBranchTargetHash(backend.objects, mapping.remoteRef.hash, mapping.localRef);
358
+ } catch (err) {
359
+ diagnostics.push({
360
+ level: "error",
361
+ message: `${describeView(mapping.viewLabel)}:${err instanceof Error ? err.message : String(err)}`,
362
+ refName: mapping.localRef
363
+ });
364
+ continue;
365
+ }
366
+ if (existingHash === targetHash) {
367
+ diagnostics.push({
368
+ level: "info",
369
+ message: `${describeView(mapping.viewLabel)}:"${mapping.localRef}" 已是最新,跳过。`,
370
+ refName: mapping.localRef
371
+ });
372
+ if (mapping.localRef.startsWith("refs/heads/")) validHeadTargets.add(mapping.localRef);
373
+ continue;
374
+ }
375
+ if (refExists && mapping.policy.mode === "create-only") {
376
+ diagnostics.push({
377
+ level: "error",
378
+ message: `${describeView(mapping.viewLabel)}:"${mapping.localRef}" 已存在,create-only 策略拒绝更新。`,
379
+ refName: mapping.localRef
380
+ });
381
+ continue;
382
+ }
383
+ if (refExists && mapping.policy.mode === "fast-forward") {
384
+ if (existingHash === null) {
385
+ diagnostics.push({
386
+ level: "error",
387
+ message: `${describeView(mapping.viewLabel)}:ref "${mapping.localRef}" 当前存在,但无法解析为可比较的提交哈希。`,
388
+ refName: mapping.localRef
389
+ });
390
+ continue;
391
+ }
392
+ if (!isAncestor(backend.objects, existingHash, targetHash)) {
393
+ diagnostics.push({
394
+ level: "error",
395
+ message: `${describeView(mapping.viewLabel)}:ref "${mapping.localRef}" 无法 fast-forward。当前 ${existingHash},目标 ${targetHash}。`,
396
+ refName: mapping.localRef
397
+ });
398
+ continue;
399
+ }
400
+ diagnostics.push({
401
+ level: "info",
402
+ message: `${describeView(mapping.viewLabel)}:"${mapping.localRef}" 的 fast-forward 检查已通过。`,
403
+ refName: mapping.localRef
404
+ });
405
+ }
406
+ if (refExists && mapping.policy.mode === "mirror") diagnostics.push({
407
+ level: "info",
408
+ message: `${describeView(mapping.viewLabel)}:"${mapping.localRef}" 将按 mirror 策略覆盖,不执行 fast-forward 限制。`,
409
+ refName: mapping.localRef
410
+ });
411
+ refOperations.push({
412
+ localRef: mapping.localRef,
413
+ newHash: targetHash,
414
+ policy: mapping.policy,
415
+ viewLabel: mapping.viewLabel
416
+ });
417
+ if (mapping.localRef.startsWith("refs/heads/")) validHeadTargets.add(mapping.localRef);
418
+ }
419
+ const pruneOperations = [];
420
+ const scheduledPruneRefs = /* @__PURE__ */ new Set();
421
+ for (const ownership of skeleton.namespaceOwnerships.values()) {
422
+ if (!ownership.prune) continue;
423
+ for (const refName of backend.refs.listAll()) if (matchRefGlob(ownership.pattern, refName) && !ownership.currentRefs.has(refName) && !scheduledPruneRefs.has(refName)) {
424
+ scheduledPruneRefs.add(refName);
425
+ pruneOperations.push({
426
+ refName,
427
+ reason: `命名空间 "${ownership.pattern}" 的 prune 清理。`,
428
+ namespacePattern: ownership.pattern,
429
+ viewLabel: ownership.viewLabel
430
+ });
431
+ }
432
+ }
433
+ let headOperation;
434
+ if (skeleton.headRequests.length > 0) {
435
+ const lastHead = skeleton.headRequests[skeleton.headRequests.length - 1];
436
+ if (skeleton.conflictedTargets.has(lastHead.localRef)) diagnostics.push({
437
+ level: "error",
438
+ message: `${describeView(lastHead.viewLabel)}:setHead() 目标 "${lastHead.localRef}" 存在冲突,HEAD 无法确定。`,
439
+ refName: lastHead.localRef
440
+ });
441
+ else if (!validHeadTargets.has(lastHead.localRef)) diagnostics.push({
442
+ level: "error",
443
+ message: `${describeView(lastHead.viewLabel)}:setHead() 目标 "${lastHead.localRef}" 对应的 branch 物化未通过校验。`,
444
+ refName: lastHead.localRef
445
+ });
446
+ else headOperation = {
447
+ targetRef: lastHead.localRef,
448
+ detach: lastHead.detach,
449
+ viewLabel: lastHead.viewLabel
450
+ };
451
+ }
452
+ if (skeleton.resolvedMappings.length > 0) diagnostics.push({
453
+ level: "info",
454
+ message: `计划更新 ${refOperations.length} 个 ref,删除 ${pruneOperations.length} 个 ref。`
455
+ });
456
+ if (pruneOperations.length > 0) diagnostics.push({
457
+ level: "info",
458
+ message: `prune 将删除 ${pruneOperations.length} 个陈旧 ref。`
459
+ });
460
+ return createPreviewResult({
461
+ selectedRefs: skeleton.selectedRefs,
462
+ objectRoots: skeleton.objectRoots,
463
+ prefetchedObjects,
464
+ localPreconditions: skeleton.localPreconditions,
465
+ refOperations,
466
+ headOperation,
467
+ pruneOperations,
468
+ diagnostics
469
+ });
470
+ }
471
+ const builder = {
472
+ materialize(view) {
473
+ const viewRefs = view.refs;
474
+ const viewLabel = getViewLabel(view);
475
+ return {
476
+ toNamespace(targetPattern, options) {
477
+ actions.push({
478
+ viewRefs,
479
+ viewLabel,
480
+ action: "namespace",
481
+ target: targetPattern,
482
+ policy: options?.policy ? clonePolicy(options.policy) : void 0,
483
+ prune: options?.prune
484
+ });
485
+ invalidatePreview();
486
+ return builder;
487
+ },
488
+ toBranch(branchName, options) {
489
+ actions.push({
490
+ viewRefs,
491
+ viewLabel,
492
+ action: "branch",
493
+ target: branchName,
494
+ policy: options?.policy ? clonePolicy(options.policy) : { mode: "fast-forward" }
495
+ });
496
+ invalidatePreview();
497
+ return builder;
498
+ },
499
+ toTag(tagName, options) {
500
+ actions.push({
501
+ viewRefs,
502
+ viewLabel,
503
+ action: "tag",
504
+ target: tagName,
505
+ policy: options?.policy ? clonePolicy(options.policy) : { mode: "create-only" }
506
+ });
507
+ invalidatePreview();
508
+ return builder;
509
+ },
510
+ setHead(options) {
511
+ actions.push({
512
+ viewRefs,
513
+ viewLabel,
514
+ action: "head",
515
+ target: "HEAD",
516
+ policy: { mode: "replace" },
517
+ detach: options?.detach
518
+ });
519
+ invalidatePreview();
520
+ return builder;
521
+ }
522
+ };
523
+ },
524
+ async preview() {
525
+ const currentVersion = planVersion;
526
+ lastPreview = null;
527
+ const skeleton = buildPreviewSkeleton();
528
+ const hasStaticErrors = skeleton.diagnostics.some((diagnostic) => diagnostic.level === "error");
529
+ let prefetchedObjects = 0;
530
+ if (!hasStaticErrors) try {
531
+ prefetchedObjects = await fetchPreviewObjects(skeleton.objectRoots, skeleton.localPreconditions);
532
+ } catch (err) {
533
+ if (err instanceof PreconditionCheckError) {
534
+ const preview = createPreviewResult({
535
+ selectedRefs: skeleton.selectedRefs,
536
+ objectRoots: skeleton.objectRoots,
537
+ prefetchedObjects,
538
+ localPreconditions: skeleton.localPreconditions,
539
+ refOperations: [],
540
+ pruneOperations: [],
541
+ diagnostics: [...skeleton.diagnostics, {
542
+ level: "error",
543
+ message: err.message
544
+ }]
545
+ });
546
+ lastPreview = null;
547
+ return preview;
548
+ }
549
+ throw err;
550
+ }
551
+ const preview = finalizePreview(skeleton, prefetchedObjects);
552
+ if (preview.canApply && currentVersion === planVersion) lastPreview = {
553
+ version: currentVersion,
554
+ preview
555
+ };
556
+ else lastPreview = null;
557
+ return preview;
558
+ },
559
+ async apply() {
560
+ const p = lastPreview !== null && lastPreview.version === planVersion ? lastPreview.preview : await builder.preview();
561
+ if (!p.canApply) {
562
+ const errorMessages = p.diagnostics.filter((d) => d.level === "error").map((d) => d.message).join("; ");
563
+ throw new Error(`导入计划包含 ${p.diagnostics.filter((d) => d.level === "error").length} 个错误,无法执行。` + (errorMessages ? ` 错误:${errorMessages}` : ""));
564
+ }
565
+ const currentLocalRefs = validateLocalPreconditions(backend, p.localPreconditions);
566
+ if (p.refOperations.length === 0 && p.objectRoots.length === 0 && !p.headOperation && p.pruneOperations.length === 0) return {
567
+ importedObjects: p.prefetchedObjects,
568
+ updatedRefs: /* @__PURE__ */ new Map(),
569
+ deletedRefs: []
570
+ };
571
+ const pendingWrites = [];
572
+ for (const op of p.refOperations) {
573
+ const refExists = backend.refs.read(op.localRef) !== null;
574
+ const currentHash = currentLocalRefs.get(op.localRef) ?? null;
575
+ if (!backend.objects.exists(op.newHash)) throw new Error(`导入计划校验失败:对象 "${op.newHash}" 在本地对象库中不存在。`);
576
+ if (op.policy.mode === "create-only" && refExists) throw new Error(`导入计划校验失败:ref "${op.localRef}" 已存在,create-only 策略拒绝更新。`);
577
+ if (op.policy.mode === "fast-forward" && refExists) {
578
+ if (currentHash === null) throw new Error(`导入计划校验失败:ref "${op.localRef}" 当前存在,但无法解析为可比较的提交哈希。`);
579
+ if (!isAncestor(backend.objects, currentHash, op.newHash)) throw new Error(`导入计划校验失败:ref "${op.localRef}" 无法 fast-forward。当前 ${currentHash},目标 ${op.newHash}。`);
580
+ }
581
+ pendingWrites.push({
582
+ localRef: op.localRef,
583
+ writeHash: op.newHash
584
+ });
585
+ }
586
+ const hooks = backend.refTransactionHooks;
587
+ const tx = backend.refs.beginTransaction(hooks);
588
+ try {
589
+ const updatedRefs = /* @__PURE__ */ new Map();
590
+ for (const op of pendingWrites) {
591
+ tx.write(op.localRef, op.writeHash);
592
+ updatedRefs.set(op.localRef, op.writeHash);
593
+ }
594
+ if (p.headOperation) {
595
+ if (!p.headOperation.targetRef.startsWith("refs/heads/")) throw new Error(`导入计划校验失败:setHead() 只能指向 refs/heads/*,当前为 "${p.headOperation.targetRef}"。`);
596
+ if (p.headOperation.detach) {
597
+ const detachedTarget = updatedRefs.get(p.headOperation.targetRef);
598
+ const existingTarget = currentLocalRefs.get(p.headOperation.targetRef);
599
+ const resolvedTarget = detachedTarget ?? existingTarget;
600
+ if (!resolvedTarget) throw new Error(`无法将 HEAD detached 到 "${p.headOperation.targetRef}":目标 ref 不存在。`);
601
+ tx.write("HEAD", resolvedTarget);
602
+ } else tx.write("HEAD", `ref: ${p.headOperation.targetRef}`);
603
+ }
604
+ const deletedRefs = [];
605
+ for (const op of p.pruneOperations) try {
606
+ tx.delete(op.refName);
607
+ deletedRefs.push(op.refName);
608
+ } catch {}
609
+ tx.commit();
610
+ return {
611
+ importedObjects: p.prefetchedObjects,
612
+ updatedRefs,
613
+ deletedRefs,
614
+ headTarget: p.headOperation?.targetRef
615
+ };
616
+ } catch (e) {
617
+ tx.rollback();
618
+ throw e;
619
+ }
620
+ }
621
+ };
622
+ return builder;
623
+ }
624
+ //#endregion
625
+ export { createPlanBuilder };