@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
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
fsyncWorkingTreePaths,
|
|
14
14
|
inspectWorkingTree,
|
|
15
15
|
inspectWorkingTreePath,
|
|
16
|
+
projectedFileEntry,
|
|
17
|
+
projectedFileEntryMatches,
|
|
16
18
|
mirrorWorkingTree,
|
|
17
19
|
normalizeWorkingTreeRelativePath,
|
|
18
20
|
planPreparedWorkingTreeMirror,
|
|
@@ -29,7 +31,7 @@ import {
|
|
|
29
31
|
describeHydrationSkipPaths,
|
|
30
32
|
parseWorkspaceSubtreeChanges
|
|
31
33
|
} from "./workspace-hydration-merge.mjs";
|
|
32
|
-
import {
|
|
34
|
+
import { WorkspaceHydrationLedger } from "./workspace-hydration-ledger.mjs";
|
|
33
35
|
import { assertManagedDirectoryPath } from "./workspace-mount-boundary.mjs";
|
|
34
36
|
const WORKSPACE_GIT_BRANCH = "main";
|
|
35
37
|
const MAX_WORKSPACE_GIT_DIFF_BYTES = 5 * 1024 * 1024;
|
|
@@ -44,12 +46,20 @@ const WORKSPACE_MERGE_HYDRATION_SWITCH = "R5D_MERGE_HYDRATION";
|
|
|
44
46
|
function workspaceMergeHydrationEnabled() {
|
|
45
47
|
return process.env[WORKSPACE_MERGE_HYDRATION_SWITCH] !== "0";
|
|
46
48
|
}
|
|
49
|
+
const WORKSPACE_PROCESS_BUSY_FENCE_SWITCH = "R5D_PROCESS_BUSY_FENCE";
|
|
50
|
+
function workspaceProcessBusyFenceEnabled() {
|
|
51
|
+
return process.env[WORKSPACE_PROCESS_BUSY_FENCE_SWITCH] === "1";
|
|
52
|
+
}
|
|
53
|
+
function mountBusy(mount) {
|
|
54
|
+
if (mount.busy !== void 0 && mount.busy()) return true;
|
|
55
|
+
return workspaceProcessBusyFenceEnabled() && mount.processLive?.() === true;
|
|
56
|
+
}
|
|
47
57
|
const hydrationPreBlobLedgers = /* @__PURE__ */ new Map();
|
|
48
58
|
function hydrationPreBlobLedger(workspacePath) {
|
|
49
59
|
const key = path.resolve(workspacePath);
|
|
50
60
|
let ledger = hydrationPreBlobLedgers.get(key);
|
|
51
61
|
if (!ledger) {
|
|
52
|
-
ledger = new
|
|
62
|
+
ledger = new WorkspaceHydrationLedger();
|
|
53
63
|
hydrationPreBlobLedgers.set(key, ledger);
|
|
54
64
|
}
|
|
55
65
|
return ledger;
|
|
@@ -489,7 +499,7 @@ function workspaceGitHydrationHolds(workspacePath, mounts, tolerateIdleRecordedB
|
|
|
489
499
|
if (head === null) return true;
|
|
490
500
|
if (!receipt) return false;
|
|
491
501
|
return mounts ? configuredWorkspaceHydrationBasisMounts(resolvedWorkspacePath, mounts).every(
|
|
492
|
-
(mount) => workspaceHydrationReceiptCoversMount(receipt, head, mount) || Boolean((tolerateIdleRecordedBasis || mount
|
|
502
|
+
(mount) => workspaceHydrationReceiptCoversMount(receipt, head, mount) || Boolean((tolerateIdleRecordedBasis || mountBusy(mount)) && workspaceHydrationReceiptMountBasisHead(receipt, mount))
|
|
493
503
|
) : Boolean(receipt && receipt.mounts.every((mount) => tolerateIdleRecordedBasis || mount.head === head));
|
|
494
504
|
}
|
|
495
505
|
const WORKSPACE_GIT_BASIS_REF_PREFIX = "refs/r5d/workspace-basis/";
|
|
@@ -1450,6 +1460,12 @@ function resolveGitObjectsSync(workspacePath, names) {
|
|
|
1450
1460
|
});
|
|
1451
1461
|
return resolved;
|
|
1452
1462
|
}
|
|
1463
|
+
function storeWorkspaceBlobs(workspacePath, blobs) {
|
|
1464
|
+
for (const [objectId, content] of blobs) {
|
|
1465
|
+
const written = gitOutputSync(workspacePath, ["hash-object", "-w", "-t", "blob", "--no-filters", "--stdin"], "store hydration ledger blob", content).toString().trim();
|
|
1466
|
+
if (written !== objectId) throw new Error(`Hydration ledger blob ${objectId} was stored as ${written}`);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1453
1469
|
function readWorkspaceBlobs(workspacePath, objectIds) {
|
|
1454
1470
|
const blobs = /* @__PURE__ */ new Map();
|
|
1455
1471
|
const unique = [...new Set(objectIds)];
|
|
@@ -1941,7 +1957,7 @@ function activeMounts(mounts) {
|
|
|
1941
1957
|
const tombstones = [];
|
|
1942
1958
|
const skipped = [];
|
|
1943
1959
|
for (const mount of mounts) {
|
|
1944
|
-
if (mount
|
|
1960
|
+
if (mountBusy(mount)) skipped.push(mount);
|
|
1945
1961
|
else if (fs.existsSync(mount.sourcePath)) active.push(mount);
|
|
1946
1962
|
else if (mount.deleteWhenSourceMissing) tombstones.push(mount);
|
|
1947
1963
|
else skipped.push(mount);
|
|
@@ -2079,9 +2095,9 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2079
2095
|
const skippedMountIds = [];
|
|
2080
2096
|
const mergeSkips = [];
|
|
2081
2097
|
const rollbackScopes = /* @__PURE__ */ new Map();
|
|
2082
|
-
const
|
|
2098
|
+
const ledgerEntries = {};
|
|
2083
2099
|
for (const mount of mounts) {
|
|
2084
|
-
if (!options.ignoreBusy && mount
|
|
2100
|
+
if (!options.ignoreBusy && mountBusy(mount)) {
|
|
2085
2101
|
skippedMountIds.push(mount.id);
|
|
2086
2102
|
continue;
|
|
2087
2103
|
}
|
|
@@ -2128,7 +2144,20 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2128
2144
|
continue;
|
|
2129
2145
|
}
|
|
2130
2146
|
hydratedMountIds.push(mount.id);
|
|
2131
|
-
|
|
2147
|
+
storeWorkspaceBlobs(workspacePath, mergePlan.decision.blobsToStore);
|
|
2148
|
+
const left = /* @__PURE__ */ new Map();
|
|
2149
|
+
for (const entry of journal.entries) left.set(entry.relativePath, entry.after);
|
|
2150
|
+
const entries = {};
|
|
2151
|
+
for (const [relativePath, preBlob] of mergePlan.decision.preBlobs) {
|
|
2152
|
+
const postBlob = mergePlan.decision.postBlobs.get(relativePath) ?? null;
|
|
2153
|
+
let after = left.get(relativePath);
|
|
2154
|
+
if (after === void 0) {
|
|
2155
|
+
const current = inspectWorkingTreePath(mount.sourcePath, relativePath);
|
|
2156
|
+
after = current.kind === "entry" ? current.stat : null;
|
|
2157
|
+
}
|
|
2158
|
+
entries[relativePath] = { preBlob, postBlob, stat: after && after.isFile() ? projectedFileEntry(after) : null };
|
|
2159
|
+
}
|
|
2160
|
+
ledgerEntries[mount.id] = entries;
|
|
2132
2161
|
continue;
|
|
2133
2162
|
}
|
|
2134
2163
|
const sourceRoot = workspaceMountOuterPath(workspacePath, mount);
|
|
@@ -2176,7 +2205,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
2176
2205
|
});
|
|
2177
2206
|
hydratedMountIds.push(mount.id);
|
|
2178
2207
|
}
|
|
2179
|
-
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort(), mergeSkips, rollbackScopes,
|
|
2208
|
+
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort(), mergeSkips, rollbackScopes, ledgerEntries };
|
|
2180
2209
|
}
|
|
2181
2210
|
async function selectMountsWithUnchangedOuterSubtree(workspacePath, receipt, head, mounts) {
|
|
2182
2211
|
const candidates = mounts.flatMap((mount) => {
|
|
@@ -2217,7 +2246,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2217
2246
|
const targetHead = input.recordCurrentHead ? await revParseAsync(workspacePath, "HEAD") : null;
|
|
2218
2247
|
const receiptBeforeTransaction = targetHead ? await readHydratedWorkspaceReceiptAsync(workspacePath) : null;
|
|
2219
2248
|
const computeSelection = () => {
|
|
2220
|
-
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount
|
|
2249
|
+
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mountBusy(mount)).map(({ id }) => id));
|
|
2221
2250
|
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
2222
2251
|
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
2223
2252
|
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
@@ -2236,7 +2265,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2236
2265
|
};
|
|
2237
2266
|
const noopCandidate = computeSelection();
|
|
2238
2267
|
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)) {
|
|
2239
|
-
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds, mergeSkips: [] };
|
|
2268
|
+
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds, mergeSkips: [], hydratedEntryPaths: {} };
|
|
2240
2269
|
}
|
|
2241
2270
|
const hooks = input.hydrationHooks;
|
|
2242
2271
|
const context = {
|
|
@@ -2256,6 +2285,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2256
2285
|
const basis = input.mergeBases?.get(mount.id);
|
|
2257
2286
|
if (basis) mergeBases[mount.id] = basis;
|
|
2258
2287
|
}
|
|
2288
|
+
const liveBefore = new Set(hydrationMounts.filter((mount) => mergeBases[mount.id] && mount.processLive?.()).map(({ id }) => id));
|
|
2259
2289
|
const hydration = await workspaceFilesystemExecutor().run("hydration_transaction", {
|
|
2260
2290
|
workspacePath,
|
|
2261
2291
|
hydrationMounts: hydrationMounts.map(workspaceGitMountData),
|
|
@@ -2264,16 +2294,27 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
2264
2294
|
...Object.keys(mergeBases).length > 0 ? { mergeBases } : {}
|
|
2265
2295
|
});
|
|
2266
2296
|
const ledger = hydrationPreBlobLedger(workspacePath);
|
|
2297
|
+
const hydratedEntryPaths = {};
|
|
2267
2298
|
for (const mount of hydrationMounts) {
|
|
2268
2299
|
if (!hydration.hydratedMountIds.includes(mount.id)) continue;
|
|
2269
|
-
const
|
|
2270
|
-
if (
|
|
2271
|
-
|
|
2300
|
+
const entries = mergeBases[mount.id] ? hydration.hydratedEntries[mount.id] : void 0;
|
|
2301
|
+
if (!entries) {
|
|
2302
|
+
ledger.clearMount(mount);
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
const processLiveAcrossHydration = liveBefore.has(mount.id) || mount.processLive?.() === true;
|
|
2306
|
+
hydratedEntryPaths[mount.id] = Object.keys(entries).sort();
|
|
2307
|
+
ledger.record(
|
|
2308
|
+
mount,
|
|
2309
|
+
Object.fromEntries(Object.entries(entries).map(([relativePath, entry]) => [relativePath, { ...entry, processLiveAcrossHydration }])),
|
|
2310
|
+
{ replacePreBlobFor: input.ledgerMergedPaths?.get(mount.id) ?? [] }
|
|
2311
|
+
);
|
|
2272
2312
|
}
|
|
2273
2313
|
return {
|
|
2274
2314
|
hydratedMountIds: hydration.hydratedMountIds,
|
|
2275
2315
|
skippedMountIds: [.../* @__PURE__ */ new Set([...selection.skippedMountIds, ...hydration.skippedMounts.map(({ mountId }) => mountId)])].sort(),
|
|
2276
|
-
mergeSkips: hydration.skippedMounts
|
|
2316
|
+
mergeSkips: hydration.skippedMounts,
|
|
2317
|
+
hydratedEntryPaths
|
|
2277
2318
|
};
|
|
2278
2319
|
} finally {
|
|
2279
2320
|
releaseHold?.();
|
|
@@ -2338,7 +2379,7 @@ function runHydrationTransactionJob(input) {
|
|
|
2338
2379
|
markHydrationTransactionDurable(workspacePath);
|
|
2339
2380
|
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
2340
2381
|
removeHydrationTransaction(workspacePath);
|
|
2341
|
-
return { hydratedMountIds: hydration.hydratedMountIds, skippedMounts,
|
|
2382
|
+
return { hydratedMountIds: hydration.hydratedMountIds, skippedMounts, hydratedEntries: hydration.ledgerEntries };
|
|
2342
2383
|
} catch (error) {
|
|
2343
2384
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
2344
2385
|
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
@@ -2392,7 +2433,7 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
2392
2433
|
const resolvedWorkspacePath = path.resolve(workspacePath);
|
|
2393
2434
|
const gitDirectory = path.join(resolvedWorkspacePath, ".git");
|
|
2394
2435
|
if (!fs.existsSync(gitDirectory)) {
|
|
2395
|
-
const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mount
|
|
2436
|
+
const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mountBusy(mount)).map(({ id }) => id).sort();
|
|
2396
2437
|
const skipped = new Set(skippedMountIds);
|
|
2397
2438
|
const selected = mounts.filter(({ id }) => !skipped.has(id));
|
|
2398
2439
|
const releaseHold = options.hydrationHooks?.holdMounts(
|
|
@@ -2437,7 +2478,7 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
2437
2478
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
2438
2479
|
);
|
|
2439
2480
|
if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
|
|
2440
|
-
const busyMountIds = new Set(options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount
|
|
2481
|
+
const busyMountIds = new Set(options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mountBusy(mount)).map(({ id }) => id));
|
|
2441
2482
|
const recoveryMounts = uncoveredBasisMounts.filter(
|
|
2442
2483
|
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id) && workspaceHydrationReceiptMountBasisHead(preservedBases, mount) !== workspaceHydrationReceiptMountBasisHead(currentReceipt, mount)
|
|
2443
2484
|
);
|
|
@@ -2465,7 +2506,7 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
2465
2506
|
async function resetWorkspaceGit(input) {
|
|
2466
2507
|
const workspacePath = path.resolve(input.workspacePath);
|
|
2467
2508
|
validateMounts(workspacePath, input.mounts);
|
|
2468
|
-
const busyResetMountIds = input.mounts.filter((mount) => mount
|
|
2509
|
+
const busyResetMountIds = input.mounts.filter((mount) => mountBusy(mount)).map(({ id }) => id);
|
|
2469
2510
|
if (busyResetMountIds.length > 0) {
|
|
2470
2511
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
2471
2512
|
}
|
|
@@ -2497,7 +2538,7 @@ async function resetWorkspaceGit(input) {
|
|
|
2497
2538
|
const selected = activeMounts(input.mounts);
|
|
2498
2539
|
const requiredLiveMountsBeforeHydration = input.mounts.filter(({ sourcePath }) => fs.existsSync(sourcePath));
|
|
2499
2540
|
const bootstrapMounts = selected.skipped.filter((mount) => {
|
|
2500
|
-
if (mount.deleteWhenSourceMissing || fs.existsSync(mount.sourcePath) || mount
|
|
2541
|
+
if (mount.deleteWhenSourceMissing || fs.existsSync(mount.sourcePath) || mountBusy(mount)) return false;
|
|
2501
2542
|
const outerPath = path.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
2502
2543
|
return fs.existsSync(outerPath);
|
|
2503
2544
|
});
|
|
@@ -2818,10 +2859,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2818
2859
|
const projectedMountBases = /* @__PURE__ */ new Map();
|
|
2819
2860
|
const mountSkipReasons = {};
|
|
2820
2861
|
const staleRewriteLedger = hydrationPreBlobLedger(workspacePath);
|
|
2821
|
-
const
|
|
2822
|
-
const
|
|
2823
|
-
return
|
|
2862
|
+
const hydrationLedgerFor = (mount) => {
|
|
2863
|
+
const entries = staleRewriteLedger.entries(mount);
|
|
2864
|
+
return entries.size > 0 ? Object.fromEntries(entries) : void 0;
|
|
2824
2865
|
};
|
|
2866
|
+
const forceHydrationMountIds = /* @__PURE__ */ new Set();
|
|
2867
|
+
const ledgerMergedPathsByMount = /* @__PURE__ */ new Map();
|
|
2825
2868
|
const recordStaleRewrite = (mount, paths) => {
|
|
2826
2869
|
projectionSkippedIds.add(mount.id);
|
|
2827
2870
|
mountSkipReasons[mount.id] = { reason: "stale_rewrite_detected", paths: [...paths] };
|
|
@@ -2835,7 +2878,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2835
2878
|
for (const mount of mounts) {
|
|
2836
2879
|
const projectionToken = projectionMutationTokens.get(mount.id);
|
|
2837
2880
|
if (projectionToken !== void 0 && mount.mutationToken?.() !== projectionToken) cycleSkippedMountIds.add(mount.id);
|
|
2838
|
-
if (mount
|
|
2881
|
+
if (mountBusy(mount)) cycleSkippedMountIds.add(mount.id);
|
|
2839
2882
|
}
|
|
2840
2883
|
};
|
|
2841
2884
|
const classifyMounts = (activeCandidates, additionalSkippedIds = []) => {
|
|
@@ -2858,16 +2901,128 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2858
2901
|
};
|
|
2859
2902
|
};
|
|
2860
2903
|
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;
|
|
2904
|
+
const mergeProjectMount = async (mount, basisHead, currentHead) => {
|
|
2905
|
+
const releaseMergeHold = input.hydrationHooks?.holdMounts([mount.id], `merge projection of mount ${mount.id}`);
|
|
2906
|
+
let merged;
|
|
2907
|
+
try {
|
|
2908
|
+
const hydrationLedger = hydrationLedgerFor(mount);
|
|
2909
|
+
merged = await workspaceFilesystemExecutor().run("projection_merge", {
|
|
2910
|
+
workspacePath,
|
|
2911
|
+
mount: {
|
|
2912
|
+
id: mount.id,
|
|
2913
|
+
sourcePath: mount.sourcePath,
|
|
2914
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2915
|
+
sourceMode: mount.sourceMode
|
|
2916
|
+
},
|
|
2917
|
+
basisHead,
|
|
2918
|
+
currentHead,
|
|
2919
|
+
attemptId,
|
|
2920
|
+
...hydrationLedger ? { hydrationLedger } : {}
|
|
2921
|
+
});
|
|
2922
|
+
} finally {
|
|
2923
|
+
releaseMergeHold?.();
|
|
2924
|
+
}
|
|
2925
|
+
if (merged.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, merged.staleRewriteCleared);
|
|
2926
|
+
if (merged.kind === "stale_rewrite") {
|
|
2927
|
+
recordStaleRewrite(mount, merged.paths);
|
|
2928
|
+
return { kind: "skipped" };
|
|
2929
|
+
}
|
|
2930
|
+
if (merged.kind === "conflict" && mount.processLive?.()) {
|
|
2931
|
+
projectionSkippedIds.add(mount.id);
|
|
2932
|
+
mountSkipReasons[mount.id] = { reason: "projection_conflict_deferred", paths: merged.conflictPaths };
|
|
2933
|
+
const holders = mount.describeHolders?.();
|
|
2934
|
+
process.stderr.write(
|
|
2935
|
+
`[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
|
|
2936
|
+
`
|
|
2937
|
+
);
|
|
2938
|
+
return { kind: "skipped" };
|
|
2939
|
+
}
|
|
2940
|
+
mergedProjectionBases.set(mount.id, {
|
|
2941
|
+
projectedCommit: merged.oursCommit,
|
|
2942
|
+
projectedFiles: merged.projectedFiles,
|
|
2943
|
+
readAtMs: merged.readAtMs
|
|
2944
|
+
});
|
|
2945
|
+
if (merged.kind === "conflict") {
|
|
2946
|
+
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: currentHead });
|
|
2947
|
+
return {
|
|
2948
|
+
kind: "conflict",
|
|
2949
|
+
result: {
|
|
2950
|
+
outcome: "conflict_blocked",
|
|
2951
|
+
startingHead,
|
|
2952
|
+
localHead: merged.oursCommit,
|
|
2953
|
+
remoteHead: initial.remoteHead,
|
|
2954
|
+
publishedHead: initial.remoteHead,
|
|
2955
|
+
rebaseCount: 0,
|
|
2956
|
+
diffSizeBytes: 0,
|
|
2957
|
+
affectedPaths: merged.conflictPaths,
|
|
2958
|
+
activeMountIds: [],
|
|
2959
|
+
skippedMountIds: input.mounts.map(({ id }) => id).sort(),
|
|
2960
|
+
conflictPaths: merged.conflictPaths,
|
|
2961
|
+
conflictSnapshotRefs: refs,
|
|
2962
|
+
conflictKind: "projection_merge",
|
|
2963
|
+
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
2964
|
+
error: merged.error
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
2969
|
+
mergedProjectionMounts.push(mount);
|
|
2970
|
+
if (merged.ledgerMergedPaths.length > 0) {
|
|
2971
|
+
forceHydrationMountIds.add(mount.id);
|
|
2972
|
+
ledgerMergedPathsByMount.set(mount.id, merged.ledgerMergedPaths);
|
|
2973
|
+
}
|
|
2974
|
+
return { kind: "projected" };
|
|
2975
|
+
};
|
|
2861
2976
|
if (!input.skipMountMirror) {
|
|
2862
2977
|
const mergeProjectionEnabled = process.env.R5D_WORKSPACE_MERGE_PROJECTION !== "0";
|
|
2863
2978
|
const mergeSupport = mergeProjectionEnabled ? workspaceMergeProjectionSupport() : { supported: false, error: "Workspace merge projection is disabled by R5D_WORKSPACE_MERGE_PROJECTION=0" };
|
|
2979
|
+
const activeProjectionIds = new Set(selected.active.map(({ id }) => id));
|
|
2864
2980
|
for (const mount of [...selected.active, ...selected.tombstones]) {
|
|
2865
2981
|
if (deferredMountIds.has(mount.id)) continue;
|
|
2866
2982
|
const basisHead = workspaceHydrationReceiptMountBasisHead(projectionBasisReceipt, mount);
|
|
2867
|
-
if (projectionBasisHead === null
|
|
2983
|
+
if (projectionBasisHead === null) {
|
|
2868
2984
|
fastProjectionMounts.push(mount);
|
|
2869
2985
|
continue;
|
|
2870
2986
|
}
|
|
2987
|
+
if (basisHead === projectionBasisHead) {
|
|
2988
|
+
const hydrationLedger = activeProjectionIds.has(mount.id) ? hydrationLedgerFor(mount) : void 0;
|
|
2989
|
+
if (!hydrationLedger || !Object.values(hydrationLedger).some(({ processLiveAcrossHydration }) => processLiveAcrossHydration) || !mergeSupport.supported) {
|
|
2990
|
+
fastProjectionMounts.push(mount);
|
|
2991
|
+
continue;
|
|
2992
|
+
}
|
|
2993
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
2994
|
+
cycleSkippedMountIds.add(mount.id);
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2997
|
+
const releaseCheckHold = input.hydrationHooks?.holdMounts([mount.id], `hydration ledger check of mount ${mount.id}`);
|
|
2998
|
+
let checked;
|
|
2999
|
+
try {
|
|
3000
|
+
checked = await workspaceFilesystemExecutor().run("projection_mirror", {
|
|
3001
|
+
workspacePath,
|
|
3002
|
+
mount: workspaceGitMountData(mount),
|
|
3003
|
+
hydrationLedger,
|
|
3004
|
+
checkOnly: true
|
|
3005
|
+
});
|
|
3006
|
+
} finally {
|
|
3007
|
+
releaseCheckHold?.();
|
|
3008
|
+
}
|
|
3009
|
+
if (checked.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, checked.staleRewriteCleared);
|
|
3010
|
+
if (checked.staleRewritePaths.length > 0) {
|
|
3011
|
+
recordStaleRewrite(mount, checked.staleRewritePaths);
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
if (checked.ledgerMergePaths.length === 0) {
|
|
3015
|
+
fastProjectionMounts.push(mount);
|
|
3016
|
+
continue;
|
|
3017
|
+
}
|
|
3018
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
3019
|
+
cycleSkippedMountIds.add(mount.id);
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
const outcome2 = await mergeProjectMount(mount, projectionBasisHead, projectionBasisHead);
|
|
3023
|
+
if (outcome2.kind === "conflict") return outcome2.result;
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
2871
3026
|
if (basisHead === null) {
|
|
2872
3027
|
deferredMountIds.add(mount.id);
|
|
2873
3028
|
continue;
|
|
@@ -2877,62 +3032,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2877
3032
|
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
2878
3033
|
continue;
|
|
2879
3034
|
}
|
|
2880
|
-
if (cycleSkippedMountIds.has(mount.id) || mount
|
|
3035
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
2881
3036
|
cycleSkippedMountIds.add(mount.id);
|
|
2882
3037
|
continue;
|
|
2883
3038
|
}
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2886
|
-
try {
|
|
2887
|
-
const staleRewriteBlobs = staleRewriteBlobsFor(mount);
|
|
2888
|
-
merged = await workspaceFilesystemExecutor().run("projection_merge", {
|
|
2889
|
-
workspacePath,
|
|
2890
|
-
mount: {
|
|
2891
|
-
id: mount.id,
|
|
2892
|
-
sourcePath: mount.sourcePath,
|
|
2893
|
-
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2894
|
-
sourceMode: mount.sourceMode
|
|
2895
|
-
},
|
|
2896
|
-
basisHead,
|
|
2897
|
-
currentHead: projectionBasisHead,
|
|
2898
|
-
attemptId,
|
|
2899
|
-
...staleRewriteBlobs ? { staleRewriteBlobs } : {}
|
|
2900
|
-
});
|
|
2901
|
-
} finally {
|
|
2902
|
-
releaseMergeHold?.();
|
|
2903
|
-
}
|
|
2904
|
-
if (merged.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, merged.staleRewriteCleared);
|
|
2905
|
-
if (merged.kind === "stale_rewrite") {
|
|
2906
|
-
recordStaleRewrite(mount, merged.paths);
|
|
2907
|
-
continue;
|
|
2908
|
-
}
|
|
2909
|
-
mergedProjectionBases.set(mount.id, {
|
|
2910
|
-
projectedCommit: merged.oursCommit,
|
|
2911
|
-
projectedFiles: merged.projectedFiles,
|
|
2912
|
-
readAtMs: merged.readAtMs
|
|
2913
|
-
});
|
|
2914
|
-
if (merged.kind === "conflict") {
|
|
2915
|
-
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: projectionBasisHead });
|
|
2916
|
-
return {
|
|
2917
|
-
outcome: "conflict_blocked",
|
|
2918
|
-
startingHead,
|
|
2919
|
-
localHead: merged.oursCommit,
|
|
2920
|
-
remoteHead: initial.remoteHead,
|
|
2921
|
-
publishedHead: initial.remoteHead,
|
|
2922
|
-
rebaseCount: 0,
|
|
2923
|
-
diffSizeBytes: 0,
|
|
2924
|
-
affectedPaths: merged.conflictPaths,
|
|
2925
|
-
activeMountIds: [],
|
|
2926
|
-
skippedMountIds: input.mounts.map(({ id }) => id).sort(),
|
|
2927
|
-
conflictPaths: merged.conflictPaths,
|
|
2928
|
-
conflictSnapshotRefs: refs,
|
|
2929
|
-
conflictKind: "projection_merge",
|
|
2930
|
-
...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
|
|
2931
|
-
error: merged.error
|
|
2932
|
-
};
|
|
2933
|
-
}
|
|
2934
|
-
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
2935
|
-
mergedProjectionMounts.push(mount);
|
|
3039
|
+
const outcome = await mergeProjectMount(mount, basisHead, projectionBasisHead);
|
|
3040
|
+
if (outcome.kind === "conflict") return outcome.result;
|
|
2936
3041
|
}
|
|
2937
3042
|
}
|
|
2938
3043
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
@@ -2942,7 +3047,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2942
3047
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
2943
3048
|
const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
2944
3049
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
2945
|
-
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
3050
|
+
const mountIsAlreadyCovered = (mount) => !forceHydrationMountIds.has(mount.id) && Boolean(
|
|
2946
3051
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
2947
3052
|
);
|
|
2948
3053
|
if (currentHeadBeforeHydration && completionReceiptMounts.every((mount) => cycleSkippedMountIds.has(mount.id) || mountIsAlreadyCovered(mount))) {
|
|
@@ -2963,6 +3068,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2963
3068
|
currentHeadBeforeHydration,
|
|
2964
3069
|
uncoveredCompletionMounts
|
|
2965
3070
|
) : /* @__PURE__ */ new Set();
|
|
3071
|
+
for (const id of forceHydrationMountIds) unchangedMountIds.delete(id);
|
|
2966
3072
|
const hydrationMounts = uncoveredCompletionMounts.filter(({ id }) => !unchangedMountIds.has(id));
|
|
2967
3073
|
const mergeBases = /* @__PURE__ */ new Map();
|
|
2968
3074
|
if (workspaceMergeHydrationEnabled()) {
|
|
@@ -2980,9 +3086,19 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2980
3086
|
requiredMounts: uncoveredRequiredLiveMounts.filter(({ id }) => !unchangedMountIds.has(id)),
|
|
2981
3087
|
recordCurrentHead: true,
|
|
2982
3088
|
hydrationHooks: input.hydrationHooks,
|
|
2983
|
-
...mergeBases.size > 0 ? { mergeBases } : {}
|
|
3089
|
+
...mergeBases.size > 0 ? { mergeBases } : {},
|
|
3090
|
+
...ledgerMergedPathsByMount.size > 0 ? { ledgerMergedPaths: ledgerMergedPathsByMount } : {}
|
|
2984
3091
|
});
|
|
2985
3092
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
3093
|
+
for (const mount of hydrationMounts) {
|
|
3094
|
+
const mergedPaths = ledgerMergedPathsByMount.get(mount.id);
|
|
3095
|
+
if (!mergedPaths || !hydration.hydratedMountIds.includes(mount.id)) continue;
|
|
3096
|
+
const written = new Set(hydration.hydratedEntryPaths[mount.id] ?? []);
|
|
3097
|
+
const settled = mergedPaths.filter((relativePath) => !written.has(relativePath));
|
|
3098
|
+
if (settled.length > 0) staleRewriteLedger.forget(mount, settled);
|
|
3099
|
+
ledgerMergedPathsByMount.delete(mount.id);
|
|
3100
|
+
forceHydrationMountIds.delete(mount.id);
|
|
3101
|
+
}
|
|
2986
3102
|
if (currentHeadBeforeHydration) {
|
|
2987
3103
|
for (const id of hydration.hydratedMountIds) {
|
|
2988
3104
|
if (mergeBases.has(id)) projectedMountBases.set(id, { projectedCommit: currentHeadBeforeHydration, projectedFiles: null });
|
|
@@ -3007,7 +3123,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3007
3123
|
const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
3008
3124
|
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
3009
3125
|
const idleMounts = completionReceiptMounts.filter(
|
|
3010
|
-
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount
|
|
3126
|
+
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mountBusy(mount) && !projectionSkippedIds.has(mount.id)
|
|
3011
3127
|
);
|
|
3012
3128
|
if (currentHead && !idleMounts.every((mount) => workspaceHydrationReceiptCoversMount(receipt, currentHead, mount))) {
|
|
3013
3129
|
throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
|
|
@@ -3049,7 +3165,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3049
3165
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
3050
3166
|
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
3051
3167
|
for (const mount of fastActiveMounts) {
|
|
3052
|
-
if (cycleSkippedMountIds.has(mount.id) || mount
|
|
3168
|
+
if (cycleSkippedMountIds.has(mount.id) || mountBusy(mount)) {
|
|
3053
3169
|
cycleSkippedMountIds.add(mount.id);
|
|
3054
3170
|
continue;
|
|
3055
3171
|
}
|
|
@@ -3059,22 +3175,33 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
3059
3175
|
}
|
|
3060
3176
|
const releaseMirrorHold = input.hydrationHooks?.holdMounts([mount.id], `projection of mount ${mount.id}`);
|
|
3061
3177
|
let staleRewritePaths;
|
|
3178
|
+
let ledgerMergePaths;
|
|
3062
3179
|
try {
|
|
3063
|
-
const
|
|
3180
|
+
const hydrationLedger = hydrationLedgerFor(mount);
|
|
3064
3181
|
const mirrored = await workspaceFilesystemExecutor().run("projection_mirror", {
|
|
3065
3182
|
workspacePath,
|
|
3066
3183
|
mount: workspaceGitMountData(mount),
|
|
3067
|
-
...
|
|
3184
|
+
...hydrationLedger ? { hydrationLedger } : {}
|
|
3068
3185
|
});
|
|
3069
3186
|
staleRewritePaths = mirrored.staleRewritePaths;
|
|
3187
|
+
ledgerMergePaths = mirrored.ledgerMergePaths;
|
|
3070
3188
|
if (mirrored.staleRewriteCleared.length > 0) staleRewriteLedger.forget(mount, mirrored.staleRewriteCleared);
|
|
3071
|
-
if (staleRewritePaths.length === 0) {
|
|
3189
|
+
if (staleRewritePaths.length === 0 && ledgerMergePaths.length === 0) {
|
|
3072
3190
|
projectedGitlinks.push({ mount, gitlinks: mirrored.gitlinks });
|
|
3073
3191
|
projectedMountFiles.set(mount.id, { files: mirrored.projectedFiles, readAtMs: mirrored.readAtMs });
|
|
3074
3192
|
}
|
|
3075
3193
|
} finally {
|
|
3076
3194
|
releaseMirrorHold?.();
|
|
3077
3195
|
}
|
|
3196
|
+
if (ledgerMergePaths.length > 0) {
|
|
3197
|
+
projectionSkippedIds.add(mount.id);
|
|
3198
|
+
mountSkipReasons[mount.id] = { reason: "hydration_target_changed", paths: ledgerMergePaths };
|
|
3199
|
+
process.stderr.write(
|
|
3200
|
+
`[r5d-worker] projection of mount ${mount.id} skipped: a running process rewrote ${describeHydrationSkipPaths(mount.sourcePath, ledgerMergePaths)} while the cycle prepared the mirror; the next cycle merges it
|
|
3201
|
+
`
|
|
3202
|
+
);
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3078
3205
|
if (staleRewritePaths.length > 0) {
|
|
3079
3206
|
recordStaleRewrite(mount, staleRewritePaths);
|
|
3080
3207
|
continue;
|
|
@@ -3296,42 +3423,59 @@ ${push.stdout}`)) {
|
|
|
3296
3423
|
}
|
|
3297
3424
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
3298
3425
|
}
|
|
3299
|
-
function
|
|
3426
|
+
function inspectHydrationLedger(workspacePath, mount, hydrationLedger) {
|
|
3300
3427
|
const outerRoot = workspaceMountOuterPath(workspacePath, mount);
|
|
3301
3428
|
const stale = [];
|
|
3429
|
+
const merge = [];
|
|
3302
3430
|
const cleared = [];
|
|
3303
|
-
for (const [relativePath,
|
|
3304
|
-
const algorithm = gitObjectHashAlgorithmFor(
|
|
3431
|
+
for (const [relativePath, entry] of Object.entries(hydrationLedger)) {
|
|
3432
|
+
const algorithm = gitObjectHashAlgorithmFor(entry.preBlob);
|
|
3305
3433
|
if (!algorithm) continue;
|
|
3306
3434
|
const source = inspectWorkingTreePath(mount.sourcePath, relativePath);
|
|
3307
3435
|
if (source.kind !== "entry" || !source.stat.isFile()) {
|
|
3308
|
-
cleared.push(relativePath);
|
|
3436
|
+
if (entry.postBlob !== null) cleared.push(relativePath);
|
|
3309
3437
|
continue;
|
|
3310
3438
|
}
|
|
3439
|
+
if (entry.stat && projectedFileEntryMatches(entry.stat, source.stat)) continue;
|
|
3311
3440
|
const content = fs.readFileSync(source.absolutePath);
|
|
3441
|
+
const hash = gitBlobHash(content, algorithm);
|
|
3442
|
+
if (hash === entry.postBlob) continue;
|
|
3312
3443
|
const outer = inspectWorkingTreePath(outerRoot, relativePath);
|
|
3313
3444
|
const outerEqual = outer.kind === "entry" && outer.stat.isFile() && fs.readFileSync(outer.absolutePath).equals(content);
|
|
3314
|
-
if (
|
|
3315
|
-
|
|
3445
|
+
if (outerEqual) {
|
|
3446
|
+
cleared.push(relativePath);
|
|
3316
3447
|
continue;
|
|
3317
3448
|
}
|
|
3318
|
-
if (
|
|
3449
|
+
if (entry.processLiveAcrossHydration) {
|
|
3450
|
+
merge.push(relativePath);
|
|
3451
|
+
continue;
|
|
3452
|
+
}
|
|
3453
|
+
if (hash !== entry.preBlob) {
|
|
3319
3454
|
cleared.push(relativePath);
|
|
3320
3455
|
continue;
|
|
3321
3456
|
}
|
|
3322
3457
|
stale.push(relativePath);
|
|
3323
3458
|
}
|
|
3324
|
-
return { stale: stale.sort(), cleared: cleared.sort() };
|
|
3459
|
+
return { stale: stale.sort(), merge: merge.sort(), cleared: cleared.sort() };
|
|
3325
3460
|
}
|
|
3326
3461
|
function runProjectionMirrorJob(input) {
|
|
3327
3462
|
const workspacePath = path.resolve(input.workspacePath);
|
|
3328
3463
|
let staleRewriteCleared = [];
|
|
3329
|
-
if (input.
|
|
3330
|
-
const
|
|
3331
|
-
staleRewriteCleared =
|
|
3332
|
-
if (
|
|
3333
|
-
return {
|
|
3464
|
+
if (input.hydrationLedger) {
|
|
3465
|
+
const inspected = inspectHydrationLedger(workspacePath, input.mount, input.hydrationLedger);
|
|
3466
|
+
staleRewriteCleared = inspected.cleared;
|
|
3467
|
+
if (inspected.stale.length > 0 || inspected.merge.length > 0 || input.checkOnly) {
|
|
3468
|
+
return {
|
|
3469
|
+
gitlinks: [],
|
|
3470
|
+
projectedFiles: {},
|
|
3471
|
+
readAtMs: Date.now(),
|
|
3472
|
+
staleRewritePaths: inspected.stale,
|
|
3473
|
+
ledgerMergePaths: inspected.merge,
|
|
3474
|
+
staleRewriteCleared
|
|
3475
|
+
};
|
|
3334
3476
|
}
|
|
3477
|
+
} else if (input.checkOnly) {
|
|
3478
|
+
return { gitlinks: [], projectedFiles: {}, readAtMs: Date.now(), staleRewritePaths: [], ledgerMergePaths: [], staleRewriteCleared };
|
|
3335
3479
|
}
|
|
3336
3480
|
const projectedFiles = /* @__PURE__ */ new Map();
|
|
3337
3481
|
const projected = mirrorMountsToWorkspace(workspacePath, [input.mount], projectedFiles);
|
|
@@ -3340,6 +3484,7 @@ function runProjectionMirrorJob(input) {
|
|
|
3340
3484
|
projectedFiles: Object.fromEntries(projectedFiles),
|
|
3341
3485
|
readAtMs: Date.now(),
|
|
3342
3486
|
staleRewritePaths: [],
|
|
3487
|
+
ledgerMergePaths: [],
|
|
3343
3488
|
staleRewriteCleared
|
|
3344
3489
|
};
|
|
3345
3490
|
}
|
|
@@ -3379,6 +3524,7 @@ export {
|
|
|
3379
3524
|
WORKSPACE_GIT_HYDRATION_RECOVERIES,
|
|
3380
3525
|
WORKSPACE_GIT_HYDRATION_TRANSACTION,
|
|
3381
3526
|
WORKSPACE_MERGE_HYDRATION_SWITCH,
|
|
3527
|
+
WORKSPACE_PROCESS_BUSY_FENCE_SWITCH,
|
|
3382
3528
|
WorkspaceHydrationRecoveryRequiredError,
|
|
3383
3529
|
WorkspaceRemediationAncestryError,
|
|
3384
3530
|
configureExistingWorkspaceGitForRemediation,
|