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.
- package/README.md +407 -0
- package/dist/backend/file-backend.d.mts +26 -0
- package/dist/backend/file-backend.mjs +83 -0
- package/dist/backend/file.d.mts +2 -0
- package/dist/backend/file.mjs +2 -0
- package/dist/backend/index.d.mts +2 -0
- package/dist/backend/index.mjs +1 -0
- package/dist/backend/memory-backend.d.mts +25 -0
- package/dist/backend/memory-backend.mjs +33 -0
- package/dist/backend/memory.d.mts +2 -0
- package/dist/backend/memory.mjs +2 -0
- package/dist/backend/types.d.mts +90 -0
- package/dist/core/errors.d.mts +124 -0
- package/dist/core/errors.mjs +168 -0
- package/dist/core/hash-digest.d.mts +35 -0
- package/dist/core/hash-digest.mjs +50 -0
- package/dist/core/hash-file.d.mts +20 -0
- package/dist/core/hash-file.mjs +27 -0
- package/dist/core/hash-path.d.mts +17 -0
- package/dist/core/hash-path.mjs +35 -0
- package/dist/core/types/odb.d.mts +63 -0
- package/dist/core/types/refs.d.mts +140 -0
- package/dist/core/types/refs.mjs +19 -0
- package/dist/core/types/shallow.d.mts +52 -0
- package/dist/core/types.d.mts +154 -0
- package/dist/core/types.mjs +43 -0
- package/dist/errors.d.mts +2 -0
- package/dist/errors.mjs +2 -0
- package/dist/hash-file.d.mts +2 -0
- package/dist/hash-file.mjs +2 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +14 -0
- package/dist/objects/author.d.mts +25 -0
- package/dist/objects/author.mjs +45 -0
- package/dist/objects/blob.d.mts +15 -0
- package/dist/objects/blob.mjs +20 -0
- package/dist/objects/codec.d.mts +46 -0
- package/dist/objects/codec.mjs +84 -0
- package/dist/objects/commit.d.mts +26 -0
- package/dist/objects/commit.mjs +160 -0
- package/dist/objects/index.d.mts +8 -0
- package/dist/objects/index.mjs +8 -0
- package/dist/objects/raw.d.mts +85 -0
- package/dist/objects/raw.mjs +111 -0
- package/dist/objects/tag.d.mts +26 -0
- package/dist/objects/tag.mjs +148 -0
- package/dist/objects/tree.d.mts +22 -0
- package/dist/objects/tree.mjs +66 -0
- package/dist/odb/file-utils.mjs +136 -0
- package/dist/odb/file.d.mts +22 -0
- package/dist/odb/file.mjs +66 -0
- package/dist/odb/memory.d.mts +22 -0
- package/dist/odb/memory.mjs +54 -0
- package/dist/pack/composite-store.d.mts +76 -0
- package/dist/pack/composite-store.mjs +133 -0
- package/dist/pack/constants.mjs +48 -0
- package/dist/pack/crc32.mjs +38 -0
- package/dist/pack/delta-apply.d.mts +21 -0
- package/dist/pack/delta-apply.mjs +71 -0
- package/dist/pack/delta-create.d.mts +28 -0
- package/dist/pack/delta-create.mjs +151 -0
- package/dist/pack/index.d.mts +16 -0
- package/dist/pack/index.mjs +13 -0
- package/dist/pack/object-header.d.mts +36 -0
- package/dist/pack/object-header.mjs +72 -0
- package/dist/pack/ofs-delta-offset.d.mts +35 -0
- package/dist/pack/ofs-delta-offset.mjs +59 -0
- package/dist/pack/pack-builder-types.d.mts +19 -0
- package/dist/pack/pack-builder.d.mts +52 -0
- package/dist/pack/pack-builder.mjs +80 -0
- package/dist/pack/pack-encoding.mjs +76 -0
- package/dist/pack/pack-index-reader.d.mts +57 -0
- package/dist/pack/pack-index-reader.mjs +90 -0
- package/dist/pack/pack-index-types.d.mts +16 -0
- package/dist/pack/pack-index-writer.d.mts +45 -0
- package/dist/pack/pack-index-writer.mjs +106 -0
- package/dist/pack/pack-reader-resolver.mjs +104 -0
- package/dist/pack/pack-reader-types.d.mts +31 -0
- package/dist/pack/pack-reader-types.mjs +22 -0
- package/dist/pack/pack-reader-utils.mjs +61 -0
- package/dist/pack/pack-reader.d.mts +81 -0
- package/dist/pack/pack-reader.mjs +171 -0
- package/dist/pack/pack-store-loader.mjs +79 -0
- package/dist/pack/pack-store-types.d.mts +16 -0
- package/dist/pack/pack-store.d.mts +55 -0
- package/dist/pack/pack-store.mjs +114 -0
- package/dist/pack/pack-writer.mjs +96 -0
- package/dist/pack/varint.d.mts +34 -0
- package/dist/pack/varint.mjs +57 -0
- package/dist/refs/file.d.mts +6 -0
- package/dist/refs/file.mjs +222 -0
- package/dist/refs/fs-utils.mjs +30 -0
- package/dist/refs/memory.d.mts +14 -0
- package/dist/refs/memory.mjs +104 -0
- package/dist/refs/names.d.mts +57 -0
- package/dist/refs/names.mjs +80 -0
- package/dist/refs/resolve.d.mts +33 -0
- package/dist/refs/resolve.mjs +55 -0
- package/dist/refs/shallow/file.d.mts +17 -0
- package/dist/refs/shallow/file.mjs +61 -0
- package/dist/refs/shallow/memory.d.mts +18 -0
- package/dist/refs/shallow/memory.mjs +33 -0
- package/dist/repository/core.d.mts +3 -0
- package/dist/repository/core.mjs +2 -0
- package/dist/repository/create.d.mts +22 -0
- package/dist/repository/create.mjs +43 -0
- package/dist/repository/file.d.mts +43 -0
- package/dist/repository/file.mjs +82 -0
- package/dist/repository/import/import-glob.mjs +44 -0
- package/dist/repository/import/import-plan-builder.mjs +625 -0
- package/dist/repository/import/import-session-types.d.mts +280 -0
- package/dist/repository/import/import-session.mjs +96 -0
- package/dist/repository/import/import-view.mjs +133 -0
- package/dist/repository/memory.d.mts +17 -0
- package/dist/repository/memory.mjs +33 -0
- package/dist/repository/ops/fetch-operations.mjs +20 -0
- package/dist/repository/ops/fetch-types.d.mts +82 -0
- package/dist/repository/ops/fetch-url.mjs +82 -0
- package/dist/repository/ops/fs-object-operations.mjs +31 -0
- package/dist/repository/ops/maintenance-operations.mjs +62 -0
- package/dist/repository/ops/maintenance-types.d.mts +42 -0
- package/dist/repository/ops/object-operations.mjs +65 -0
- package/dist/repository/ops/object-types.d.mts +109 -0
- package/dist/repository/ops/push-operations.mjs +16 -0
- package/dist/repository/ops/push-resolution.mjs +17 -0
- package/dist/repository/ops/push-types.d.mts +72 -0
- package/dist/repository/ops/push-url.mjs +45 -0
- package/dist/repository/ops/reachability.mjs +60 -0
- package/dist/repository/ops/ref-operations.mjs +86 -0
- package/dist/repository/ops/ref-types.d.mts +70 -0
- package/dist/repository/tree/tree-patch.d.mts +64 -0
- package/dist/repository/tree/tree-patch.mjs +268 -0
- package/dist/repository/tree/tree-walk.d.mts +46 -0
- package/dist/repository/tree/tree-walk.mjs +65 -0
- package/dist/repository/tree/tree-writer.mjs +68 -0
- package/dist/repository/types.d.mts +36 -0
- package/dist/sha1.d.mts +4 -0
- package/dist/sha1.mjs +4 -0
- package/dist/transport/client/receive-pack/http.d.mts +33 -0
- package/dist/transport/client/receive-pack/http.mjs +99 -0
- package/dist/transport/client/receive-pack/push-error.d.mts +23 -0
- package/dist/transport/client/receive-pack/push-error.mjs +32 -0
- package/dist/transport/client/receive-pack/push-pack-plan.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-pack-plan.mjs +60 -0
- package/dist/transport/client/receive-pack/push-policy.d.mts +19 -0
- package/dist/transport/client/receive-pack/push-policy.mjs +64 -0
- package/dist/transport/client/receive-pack/push-ref-plan.d.mts +45 -0
- package/dist/transport/client/receive-pack/push-ref-plan.mjs +108 -0
- package/dist/transport/client/receive-pack/push-report.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-report.mjs +84 -0
- package/dist/transport/client/receive-pack/push-request-plan.mjs +52 -0
- package/dist/transport/client/receive-pack/push.d.mts +32 -0
- package/dist/transport/client/receive-pack/push.mjs +97 -0
- package/dist/transport/client/receive-pack/request.d.mts +39 -0
- package/dist/transport/client/receive-pack/request.mjs +46 -0
- package/dist/transport/client/receive-pack/response.d.mts +26 -0
- package/dist/transport/client/receive-pack/response.mjs +52 -0
- package/dist/transport/client/receive-pack/result.d.mts +34 -0
- package/dist/transport/client/receive-pack/result.mjs +100 -0
- package/dist/transport/client/upload-pack/capability-advertisement.d.mts +56 -0
- package/dist/transport/client/upload-pack/capability-advertisement.mjs +130 -0
- package/dist/transport/client/upload-pack/fetch.d.mts +109 -0
- package/dist/transport/client/upload-pack/fetch.mjs +392 -0
- package/dist/transport/client/upload-pack/http.d.mts +29 -0
- package/dist/transport/client/upload-pack/http.mjs +79 -0
- package/dist/transport/client/upload-pack/ls-refs.d.mts +75 -0
- package/dist/transport/client/upload-pack/ls-refs.mjs +150 -0
- package/dist/transport/client/upload-pack/object-info.d.mts +65 -0
- package/dist/transport/client/upload-pack/object-info.mjs +111 -0
- package/dist/transport/client/upload-pack/types.d.mts +153 -0
- package/dist/transport/http/index.d.mts +3 -0
- package/dist/transport/http/index.mjs +2 -0
- package/dist/transport/http/smart-http.d.mts +46 -0
- package/dist/transport/http/smart-http.mjs +176 -0
- package/dist/transport/http/types.d.mts +27 -0
- package/dist/transport/index.d.mts +9 -0
- package/dist/transport/index.mjs +8 -0
- package/dist/transport/protocol/object-graph.d.mts +63 -0
- package/dist/transport/protocol/object-graph.mjs +149 -0
- package/dist/transport/protocol/pkt-line.d.mts +109 -0
- package/dist/transport/protocol/pkt-line.mjs +195 -0
- package/dist/transport/protocol/ref-advertisement.mjs +185 -0
- package/dist/transport/protocol/ref-collection.d.mts +38 -0
- package/dist/transport/protocol/ref-collection.mjs +63 -0
- package/dist/transport/protocol/ref-match.d.mts +39 -0
- package/dist/transport/protocol/ref-match.mjs +42 -0
- package/dist/transport/protocol/refspec.d.mts +44 -0
- package/dist/transport/protocol/refspec.mjs +79 -0
- package/dist/transport/protocol/side-band.d.mts +65 -0
- package/dist/transport/protocol/side-band.mjs +142 -0
- package/dist/transport/protocol/transport-capabilities.mjs +46 -0
- package/dist/transport/protocol/types.d.mts +148 -0
- package/dist/transport/protocol/update-refs.d.mts +68 -0
- package/dist/transport/protocol/update-refs.mjs +126 -0
- package/dist/transport/receive-pack.d.mts +11 -0
- package/dist/transport/receive-pack.mjs +11 -0
- package/dist/transport/server/receive-pack/advertise.d.mts +28 -0
- package/dist/transport/server/receive-pack/advertise.mjs +88 -0
- package/dist/transport/server/receive-pack/handler.d.mts +30 -0
- package/dist/transport/server/receive-pack/handler.mjs +156 -0
- package/dist/transport/server/receive-pack/index.d.mts +6 -0
- package/dist/transport/server/receive-pack/index.mjs +6 -0
- package/dist/transport/server/receive-pack/parse.d.mts +17 -0
- package/dist/transport/server/receive-pack/parse.mjs +80 -0
- package/dist/transport/server/receive-pack/report-status.mjs +64 -0
- package/dist/transport/server/receive-pack/service.d.mts +41 -0
- package/dist/transport/server/receive-pack/service.mjs +39 -0
- package/dist/transport/server/receive-pack/types.d.mts +56 -0
- package/dist/transport/server/receive-pack/types.mjs +25 -0
- package/dist/transport/server/receive-pack/unpack.mjs +119 -0
- package/dist/transport/server/upload-pack/advertise.d.mts +20 -0
- package/dist/transport/server/upload-pack/advertise.mjs +30 -0
- package/dist/transport/server/upload-pack/command.d.mts +43 -0
- package/dist/transport/server/upload-pack/command.mjs +56 -0
- package/dist/transport/server/upload-pack/fetch.d.mts +43 -0
- package/dist/transport/server/upload-pack/fetch.mjs +217 -0
- package/dist/transport/server/upload-pack/index.d.mts +7 -0
- package/dist/transport/server/upload-pack/index.mjs +7 -0
- package/dist/transport/server/upload-pack/ls-refs.d.mts +38 -0
- package/dist/transport/server/upload-pack/ls-refs.mjs +113 -0
- package/dist/transport/server/upload-pack/service.d.mts +40 -0
- package/dist/transport/server/upload-pack/service.mjs +51 -0
- package/dist/transport/server/upload-pack/types.d.mts +11 -0
- package/dist/transport/server/upload-pack/types.mjs +21 -0
- package/dist/transport/upload-pack.d.mts +7 -0
- package/dist/transport/upload-pack.mjs +6 -0
- 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 };
|