@ricsam/r5d-worker 0.0.123 → 0.0.124
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 +488 -238
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-checkout-garbage.cjs +32 -3
- package/dist/cjs/project-workspace-state.cjs +51 -35
- package/dist/cjs/project-worktrees.cjs +79 -34
- package/dist/cjs/recovery-journal-protocol.cjs +56 -0
- package/dist/cjs/recovery-journal-runtime.cjs +133 -0
- package/dist/cjs/recovery-journal-thread.cjs +9 -0
- package/dist/cjs/recovery-journal.cjs +735 -0
- package/dist/cjs/recovery-store.cjs +8 -0
- package/dist/cjs/session-file-mutations.cjs +61 -0
- package/dist/cjs/working-tree-mirror.cjs +1 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +37 -8
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +36 -0
- package/dist/cjs/workspace-filesystem-executor.cjs +327 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +134 -0
- package/dist/cjs/workspace-filesystem-jobs.cjs +57 -0
- package/dist/cjs/workspace-git-sync.cjs +275 -201
- package/dist/cjs/workspace-mount-hold-fence.cjs +120 -0
- package/dist/mjs/main.mjs +491 -244
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-checkout-garbage.mjs +30 -2
- package/dist/mjs/project-workspace-state.mjs +51 -35
- package/dist/mjs/project-worktrees.mjs +74 -34
- package/dist/mjs/recovery-journal-protocol.mjs +30 -0
- package/dist/mjs/recovery-journal-runtime.mjs +112 -0
- package/dist/mjs/recovery-journal-thread.mjs +8 -0
- package/dist/mjs/recovery-journal.mjs +687 -0
- package/dist/mjs/recovery-store.mjs +8 -0
- package/dist/mjs/session-file-mutations.mjs +37 -0
- package/dist/mjs/working-tree-mirror.mjs +1 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +37 -8
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +38 -0
- package/dist/mjs/workspace-filesystem-executor.mjs +287 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +106 -0
- package/dist/mjs/workspace-filesystem-jobs.mjs +51 -0
- package/dist/mjs/workspace-git-sync.mjs +264 -202
- package/dist/mjs/workspace-mount-hold-fence.mjs +95 -0
- package/dist/types/main.d.ts +24 -19
- package/dist/types/project-checkout-garbage.d.ts +19 -2
- package/dist/types/project-workspace-state.d.ts +9 -9
- package/dist/types/project-worktrees.d.ts +49 -5
- package/dist/types/recovery-journal-protocol.d.ts +35 -0
- package/dist/types/recovery-journal-runtime.d.ts +12 -0
- package/dist/types/recovery-journal-stall-fixture.d.ts +1 -0
- package/dist/types/recovery-journal-thread.d.ts +1 -0
- package/dist/types/recovery-journal.d.ts +246 -0
- package/dist/types/recovery-store.d.ts +6 -0
- package/dist/types/session-file-mutations.d.ts +22 -0
- package/dist/types/workspace-command-sync-policy.d.ts +22 -7
- package/dist/types/workspace-filesystem-executor-thread.d.ts +1 -0
- package/dist/types/workspace-filesystem-executor.d.ts +123 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +246 -0
- package/dist/types/workspace-filesystem-jobs.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +113 -7
- package/dist/types/workspace-mount-hold-fence.d.ts +42 -0
- package/package.json +1 -1
- package/dist/cjs/project-snapshot-recovery-runner.cjs +0 -171
- package/dist/mjs/project-snapshot-recovery-runner.mjs +0 -135
- package/dist/types/project-snapshot-recovery-runner.d.ts +0 -10
|
@@ -3,9 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import { gitCredentialUsernameConfigKey, gitTransportSecurityArgs, workerGitProcessEnvironment } from "./git-process-environment.mjs";
|
|
4
4
|
import {
|
|
5
5
|
materializeWorkspaceProjectionTree,
|
|
6
|
-
mergeWorkspaceProjectionMount,
|
|
7
6
|
workspaceMergeProjectionSupport
|
|
8
7
|
} from "./workspace-merge-projection.mjs";
|
|
8
|
+
import { workspaceFilesystemExecutor } from "./workspace-filesystem-executor.mjs";
|
|
9
9
|
import {
|
|
10
10
|
captureWorkingTreeMirrorPlan,
|
|
11
11
|
fsyncWorkingTreePaths,
|
|
@@ -23,6 +23,22 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
|
23
23
|
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
24
24
|
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
25
25
|
const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
26
|
+
function workspaceGitMountData(mount) {
|
|
27
|
+
return {
|
|
28
|
+
id: mount.id,
|
|
29
|
+
hydrationIncarnationKey: mount.hydrationIncarnationKey,
|
|
30
|
+
sourcePath: mount.sourcePath,
|
|
31
|
+
durabilityRootPath: mount.durabilityRootPath,
|
|
32
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
33
|
+
sourceMode: mount.sourceMode,
|
|
34
|
+
hydrateDeletionMode: mount.hydrateDeletionMode,
|
|
35
|
+
...mount.preserveLocalOnInitialOuterAbsence === void 0 ? {} : { preserveLocalOnInitialOuterAbsence: mount.preserveLocalOnInitialOuterAbsence },
|
|
36
|
+
...mount.preserveLocalOnHydrationBasisChange === void 0 ? {} : { preserveLocalOnHydrationBasisChange: mount.preserveLocalOnHydrationBasisChange },
|
|
37
|
+
...mount.initialPublicationSourceRelativePath === void 0 ? {} : { initialPublicationSourceRelativePath: mount.initialPublicationSourceRelativePath },
|
|
38
|
+
...mount.deleteWhenSourceMissing === void 0 ? {} : { deleteWhenSourceMissing: mount.deleteWhenSourceMissing },
|
|
39
|
+
...mount.lastProjectedMutationToken === void 0 ? {} : { lastProjectedMutationToken: mount.lastProjectedMutationToken }
|
|
40
|
+
};
|
|
41
|
+
}
|
|
26
42
|
function workspaceGitMountsForRemediation(mounts) {
|
|
27
43
|
return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
|
|
28
44
|
}
|
|
@@ -114,9 +130,6 @@ async function revParseAsync(workspacePath, revision) {
|
|
|
114
130
|
const result = await gitResultAsync(workspacePath, ["rev-parse", "--verify", revision]);
|
|
115
131
|
return result.exitCode === 0 ? result.stdout : null;
|
|
116
132
|
}
|
|
117
|
-
function yieldToWorkerEventLoop() {
|
|
118
|
-
return new Promise((resolve) => setImmediate(resolve));
|
|
119
|
-
}
|
|
120
133
|
function workspaceCommitsExist(workspacePath, heads) {
|
|
121
134
|
const uniqueHeads = [...new Set(heads)];
|
|
122
135
|
if (uniqueHeads.length === 0) return true;
|
|
@@ -381,20 +394,35 @@ function readHydratedWorkspaceReceipt(workspacePath) {
|
|
|
381
394
|
}
|
|
382
395
|
return unverified.receipt;
|
|
383
396
|
}
|
|
397
|
+
async function readHydratedWorkspaceReceiptUnverifiedAsync(workspacePath) {
|
|
398
|
+
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
399
|
+
let status;
|
|
400
|
+
try {
|
|
401
|
+
status = await fs.promises.lstat(receiptPath);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error.code === "ENOENT") return null;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
if (!status.isFile() || status.isSymbolicLink()) {
|
|
407
|
+
throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
|
|
408
|
+
}
|
|
409
|
+
const content = await fs.promises.readFile(receiptPath, "utf8");
|
|
410
|
+
return { ...parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content), receiptPath };
|
|
411
|
+
}
|
|
384
412
|
async function readHydratedWorkspaceReceiptAsync(workspacePath) {
|
|
385
|
-
const unverified =
|
|
413
|
+
const unverified = await readHydratedWorkspaceReceiptUnverifiedAsync(workspacePath);
|
|
386
414
|
if (!unverified) return null;
|
|
387
415
|
if (!await workspaceCommitsExistAsync(workspacePath, unverified.headsToVerify)) {
|
|
388
416
|
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
|
|
389
417
|
}
|
|
390
418
|
return unverified.receipt;
|
|
391
419
|
}
|
|
392
|
-
function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
420
|
+
async function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
393
421
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
394
|
-
const status = lstatIfExists(receiptPath);
|
|
395
|
-
if (!status?.isFile() || status.isSymbolicLink()) return false;
|
|
396
422
|
try {
|
|
397
|
-
|
|
423
|
+
const status = await fs.promises.lstat(receiptPath);
|
|
424
|
+
if (!status.isFile() || status.isSymbolicLink()) return false;
|
|
425
|
+
return JSON.parse(await fs.promises.readFile(receiptPath, "utf8")).version === 3;
|
|
398
426
|
} catch {
|
|
399
427
|
return false;
|
|
400
428
|
}
|
|
@@ -491,24 +519,6 @@ function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
|
|
|
491
519
|
throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
|
|
492
520
|
}
|
|
493
521
|
}
|
|
494
|
-
async function runWorkspaceRefUpdateBatchAsync(workspacePath, instructions, action) {
|
|
495
|
-
if (instructions.length === 0) return;
|
|
496
|
-
const subprocess = Bun.spawn(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
497
|
-
cwd: workspacePath,
|
|
498
|
-
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
499
|
-
stdout: "pipe",
|
|
500
|
-
stderr: "pipe",
|
|
501
|
-
env: workerGitProcessEnvironment()
|
|
502
|
-
});
|
|
503
|
-
const [, stderr, exitCode] = await Promise.all([
|
|
504
|
-
new Response(subprocess.stdout).text(),
|
|
505
|
-
new Response(subprocess.stderr).text(),
|
|
506
|
-
subprocess.exited
|
|
507
|
-
]);
|
|
508
|
-
if (exitCode !== 0) {
|
|
509
|
-
throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
522
|
function workspaceBasisRefTransaction(receipt, stableRefs) {
|
|
513
523
|
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
514
524
|
const transaction = ["start"];
|
|
@@ -531,24 +541,6 @@ function updateWorkspaceBasisRefs(workspacePath, receipt) {
|
|
|
531
541
|
"remove staged workspace hydration basis refs"
|
|
532
542
|
);
|
|
533
543
|
}
|
|
534
|
-
async function updateWorkspaceBasisRefsAsync(workspacePath, receipt) {
|
|
535
|
-
const stableRefs = (await gitAsync(workspacePath, ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_REF_PREFIX], "list workspace basis refs")).split("\n").filter(Boolean).sort();
|
|
536
|
-
await runWorkspaceRefUpdateBatchAsync(
|
|
537
|
-
workspacePath,
|
|
538
|
-
workspaceBasisRefTransaction(receipt, stableRefs),
|
|
539
|
-
"Synchronize workspace hydration basis refs"
|
|
540
|
-
);
|
|
541
|
-
const stagedRefs = (await gitAsync(
|
|
542
|
-
workspacePath,
|
|
543
|
-
["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
|
|
544
|
-
"list staged workspace basis refs"
|
|
545
|
-
)).split("\n").filter(Boolean).sort();
|
|
546
|
-
await runWorkspaceRefUpdateBatchAsync(
|
|
547
|
-
workspacePath,
|
|
548
|
-
stagedRefs.map((ref) => `delete ${ref}`),
|
|
549
|
-
"remove staged workspace hydration basis refs"
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
544
|
function assertCanonicalHydrationReceiptHeads(receipt) {
|
|
553
545
|
for (const mount of receipt.mounts) {
|
|
554
546
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
|
|
@@ -562,20 +554,15 @@ function throwMissingHydrationReceiptCommit(workspacePath, receipt) {
|
|
|
562
554
|
}
|
|
563
555
|
function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
564
556
|
assertCanonicalHydrationReceiptHeads(receipt);
|
|
565
|
-
if (!workspaceCommitsExist(
|
|
557
|
+
if (!workspaceCommitsExist(
|
|
558
|
+
workspacePath,
|
|
559
|
+
receipt.mounts.map(({ head }) => head)
|
|
560
|
+
)) {
|
|
566
561
|
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
567
562
|
}
|
|
568
563
|
readHydratedWorkspaceReceipt(workspacePath);
|
|
569
564
|
writeHydratedWorkspaceReceiptCore(workspacePath, receipt);
|
|
570
565
|
}
|
|
571
|
-
async function updateHydratedWorkspaceReceiptAsync(workspacePath, receipt) {
|
|
572
|
-
assertCanonicalHydrationReceiptHeads(receipt);
|
|
573
|
-
if (!await workspaceCommitsExistAsync(workspacePath, receipt.mounts.map(({ head }) => head))) {
|
|
574
|
-
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
575
|
-
}
|
|
576
|
-
await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
577
|
-
await writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt);
|
|
578
|
-
}
|
|
579
566
|
function prepareHydratedWorkspaceReceiptWrite(workspacePath) {
|
|
580
567
|
const gitDirectory = path.join(workspacePath, ".git");
|
|
581
568
|
const gitDirectoryStatus = fs.lstatSync(gitDirectory);
|
|
@@ -630,17 +617,6 @@ function writeHydratedWorkspaceReceiptCore(workspacePath, receipt) {
|
|
|
630
617
|
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
631
618
|
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
632
619
|
}
|
|
633
|
-
async function writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt) {
|
|
634
|
-
const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
|
|
635
|
-
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
636
|
-
await runWorkspaceRefUpdateBatchAsync(
|
|
637
|
-
workspacePath,
|
|
638
|
-
stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
|
|
639
|
-
"stage workspace hydration basis refs"
|
|
640
|
-
);
|
|
641
|
-
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
642
|
-
await updateWorkspaceBasisRefsAsync(workspacePath, receipt);
|
|
643
|
-
}
|
|
644
620
|
function hydrationTransactionPath(workspacePath) {
|
|
645
621
|
return path.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
|
|
646
622
|
}
|
|
@@ -735,8 +711,21 @@ function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
|
|
|
735
711
|
return unverified.record;
|
|
736
712
|
}
|
|
737
713
|
async function readWorkspaceCheckoutDurabilityRecordAsync(workspacePath) {
|
|
738
|
-
const
|
|
739
|
-
|
|
714
|
+
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
715
|
+
let status;
|
|
716
|
+
try {
|
|
717
|
+
status = await fs.promises.lstat(recordPath);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error.code === "ENOENT") return null;
|
|
720
|
+
throw error;
|
|
721
|
+
}
|
|
722
|
+
if (!status.isFile() || status.isSymbolicLink()) {
|
|
723
|
+
throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
|
|
724
|
+
}
|
|
725
|
+
const unverified = {
|
|
726
|
+
record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, await fs.promises.readFile(recordPath, "utf8")),
|
|
727
|
+
recordPath
|
|
728
|
+
};
|
|
740
729
|
if (unverified.record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
|
|
741
730
|
throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
|
|
742
731
|
}
|
|
@@ -763,13 +752,6 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
|
763
752
|
readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
764
753
|
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
765
754
|
}
|
|
766
|
-
async function updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, record) {
|
|
767
|
-
if (record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
768
|
-
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
769
|
-
}
|
|
770
|
-
await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
771
|
-
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
772
|
-
}
|
|
773
755
|
function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
774
756
|
const status = fs.lstatSync(workspacePath);
|
|
775
757
|
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
@@ -781,40 +763,6 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
|
781
763
|
}
|
|
782
764
|
fsyncDirectory(workspacePath);
|
|
783
765
|
}
|
|
784
|
-
const WORKSPACE_FSYNC_YIELD_INTERVAL = 64;
|
|
785
|
-
async function fsyncWorkspaceCheckoutTreeYielding(workspacePath) {
|
|
786
|
-
const status = fs.lstatSync(workspacePath);
|
|
787
|
-
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
788
|
-
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
789
|
-
}
|
|
790
|
-
let entriesSinceYield = 0;
|
|
791
|
-
const syncEntry = async (entryPath) => {
|
|
792
|
-
const entryStatus = fs.lstatSync(entryPath);
|
|
793
|
-
if (entryStatus.isSymbolicLink()) return;
|
|
794
|
-
if (entryStatus.isDirectory()) {
|
|
795
|
-
for (const name of fs.readdirSync(entryPath)) await syncEntry(path.join(entryPath, name));
|
|
796
|
-
fsyncDirectory(entryPath);
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
if (!entryStatus.isFile()) return;
|
|
800
|
-
const descriptor = fs.openSync(entryPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
801
|
-
try {
|
|
802
|
-
fs.fsyncSync(descriptor);
|
|
803
|
-
} finally {
|
|
804
|
-
fs.closeSync(descriptor);
|
|
805
|
-
}
|
|
806
|
-
entriesSinceYield += 1;
|
|
807
|
-
if (entriesSinceYield >= WORKSPACE_FSYNC_YIELD_INTERVAL) {
|
|
808
|
-
entriesSinceYield = 0;
|
|
809
|
-
await yieldToWorkerEventLoop();
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
for (const name of fs.readdirSync(workspacePath)) {
|
|
813
|
-
if (name === ".git") continue;
|
|
814
|
-
await syncEntry(path.join(workspacePath, name));
|
|
815
|
-
}
|
|
816
|
-
fsyncDirectory(workspacePath);
|
|
817
|
-
}
|
|
818
766
|
function workspaceRebaseInProgress(workspacePath) {
|
|
819
767
|
const gitDirectory = path.join(workspacePath, ".git");
|
|
820
768
|
return fs.existsSync(path.join(gitDirectory, "rebase-merge")) || fs.existsSync(path.join(gitDirectory, "rebase-apply"));
|
|
@@ -883,22 +831,36 @@ async function workspaceCheckoutDurabilityIsCurrentAsync(workspacePath) {
|
|
|
883
831
|
const record = await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
884
832
|
return Boolean(record?.state === "durable" && record.head === await revParseAsync(workspacePath, "HEAD"));
|
|
885
833
|
}
|
|
886
|
-
function
|
|
834
|
+
function runCheckoutTransitionBeginJob(input) {
|
|
835
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
887
836
|
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
888
837
|
const head = revParse(workspacePath, "HEAD");
|
|
889
838
|
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
function runCheckoutTransitionCompleteJob(input) {
|
|
842
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
843
|
+
fsyncWorkspaceCheckoutTree(workspacePath);
|
|
844
|
+
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "durable", head: revParse(workspacePath, "HEAD") });
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
function runCheckoutDurabilityRecoveryJob(input) {
|
|
848
|
+
recoverWorkspaceCheckoutDurability(path.resolve(input.workspacePath), input.preserveResolutionInProgress);
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
function runOuterCheckoutFsyncJob(input) {
|
|
852
|
+
fsyncWorkspaceCheckoutTree(path.resolve(input.workspacePath));
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
async function beginWorkspaceCheckoutTransition(workspacePath) {
|
|
856
|
+
await workspaceFilesystemExecutor().run("checkout_transition_begin", { workspacePath });
|
|
890
857
|
}
|
|
891
858
|
async function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
892
|
-
await
|
|
893
|
-
await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
|
|
894
|
-
version: 1,
|
|
895
|
-
state: "durable",
|
|
896
|
-
head: await revParseAsync(workspacePath, "HEAD")
|
|
897
|
-
});
|
|
859
|
+
await workspaceFilesystemExecutor().run("checkout_transition_complete", { workspacePath });
|
|
898
860
|
}
|
|
899
|
-
function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
861
|
+
async function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
900
862
|
try {
|
|
901
|
-
|
|
863
|
+
await workspaceFilesystemExecutor().run("checkout_durability_recovery", { workspacePath, preserveResolutionInProgress: false });
|
|
902
864
|
} catch (recoveryError) {
|
|
903
865
|
throw new AggregateError(
|
|
904
866
|
[error, recoveryError],
|
|
@@ -908,12 +870,12 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
|
908
870
|
throw error;
|
|
909
871
|
}
|
|
910
872
|
async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
911
|
-
beginWorkspaceCheckoutTransition(workspacePath);
|
|
873
|
+
await beginWorkspaceCheckoutTransition(workspacePath);
|
|
912
874
|
try {
|
|
913
875
|
await mutate();
|
|
914
876
|
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
915
877
|
} catch (error) {
|
|
916
|
-
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
878
|
+
await throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
917
879
|
}
|
|
918
880
|
}
|
|
919
881
|
function parseHydrationTransactionManifest(transactionPath) {
|
|
@@ -1295,8 +1257,16 @@ async function configureWorkspaceRepository(input) {
|
|
|
1295
1257
|
await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
|
|
1296
1258
|
}
|
|
1297
1259
|
await tryGitAsync(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
|
|
1298
|
-
await gitAsync(
|
|
1299
|
-
|
|
1260
|
+
await gitAsync(
|
|
1261
|
+
input.workspacePath,
|
|
1262
|
+
["config", "--local", "--replace-all", "credential.helper", ""],
|
|
1263
|
+
"reset workspace credential helpers"
|
|
1264
|
+
);
|
|
1265
|
+
await gitAsync(
|
|
1266
|
+
input.workspacePath,
|
|
1267
|
+
["config", "--local", "credential.useHttpPath", "false"],
|
|
1268
|
+
"configure workspace credential path matching"
|
|
1269
|
+
);
|
|
1300
1270
|
if (input.credentialUsername) {
|
|
1301
1271
|
await gitAsync(
|
|
1302
1272
|
input.workspacePath,
|
|
@@ -1394,19 +1364,24 @@ async function ensureWorkspaceGitClone(input) {
|
|
|
1394
1364
|
input.credentialHelper,
|
|
1395
1365
|
input.credentialUsername
|
|
1396
1366
|
);
|
|
1397
|
-
const clone = Bun.
|
|
1367
|
+
const clone = Bun.spawn(cloneCommand, {
|
|
1368
|
+
stdin: "ignore",
|
|
1398
1369
|
stdout: "pipe",
|
|
1399
1370
|
stderr: "pipe",
|
|
1400
1371
|
env: workerGitProcessEnvironment()
|
|
1401
1372
|
});
|
|
1402
|
-
|
|
1373
|
+
const [, , cloneExitCode] = await Promise.all([new Response(clone.stdout).text(), new Response(clone.stderr).text(), clone.exited]);
|
|
1374
|
+
if (cloneExitCode !== 0) {
|
|
1403
1375
|
fs.rmSync(workspacePath, { recursive: true, force: true });
|
|
1404
1376
|
fs.mkdirSync(workspacePath, { recursive: true });
|
|
1405
1377
|
git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
|
|
1406
1378
|
}
|
|
1407
1379
|
}
|
|
1408
1380
|
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1409
|
-
|
|
1381
|
+
await workspaceFilesystemExecutor().run("checkout_durability_recovery", {
|
|
1382
|
+
workspacePath,
|
|
1383
|
+
preserveResolutionInProgress: input.preserveResolutionInProgress === true
|
|
1384
|
+
});
|
|
1410
1385
|
const previousRemoteHead = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1411
1386
|
const localHeadBeforeFetch = await revParseAsync(workspacePath, "HEAD");
|
|
1412
1387
|
if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
@@ -1685,7 +1660,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1685
1660
|
};
|
|
1686
1661
|
};
|
|
1687
1662
|
const noopCandidate = computeSelection();
|
|
1688
|
-
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
1663
|
+
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)) {
|
|
1689
1664
|
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1690
1665
|
}
|
|
1691
1666
|
const hooks = input.hydrationHooks;
|
|
@@ -1698,47 +1673,92 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1698
1673
|
try {
|
|
1699
1674
|
const selection = computeSelection();
|
|
1700
1675
|
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1701
|
-
const
|
|
1676
|
+
const heldMountIds = [...new Set([...hydrationMounts, ...advancedDurabilityMounts].map(({ id }) => id))];
|
|
1677
|
+
const releaseHold = hooks?.holdMounts(heldMountIds, `hydration transaction ${context.transactionId}`);
|
|
1702
1678
|
try {
|
|
1703
|
-
const hydration =
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
return {
|
|
1713
|
-
hydratedMountIds: hydration.hydratedMountIds,
|
|
1714
|
-
skippedMountIds: selection.skippedMountIds
|
|
1715
|
-
};
|
|
1716
|
-
} catch (error) {
|
|
1717
|
-
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1718
|
-
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1719
|
-
throw error;
|
|
1720
|
-
}
|
|
1721
|
-
try {
|
|
1722
|
-
restoreHydrationTransaction(workspacePath, manifest);
|
|
1723
|
-
removeHydrationTransaction(workspacePath);
|
|
1724
|
-
} catch (restoreError) {
|
|
1725
|
-
throw new AggregateError(
|
|
1726
|
-
[error, restoreError],
|
|
1727
|
-
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1728
|
-
);
|
|
1729
|
-
}
|
|
1730
|
-
throw error;
|
|
1679
|
+
const hydration = await workspaceFilesystemExecutor().run("hydration_transaction", {
|
|
1680
|
+
workspacePath,
|
|
1681
|
+
hydrationMounts: hydrationMounts.map(workspaceGitMountData),
|
|
1682
|
+
advancedDurabilityMounts: advancedDurabilityMounts.map(workspaceGitMountData),
|
|
1683
|
+
targetReceipt
|
|
1684
|
+
});
|
|
1685
|
+
return { hydratedMountIds: hydration.hydratedMountIds, skippedMountIds: selection.skippedMountIds };
|
|
1686
|
+
} finally {
|
|
1687
|
+
releaseHold?.();
|
|
1731
1688
|
}
|
|
1732
1689
|
} finally {
|
|
1733
1690
|
hooks?.afterTransaction(context);
|
|
1734
1691
|
}
|
|
1735
1692
|
}
|
|
1693
|
+
function runHydrationTransactionJob(input) {
|
|
1694
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
1695
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = input;
|
|
1696
|
+
const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1697
|
+
try {
|
|
1698
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
1699
|
+
const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
|
|
1700
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
|
|
1701
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1702
|
+
}
|
|
1703
|
+
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1704
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1705
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1706
|
+
removeHydrationTransaction(workspacePath);
|
|
1707
|
+
return { hydratedMountIds: hydration.hydratedMountIds };
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1710
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1711
|
+
throw error;
|
|
1712
|
+
}
|
|
1713
|
+
try {
|
|
1714
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1715
|
+
removeHydrationTransaction(workspacePath);
|
|
1716
|
+
} catch (restoreError) {
|
|
1717
|
+
throw new AggregateError(
|
|
1718
|
+
[error, restoreError],
|
|
1719
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
throw error;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function runHydrationRawJob(input) {
|
|
1726
|
+
return {
|
|
1727
|
+
hydratedMountIds: hydrateWorkspaceGitMountsRaw(path.resolve(input.workspacePath), input.mounts, { ignoreBusy: true }).hydratedMountIds
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function runHydrationRecoveryJob(input) {
|
|
1731
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
1732
|
+
recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
|
|
1733
|
+
recoverPendingHydrationTransaction(workspacePath);
|
|
1734
|
+
repairWorkspaceBasisRefs(workspacePath, readHydratedWorkspaceReceipt(workspacePath));
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1736
1737
|
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1737
|
-
const
|
|
1738
|
+
const resolvedWorkspacePath = path.resolve(workspacePath);
|
|
1739
|
+
const gitDirectory = path.join(resolvedWorkspacePath, ".git");
|
|
1738
1740
|
if (!fs.existsSync(gitDirectory)) {
|
|
1739
|
-
|
|
1741
|
+
const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mount.busy?.()).map(({ id }) => id).sort();
|
|
1742
|
+
const skipped = new Set(skippedMountIds);
|
|
1743
|
+
const selected = mounts.filter(({ id }) => !skipped.has(id));
|
|
1744
|
+
const releaseHold = options.hydrationHooks?.holdMounts(
|
|
1745
|
+
selected.map(({ id }) => id),
|
|
1746
|
+
"pre-clone hydration"
|
|
1747
|
+
);
|
|
1748
|
+
try {
|
|
1749
|
+
const hydration = await workspaceFilesystemExecutor().run("hydration_raw", {
|
|
1750
|
+
workspacePath: resolvedWorkspacePath,
|
|
1751
|
+
mounts: selected.map(workspaceGitMountData)
|
|
1752
|
+
});
|
|
1753
|
+
return { hydratedMountIds: hydration.hydratedMountIds, skippedMountIds };
|
|
1754
|
+
} finally {
|
|
1755
|
+
releaseHold?.();
|
|
1756
|
+
}
|
|
1740
1757
|
}
|
|
1741
|
-
|
|
1758
|
+
await workspaceFilesystemExecutor().run("checkout_durability_recovery", {
|
|
1759
|
+
workspacePath: resolvedWorkspacePath,
|
|
1760
|
+
preserveResolutionInProgress: false
|
|
1761
|
+
});
|
|
1742
1762
|
return hydrateWorkspaceGitMountsTransactionally({
|
|
1743
1763
|
workspacePath,
|
|
1744
1764
|
mounts,
|
|
@@ -1749,13 +1769,12 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1749
1769
|
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1750
1770
|
workspacePath = path.resolve(workspacePath);
|
|
1751
1771
|
validateMounts(workspacePath, mounts);
|
|
1752
|
-
|
|
1753
|
-
|
|
1772
|
+
await workspaceFilesystemExecutor().run("hydration_recovery", {
|
|
1773
|
+
workspacePath,
|
|
1774
|
+
preserveResolutionInProgress: options.preserveResolutionInProgress === true
|
|
1775
|
+
});
|
|
1754
1776
|
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1755
1777
|
const currentReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1756
|
-
if (!await workspaceBasisRefsAreCurrentAsync(workspacePath, currentReceipt ?? { version: 3, mounts: [] })) {
|
|
1757
|
-
repairWorkspaceBasisRefs(workspacePath, currentReceipt);
|
|
1758
|
-
}
|
|
1759
1778
|
if (!currentHead) return;
|
|
1760
1779
|
const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1761
1780
|
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
@@ -1796,13 +1815,17 @@ async function resetWorkspaceGit(input) {
|
|
|
1796
1815
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
1797
1816
|
}
|
|
1798
1817
|
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1799
|
-
|
|
1818
|
+
await workspaceFilesystemExecutor().run("hydration_recovery", { workspacePath, preserveResolutionInProgress: false });
|
|
1800
1819
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
1801
1820
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
1802
1821
|
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1803
1822
|
if (remoteHead) {
|
|
1804
1823
|
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1805
|
-
await gitAsync(
|
|
1824
|
+
await gitAsync(
|
|
1825
|
+
workspacePath,
|
|
1826
|
+
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead],
|
|
1827
|
+
"reset workspace main"
|
|
1828
|
+
);
|
|
1806
1829
|
await gitAsync(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
|
|
1807
1830
|
});
|
|
1808
1831
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
@@ -2013,7 +2036,7 @@ function snapshotConflict(input) {
|
|
|
2013
2036
|
}
|
|
2014
2037
|
async function rebaseWorkspace(input) {
|
|
2015
2038
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
2016
|
-
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
2039
|
+
await beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
2017
2040
|
const rebase = await gitResultAsync(
|
|
2018
2041
|
input.workspacePath,
|
|
2019
2042
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
@@ -2183,18 +2206,24 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2183
2206
|
cycleSkippedMountIds.add(mount.id);
|
|
2184
2207
|
continue;
|
|
2185
2208
|
}
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2209
|
+
const releaseMergeHold = input.hydrationHooks?.holdMounts([mount.id], `merge projection of mount ${mount.id}`);
|
|
2210
|
+
let merged;
|
|
2211
|
+
try {
|
|
2212
|
+
merged = await workspaceFilesystemExecutor().run("projection_merge", {
|
|
2213
|
+
workspacePath,
|
|
2214
|
+
mount: {
|
|
2215
|
+
id: mount.id,
|
|
2216
|
+
sourcePath: mount.sourcePath,
|
|
2217
|
+
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2218
|
+
sourceMode: mount.sourceMode
|
|
2219
|
+
},
|
|
2220
|
+
basisHead,
|
|
2221
|
+
currentHead: projectionBasisHead,
|
|
2222
|
+
attemptId
|
|
2223
|
+
});
|
|
2224
|
+
} finally {
|
|
2225
|
+
releaseMergeHold?.();
|
|
2226
|
+
}
|
|
2198
2227
|
if (merged.kind === "conflict") {
|
|
2199
2228
|
const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: projectionBasisHead });
|
|
2200
2229
|
return {
|
|
@@ -2217,7 +2246,6 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2217
2246
|
}
|
|
2218
2247
|
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
2219
2248
|
mergedProjectionMounts.push(mount);
|
|
2220
|
-
await yieldToWorkerEventLoop();
|
|
2221
2249
|
}
|
|
2222
2250
|
}
|
|
2223
2251
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
@@ -2300,7 +2328,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2300
2328
|
const projectedFastMounts = [];
|
|
2301
2329
|
const currentProjectionMounts = [];
|
|
2302
2330
|
if (!input.skipMountMirror) {
|
|
2303
|
-
beginWorkspaceCheckoutTransition(workspacePath);
|
|
2331
|
+
await beginWorkspaceCheckoutTransition(workspacePath);
|
|
2304
2332
|
try {
|
|
2305
2333
|
const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
|
|
2306
2334
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
@@ -2314,30 +2342,34 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2314
2342
|
currentProjectionMounts.push(mount);
|
|
2315
2343
|
continue;
|
|
2316
2344
|
}
|
|
2317
|
-
|
|
2345
|
+
const releaseMirrorHold = input.hydrationHooks?.holdMounts([mount.id], `projection of mount ${mount.id}`);
|
|
2346
|
+
try {
|
|
2347
|
+
const mirrored = await workspaceFilesystemExecutor().run("projection_mirror", {
|
|
2348
|
+
workspacePath,
|
|
2349
|
+
mount: workspaceGitMountData(mount)
|
|
2350
|
+
});
|
|
2351
|
+
projectedGitlinks.push({ mount, gitlinks: mirrored.gitlinks });
|
|
2352
|
+
} finally {
|
|
2353
|
+
releaseMirrorHold?.();
|
|
2354
|
+
}
|
|
2318
2355
|
projectedFastMounts.push(mount);
|
|
2319
|
-
await yieldToWorkerEventLoop();
|
|
2320
2356
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
workspacePath,
|
|
2357
|
+
await workspaceFilesystemExecutor().run("projection_outer_apply", {
|
|
2358
|
+
workspacePath,
|
|
2359
|
+
removeMounts: fastTombstones.map(workspaceGitMountData),
|
|
2360
|
+
materialize: mergedProjectionMounts.map((mount) => ({
|
|
2326
2361
|
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
2327
2362
|
resultTree: mergedProjectionTrees.get(mount.id)
|
|
2328
|
-
})
|
|
2329
|
-
|
|
2330
|
-
}
|
|
2331
|
-
restoreDeferredWorkspaceMountsFromHead(
|
|
2332
|
-
workspacePath,
|
|
2333
|
-
input.mounts.filter(
|
|
2363
|
+
})),
|
|
2364
|
+
restoreFromHead: input.mounts.filter(
|
|
2334
2365
|
(mount) => deferredMountIds.has(mount.id) || mount.preserveLocalOnHydrationBasisChange === true && cycleSkippedMountIds.has(mount.id)
|
|
2335
|
-
)
|
|
2336
|
-
);
|
|
2366
|
+
).map(workspaceGitMountData)
|
|
2367
|
+
});
|
|
2368
|
+
projectedFastMounts.push(...fastTombstones);
|
|
2337
2369
|
await stageAndCommitWorkspace();
|
|
2338
|
-
await
|
|
2370
|
+
await workspaceFilesystemExecutor().run("outer_checkout_fsync", { workspacePath });
|
|
2339
2371
|
} catch (error) {
|
|
2340
|
-
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
2372
|
+
await throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
2341
2373
|
}
|
|
2342
2374
|
} else {
|
|
2343
2375
|
await stageAndCommitWorkspace();
|
|
@@ -2353,12 +2385,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2353
2385
|
allProjectionReceiptMounts
|
|
2354
2386
|
);
|
|
2355
2387
|
const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
2356
|
-
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2388
|
+
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !await hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
|
|
2389
|
+
await workspaceFilesystemExecutor().run("projection_receipt", {
|
|
2390
|
+
workspacePath,
|
|
2391
|
+
fsyncMounts: projectedFastMounts.map(workspaceGitMountData),
|
|
2392
|
+
receipt: targetProjectionReceipt
|
|
2393
|
+
});
|
|
2362
2394
|
}
|
|
2363
2395
|
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
2364
2396
|
}
|
|
@@ -2525,6 +2557,25 @@ ${push.stdout}`)) {
|
|
|
2525
2557
|
}
|
|
2526
2558
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
2527
2559
|
}
|
|
2560
|
+
function runProjectionMirrorJob(input) {
|
|
2561
|
+
const projected = mirrorMountsToWorkspace(path.resolve(input.workspacePath), [input.mount]);
|
|
2562
|
+
return { gitlinks: projected[0]?.gitlinks ?? [] };
|
|
2563
|
+
}
|
|
2564
|
+
function runProjectionOuterApplyJob(input) {
|
|
2565
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
2566
|
+
removeWorkspaceMounts(workspacePath, input.removeMounts);
|
|
2567
|
+
for (const { workspaceRelativePath, resultTree } of input.materialize) {
|
|
2568
|
+
materializeWorkspaceProjectionTree({ workspacePath, workspaceRelativePath, resultTree });
|
|
2569
|
+
}
|
|
2570
|
+
restoreDeferredWorkspaceMountsFromHead(workspacePath, input.restoreFromHead);
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
function runProjectionReceiptJob(input) {
|
|
2574
|
+
const workspacePath = path.resolve(input.workspacePath);
|
|
2575
|
+
for (const mount of input.fsyncMounts) fsyncHydratedWorkspaceMount(mount);
|
|
2576
|
+
updateHydratedWorkspaceReceipt(workspacePath, input.receipt);
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2528
2579
|
const workspaceGitSyncTestHarness = {
|
|
2529
2580
|
commandArgs: gitCommandArgs,
|
|
2530
2581
|
workspaceCloneCommandArgs,
|
|
@@ -2545,9 +2596,20 @@ export {
|
|
|
2545
2596
|
hydrateWorkspaceGitMounts,
|
|
2546
2597
|
recoverWorkspaceGitHydration,
|
|
2547
2598
|
resetWorkspaceGit,
|
|
2599
|
+
runCheckoutDurabilityRecoveryJob,
|
|
2600
|
+
runCheckoutTransitionBeginJob,
|
|
2601
|
+
runCheckoutTransitionCompleteJob,
|
|
2602
|
+
runHydrationRawJob,
|
|
2603
|
+
runHydrationRecoveryJob,
|
|
2604
|
+
runHydrationTransactionJob,
|
|
2605
|
+
runOuterCheckoutFsyncJob,
|
|
2606
|
+
runProjectionMirrorJob,
|
|
2607
|
+
runProjectionOuterApplyJob,
|
|
2608
|
+
runProjectionReceiptJob,
|
|
2548
2609
|
synchronizeWorkspaceGit,
|
|
2549
2610
|
workspaceGitHydrationIsCurrent,
|
|
2550
2611
|
workspaceGitHydrationIsRecoverable,
|
|
2612
|
+
workspaceGitMountData,
|
|
2551
2613
|
workspaceGitMountsForRemediation,
|
|
2552
2614
|
workspaceGitSyncTestHarness
|
|
2553
2615
|
};
|