@ricsam/r5d-worker 0.0.81 → 0.0.83
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/dist/cjs/main.cjs +743 -121
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-command-sync-policy.cjs +3 -3
- package/dist/cjs/workspace-git-sync.cjs +446 -123
- package/dist/cjs/workspace-merge-projection.cjs +392 -0
- package/dist/mjs/main.mjs +744 -122
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +3 -3
- package/dist/mjs/workspace-git-sync.mjs +448 -123
- package/dist/mjs/workspace-merge-projection.mjs +355 -0
- package/dist/types/main.d.ts +395 -0
- package/dist/types/working-tree-mirror.d.ts +1 -2
- package/dist/types/workspace-command-sync-policy.d.ts +4 -3
- package/dist/types/workspace-git-sync.d.ts +34 -0
- package/dist/types/workspace-merge-projection.d.ts +42 -0
- package/package.json +1 -1
|
@@ -34,6 +34,8 @@ __export(workspace_git_sync_exports, {
|
|
|
34
34
|
WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION: () => WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
|
|
35
35
|
WORKSPACE_GIT_HYDRATED_RECEIPT: () => WORKSPACE_GIT_HYDRATED_RECEIPT,
|
|
36
36
|
WORKSPACE_GIT_HYDRATION_TRANSACTION: () => WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
37
|
+
WorkspaceRemediationAncestryError: () => WorkspaceRemediationAncestryError,
|
|
38
|
+
configureExistingWorkspaceGitForRemediation: () => configureExistingWorkspaceGitForRemediation,
|
|
37
39
|
ensureWorkspaceGitClone: () => ensureWorkspaceGitClone,
|
|
38
40
|
hydrateWorkspaceGitMounts: () => hydrateWorkspaceGitMounts,
|
|
39
41
|
recoverWorkspaceGitHydration: () => recoverWorkspaceGitHydration,
|
|
@@ -46,6 +48,7 @@ module.exports = __toCommonJS(workspace_git_sync_exports);
|
|
|
46
48
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
47
49
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
48
50
|
var import_git_process_environment = require("./git-process-environment.cjs");
|
|
51
|
+
var import_workspace_merge_projection = require("./workspace-merge-projection.cjs");
|
|
49
52
|
var import_working_tree_mirror = require("./working-tree-mirror.cjs");
|
|
50
53
|
var import_workspace_mount_boundary = require("./workspace-mount-boundary.cjs");
|
|
51
54
|
const WORKSPACE_GIT_BRANCH = "main";
|
|
@@ -55,6 +58,12 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
|
55
58
|
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
56
59
|
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
57
60
|
const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
61
|
+
class WorkspaceRemediationAncestryError extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "WorkspaceRemediationAncestryError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
const NON_RECURSIVE_GIT_CONFIG = [
|
|
59
68
|
"-c",
|
|
60
69
|
"submodule.recurse=false",
|
|
@@ -94,6 +103,29 @@ function revParse(workspacePath, revision) {
|
|
|
94
103
|
const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
|
|
95
104
|
return result.exitCode === 0 ? result.stdout : null;
|
|
96
105
|
}
|
|
106
|
+
function requiredWorkspaceAncestorHeads(value) {
|
|
107
|
+
if (value === void 0) return [];
|
|
108
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((head) => typeof head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head)) || new Set(value).size !== value.length) {
|
|
109
|
+
throw new WorkspaceRemediationAncestryError("Workspace remediation ancestor requirements are invalid");
|
|
110
|
+
}
|
|
111
|
+
return [...value];
|
|
112
|
+
}
|
|
113
|
+
function assertWorkspaceRemediationAncestry(input) {
|
|
114
|
+
if (input.requiredAncestorHeads.length === 0) return;
|
|
115
|
+
if (!input.currentHead) throw new WorkspaceRemediationAncestryError("Workspace remediation has no resolved HEAD to verify");
|
|
116
|
+
for (const requiredHead of input.requiredAncestorHeads) {
|
|
117
|
+
if (!tryGit(input.workspacePath, ["cat-file", "-e", `${requiredHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", requiredHead, input.currentHead])) {
|
|
118
|
+
throw new WorkspaceRemediationAncestryError(
|
|
119
|
+
`Workspace remediation HEAD does not descend from required conflict commit ${requiredHead}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!input.remoteHead || !tryGit(input.workspacePath, ["cat-file", "-e", `${input.remoteHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", input.remoteHead, input.currentHead])) {
|
|
124
|
+
throw new WorkspaceRemediationAncestryError(
|
|
125
|
+
"Fetched workspace head is not an ancestor of the resolved remediation HEAD; merge it explicitly before synchronizing"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
97
129
|
function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
98
130
|
git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
|
|
99
131
|
}
|
|
@@ -140,22 +172,52 @@ function compareWorkspaceHydrationMountBasis(left, right) {
|
|
|
140
172
|
return left.id.localeCompare(right.id);
|
|
141
173
|
}
|
|
142
174
|
function workspaceHydrationReceipt(head, mounts) {
|
|
143
|
-
const bases = mounts.map(workspaceHydrationMountBasis).sort(compareWorkspaceHydrationMountBasis);
|
|
175
|
+
const bases = mounts.map((mount) => ({ ...workspaceHydrationMountBasis(mount), head })).sort(compareWorkspaceHydrationMountBasis);
|
|
144
176
|
const ids = /* @__PURE__ */ new Set();
|
|
145
177
|
for (const basis of bases) {
|
|
146
178
|
if (ids.has(basis.id)) throw new Error(`Workspace hydration receipt mount id must be unique: ${basis.id}`);
|
|
147
179
|
ids.add(basis.id);
|
|
148
180
|
}
|
|
149
|
-
return { version:
|
|
181
|
+
return { version: 3, mounts: bases };
|
|
182
|
+
}
|
|
183
|
+
function receiptWithMountsAtHead(previous, head, mounts, allConfiguredMounts) {
|
|
184
|
+
const advanceIds = new Set(mounts.map(({ id }) => id));
|
|
185
|
+
const previousById = new Map(previous?.mounts.map((entry) => [entry.id, entry]) ?? []);
|
|
186
|
+
const entries = allConfiguredMounts.flatMap((mount) => {
|
|
187
|
+
const basis = workspaceHydrationMountBasis(mount);
|
|
188
|
+
if (advanceIds.has(mount.id)) return [{ ...basis, head }];
|
|
189
|
+
const carried = previousById.get(mount.id);
|
|
190
|
+
if (!carried) return [];
|
|
191
|
+
return [carried];
|
|
192
|
+
});
|
|
193
|
+
entries.sort(compareWorkspaceHydrationMountBasis);
|
|
194
|
+
const ids = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (ids.has(entry.id)) throw new Error(`Workspace hydration receipt mount id must be unique: ${entry.id}`);
|
|
197
|
+
ids.add(entry.id);
|
|
198
|
+
}
|
|
199
|
+
return { version: 3, mounts: entries };
|
|
150
200
|
}
|
|
151
201
|
function workspaceHydrationReceiptsEqual(left, right) {
|
|
152
|
-
|
|
202
|
+
if (JSON.stringify(left) !== JSON.stringify(right)) return false;
|
|
203
|
+
const effectiveLegacyHead = (receipt) => receipt?.mounts.length === 0 ? receipt.legacyHead ?? null : null;
|
|
204
|
+
return effectiveLegacyHead(left) === effectiveLegacyHead(right);
|
|
153
205
|
}
|
|
154
206
|
function workspaceHydrationReceiptCoversMount(receipt, head, mount) {
|
|
155
|
-
if (!receipt
|
|
207
|
+
if (!receipt) return false;
|
|
208
|
+
const expected = workspaceHydrationMountBasis(mount);
|
|
209
|
+
const actual = receipt.mounts.find(({ id }) => id === expected.id);
|
|
210
|
+
if (!actual || actual.head !== head) return false;
|
|
211
|
+
const { head: _head, ...actualBasis } = actual;
|
|
212
|
+
return JSON.stringify(actualBasis) === JSON.stringify(expected);
|
|
213
|
+
}
|
|
214
|
+
function workspaceHydrationReceiptMountBasisHead(receipt, mount) {
|
|
215
|
+
if (!receipt) return null;
|
|
156
216
|
const expected = workspaceHydrationMountBasis(mount);
|
|
157
217
|
const actual = receipt.mounts.find(({ id }) => id === expected.id);
|
|
158
|
-
|
|
218
|
+
if (!actual) return null;
|
|
219
|
+
const { head, ...actualBasis } = actual;
|
|
220
|
+
return JSON.stringify(actualBasis) === JSON.stringify(expected) ? head : null;
|
|
159
221
|
}
|
|
160
222
|
function workspaceHydrationReceiptMatchesMounts(receipt, head, mounts) {
|
|
161
223
|
return workspaceHydrationReceiptsEqual(receipt, workspaceHydrationReceipt(head, mounts));
|
|
@@ -176,7 +238,9 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
176
238
|
if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
|
|
177
239
|
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
178
240
|
}
|
|
179
|
-
|
|
241
|
+
const receipt2 = { version: 3, mounts: [] };
|
|
242
|
+
Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
|
|
243
|
+
return receipt2;
|
|
180
244
|
}
|
|
181
245
|
let parsed;
|
|
182
246
|
try {
|
|
@@ -188,7 +252,8 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
188
252
|
throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
|
|
189
253
|
}
|
|
190
254
|
const candidate = parsed;
|
|
191
|
-
|
|
255
|
+
const liftedV2Head = candidate.version === 2 && typeof candidate.head === "string" && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(candidate.head) ? candidate.head : null;
|
|
256
|
+
if (candidate.version !== 3 && liftedV2Head === null) {
|
|
192
257
|
throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
|
|
193
258
|
}
|
|
194
259
|
if (!Array.isArray(candidate.mounts)) {
|
|
@@ -202,23 +267,51 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
202
267
|
const mount = rawMount;
|
|
203
268
|
const normalizedRelativePath = typeof mount.workspaceRelativePath === "string" ? normalizedWorkspaceMountPath(mount.workspaceRelativePath) : "";
|
|
204
269
|
const durabilityRelative = typeof mount.sourcePath === "string" && typeof mount.durabilityRootPath === "string" ? import_node_path.default.relative(mount.durabilityRootPath, mount.sourcePath) : "..";
|
|
205
|
-
if (typeof mount.id !== "string" || !mount.id || seenIds.has(mount.id) || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative) || typeof mount.workspaceRelativePath !== "string" || mount.workspaceRelativePath !== normalizedRelativePath || !normalizedRelativePath || normalizedRelativePath === "." || normalizedRelativePath.split("/").some((segment) => segment === ".." || segment === ".git") || mount.sourceMode !== "all" && mount.sourceMode !== "git" || mount.hydrateDeletionMode !== "all" && mount.hydrateDeletionMode !== "git" || typeof mount.preserveLocalOnInitialOuterAbsence !== "boolean" || typeof mount.preserveLocalOnHydrationBasisChange !== "boolean" || typeof mount.deleteWhenSourceMissing !== "boolean") {
|
|
270
|
+
if (typeof mount.id !== "string" || !mount.id || seenIds.has(mount.id) || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative) || typeof mount.workspaceRelativePath !== "string" || mount.workspaceRelativePath !== normalizedRelativePath || !normalizedRelativePath || normalizedRelativePath === "." || normalizedRelativePath.split("/").some((segment) => segment === ".." || segment === ".git") || mount.sourceMode !== "all" && mount.sourceMode !== "git" || mount.hydrateDeletionMode !== "all" && mount.hydrateDeletionMode !== "git" || typeof mount.preserveLocalOnInitialOuterAbsence !== "boolean" || typeof mount.preserveLocalOnHydrationBasisChange !== "boolean" || typeof mount.deleteWhenSourceMissing !== "boolean" || candidate.version === 3 && (typeof mount.head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head))) {
|
|
206
271
|
throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
|
|
207
272
|
}
|
|
208
273
|
seenIds.add(mount.id);
|
|
209
|
-
return
|
|
274
|
+
return {
|
|
275
|
+
id: mount.id,
|
|
276
|
+
incarnationKey: mount.incarnationKey,
|
|
277
|
+
sourcePath: mount.sourcePath,
|
|
278
|
+
durabilityRootPath: mount.durabilityRootPath,
|
|
279
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
280
|
+
sourceMode: mount.sourceMode,
|
|
281
|
+
hydrateDeletionMode: mount.hydrateDeletionMode,
|
|
282
|
+
preserveLocalOnInitialOuterAbsence: mount.preserveLocalOnInitialOuterAbsence,
|
|
283
|
+
preserveLocalOnHydrationBasisChange: mount.preserveLocalOnHydrationBasisChange,
|
|
284
|
+
deleteWhenSourceMissing: mount.deleteWhenSourceMissing,
|
|
285
|
+
head: candidate.version === 3 ? mount.head : liftedV2Head
|
|
286
|
+
};
|
|
210
287
|
});
|
|
288
|
+
const sortedMounts = mounts.sort(compareWorkspaceHydrationMountBasis);
|
|
289
|
+
if (liftedV2Head) {
|
|
290
|
+
const legacyReceipt = {
|
|
291
|
+
version: 2,
|
|
292
|
+
head: liftedV2Head,
|
|
293
|
+
mounts: sortedMounts.map(({ head: _head, ...basis }) => basis)
|
|
294
|
+
};
|
|
295
|
+
if (content !== `${JSON.stringify(legacyReceipt)}
|
|
296
|
+
`) {
|
|
297
|
+
throw new Error(`Workspace hydration receipt contains a noncanonical mount basis: ${receiptPath}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
211
300
|
const receipt = {
|
|
212
|
-
version:
|
|
213
|
-
|
|
214
|
-
mounts: mounts.sort(compareWorkspaceHydrationMountBasis)
|
|
301
|
+
version: 3,
|
|
302
|
+
mounts: sortedMounts
|
|
215
303
|
};
|
|
216
|
-
if (
|
|
304
|
+
if (liftedV2Head) Object.defineProperty(receipt, "legacyHead", { value: liftedV2Head, enumerable: false });
|
|
305
|
+
if (candidate.version === 3 && content !== `${JSON.stringify(receipt)}
|
|
217
306
|
`) {
|
|
218
307
|
throw new Error(`Workspace hydration receipt contains a noncanonical mount basis: ${receiptPath}`);
|
|
219
308
|
}
|
|
220
|
-
|
|
221
|
-
|
|
309
|
+
const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
|
|
310
|
+
if (liftedV2Head) receiptHeads.add(liftedV2Head);
|
|
311
|
+
for (const head of receiptHeads) {
|
|
312
|
+
if (!tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`])) {
|
|
313
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
314
|
+
}
|
|
222
315
|
}
|
|
223
316
|
return receipt;
|
|
224
317
|
}
|
|
@@ -232,22 +325,92 @@ function readHydratedWorkspaceReceipt(workspacePath) {
|
|
|
232
325
|
const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
|
|
233
326
|
return parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content);
|
|
234
327
|
}
|
|
328
|
+
function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
329
|
+
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
330
|
+
const status = lstatIfExists(receiptPath);
|
|
331
|
+
if (!status?.isFile() || status.isSymbolicLink()) return false;
|
|
332
|
+
try {
|
|
333
|
+
return JSON.parse(import_node_fs.default.readFileSync(receiptPath, "utf8")).version === 3;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
235
338
|
function workspaceGitHydrationIsCurrent(workspacePath, mounts) {
|
|
236
339
|
const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
|
|
237
340
|
if (!import_node_fs.default.existsSync(import_node_path.default.join(resolvedWorkspacePath, ".git"))) return true;
|
|
238
341
|
if (mounts) validateMounts(resolvedWorkspacePath, mounts);
|
|
239
342
|
const head = revParse(resolvedWorkspacePath, "HEAD");
|
|
240
343
|
if (!workspaceCheckoutDurabilityIsCurrent(resolvedWorkspacePath)) return false;
|
|
241
|
-
if (head === null) return true;
|
|
242
344
|
const receipt = readHydratedWorkspaceReceipt(resolvedWorkspacePath);
|
|
243
|
-
|
|
345
|
+
if (!workspaceBasisRefsAreCurrent(resolvedWorkspacePath, receipt ?? { version: 3, mounts: [] })) return false;
|
|
346
|
+
if (head === null) return true;
|
|
347
|
+
if (!receipt) return false;
|
|
348
|
+
return mounts ? configuredWorkspaceHydrationBasisMounts(resolvedWorkspacePath, mounts).every(
|
|
349
|
+
(mount) => workspaceHydrationReceiptCoversMount(receipt, head, mount) || Boolean(mount.busy?.() && workspaceHydrationReceiptMountBasisHead(receipt, mount))
|
|
350
|
+
) : Boolean(receipt && receipt.mounts.every((mount) => mount.head === head));
|
|
351
|
+
}
|
|
352
|
+
const WORKSPACE_GIT_BASIS_REF_PREFIX = "refs/r5d/workspace-basis/";
|
|
353
|
+
const WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX = "refs/r5d/workspace-basis-staging/";
|
|
354
|
+
function workspaceBasisRefName(mountId) {
|
|
355
|
+
const sanitized = Buffer.from(mountId, "utf8").toString("base64url");
|
|
356
|
+
if (!sanitized) throw new Error(`Cannot derive a workspace basis ref for mount id: ${mountId}`);
|
|
357
|
+
return `${WORKSPACE_GIT_BASIS_REF_PREFIX}${sanitized}`;
|
|
358
|
+
}
|
|
359
|
+
function listWorkspaceRefs(workspacePath, prefix) {
|
|
360
|
+
return git(workspacePath, ["for-each-ref", "--format=%(refname)", prefix], `list ${prefix} refs`).split("\n").filter(Boolean).sort();
|
|
361
|
+
}
|
|
362
|
+
function desiredWorkspaceBasisRefs(receipt) {
|
|
363
|
+
const desired = /* @__PURE__ */ new Map();
|
|
364
|
+
for (const mount of receipt.mounts) {
|
|
365
|
+
const ref = workspaceBasisRefName(mount.id);
|
|
366
|
+
if (desired.has(ref)) throw new Error(`Workspace hydration mount ids collide at basis ref ${ref}`);
|
|
367
|
+
desired.set(ref, mount.head);
|
|
368
|
+
}
|
|
369
|
+
return desired;
|
|
370
|
+
}
|
|
371
|
+
function workspaceBasisRefsMatch(workspacePath, receipt) {
|
|
372
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
373
|
+
const actualRefs = listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX);
|
|
374
|
+
if (actualRefs.length !== desired.size) return false;
|
|
375
|
+
return actualRefs.every((ref) => desired.get(ref) === revParse(workspacePath, ref));
|
|
376
|
+
}
|
|
377
|
+
function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
|
|
378
|
+
return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
|
|
379
|
+
}
|
|
380
|
+
function repairWorkspaceBasisRefs(workspacePath, receipt) {
|
|
381
|
+
const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
|
|
382
|
+
if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
|
|
383
|
+
}
|
|
384
|
+
function updateWorkspaceBasisRefs(workspacePath, receipt) {
|
|
385
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
386
|
+
const transaction = ["start"];
|
|
387
|
+
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
388
|
+
for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
|
|
389
|
+
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
390
|
+
}
|
|
391
|
+
transaction.push("prepare", "commit", "");
|
|
392
|
+
const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
393
|
+
cwd: workspacePath,
|
|
394
|
+
stdin: Buffer.from(transaction.join("\n")),
|
|
395
|
+
stdout: "pipe",
|
|
396
|
+
stderr: "pipe",
|
|
397
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
398
|
+
});
|
|
399
|
+
if (updated.exitCode !== 0) {
|
|
400
|
+
throw new Error(`Synchronize workspace hydration basis refs: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
|
|
401
|
+
}
|
|
402
|
+
for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) {
|
|
403
|
+
git(workspacePath, ["update-ref", "-d", ref], "remove staged workspace hydration basis ref");
|
|
404
|
+
}
|
|
244
405
|
}
|
|
245
406
|
function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
407
|
+
for (const mount of receipt.mounts) {
|
|
408
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
|
|
409
|
+
throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
|
|
410
|
+
}
|
|
411
|
+
if (!tryGit(workspacePath, ["cat-file", "-e", `${mount.head}^{commit}`])) {
|
|
412
|
+
throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${mount.head}`);
|
|
413
|
+
}
|
|
251
414
|
}
|
|
252
415
|
readHydratedWorkspaceReceipt(workspacePath);
|
|
253
416
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
@@ -267,6 +430,12 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
267
430
|
throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
|
|
268
431
|
}
|
|
269
432
|
const temporaryPath = import_node_path.default.join(receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
433
|
+
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
434
|
+
desiredWorkspaceBasisRefs(receipt);
|
|
435
|
+
for (const mount of receipt.mounts) {
|
|
436
|
+
const stagedRef = `${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)}`;
|
|
437
|
+
git(workspacePath, ["update-ref", stagedRef, mount.head], "stage workspace hydration basis ref");
|
|
438
|
+
}
|
|
270
439
|
let descriptor;
|
|
271
440
|
try {
|
|
272
441
|
descriptor = import_node_fs.default.openSync(temporaryPath, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL, 384);
|
|
@@ -277,6 +446,7 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
277
446
|
descriptor = void 0;
|
|
278
447
|
import_node_fs.default.renameSync(temporaryPath, receiptPath);
|
|
279
448
|
fsyncDirectory(receiptDirectory);
|
|
449
|
+
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
280
450
|
} catch (error) {
|
|
281
451
|
if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
|
|
282
452
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
@@ -404,6 +574,16 @@ function workspaceRebaseInProgress(workspacePath) {
|
|
|
404
574
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
405
575
|
return import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-merge")) || import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-apply"));
|
|
406
576
|
}
|
|
577
|
+
function workspaceResolutionInProgress(workspacePath) {
|
|
578
|
+
return workspaceRebaseInProgress(workspacePath) || import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git", "MERGE_HEAD"));
|
|
579
|
+
}
|
|
580
|
+
function workspaceHasUnmergedEntries(workspacePath) {
|
|
581
|
+
const result = gitResult(workspacePath, ["ls-files", "-u", "-z"]);
|
|
582
|
+
if (result.exitCode !== 0) {
|
|
583
|
+
throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
584
|
+
}
|
|
585
|
+
return result.stdout.length > 0;
|
|
586
|
+
}
|
|
407
587
|
function clearWorkspaceCheckoutTree(workspacePath) {
|
|
408
588
|
for (const name of import_node_fs.default.readdirSync(workspacePath)) {
|
|
409
589
|
if (name === ".git") continue;
|
|
@@ -411,15 +591,18 @@ function clearWorkspaceCheckoutTree(workspacePath) {
|
|
|
411
591
|
}
|
|
412
592
|
fsyncDirectory(workspacePath);
|
|
413
593
|
}
|
|
414
|
-
function recoverWorkspaceCheckoutDurability(workspacePath) {
|
|
594
|
+
function recoverWorkspaceCheckoutDurability(workspacePath, preserveResolutionInProgress = false) {
|
|
415
595
|
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
416
596
|
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
417
597
|
const observedHead = revParse(workspacePath, "HEAD");
|
|
418
598
|
if (record?.state === "durable" && record.head === observedHead) return;
|
|
599
|
+
if (preserveResolutionInProgress && workspaceResolutionInProgress(workspacePath)) {
|
|
600
|
+
throw new Error("Workspace resolution is in progress; finish or abort it before synchronizing");
|
|
601
|
+
}
|
|
419
602
|
let targetHead = record?.state === "transition" ? record.head : observedHead;
|
|
420
603
|
if (record?.state === "transition" && observedHead && observedHead !== record.head) {
|
|
421
604
|
const hydrationReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
422
|
-
if (hydrationReceipt?.head === observedHead) {
|
|
605
|
+
if (hydrationReceipt?.legacyHead === observedHead || hydrationReceipt?.mounts.some((mount) => mount.head === observedHead)) {
|
|
423
606
|
targetHead = observedHead;
|
|
424
607
|
}
|
|
425
608
|
}
|
|
@@ -501,7 +684,7 @@ function parseHydrationTransactionManifest(transactionPath) {
|
|
|
501
684
|
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
502
685
|
}
|
|
503
686
|
const candidate = parsed;
|
|
504
|
-
if (candidate.version !== 2 || !Array.isArray(candidate.mounts)) {
|
|
687
|
+
if (candidate.version !== 2 && candidate.version !== 3 || !Array.isArray(candidate.mounts)) {
|
|
505
688
|
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
506
689
|
}
|
|
507
690
|
const parseReceipt = (rawReceipt) => {
|
|
@@ -533,7 +716,7 @@ function parseHydrationTransactionManifest(transactionPath) {
|
|
|
533
716
|
return mount;
|
|
534
717
|
});
|
|
535
718
|
return {
|
|
536
|
-
version:
|
|
719
|
+
version: 3,
|
|
537
720
|
targetReceipt,
|
|
538
721
|
receiptBefore,
|
|
539
722
|
mounts
|
|
@@ -626,7 +809,7 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
|
626
809
|
const snapshotRoot = import_node_path.default.join(stagingPath, "mounts");
|
|
627
810
|
import_node_fs.default.mkdirSync(snapshotRoot, { mode: 448 });
|
|
628
811
|
const manifest = {
|
|
629
|
-
version:
|
|
812
|
+
version: 3,
|
|
630
813
|
targetReceipt,
|
|
631
814
|
receiptBefore: readHydratedWorkspaceReceipt(workspacePath),
|
|
632
815
|
mounts: mounts.map((mount, index) => {
|
|
@@ -805,9 +988,22 @@ function configureWorkspaceRepository(input) {
|
|
|
805
988
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
806
989
|
git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
|
|
807
990
|
git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
|
|
808
|
-
git(
|
|
991
|
+
git(
|
|
992
|
+
input.workspacePath,
|
|
993
|
+
["config", "--local", "core.fsync", "committed,reference"],
|
|
994
|
+
"configure durable workspace commits and references"
|
|
995
|
+
);
|
|
809
996
|
git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
810
997
|
}
|
|
998
|
+
function configureExistingWorkspaceGitForRemediation(input) {
|
|
999
|
+
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1000
|
+
const workspaceStatus = lstatIfExists(workspacePath);
|
|
1001
|
+
const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
|
|
1002
|
+
if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
1003
|
+
throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
|
|
1004
|
+
}
|
|
1005
|
+
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1006
|
+
}
|
|
811
1007
|
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
812
1008
|
const result = gitResult(workspacePath, [
|
|
813
1009
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
|
|
@@ -869,7 +1065,7 @@ function ensureWorkspaceGitClone(input) {
|
|
|
869
1065
|
}
|
|
870
1066
|
}
|
|
871
1067
|
configureWorkspaceRepository({ ...input, workspacePath });
|
|
872
|
-
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
1068
|
+
recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
|
|
873
1069
|
const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
874
1070
|
const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
|
|
875
1071
|
if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
@@ -1019,31 +1215,40 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1019
1215
|
}
|
|
1020
1216
|
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1021
1217
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1218
|
+
const durabilityMounts = input.durabilityMounts ?? input.mounts;
|
|
1219
|
+
const receiptMounts = input.receiptMounts ?? durabilityMounts;
|
|
1220
|
+
const requiredMounts = input.requiredMounts ?? input.mounts;
|
|
1221
|
+
const candidateMounts = [
|
|
1222
|
+
...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
|
|
1223
|
+
];
|
|
1224
|
+
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1225
|
+
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1226
|
+
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1227
|
+
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1228
|
+
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1229
|
+
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1230
|
+
);
|
|
1231
|
+
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1031
1232
|
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1032
1233
|
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1033
1234
|
}
|
|
1034
1235
|
const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
|
|
1035
|
-
const targetReceipt = targetHead ?
|
|
1036
|
-
const manifest = beginHydrationTransaction(workspacePath,
|
|
1236
|
+
const targetReceipt = targetHead ? receiptWithMountsAtHead(readHydratedWorkspaceReceipt(workspacePath), targetHead, advancedDurabilityMounts, receiptMounts) : null;
|
|
1237
|
+
const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1037
1238
|
try {
|
|
1038
|
-
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath,
|
|
1039
|
-
|
|
1239
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
1240
|
+
const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
|
|
1241
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
|
|
1040
1242
|
throw new Error("Workspace hydration did not include every required mount");
|
|
1041
1243
|
}
|
|
1042
|
-
fsyncHydratedWorkspaceMounts(
|
|
1244
|
+
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts);
|
|
1043
1245
|
markHydrationTransactionDurable(workspacePath);
|
|
1044
1246
|
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1045
1247
|
removeHydrationTransaction(workspacePath);
|
|
1046
|
-
return
|
|
1248
|
+
return {
|
|
1249
|
+
hydratedMountIds: hydration.hydratedMountIds,
|
|
1250
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort()
|
|
1251
|
+
};
|
|
1047
1252
|
} catch (error) {
|
|
1048
1253
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1049
1254
|
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
@@ -1076,44 +1281,44 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1076
1281
|
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1077
1282
|
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
1078
1283
|
validateMounts(workspacePath, mounts);
|
|
1079
|
-
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
1284
|
+
recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
|
|
1080
1285
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1081
1286
|
const currentHead = revParse(workspacePath, "HEAD");
|
|
1082
|
-
if (!currentHead) return;
|
|
1083
1287
|
const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1288
|
+
repairWorkspaceBasisRefs(workspacePath, currentReceipt);
|
|
1289
|
+
if (!currentHead) return;
|
|
1084
1290
|
const outerHeadContainsMount = (mount) => tryGit(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1085
1291
|
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1086
1292
|
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1087
1293
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
1088
1294
|
);
|
|
1089
1295
|
if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
|
|
1090
|
-
const busyMountIds = options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
(mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
|
|
1296
|
+
const busyMountIds = new Set(options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1297
|
+
const recoveryMounts = uncoveredBasisMounts.filter(
|
|
1298
|
+
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
|
|
1299
|
+
);
|
|
1300
|
+
const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
|
|
1301
|
+
const hydrationTargets = idleBasislessMounts.filter(
|
|
1302
|
+
(mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
|
|
1098
1303
|
);
|
|
1099
1304
|
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1100
1305
|
workspacePath,
|
|
1101
1306
|
mounts: hydrationTargets,
|
|
1102
|
-
durabilityMounts:
|
|
1307
|
+
durabilityMounts: idleBasislessMounts,
|
|
1103
1308
|
receiptMounts: configuredBasisMounts,
|
|
1104
1309
|
requiredMounts: hydrationTargets,
|
|
1105
1310
|
ignoreBusy: options.ignoreBusy,
|
|
1106
1311
|
recordCurrentHead: true
|
|
1107
1312
|
});
|
|
1108
|
-
|
|
1109
|
-
throw new Error(
|
|
1110
|
-
`Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${hydration.skippedMountIds.join(", ")}`
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1313
|
+
void hydration;
|
|
1113
1314
|
}
|
|
1114
1315
|
function resetWorkspaceGit(input) {
|
|
1115
1316
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1116
1317
|
validateMounts(workspacePath, input.mounts);
|
|
1318
|
+
const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1319
|
+
if (busyResetMountIds.length > 0) {
|
|
1320
|
+
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
1321
|
+
}
|
|
1117
1322
|
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1118
1323
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1119
1324
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
@@ -1300,69 +1505,137 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1300
1505
|
const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
|
|
1301
1506
|
const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
|
|
1302
1507
|
validateMounts(workspacePath, input.mounts);
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
publishedHead: remoteHead2,
|
|
1314
|
-
rebaseCount: 0,
|
|
1315
|
-
diffSizeBytes: 0,
|
|
1316
|
-
affectedPaths: [],
|
|
1317
|
-
activeMountIds: [],
|
|
1318
|
-
skippedMountIds: input.mounts.map(({ id }) => id).sort()
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1508
|
+
const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
|
|
1509
|
+
const preserveResolutionInProgress = input.skipMountMirror === true;
|
|
1510
|
+
const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1511
|
+
assertWorkspaceRemediationAncestry({
|
|
1512
|
+
workspacePath,
|
|
1513
|
+
currentHead: initial.localHead,
|
|
1514
|
+
remoteHead: initial.remoteHead,
|
|
1515
|
+
requiredAncestorHeads
|
|
1516
|
+
});
|
|
1517
|
+
const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
|
|
1322
1518
|
const startingHead = initial.localHead;
|
|
1323
1519
|
const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1324
1520
|
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1325
|
-
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead
|
|
1521
|
+
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
|
|
1326
1522
|
for (const mount of input.mounts) {
|
|
1327
|
-
if (!mount.deleteWhenSourceMissing && mount.preserveLocalOnInitialOuterAbsence !== true && mount.preserveLocalOnHydrationBasisChange !== true &&
|
|
1523
|
+
if (!mount.deleteWhenSourceMissing && mount.preserveLocalOnInitialOuterAbsence !== true && mount.preserveLocalOnHydrationBasisChange !== true && workspaceHydrationReceiptMountBasisHead(receiptBeforeInitialHydration, mount) === null) {
|
|
1328
1524
|
deferredMountIds.add(mount.id);
|
|
1329
1525
|
}
|
|
1330
1526
|
}
|
|
1331
1527
|
}
|
|
1332
1528
|
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1333
|
-
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {}
|
|
1529
|
+
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1530
|
+
preserveResolutionInProgress,
|
|
1531
|
+
preserveStaleBases: true
|
|
1334
1532
|
});
|
|
1335
|
-
const receiptRequiredLiveMounts = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
1336
1533
|
const selected = activeMounts(input.mounts);
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1534
|
+
const projectionMutationTokens = new Map(
|
|
1535
|
+
input.mounts.flatMap((mount) => mount.mutationToken ? [[mount.id, mount.mutationToken()]] : [])
|
|
1536
|
+
);
|
|
1537
|
+
const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
|
|
1538
|
+
const receiptRequiredLiveMounts = selected.active;
|
|
1339
1539
|
const projectionBasisHead = revParse(workspacePath, "HEAD");
|
|
1340
1540
|
const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
);
|
|
1541
|
+
const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1542
|
+
const fastProjectionMounts = [];
|
|
1543
|
+
const mergedProjectionTrees = /* @__PURE__ */ new Map();
|
|
1544
|
+
const mergedProjectionMounts = [];
|
|
1545
|
+
const projectionSkippedIds = /* @__PURE__ */ new Set();
|
|
1546
|
+
let projectionWarning;
|
|
1547
|
+
const refreshCycleSkippedMounts = (mounts = input.mounts) => {
|
|
1548
|
+
for (const mount of mounts) {
|
|
1549
|
+
const projectionToken = projectionMutationTokens.get(mount.id);
|
|
1550
|
+
if (projectionToken !== void 0 && mount.mutationToken?.() !== projectionToken) cycleSkippedMountIds.add(mount.id);
|
|
1551
|
+
if (mount.busy?.()) cycleSkippedMountIds.add(mount.id);
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
const classifyMounts = (activeCandidates, additionalSkippedIds = []) => {
|
|
1555
|
+
refreshCycleSkippedMounts();
|
|
1556
|
+
const skippedIds = /* @__PURE__ */ new Set([...cycleSkippedMountIds, ...projectionSkippedIds, ...additionalSkippedIds]);
|
|
1557
|
+
return {
|
|
1558
|
+
activeMountIds: [...new Set(activeCandidates.filter(({ id }) => !skippedIds.has(id)).map(({ id }) => id))].sort(),
|
|
1559
|
+
skippedMountIds: [...skippedIds].sort()
|
|
1560
|
+
};
|
|
1561
|
+
};
|
|
1562
|
+
if (!input.skipMountMirror) {
|
|
1563
|
+
const mergeProjectionEnabled = process.env.R5D_WORKSPACE_MERGE_PROJECTION !== "0";
|
|
1564
|
+
const mergeSupport = mergeProjectionEnabled ? (0, import_workspace_merge_projection.workspaceMergeProjectionSupport)() : { supported: false, error: "Workspace merge projection is disabled by R5D_WORKSPACE_MERGE_PROJECTION=0" };
|
|
1565
|
+
for (const mount of [...selected.active, ...selected.tombstones]) {
|
|
1566
|
+
if (deferredMountIds.has(mount.id)) continue;
|
|
1567
|
+
const basisHead = workspaceHydrationReceiptMountBasisHead(projectionBasisReceipt, mount);
|
|
1568
|
+
if (projectionBasisHead === null || basisHead === projectionBasisHead) {
|
|
1569
|
+
fastProjectionMounts.push(mount);
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
if (basisHead === null) {
|
|
1573
|
+
deferredMountIds.add(mount.id);
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
if (!mergeSupport.supported) {
|
|
1577
|
+
projectionSkippedIds.add(mount.id);
|
|
1578
|
+
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
|
|
1582
|
+
workspacePath,
|
|
1583
|
+
mount: {
|
|
1584
|
+
id: mount.id,
|
|
1585
|
+
sourcePath: mount.sourcePath,
|
|
1586
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
1587
|
+
sourceMode: mount.sourceMode
|
|
1588
|
+
},
|
|
1589
|
+
basisHead,
|
|
1590
|
+
currentHead: projectionBasisHead,
|
|
1591
|
+
attemptId
|
|
1592
|
+
});
|
|
1593
|
+
if (merged.kind === "conflict") {
|
|
1594
|
+
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: projectionBasisHead });
|
|
1595
|
+
return {
|
|
1596
|
+
outcome: "conflict_blocked",
|
|
1597
|
+
startingHead,
|
|
1598
|
+
localHead: merged.oursCommit,
|
|
1599
|
+
remoteHead: initial.remoteHead,
|
|
1600
|
+
publishedHead: initial.remoteHead,
|
|
1601
|
+
rebaseCount: 0,
|
|
1602
|
+
diffSizeBytes: 0,
|
|
1603
|
+
affectedPaths: merged.conflictPaths,
|
|
1604
|
+
activeMountIds: [],
|
|
1605
|
+
skippedMountIds: input.mounts.map(({ id }) => id).sort(),
|
|
1606
|
+
conflictPaths: merged.conflictPaths,
|
|
1607
|
+
conflictSnapshotRefs: refs,
|
|
1608
|
+
conflictKind: "projection_merge",
|
|
1609
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1610
|
+
error: merged.error
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
1614
|
+
mergedProjectionMounts.push(mount);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
1618
|
+
`);
|
|
1344
1619
|
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1345
1620
|
const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
|
|
1346
1621
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1347
|
-
if (currentHeadBeforeHydration && workspaceHydrationReceiptMatchesMounts(
|
|
1348
|
-
readHydratedWorkspaceReceipt(workspacePath),
|
|
1349
|
-
currentHeadBeforeHydration,
|
|
1350
|
-
completionReceiptMounts
|
|
1351
|
-
)) {
|
|
1352
|
-
const lateBusyIds = selected.active.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1353
|
-
const lateBusy = new Set(lateBusyIds);
|
|
1354
|
-
return {
|
|
1355
|
-
activeMountIds: [...selected.active.filter(({ id }) => !lateBusy.has(id)), ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1356
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...lateBusyIds])].sort()
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
1622
|
const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
|
|
1623
|
+
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1360
1624
|
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1361
1625
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
1362
1626
|
);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1627
|
+
if (currentHeadBeforeHydration && completionReceiptMounts.every((mount) => cycleSkippedMountIds.has(mount.id) || mountIsAlreadyCovered(mount))) {
|
|
1628
|
+
return classifyMounts([...selected.active, ...selected.tombstones]);
|
|
1629
|
+
}
|
|
1630
|
+
const uncoveredCompletionMounts = selected.active.filter(
|
|
1631
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1632
|
+
);
|
|
1633
|
+
const uncoveredRequiredLiveMounts = receiptRequiredLiveMounts.filter(
|
|
1634
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1635
|
+
);
|
|
1636
|
+
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
|
|
1637
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1638
|
+
);
|
|
1366
1639
|
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1367
1640
|
workspacePath,
|
|
1368
1641
|
mounts: uncoveredCompletionMounts,
|
|
@@ -1371,22 +1644,32 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1371
1644
|
requiredMounts: uncoveredRequiredLiveMounts,
|
|
1372
1645
|
recordCurrentHead: true
|
|
1373
1646
|
});
|
|
1647
|
+
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
1648
|
+
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1374
1649
|
if (requireCurrentHeadReceipt) {
|
|
1375
1650
|
const currentHead = revParse(workspacePath, "HEAD");
|
|
1376
|
-
|
|
1651
|
+
const receipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1652
|
+
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
1653
|
+
const idleMounts = completionReceiptMounts.filter(
|
|
1654
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
|
|
1655
|
+
);
|
|
1656
|
+
if (currentHead && !idleMounts.every((mount) => workspaceHydrationReceiptCoversMount(receipt, currentHead, mount))) {
|
|
1377
1657
|
throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
|
|
1378
1658
|
}
|
|
1379
1659
|
}
|
|
1380
|
-
return
|
|
1381
|
-
activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
|
|
1382
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1383
|
-
};
|
|
1660
|
+
return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
|
|
1384
1661
|
};
|
|
1385
1662
|
let newlyStagedPaths = [];
|
|
1386
1663
|
const stageAndCommitWorkspace = () => {
|
|
1664
|
+
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1665
|
+
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1666
|
+
}
|
|
1387
1667
|
git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1388
1668
|
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1389
1669
|
if (newlyStagedPaths.length > 0) {
|
|
1670
|
+
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1671
|
+
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1672
|
+
}
|
|
1390
1673
|
git(
|
|
1391
1674
|
workspacePath,
|
|
1392
1675
|
[
|
|
@@ -1401,11 +1684,22 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1401
1684
|
if (!input.skipMountMirror) {
|
|
1402
1685
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1403
1686
|
try {
|
|
1404
|
-
|
|
1405
|
-
|
|
1687
|
+
const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
|
|
1688
|
+
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
1689
|
+
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
1690
|
+
mirrorMountsToWorkspace(workspacePath, fastActiveMounts);
|
|
1691
|
+
removeWorkspaceMounts(workspacePath, fastTombstones);
|
|
1692
|
+
for (const mount of mergedProjectionMounts) {
|
|
1693
|
+
(0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
|
|
1694
|
+
workspacePath,
|
|
1695
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
1696
|
+
resultTree: mergedProjectionTrees.get(mount.id)
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
|
|
1406
1700
|
restoreDeferredWorkspaceMountsFromHead(
|
|
1407
1701
|
workspacePath,
|
|
1408
|
-
input.mounts.filter(({ id }) =>
|
|
1702
|
+
input.mounts.filter(({ id }) => restoreFromHeadIds.has(id))
|
|
1409
1703
|
);
|
|
1410
1704
|
stageAndCommitWorkspace();
|
|
1411
1705
|
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
@@ -1419,9 +1713,15 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1419
1713
|
if (!input.skipMountMirror) {
|
|
1420
1714
|
const projectedHead = revParse(workspacePath, "HEAD");
|
|
1421
1715
|
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1422
|
-
const targetProjectionReceipt =
|
|
1423
|
-
|
|
1424
|
-
|
|
1716
|
+
const targetProjectionReceipt = receiptWithMountsAtHead(
|
|
1717
|
+
projectionBasisReceipt,
|
|
1718
|
+
projectedHead,
|
|
1719
|
+
fastProjectionMounts,
|
|
1720
|
+
allProjectionReceiptMounts
|
|
1721
|
+
);
|
|
1722
|
+
const currentProjectionReceipt = readHydratedWorkspaceReceipt(workspacePath);
|
|
1723
|
+
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !workspaceBasisRefsAreCurrent(workspacePath, targetProjectionReceipt)) {
|
|
1724
|
+
fsyncHydratedWorkspaceMounts(fastProjectionMounts);
|
|
1425
1725
|
updateHydratedWorkspaceReceipt(workspacePath, targetProjectionReceipt);
|
|
1426
1726
|
}
|
|
1427
1727
|
completeWorkspaceCheckoutTransition(workspacePath);
|
|
@@ -1430,9 +1730,16 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1430
1730
|
let rebaseCount = 0;
|
|
1431
1731
|
let updated = false;
|
|
1432
1732
|
for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
|
|
1733
|
+
assertWorkspaceRemediationAncestry({
|
|
1734
|
+
workspacePath,
|
|
1735
|
+
currentHead: revParse(workspacePath, "HEAD"),
|
|
1736
|
+
remoteHead,
|
|
1737
|
+
requiredAncestorHeads
|
|
1738
|
+
});
|
|
1433
1739
|
const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
1434
1740
|
if (reconciled.kind === "conflict") {
|
|
1435
1741
|
const localHead2 = revParse(workspacePath, "HEAD");
|
|
1742
|
+
const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
|
|
1436
1743
|
return {
|
|
1437
1744
|
outcome: "conflict_blocked",
|
|
1438
1745
|
startingHead,
|
|
@@ -1442,10 +1749,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1442
1749
|
rebaseCount: rebaseCount + 1,
|
|
1443
1750
|
diffSizeBytes: 0,
|
|
1444
1751
|
affectedPaths: [.../* @__PURE__ */ new Set([...newlyStagedPaths, ...reconciled.conflictPaths])].sort(),
|
|
1445
|
-
|
|
1446
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...deferredMountIds])].sort(),
|
|
1752
|
+
...classifiedMounts,
|
|
1447
1753
|
conflictPaths: reconciled.conflictPaths,
|
|
1448
1754
|
conflictSnapshotRefs: reconciled.refs,
|
|
1755
|
+
conflictKind: "integration_rebase",
|
|
1756
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1449
1757
|
error: reconciled.error
|
|
1450
1758
|
};
|
|
1451
1759
|
}
|
|
@@ -1454,6 +1762,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1454
1762
|
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
1455
1763
|
const localHead = revParse(workspacePath, "HEAD");
|
|
1456
1764
|
if (!localHead) {
|
|
1765
|
+
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
|
|
1457
1766
|
return {
|
|
1458
1767
|
outcome: "no_change",
|
|
1459
1768
|
startingHead,
|
|
@@ -1463,13 +1772,16 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1463
1772
|
rebaseCount,
|
|
1464
1773
|
diffSizeBytes: 0,
|
|
1465
1774
|
affectedPaths: [],
|
|
1466
|
-
|
|
1467
|
-
|
|
1775
|
+
...classifiedMounts,
|
|
1776
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1777
|
+
...projectionWarning ? { error: projectionWarning } : {}
|
|
1468
1778
|
};
|
|
1469
1779
|
}
|
|
1470
1780
|
const paths = changedPaths(workspacePath, remoteHead, localHead);
|
|
1471
|
-
const size = paths.length > 0 ? await diffSizeBytes(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
|
|
1781
|
+
const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
|
|
1782
|
+
input.assertStillAdmitted?.();
|
|
1472
1783
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
1784
|
+
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones]);
|
|
1473
1785
|
return {
|
|
1474
1786
|
outcome: "large_diff_blocked",
|
|
1475
1787
|
startingHead,
|
|
@@ -1479,17 +1791,19 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1479
1791
|
rebaseCount,
|
|
1480
1792
|
diffSizeBytes: size,
|
|
1481
1793
|
affectedPaths: paths,
|
|
1482
|
-
|
|
1483
|
-
|
|
1794
|
+
...classifiedMounts,
|
|
1795
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1484
1796
|
error: `Workspace diff exceeds the ${maxDiffBytes}-byte automatic publication limit`
|
|
1485
1797
|
};
|
|
1486
1798
|
}
|
|
1487
1799
|
if (localHead === remoteHead) {
|
|
1800
|
+
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1488
1801
|
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
1489
1802
|
await input.afterWorkspacePublished?.({
|
|
1490
1803
|
publishedHead: localHead,
|
|
1491
1804
|
activeMountIds: completedMounts.activeMountIds
|
|
1492
1805
|
});
|
|
1806
|
+
input.assertStillAdmitted?.();
|
|
1493
1807
|
return {
|
|
1494
1808
|
outcome: updated ? "updated" : "no_change",
|
|
1495
1809
|
startingHead,
|
|
@@ -1499,9 +1813,12 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1499
1813
|
rebaseCount,
|
|
1500
1814
|
diffSizeBytes: size,
|
|
1501
1815
|
affectedPaths: paths,
|
|
1502
|
-
...completedMounts
|
|
1816
|
+
...completedMounts,
|
|
1817
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1818
|
+
...projectionWarning ? { error: projectionWarning } : {}
|
|
1503
1819
|
};
|
|
1504
1820
|
}
|
|
1821
|
+
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1505
1822
|
const pushArgs = [
|
|
1506
1823
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(input.remoteUrl, input.credentialHelper, input.credentialUsername),
|
|
1507
1824
|
"push",
|
|
@@ -1511,12 +1828,14 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1511
1828
|
pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
|
|
1512
1829
|
const push = gitResult(workspacePath, pushArgs);
|
|
1513
1830
|
if (push.exitCode === 0) {
|
|
1831
|
+
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1514
1832
|
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1515
1833
|
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
1516
1834
|
await input.afterWorkspacePublished?.({
|
|
1517
1835
|
publishedHead: localHead,
|
|
1518
1836
|
activeMountIds: completedMounts.activeMountIds
|
|
1519
1837
|
});
|
|
1838
|
+
input.assertStillAdmitted?.();
|
|
1520
1839
|
return {
|
|
1521
1840
|
outcome: "pushed",
|
|
1522
1841
|
startingHead,
|
|
@@ -1526,7 +1845,9 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1526
1845
|
rebaseCount,
|
|
1527
1846
|
diffSizeBytes: size,
|
|
1528
1847
|
affectedPaths: paths,
|
|
1529
|
-
...completedMounts
|
|
1848
|
+
...completedMounts,
|
|
1849
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
1850
|
+
...projectionWarning ? { error: projectionWarning } : {}
|
|
1530
1851
|
};
|
|
1531
1852
|
}
|
|
1532
1853
|
if (!/(non-fast-forward|fetch first|rejected|failed to push some refs)/i.test(`${push.stderr}
|
|
@@ -1552,6 +1873,8 @@ const workspaceGitSyncTestHarness = {
|
|
|
1552
1873
|
WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
|
|
1553
1874
|
WORKSPACE_GIT_HYDRATED_RECEIPT,
|
|
1554
1875
|
WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
1876
|
+
WorkspaceRemediationAncestryError,
|
|
1877
|
+
configureExistingWorkspaceGitForRemediation,
|
|
1555
1878
|
ensureWorkspaceGitClone,
|
|
1556
1879
|
hydrateWorkspaceGitMounts,
|
|
1557
1880
|
recoverWorkspaceGitHydration,
|