@ricsam/r5d-worker 0.0.135 → 0.0.137
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 +7 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-git-sync.cjs +241 -96
- package/dist/cjs/workspace-hydration-ledger.cjs +28 -11
- package/dist/cjs/workspace-hydration-merge.cjs +17 -2
- package/dist/cjs/workspace-merge-projection.cjs +190 -29
- package/dist/mjs/main.mjs +7 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-git-sync.mjs +243 -97
- package/dist/mjs/workspace-hydration-ledger.mjs +27 -11
- package/dist/mjs/workspace-hydration-merge.mjs +17 -2
- package/dist/mjs/workspace-merge-projection.mjs +190 -29
- package/dist/types/runtime/adapter.d.ts +1 -0
- package/dist/types/runtime/client.d.ts +15 -0
- package/dist/types/runtime/daemon.d.ts +8 -0
- package/dist/types/runtime/executor.d.ts +30 -0
- package/dist/types/runtime/index.d.ts +5 -0
- package/dist/types/runtime/protocol.d.ts +372 -0
- package/dist/types/runtime/pty.d.ts +29 -0
- package/dist/types/runtime/storage.d.ts +21 -0
- package/dist/types/runtime/test-harness.d.ts +53 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +19 -5
- package/dist/types/workspace-git-sync.d.ts +25 -6
- package/dist/types/workspace-hydration-ledger.d.ts +51 -18
- package/dist/types/workspace-hydration-merge.d.ts +8 -0
- package/dist/types/workspace-merge-projection.d.ts +10 -1
- package/package.json +2 -2
package/dist/cjs/main.cjs
CHANGED
|
@@ -4301,6 +4301,8 @@ async function startWorker(options, projectRuntime = {
|
|
|
4301
4301
|
});
|
|
4302
4302
|
};
|
|
4303
4303
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
4304
|
+
const projectBranchMountFenced = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectConfigById.get(projectId)?.executionDisabled === true || hasProjectWorktree(branchPath) && (0, import_project_worktrees.projectWorktreeOperationInProgress)(branchPath) || canonicalWorkspaceMutationIsActive(activeWorkspaceMutationTargets());
|
|
4305
|
+
const projectBranchMountProcessLive = (projectId, branchName) => (0, import_workspace_automatic_sync_policy.projectBranchHasActiveWorkspaceTarget)(projectId, branchName, activeWorkspaceMutationTargets());
|
|
4304
4306
|
const inventoryConfiguredProjectCheckouts = () => {
|
|
4305
4307
|
const inventory = { projects: [], missingCheckouts: [] };
|
|
4306
4308
|
for (const project of projectConfigById.values()) {
|
|
@@ -4386,7 +4388,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
4386
4388
|
].filter((id) => typeof id === "string")
|
|
4387
4389
|
)
|
|
4388
4390
|
].join(", ") || "no live declared holder",
|
|
4389
|
-
|
|
4391
|
+
// A running job no longer pins its checkout: merge hydration folds
|
|
4392
|
+
// inbound changes into it. The plan mount below keeps the old fence
|
|
4393
|
+
// because its all-mode hydration is a plain mirror.
|
|
4394
|
+
busy: () => projectBranchMountFenced(project.projectId, branch.branchName, branchPath),
|
|
4395
|
+
processLive: () => projectBranchMountProcessLive(project.projectId, branch.branchName),
|
|
4390
4396
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
4391
4397
|
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
4392
4398
|
});
|
package/dist/cjs/package.json
CHANGED
|
@@ -36,6 +36,7 @@ __export(workspace_git_sync_exports, {
|
|
|
36
36
|
WORKSPACE_GIT_HYDRATION_RECOVERIES: () => WORKSPACE_GIT_HYDRATION_RECOVERIES,
|
|
37
37
|
WORKSPACE_GIT_HYDRATION_TRANSACTION: () => WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
38
38
|
WORKSPACE_MERGE_HYDRATION_SWITCH: () => WORKSPACE_MERGE_HYDRATION_SWITCH,
|
|
39
|
+
WORKSPACE_PROCESS_BUSY_FENCE_SWITCH: () => WORKSPACE_PROCESS_BUSY_FENCE_SWITCH,
|
|
39
40
|
WorkspaceHydrationRecoveryRequiredError: () => WorkspaceHydrationRecoveryRequiredError,
|
|
40
41
|
WorkspaceRemediationAncestryError: () => WorkspaceRemediationAncestryError,
|
|
41
42
|
configureExistingWorkspaceGitForRemediation: () => configureExistingWorkspaceGitForRemediation,
|
|
@@ -86,12 +87,20 @@ const WORKSPACE_MERGE_HYDRATION_SWITCH = "R5D_MERGE_HYDRATION";
|
|
|
86
87
|
function workspaceMergeHydrationEnabled() {
|
|
87
88
|
return process.env[WORKSPACE_MERGE_HYDRATION_SWITCH] !== "0";
|
|
88
89
|
}
|
|
90
|
+
const WORKSPACE_PROCESS_BUSY_FENCE_SWITCH = "R5D_PROCESS_BUSY_FENCE";
|
|
91
|
+
function workspaceProcessBusyFenceEnabled() {
|
|
92
|
+
return process.env[WORKSPACE_PROCESS_BUSY_FENCE_SWITCH] === "1";
|
|
93
|
+
}
|
|
94
|
+
function mountBusy(mount) {
|
|
95
|
+
if (mount.busy !== void 0 && mount.busy()) return true;
|
|
96
|
+
return workspaceProcessBusyFenceEnabled() && mount.processLive?.() === true;
|
|
97
|
+
}
|
|
89
98
|
const hydrationPreBlobLedgers = /* @__PURE__ */ new Map();
|
|
90
99
|
function hydrationPreBlobLedger(workspacePath) {
|
|
91
100
|
const key = import_node_path.default.resolve(workspacePath);
|
|
92
101
|
let ledger = hydrationPreBlobLedgers.get(key);
|
|
93
102
|
if (!ledger) {
|
|
94
|
-
ledger = new import_workspace_hydration_ledger.
|
|
103
|
+
ledger = new import_workspace_hydration_ledger.WorkspaceHydrationLedger();
|
|
95
104
|
hydrationPreBlobLedgers.set(key, ledger);
|
|
96
105
|
}
|
|
97
106
|
return ledger;
|
|
@@ -531,7 +540,7 @@ function workspaceGitHydrationHolds(workspacePath, mounts, tolerateIdleRecordedB
|
|
|
531
540
|
if (head === null) return true;
|
|
532
541
|
if (!receipt) return false;
|
|
533
542
|
return mounts ? configuredWorkspaceHydrationBasisMounts(resolvedWorkspacePath, mounts).every(
|
|
534
|
-
(mount) => workspaceHydrationReceiptCoversMount(receipt, head, mount) || Boolean((tolerateIdleRecordedBasis || mount
|
|
543
|
+
(mount) => workspaceHydrationReceiptCoversMount(receipt, head, mount) || Boolean((tolerateIdleRecordedBasis || mountBusy(mount)) && workspaceHydrationReceiptMountBasisHead(receipt, mount))
|
|
535
544
|
) : Boolean(receipt && receipt.mounts.every((mount) => tolerateIdleRecordedBasis || mount.head === head));
|
|
536
545
|
}
|
|
537
546
|
const WORKSPACE_GIT_BASIS_REF_PREFIX = "refs/r5d/workspace-basis/";
|
|
@@ -1492,6 +1501,12 @@ function resolveGitObjectsSync(workspacePath, names) {
|
|
|
1492
1501
|
});
|
|
1493
1502
|
return resolved;
|
|
1494
1503
|
}
|
|
1504
|
+
function storeWorkspaceBlobs(workspacePath, blobs) {
|
|
1505
|
+
for (const [objectId, content] of blobs) {
|
|
1506
|
+
const written = gitOutputSync(workspacePath, ["hash-object", "-w", "-t", "blob", "--no-filters", "--stdin"], "store hydration ledger blob", content).toString().trim();
|
|
1507
|
+
if (written !== objectId) throw new Error(`Hydration ledger blob ${objectId} was stored as ${written}`);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1495
1510
|
function readWorkspaceBlobs(workspacePath, objectIds) {
|
|
1496
1511
|
const blobs = /* @__PURE__ */ new Map();
|
|
1497
1512
|
const unique = [...new Set(objectIds)];
|
|
@@ -1983,7 +1998,7 @@ function activeMounts(mounts) {
|
|
|
1983
1998
|
const tombstones = [];
|
|
1984
1999
|
const skipped = [];
|
|
1985
2000
|
for (const mount of mounts) {
|
|
1986
|
-
if (mount
|
|
2001
|
+
if (mountBusy(mount)) skipped.push(mount);
|
|
1987
2002
|
else if (import_node_fs.default.existsSync(mount.sourcePath)) active.push(mount);
|
|
1988
2003
|
else if (mount.deleteWhenSourceMissing) tombstones.push(mount);
|
|
1989
2004
|
else skipped.push(mount);
|
|
@@ -2121,9 +2136,9 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2121
2136
|
const skippedMountIds = [];
|
|
2122
2137
|
const mergeSkips = [];
|
|
2123
2138
|
const rollbackScopes = /* @__PURE__ */ new Map();
|
|
2124
|
-
const
|
|
2139
|
+
const ledgerEntries = {};
|
|
2125
2140
|
for (const mount of mounts) {
|
|
2126
|
-
if (!options.ignoreBusy && mount
|
|
2141
|
+
if (!options.ignoreBusy && mountBusy(mount)) {
|
|
2127
2142
|
skippedMountIds.push(mount.id);
|
|
2128
2143
|
continue;
|
|
2129
2144
|
}
|
|
@@ -2170,7 +2185,20 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2170
2185
|
continue;
|
|
2171
2186
|
}
|
|
2172
2187
|
hydratedMountIds.push(mount.id);
|
|
2173
|
-
|
|
2188
|
+
storeWorkspaceBlobs(workspacePath, mergePlan.decision.blobsToStore);
|
|
2189
|
+
const left = /* @__PURE__ */ new Map();
|
|
2190
|
+
for (const entry of journal.entries) left.set(entry.relativePath, entry.after);
|
|
2191
|
+
const entries = {};
|
|
2192
|
+
for (const [relativePath, preBlob] of mergePlan.decision.preBlobs) {
|
|
2193
|
+
const postBlob = mergePlan.decision.postBlobs.get(relativePath) ?? null;
|
|
2194
|
+
let after = left.get(relativePath);
|
|
2195
|
+
if (after === void 0) {
|
|
2196
|
+
const current = (0, import_working_tree_mirror.inspectWorkingTreePath)(mount.sourcePath, relativePath);
|
|
2197
|
+
after = current.kind === "entry" ? current.stat : null;
|
|
2198
|
+
}
|
|
2199
|
+
entries[relativePath] = { preBlob, postBlob, stat: after && after.isFile() ? (0, import_working_tree_mirror.projectedFileEntry)(after) : null };
|
|
2200
|
+
}
|
|
2201
|
+
ledgerEntries[mount.id] = entries;
|
|
2174
2202
|
continue;
|
|
2175
2203
|
}
|
|
2176
2204
|
const sourceRoot = workspaceMountOuterPath(workspacePath, mount);
|
|
@@ -2218,7 +2246,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2218
2246
|
});
|
|
2219
2247
|
hydratedMountIds.push(mount.id);
|
|
2220
2248
|
}
|
|
2221
|
-
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort(), mergeSkips, rollbackScopes,
|
|
2249
|
+
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort(), mergeSkips, rollbackScopes, ledgerEntries };
|
|
2222
2250
|
}
|
|
2223
2251
|
async function selectMountsWithUnchangedOuterSubtree(workspacePath, receipt, head, mounts) {
|
|
2224
2252
|
const candidates = mounts.flatMap((mount) => {
|
|
@@ -2259,7 +2287,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2259
2287
|
const targetHead = input.recordCurrentHead ? await revParseAsync(workspacePath, "HEAD") : null;
|
|
2260
2288
|
const receiptBeforeTransaction = targetHead ? await readHydratedWorkspaceReceiptAsync(workspacePath) : null;
|
|
2261
2289
|
const computeSelection = () => {
|
|
2262
|
-
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount
|
|
2290
|
+
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mountBusy(mount)).map(({ id }) => id));
|
|
2263
2291
|
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
2264
2292
|
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
2265
2293
|
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
@@ -2278,7 +2306,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2278
2306
|
};
|
|
2279
2307
|
const noopCandidate = computeSelection();
|
|
2280
2308
|
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && await hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
2281
|
-
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds, mergeSkips: [] };
|
|
2309
|
+
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds, mergeSkips: [], hydratedEntryPaths: {} };
|
|
2282
2310
|
}
|
|
2283
2311
|
const hooks = input.hydrationHooks;
|
|
2284
2312
|
const context = {
|
|
@@ -2298,6 +2326,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2298
2326
|
const basis = input.mergeBases?.get(mount.id);
|
|
2299
2327
|
if (basis) mergeBases[mount.id] = basis;
|
|
2300
2328
|
}
|
|
2329
|
+
const liveBefore = new Set(hydrationMounts.filter((mount) => mergeBases[mount.id] && mount.processLive?.()).map(({ id }) => id));
|
|
2301
2330
|
const hydration = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_transaction", {
|
|
2302
2331
|
workspacePath,
|
|
2303
2332
|
hydrationMounts: hydrationMounts.map(workspaceGitMountData),
|
|
@@ -2306,16 +2335,27 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2306
2335
|
...Object.keys(mergeBases).length > 0 ? { mergeBases } : {}
|
|
2307
2336
|
});
|
|
2308
2337
|
const ledger = hydrationPreBlobLedger(workspacePath);
|
|
2338
|
+
const hydratedEntryPaths = {};
|
|
2309
2339
|
for (const mount of hydrationMounts) {
|
|
2310
2340
|
if (!hydration.hydratedMountIds.includes(mount.id)) continue;
|
|
2311
|
-
const
|
|
2312
|
-
if (
|
|
2313
|
-
|
|
2341
|
+
const entries = mergeBases[mount.id] ? hydration.hydratedEntries[mount.id] : void 0;
|
|
2342
|
+
if (!entries) {
|
|
2343
|
+
ledger.clearMount(mount);
|
|
2344
|
+
continue;
|
|
2345
|
+
}
|
|
2346
|
+
const processLiveAcrossHydration = liveBefore.has(mount.id) || mount.processLive?.() === true;
|
|
2347
|
+
hydratedEntryPaths[mount.id] = Object.keys(entries).sort();
|
|
2348
|
+
ledger.record(
|
|
2349
|
+
mount,
|
|
2350
|
+
Object.fromEntries(Object.entries(entries).map(([relativePath, entry]) => [relativePath, { ...entry, processLiveAcrossHydration }])),
|
|
2351
|
+
{ replacePreBlobFor: input.ledgerMergedPaths?.get(mount.id) ?? [] }
|
|
2352
|
+
);
|
|
2314
2353
|
}
|
|
2315
2354
|
return {
|
|
2316
2355
|
hydratedMountIds: hydration.hydratedMountIds,
|
|
2317
2356
|
skippedMountIds: [.../* @__PURE__ */ new Set([...selection.skippedMountIds, ...hydration.skippedMounts.map(({ mountId }) => mountId)])].sort(),
|
|
2318
|
-
mergeSkips: hydration.skippedMounts
|
|
2357
|
+
mergeSkips: hydration.skippedMounts,
|
|
2358
|
+
hydratedEntryPaths
|
|
2319
2359
|
};
|
|
2320
2360
|
} finally {
|
|
2321
2361
|
releaseHold?.();
|
|
@@ -2380,7 +2420,7 @@ function runHydrationTransactionJob(input) {
|
|
|
2380
2420
|
markHydrationTransactionDurable(workspacePath);
|
|
2381
2421
|
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
2382
2422
|
removeHydrationTransaction(workspacePath);
|
|
2383
|
-
return { hydratedMountIds: hydration.hydratedMountIds, skippedMounts,
|
|
2423
|
+
return { hydratedMountIds: hydration.hydratedMountIds, skippedMounts, hydratedEntries: hydration.ledgerEntries };
|
|
2384
2424
|
} catch (error) {
|
|
2385
2425
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
2386
2426
|
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
@@ -2434,7 +2474,7 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
2434
2474
|
const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
|
|
2435
2475
|
const gitDirectory = import_node_path.default.join(resolvedWorkspacePath, ".git");
|
|
2436
2476
|
if (!import_node_fs.default.existsSync(gitDirectory)) {
|
|
2437
|
-
const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mount
|
|
2477
|
+
const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mountBusy(mount)).map(({ id }) => id).sort();
|
|
2438
2478
|
const skipped = new Set(skippedMountIds);
|
|
2439
2479
|
const selected = mounts.filter(({ id }) => !skipped.has(id));
|
|
2440
2480
|
const releaseHold = options.hydrationHooks?.holdMounts(
|
|
@@ -2479,7 +2519,7 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
2479
2519
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
2480
2520
|
);
|
|
2481
2521
|
if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
|
|
2482
|
-
const busyMountIds = new Set(options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount
|
|
2522
|
+
const busyMountIds = new Set(options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mountBusy(mount)).map(({ id }) => id));
|
|
2483
2523
|
const recoveryMounts = uncoveredBasisMounts.filter(
|
|
2484
2524
|
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id) && workspaceHydrationReceiptMountBasisHead(preservedBases, mount) !== workspaceHydrationReceiptMountBasisHead(currentReceipt, mount)
|
|
2485
2525
|
);
|
|
@@ -2507,7 +2547,7 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
2507
2547
|
async function resetWorkspaceGit(input) {
|
|
2508
2548
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
2509
2549
|
validateMounts(workspacePath, input.mounts);
|
|
2510
|
-
const busyResetMountIds = input.mounts.filter((mount) => mount
|
|
2550
|
+
const busyResetMountIds = input.mounts.filter((mount) => mountBusy(mount)).map(({ id }) => id);
|
|
2511
2551
|
if (busyResetMountIds.length > 0) {
|
|
2512
2552
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
2513
2553
|
}
|
|
@@ -2539,7 +2579,7 @@ async function resetWorkspaceGit(input) {
|
|
|
2539
2579
|
const selected = activeMounts(input.mounts);
|
|
2540
2580
|
const requiredLiveMountsBeforeHydration = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
|
|
2541
2581
|
const bootstrapMounts = selected.skipped.filter((mount) => {
|
|
2542
|
-
if (mount.deleteWhenSourceMissing || import_node_fs.default.existsSync(mount.sourcePath) || mount
|
|
2582
|
+
if (mount.deleteWhenSourceMissing || import_node_fs.default.existsSync(mount.sourcePath) || mountBusy(mount)) return false;
|
|
2543
2583
|
const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
2544
2584
|
return import_node_fs.default.existsSync(outerPath);
|
|
2545
2585
|
});
|
|
@@ -2860,10 +2900,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2860
2900
|
const projectedMountBases = /* @__PURE__ */ new Map();
|
|
2861
2901
|
const mountSkipReasons = {};
|
|
2862
2902
|
const staleRewriteLedger = hydrationPreBlobLedger(workspacePath);
|
|
2863
|
-
const
|
|
2864
|
-
const
|
|
2865
|
-
return
|
|
2903
|
+
const hydrationLedgerFor = (mount) => {
|
|
2904
|
+
const entries = staleRewriteLedger.entries(mount);
|
|
2905
|
+
return entries.size > 0 ? Object.fromEntries(entries) : void 0;
|
|
2866
2906
|
};
|
|
2907
|
+
const forceHydrationMountIds = /* @__PURE__ */ new Set();
|
|
2908
|
+
const ledgerMergedPathsByMount = /* @__PURE__ */ new Map();
|
|
2867
2909
|
const recordStaleRewrite = (mount, paths) => {
|
|
2868
2910
|
projectionSkippedIds.add(mount.id);
|
|
2869
2911
|
mountSkipReasons[mount.id] = { reason: "stale_rewrite_detected", paths: [...paths] };
|
|
@@ -2877,7 +2919,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2877
2919
|
for (const mount of mounts) {
|
|
2878
2920
|
const projectionToken = projectionMutationTokens.get(mount.id);
|
|
2879
2921
|
if (projectionToken !== void 0 && mount.mutationToken?.() !== projectionToken) cycleSkippedMountIds.add(mount.id);
|
|
2880
|
-
if (mount
|
|
2922
|
+
if (mountBusy(mount)) cycleSkippedMountIds.add(mount.id);
|
|
2881
2923
|
}
|
|
2882
2924
|
};
|
|
2883
2925
|
const classifyMounts = (activeCandidates, additionalSkippedIds = []) => {
|
|
@@ -2900,16 +2942,128 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2900
2942
|
};
|
|
2901
2943
|
};
|
|
2902
2944
|
const mountProjectionIsCurrent = (mount) => mount.lastProjectedMutationToken !== void 0 && !mount.deleteWhenSourceMissing && mount.preserveLocalOnHydrationBasisChange !== true && projectionBasisHead !== null && workspaceHydrationReceiptMountBasisHead(projectionBasisReceipt, mount) === projectionBasisHead && projectionMutationTokens.get(mount.id) === mount.lastProjectedMutationToken && mount.mutationToken?.() === mount.lastProjectedMutationToken;
|
|
2945
|
+
const mergeProjectMount = async (mount, basisHead, currentHead) => {
|
|
2946
|
+
const releaseMergeHold = input.hydrationHooks?.holdMounts([mount.id], `merge projection of mount ${mount.id}`);
|
|
2947
|
+
let merged;
|
|
2948
|
+
try {
|
|
2949
|
+
const hydrationLedger = hydrationLedgerFor(mount);
|
|
2950
|
+
merged = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_merge", {
|
|
2951
|
+
workspacePath,
|
|
2952
|
+
mount: {
|
|
2953
|
+
id: mount.id,
|
|
2954
|
+
sourcePath: mount.sourcePath,
|
|
2955
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2956
|
+
sourceMode: mount.sourceMode
|
|
2957
|
+
},
|
|
2958
|
+
basisHead,
|
|
2959
|
+
currentHead,
|
|
2960
|
+
attemptId,
|
|
2961
|
+
...hydrationLedger ? { hydrationLedger } : {}
|
|
2962
|
+
});
|
|
2963
|
+
} finally {
|
|
2964
|
+
releaseMergeHold?.();
|
|
2965
|
+
}
|
|
2966
|
+
if (merged.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, merged.staleRewriteCleared);
|
|
2967
|
+
if (merged.kind === "stale_rewrite") {
|
|
2968
|
+
recordStaleRewrite(mount, merged.paths);
|
|
2969
|
+
return { kind: "skipped" };
|
|
2970
|
+
}
|
|
2971
|
+
if (merged.kind === "conflict" && mount.processLive?.()) {
|
|
2972
|
+
projectionSkippedIds.add(mount.id);
|
|
2973
|
+
mountSkipReasons[mount.id] = { reason: "projection_conflict_deferred", paths: merged.conflictPaths };
|
|
2974
|
+
const holders = mount.describeHolders?.();
|
|
2975
|
+
process.stderr.write(
|
|
2976
|
+
`[r5d-worker] projection of mount ${mount.id} conflicts with the workspace head at ${merged.conflictPaths.join(", ")}; deferred while a process runs in the checkout${holders ? ` (${holders})` : ""}, reported once the mount is idle
|
|
2977
|
+
`
|
|
2978
|
+
);
|
|
2979
|
+
return { kind: "skipped" };
|
|
2980
|
+
}
|
|
2981
|
+
mergedProjectionBases.set(mount.id, {
|
|
2982
|
+
projectedCommit: merged.oursCommit,
|
|
2983
|
+
projectedFiles: merged.projectedFiles,
|
|
2984
|
+
readAtMs: merged.readAtMs
|
|
2985
|
+
});
|
|
2986
|
+
if (merged.kind === "conflict") {
|
|
2987
|
+
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: currentHead });
|
|
2988
|
+
return {
|
|
2989
|
+
kind: "conflict",
|
|
2990
|
+
result: {
|
|
2991
|
+
outcome: "conflict_blocked",
|
|
2992
|
+
startingHead,
|
|
2993
|
+
localHead: merged.oursCommit,
|
|
2994
|
+
remoteHead: initial.remoteHead,
|
|
2995
|
+
publishedHead: initial.remoteHead,
|
|
2996
|
+
rebaseCount: 0,
|
|
2997
|
+
diffSizeBytes: 0,
|
|
2998
|
+
affectedPaths: merged.conflictPaths,
|
|
2999
|
+
activeMountIds: [],
|
|
3000
|
+
skippedMountIds: input.mounts.map(({ id }) => id).sort(),
|
|
3001
|
+
conflictPaths: merged.conflictPaths,
|
|
3002
|
+
conflictSnapshotRefs: refs,
|
|
3003
|
+
conflictKind: "projection_merge",
|
|
3004
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
3005
|
+
error: merged.error
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
3010
|
+
mergedProjectionMounts.push(mount);
|
|
3011
|
+
if (merged.ledgerMergedPaths.length > 0) {
|
|
3012
|
+
forceHydrationMountIds.add(mount.id);
|
|
3013
|
+
ledgerMergedPathsByMount.set(mount.id, merged.ledgerMergedPaths);
|
|
3014
|
+
}
|
|
3015
|
+
return { kind: "projected" };
|
|
3016
|
+
};
|
|
2903
3017
|
if (!input.skipMountMirror) {
|
|
2904
3018
|
const mergeProjectionEnabled = process.env.R5D_WORKSPACE_MERGE_PROJECTION !== "0";
|
|
2905
3019
|
const mergeSupport = mergeProjectionEnabled ? (0, import_workspace_merge_projection.workspaceMergeProjectionSupport)() : { supported: false, error: "Workspace merge projection is disabled by R5D_WORKSPACE_MERGE_PROJECTION=0" };
|
|
3020
|
+
const activeProjectionIds = new Set(selected.active.map(({ id }) => id));
|
|
2906
3021
|
for (const mount of [...selected.active, ...selected.tombstones]) {
|
|
2907
3022
|
if (deferredMountIds.has(mount.id)) continue;
|
|
2908
3023
|
const basisHead = workspaceHydrationReceiptMountBasisHead(projectionBasisReceipt, mount);
|
|
2909
|
-
if (projectionBasisHead === null
|
|
3024
|
+
if (projectionBasisHead === null) {
|
|
2910
3025
|
fastProjectionMounts.push(mount);
|
|
2911
3026
|
continue;
|
|
2912
3027
|
}
|
|
3028
|
+
if (basisHead === projectionBasisHead) {
|
|
3029
|
+
const hydrationLedger = activeProjectionIds.has(mount.id) ? hydrationLedgerFor(mount) : void 0;
|
|
3030
|
+
if (!hydrationLedger || !Object.values(hydrationLedger).some(({ processLiveAcrossHydration }) => processLiveAcrossHydration) || !mergeSupport.supported) {
|
|
3031
|
+
fastProjectionMounts.push(mount);
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
3035
|
+
cycleSkippedMountIds.add(mount.id);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
const releaseCheckHold = input.hydrationHooks?.holdMounts([mount.id], `hydration ledger check of mount ${mount.id}`);
|
|
3039
|
+
let checked;
|
|
3040
|
+
try {
|
|
3041
|
+
checked = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_mirror", {
|
|
3042
|
+
workspacePath,
|
|
3043
|
+
mount: workspaceGitMountData(mount),
|
|
3044
|
+
hydrationLedger,
|
|
3045
|
+
checkOnly: true
|
|
3046
|
+
});
|
|
3047
|
+
} finally {
|
|
3048
|
+
releaseCheckHold?.();
|
|
3049
|
+
}
|
|
3050
|
+
if (checked.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, checked.staleRewriteCleared);
|
|
3051
|
+
if (checked.staleRewritePaths.length > 0) {
|
|
3052
|
+
recordStaleRewrite(mount, checked.staleRewritePaths);
|
|
3053
|
+
continue;
|
|
3054
|
+
}
|
|
3055
|
+
if (checked.ledgerMergePaths.length === 0) {
|
|
3056
|
+
fastProjectionMounts.push(mount);
|
|
3057
|
+
continue;
|
|
3058
|
+
}
|
|
3059
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
3060
|
+
cycleSkippedMountIds.add(mount.id);
|
|
3061
|
+
continue;
|
|
3062
|
+
}
|
|
3063
|
+
const outcome2 = await mergeProjectMount(mount, projectionBasisHead, projectionBasisHead);
|
|
3064
|
+
if (outcome2.kind === "conflict") return outcome2.result;
|
|
3065
|
+
continue;
|
|
3066
|
+
}
|
|
2913
3067
|
if (basisHead === null) {
|
|
2914
3068
|
deferredMountIds.add(mount.id);
|
|
2915
3069
|
continue;
|
|
@@ -2919,62 +3073,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2919
3073
|
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
2920
3074
|
continue;
|
|
2921
3075
|
}
|
|
2922
|
-
if (cycleSkippedMountIds.has(mount.id) || mount
|
|
3076
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
2923
3077
|
cycleSkippedMountIds.add(mount.id);
|
|
2924
3078
|
continue;
|
|
2925
3079
|
}
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
try {
|
|
2929
|
-
const staleRewriteBlobs = staleRewriteBlobsFor(mount);
|
|
2930
|
-
merged = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_merge", {
|
|
2931
|
-
workspacePath,
|
|
2932
|
-
mount: {
|
|
2933
|
-
id: mount.id,
|
|
2934
|
-
sourcePath: mount.sourcePath,
|
|
2935
|
-
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2936
|
-
sourceMode: mount.sourceMode
|
|
2937
|
-
},
|
|
2938
|
-
basisHead,
|
|
2939
|
-
currentHead: projectionBasisHead,
|
|
2940
|
-
attemptId,
|
|
2941
|
-
...staleRewriteBlobs ? { staleRewriteBlobs } : {}
|
|
2942
|
-
});
|
|
2943
|
-
} finally {
|
|
2944
|
-
releaseMergeHold?.();
|
|
2945
|
-
}
|
|
2946
|
-
if (merged.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, merged.staleRewriteCleared);
|
|
2947
|
-
if (merged.kind === "stale_rewrite") {
|
|
2948
|
-
recordStaleRewrite(mount, merged.paths);
|
|
2949
|
-
continue;
|
|
2950
|
-
}
|
|
2951
|
-
mergedProjectionBases.set(mount.id, {
|
|
2952
|
-
projectedCommit: merged.oursCommit,
|
|
2953
|
-
projectedFiles: merged.projectedFiles,
|
|
2954
|
-
readAtMs: merged.readAtMs
|
|
2955
|
-
});
|
|
2956
|
-
if (merged.kind === "conflict") {
|
|
2957
|
-
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: projectionBasisHead });
|
|
2958
|
-
return {
|
|
2959
|
-
outcome: "conflict_blocked",
|
|
2960
|
-
startingHead,
|
|
2961
|
-
localHead: merged.oursCommit,
|
|
2962
|
-
remoteHead: initial.remoteHead,
|
|
2963
|
-
publishedHead: initial.remoteHead,
|
|
2964
|
-
rebaseCount: 0,
|
|
2965
|
-
diffSizeBytes: 0,
|
|
2966
|
-
affectedPaths: merged.conflictPaths,
|
|
2967
|
-
activeMountIds: [],
|
|
2968
|
-
skippedMountIds: input.mounts.map(({ id }) => id).sort(),
|
|
2969
|
-
conflictPaths: merged.conflictPaths,
|
|
2970
|
-
conflictSnapshotRefs: refs,
|
|
2971
|
-
conflictKind: "projection_merge",
|
|
2972
|
-
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
2973
|
-
error: merged.error
|
|
2974
|
-
};
|
|
2975
|
-
}
|
|
2976
|
-
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
2977
|
-
mergedProjectionMounts.push(mount);
|
|
3080
|
+
const outcome = await mergeProjectMount(mount, basisHead, projectionBasisHead);
|
|
3081
|
+
if (outcome.kind === "conflict") return outcome.result;
|
|
2978
3082
|
}
|
|
2979
3083
|
}
|
|
2980
3084
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
@@ -2984,7 +3088,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2984
3088
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
2985
3089
|
const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
2986
3090
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
2987
|
-
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
3091
|
+
const mountIsAlreadyCovered = (mount) => !forceHydrationMountIds.has(mount.id) && Boolean(
|
|
2988
3092
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
2989
3093
|
);
|
|
2990
3094
|
if (currentHeadBeforeHydration && completionReceiptMounts.every((mount) => cycleSkippedMountIds.has(mount.id) || mountIsAlreadyCovered(mount))) {
|
|
@@ -3005,6 +3109,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3005
3109
|
currentHeadBeforeHydration,
|
|
3006
3110
|
uncoveredCompletionMounts
|
|
3007
3111
|
) : /* @__PURE__ */ new Set();
|
|
3112
|
+
for (const id of forceHydrationMountIds) unchangedMountIds.delete(id);
|
|
3008
3113
|
const hydrationMounts = uncoveredCompletionMounts.filter(({ id }) => !unchangedMountIds.has(id));
|
|
3009
3114
|
const mergeBases = /* @__PURE__ */ new Map();
|
|
3010
3115
|
if (workspaceMergeHydrationEnabled()) {
|
|
@@ -3022,9 +3127,19 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3022
3127
|
requiredMounts: uncoveredRequiredLiveMounts.filter(({ id }) => !unchangedMountIds.has(id)),
|
|
3023
3128
|
recordCurrentHead: true,
|
|
3024
3129
|
hydrationHooks: input.hydrationHooks,
|
|
3025
|
-
...mergeBases.size > 0 ? { mergeBases } : {}
|
|
3130
|
+
...mergeBases.size > 0 ? { mergeBases } : {},
|
|
3131
|
+
...ledgerMergedPathsByMount.size > 0 ? { ledgerMergedPaths: ledgerMergedPathsByMount } : {}
|
|
3026
3132
|
});
|
|
3027
3133
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
3134
|
+
for (const mount of hydrationMounts) {
|
|
3135
|
+
const mergedPaths = ledgerMergedPathsByMount.get(mount.id);
|
|
3136
|
+
if (!mergedPaths || !hydration.hydratedMountIds.includes(mount.id)) continue;
|
|
3137
|
+
const written = new Set(hydration.hydratedEntryPaths[mount.id] ?? []);
|
|
3138
|
+
const settled = mergedPaths.filter((relativePath) => !written.has(relativePath));
|
|
3139
|
+
if (settled.length > 0) staleRewriteLedger.forget(mount, settled);
|
|
3140
|
+
ledgerMergedPathsByMount.delete(mount.id);
|
|
3141
|
+
forceHydrationMountIds.delete(mount.id);
|
|
3142
|
+
}
|
|
3028
3143
|
if (currentHeadBeforeHydration) {
|
|
3029
3144
|
for (const id of hydration.hydratedMountIds) {
|
|
3030
3145
|
if (mergeBases.has(id)) projectedMountBases.set(id, { projectedCommit: currentHeadBeforeHydration, projectedFiles: null });
|
|
@@ -3049,7 +3164,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3049
3164
|
const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
3050
3165
|
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
3051
3166
|
const idleMounts = completionReceiptMounts.filter(
|
|
3052
|
-
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount
|
|
3167
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mountBusy(mount) && !projectionSkippedIds.has(mount.id)
|
|
3053
3168
|
);
|
|
3054
3169
|
if (currentHead && !idleMounts.every((mount) => workspaceHydrationReceiptCoversMount(receipt, currentHead, mount))) {
|
|
3055
3170
|
throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
|
|
@@ -3091,7 +3206,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3091
3206
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
3092
3207
|
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
3093
3208
|
for (const mount of fastActiveMounts) {
|
|
3094
|
-
if (cycleSkippedMountIds.has(mount.id) || mount
|
|
3209
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
3095
3210
|
cycleSkippedMountIds.add(mount.id);
|
|
3096
3211
|
continue;
|
|
3097
3212
|
}
|
|
@@ -3101,22 +3216,33 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3101
3216
|
}
|
|
3102
3217
|
const releaseMirrorHold = input.hydrationHooks?.holdMounts([mount.id], `projection of mount ${mount.id}`);
|
|
3103
3218
|
let staleRewritePaths;
|
|
3219
|
+
let ledgerMergePaths;
|
|
3104
3220
|
try {
|
|
3105
|
-
const
|
|
3221
|
+
const hydrationLedger = hydrationLedgerFor(mount);
|
|
3106
3222
|
const mirrored = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_mirror", {
|
|
3107
3223
|
workspacePath,
|
|
3108
3224
|
mount: workspaceGitMountData(mount),
|
|
3109
|
-
...
|
|
3225
|
+
...hydrationLedger ? { hydrationLedger } : {}
|
|
3110
3226
|
});
|
|
3111
3227
|
staleRewritePaths = mirrored.staleRewritePaths;
|
|
3228
|
+
ledgerMergePaths = mirrored.ledgerMergePaths;
|
|
3112
3229
|
if (mirrored.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, mirrored.staleRewriteCleared);
|
|
3113
|
-
if (staleRewritePaths.length === 0) {
|
|
3230
|
+
if (staleRewritePaths.length === 0 && ledgerMergePaths.length === 0) {
|
|
3114
3231
|
projectedGitlinks.push({ mount, gitlinks: mirrored.gitlinks });
|
|
3115
3232
|
projectedMountFiles.set(mount.id, { files: mirrored.projectedFiles, readAtMs: mirrored.readAtMs });
|
|
3116
3233
|
}
|
|
3117
3234
|
} finally {
|
|
3118
3235
|
releaseMirrorHold?.();
|
|
3119
3236
|
}
|
|
3237
|
+
if (ledgerMergePaths.length > 0) {
|
|
3238
|
+
projectionSkippedIds.add(mount.id);
|
|
3239
|
+
mountSkipReasons[mount.id] = { reason: "hydration_target_changed", paths: ledgerMergePaths };
|
|
3240
|
+
process.stderr.write(
|
|
3241
|
+
`[r5d-worker] projection of mount ${mount.id} skipped: a running process rewrote ${(0, import_workspace_hydration_merge.describeHydrationSkipPaths)(mount.sourcePath, ledgerMergePaths)} while the cycle prepared the mirror; the next cycle merges it
|
|
3242
|
+
`
|
|
3243
|
+
);
|
|
3244
|
+
continue;
|
|
3245
|
+
}
|
|
3120
3246
|
if (staleRewritePaths.length > 0) {
|
|
3121
3247
|
recordStaleRewrite(mount, staleRewritePaths);
|
|
3122
3248
|
continue;
|
|
@@ -3338,42 +3464,59 @@ ${push.stdout}`)) {
|
|
|
3338
3464
|
}
|
|
3339
3465
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
3340
3466
|
}
|
|
3341
|
-
function
|
|
3467
|
+
function inspectHydrationLedger(workspacePath, mount, hydrationLedger) {
|
|
3342
3468
|
const outerRoot = workspaceMountOuterPath(workspacePath, mount);
|
|
3343
3469
|
const stale = [];
|
|
3470
|
+
const merge = [];
|
|
3344
3471
|
const cleared = [];
|
|
3345
|
-
for (const [relativePath,
|
|
3346
|
-
const algorithm = (0, import_git_blob_hash.gitObjectHashAlgorithmFor)(
|
|
3472
|
+
for (const [relativePath, entry] of Object.entries(hydrationLedger)) {
|
|
3473
|
+
const algorithm = (0, import_git_blob_hash.gitObjectHashAlgorithmFor)(entry.preBlob);
|
|
3347
3474
|
if (!algorithm) continue;
|
|
3348
3475
|
const source = (0, import_working_tree_mirror.inspectWorkingTreePath)(mount.sourcePath, relativePath);
|
|
3349
3476
|
if (source.kind !== "entry" || !source.stat.isFile()) {
|
|
3350
|
-
cleared.push(relativePath);
|
|
3477
|
+
if (entry.postBlob !== null) cleared.push(relativePath);
|
|
3351
3478
|
continue;
|
|
3352
3479
|
}
|
|
3480
|
+
if (entry.stat && (0, import_working_tree_mirror.projectedFileEntryMatches)(entry.stat, source.stat)) continue;
|
|
3353
3481
|
const content = import_node_fs.default.readFileSync(source.absolutePath);
|
|
3482
|
+
const hash = (0, import_git_blob_hash.gitBlobHash)(content, algorithm);
|
|
3483
|
+
if (hash === entry.postBlob) continue;
|
|
3354
3484
|
const outer = (0, import_working_tree_mirror.inspectWorkingTreePath)(outerRoot, relativePath);
|
|
3355
3485
|
const outerEqual = outer.kind === "entry" && outer.stat.isFile() && import_node_fs.default.readFileSync(outer.absolutePath).equals(content);
|
|
3356
|
-
if (
|
|
3357
|
-
|
|
3486
|
+
if (outerEqual) {
|
|
3487
|
+
cleared.push(relativePath);
|
|
3358
3488
|
continue;
|
|
3359
3489
|
}
|
|
3360
|
-
if (
|
|
3490
|
+
if (entry.processLiveAcrossHydration) {
|
|
3491
|
+
merge.push(relativePath);
|
|
3492
|
+
continue;
|
|
3493
|
+
}
|
|
3494
|
+
if (hash !== entry.preBlob) {
|
|
3361
3495
|
cleared.push(relativePath);
|
|
3362
3496
|
continue;
|
|
3363
3497
|
}
|
|
3364
3498
|
stale.push(relativePath);
|
|
3365
3499
|
}
|
|
3366
|
-
return { stale: stale.sort(), cleared: cleared.sort() };
|
|
3500
|
+
return { stale: stale.sort(), merge: merge.sort(), cleared: cleared.sort() };
|
|
3367
3501
|
}
|
|
3368
3502
|
function runProjectionMirrorJob(input) {
|
|
3369
3503
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
3370
3504
|
let staleRewriteCleared = [];
|
|
3371
|
-
if (input.
|
|
3372
|
-
const
|
|
3373
|
-
staleRewriteCleared =
|
|
3374
|
-
if (
|
|
3375
|
-
return {
|
|
3505
|
+
if (input.hydrationLedger) {
|
|
3506
|
+
const inspected = inspectHydrationLedger(workspacePath, input.mount, input.hydrationLedger);
|
|
3507
|
+
staleRewriteCleared = inspected.cleared;
|
|
3508
|
+
if (inspected.stale.length > 0 || inspected.merge.length > 0 || input.checkOnly) {
|
|
3509
|
+
return {
|
|
3510
|
+
gitlinks: [],
|
|
3511
|
+
projectedFiles: {},
|
|
3512
|
+
readAtMs: Date.now(),
|
|
3513
|
+
staleRewritePaths: inspected.stale,
|
|
3514
|
+
ledgerMergePaths: inspected.merge,
|
|
3515
|
+
staleRewriteCleared
|
|
3516
|
+
};
|
|
3376
3517
|
}
|
|
3518
|
+
} else if (input.checkOnly) {
|
|
3519
|
+
return { gitlinks: [], projectedFiles: {}, readAtMs: Date.now(), staleRewritePaths: [], ledgerMergePaths: [], staleRewriteCleared };
|
|
3377
3520
|
}
|
|
3378
3521
|
const projectedFiles = /* @__PURE__ */ new Map();
|
|
3379
3522
|
const projected = mirrorMountsToWorkspace(workspacePath, [input.mount], projectedFiles);
|
|
@@ -3382,6 +3525,7 @@ function runProjectionMirrorJob(input) {
|
|
|
3382
3525
|
projectedFiles: Object.fromEntries(projectedFiles),
|
|
3383
3526
|
readAtMs: Date.now(),
|
|
3384
3527
|
staleRewritePaths: [],
|
|
3528
|
+
ledgerMergePaths: [],
|
|
3385
3529
|
staleRewriteCleared
|
|
3386
3530
|
};
|
|
3387
3531
|
}
|
|
@@ -3422,6 +3566,7 @@ const workspaceGitSyncTestHarness = {
|
|
|
3422
3566
|
WORKSPACE_GIT_HYDRATION_RECOVERIES,
|
|
3423
3567
|
WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
3424
3568
|
WORKSPACE_MERGE_HYDRATION_SWITCH,
|
|
3569
|
+
WORKSPACE_PROCESS_BUSY_FENCE_SWITCH,
|
|
3425
3570
|
WorkspaceHydrationRecoveryRequiredError,
|
|
3426
3571
|
WorkspaceRemediationAncestryError,
|
|
3427
3572
|
configureExistingWorkspaceGitForRemediation,
|