@ricsam/r5d-worker 0.0.83 → 0.0.87

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.
@@ -42,6 +42,7 @@ __export(workspace_git_sync_exports, {
42
42
  resetWorkspaceGit: () => resetWorkspaceGit,
43
43
  synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
44
44
  workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
45
+ workspaceGitMountsForRemediation: () => workspaceGitMountsForRemediation,
45
46
  workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
46
47
  });
47
48
  module.exports = __toCommonJS(workspace_git_sync_exports);
@@ -58,6 +59,9 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
58
59
  const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
59
60
  const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
60
61
  const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
62
+ function workspaceGitMountsForRemediation(mounts) {
63
+ return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
64
+ }
61
65
  class WorkspaceRemediationAncestryError extends Error {
62
66
  constructor(message) {
63
67
  super(message);
@@ -103,6 +107,71 @@ function revParse(workspacePath, revision) {
103
107
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
104
108
  return result.exitCode === 0 ? result.stdout : null;
105
109
  }
110
+ async function gitResultAsync(cwd, args) {
111
+ const subprocess = Bun.spawn(gitCommandArgs(args), {
112
+ cwd,
113
+ stdin: "ignore",
114
+ stdout: "pipe",
115
+ stderr: "pipe",
116
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
117
+ });
118
+ const [stdout, stderr, exitCode] = await Promise.all([
119
+ new Response(subprocess.stdout).text(),
120
+ new Response(subprocess.stderr).text(),
121
+ subprocess.exited
122
+ ]);
123
+ return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
124
+ }
125
+ async function gitAsync(cwd, args, action) {
126
+ const result = await gitResultAsync(cwd, args);
127
+ if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
128
+ return result.stdout;
129
+ }
130
+ async function tryGitAsync(cwd, args) {
131
+ return (await gitResultAsync(cwd, args)).exitCode === 0;
132
+ }
133
+ async function revParseAsync(workspacePath, revision) {
134
+ const result = await gitResultAsync(workspacePath, ["rev-parse", "--verify", revision]);
135
+ return result.exitCode === 0 ? result.stdout : null;
136
+ }
137
+ function yieldToWorkerEventLoop() {
138
+ return new Promise((resolve) => setImmediate(resolve));
139
+ }
140
+ function workspaceCommitsExist(workspacePath, heads) {
141
+ const uniqueHeads = [...new Set(heads)];
142
+ if (uniqueHeads.length === 0) return true;
143
+ const batch = Bun.spawnSync(gitCommandArgs(["cat-file", "--batch-check"]), {
144
+ cwd: workspacePath,
145
+ stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
146
+ `).join("")),
147
+ stdout: "pipe",
148
+ stderr: "pipe",
149
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
150
+ });
151
+ if (batch.exitCode !== 0) return false;
152
+ const lines = batch.stdout.toString().split("\n").filter(Boolean);
153
+ return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
154
+ }
155
+ async function workspaceCommitsExistAsync(workspacePath, heads) {
156
+ const uniqueHeads = [...new Set(heads)];
157
+ if (uniqueHeads.length === 0) return true;
158
+ const subprocess = Bun.spawn(gitCommandArgs(["cat-file", "--batch-check"]), {
159
+ cwd: workspacePath,
160
+ stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
161
+ `).join("")),
162
+ stdout: "pipe",
163
+ stderr: "pipe",
164
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
165
+ });
166
+ const [stdout, , exitCode] = await Promise.all([
167
+ new Response(subprocess.stdout).text(),
168
+ new Response(subprocess.stderr).text(),
169
+ subprocess.exited
170
+ ]);
171
+ if (exitCode !== 0) return false;
172
+ const lines = stdout.split("\n").filter(Boolean);
173
+ return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
174
+ }
106
175
  function requiredWorkspaceAncestorHeads(value) {
107
176
  if (value === void 0) return [];
108
177
  if (!Array.isArray(value) || value.length === 0 || value.some((head) => typeof head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head)) || new Set(value).size !== value.length) {
@@ -126,8 +195,8 @@ function assertWorkspaceRemediationAncestry(input) {
126
195
  );
127
196
  }
128
197
  }
129
- function updateIntegratedWorkspaceHead(workspacePath, head) {
130
- git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
198
+ async function updateIntegratedWorkspaceHead(workspacePath, head) {
199
+ await gitAsync(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
131
200
  }
132
201
  function fsyncDirectory(directoryPath) {
133
202
  const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
@@ -231,16 +300,13 @@ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMou
231
300
  return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
232
301
  });
233
302
  }
234
- function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
303
+ function parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content) {
235
304
  const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
236
305
  if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
237
306
  `) {
238
- if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
239
- throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
240
- }
241
307
  const receipt2 = { version: 3, mounts: [] };
242
308
  Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
243
- return receipt2;
309
+ return { receipt: receipt2, headsToVerify: [legacyHead] };
244
310
  }
245
311
  let parsed;
246
312
  try {
@@ -308,14 +374,16 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
308
374
  }
309
375
  const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
310
376
  if (liftedV2Head) receiptHeads.add(liftedV2Head);
311
- for (const head of receiptHeads) {
312
- if (!tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`])) {
313
- throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
314
- }
377
+ return { receipt, headsToVerify: [...receiptHeads] };
378
+ }
379
+ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
380
+ const { receipt, headsToVerify } = parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content);
381
+ if (!workspaceCommitsExist(workspacePath, headsToVerify)) {
382
+ throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
315
383
  }
316
384
  return receipt;
317
385
  }
318
- function readHydratedWorkspaceReceipt(workspacePath) {
386
+ function readHydratedWorkspaceReceiptUnverified(workspacePath) {
319
387
  const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
320
388
  const status = lstatIfExists(receiptPath);
321
389
  if (!status) return null;
@@ -323,7 +391,23 @@ function readHydratedWorkspaceReceipt(workspacePath) {
323
391
  throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
324
392
  }
325
393
  const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
326
- return parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content);
394
+ return { ...parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content), receiptPath };
395
+ }
396
+ function readHydratedWorkspaceReceipt(workspacePath) {
397
+ const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
398
+ if (!unverified) return null;
399
+ if (!workspaceCommitsExist(workspacePath, unverified.headsToVerify)) {
400
+ throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
401
+ }
402
+ return unverified.receipt;
403
+ }
404
+ async function readHydratedWorkspaceReceiptAsync(workspacePath) {
405
+ const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
406
+ if (!unverified) return null;
407
+ if (!await workspaceCommitsExistAsync(workspacePath, unverified.headsToVerify)) {
408
+ throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
409
+ }
410
+ return unverified.receipt;
327
411
  }
328
412
  function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
329
413
  const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
@@ -370,49 +454,142 @@ function desiredWorkspaceBasisRefs(receipt) {
370
454
  }
371
455
  function workspaceBasisRefsMatch(workspacePath, receipt) {
372
456
  const desired = desiredWorkspaceBasisRefs(receipt);
373
- const actualRefs = listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX);
374
- if (actualRefs.length !== desired.size) return false;
375
- return actualRefs.every((ref) => desired.get(ref) === revParse(workspacePath, ref));
457
+ const actual = git(
458
+ workspacePath,
459
+ ["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX],
460
+ `list ${WORKSPACE_GIT_BASIS_REF_PREFIX} refs`
461
+ ).split("\n").filter(Boolean);
462
+ if (actual.length !== desired.size) return false;
463
+ return actual.every((line) => {
464
+ const separator = line.lastIndexOf(" ");
465
+ if (separator <= 0) return false;
466
+ return desired.get(line.slice(0, separator)) === line.slice(separator + 1);
467
+ });
376
468
  }
377
469
  function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
378
470
  return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
379
471
  }
472
+ async function workspaceBasisRefsAreCurrentAsync(workspacePath, receipt) {
473
+ const desired = desiredWorkspaceBasisRefs(receipt);
474
+ const listing = await gitAsync(
475
+ workspacePath,
476
+ ["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
477
+ "list workspace hydration basis refs"
478
+ );
479
+ const stable = /* @__PURE__ */ new Map();
480
+ for (const line of listing.split("\n").filter(Boolean)) {
481
+ const separator = line.lastIndexOf(" ");
482
+ if (separator <= 0) return false;
483
+ const ref = line.slice(0, separator);
484
+ if (ref.startsWith(WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) return false;
485
+ stable.set(ref, line.slice(separator + 1));
486
+ }
487
+ if (stable.size !== desired.size) return false;
488
+ return [...stable].every(([ref, head]) => desired.get(ref) === head);
489
+ }
380
490
  function repairWorkspaceBasisRefs(workspacePath, receipt) {
381
491
  const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
382
492
  if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
383
493
  }
384
- function updateWorkspaceBasisRefs(workspacePath, receipt) {
385
- const desired = desiredWorkspaceBasisRefs(receipt);
386
- const transaction = ["start"];
387
- for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
388
- for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
389
- if (!desired.has(ref)) transaction.push(`delete ${ref}`);
390
- }
391
- transaction.push("prepare", "commit", "");
494
+ function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
495
+ if (instructions.length === 0) return;
392
496
  const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
393
497
  cwd: workspacePath,
394
- stdin: Buffer.from(transaction.join("\n")),
498
+ stdin: Buffer.from([...instructions, ""].join("\n")),
395
499
  stdout: "pipe",
396
500
  stderr: "pipe",
397
501
  env: (0, import_git_process_environment.workerGitProcessEnvironment)()
398
502
  });
399
503
  if (updated.exitCode !== 0) {
400
- throw new Error(`Synchronize workspace hydration basis refs: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
504
+ throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
401
505
  }
402
- for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) {
403
- git(workspacePath, ["update-ref", "-d", ref], "remove staged workspace hydration basis ref");
506
+ }
507
+ async function runWorkspaceRefUpdateBatchAsync(workspacePath, instructions, action) {
508
+ if (instructions.length === 0) return;
509
+ const subprocess = Bun.spawn(gitCommandArgs(["update-ref", "--stdin"]), {
510
+ cwd: workspacePath,
511
+ stdin: Buffer.from([...instructions, ""].join("\n")),
512
+ stdout: "pipe",
513
+ stderr: "pipe",
514
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
515
+ });
516
+ const [, stderr, exitCode] = await Promise.all([
517
+ new Response(subprocess.stdout).text(),
518
+ new Response(subprocess.stderr).text(),
519
+ subprocess.exited
520
+ ]);
521
+ if (exitCode !== 0) {
522
+ throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
404
523
  }
405
524
  }
406
- function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
525
+ function workspaceBasisRefTransaction(receipt, stableRefs) {
526
+ const desired = desiredWorkspaceBasisRefs(receipt);
527
+ const transaction = ["start"];
528
+ for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
529
+ for (const ref of stableRefs) {
530
+ if (!desired.has(ref)) transaction.push(`delete ${ref}`);
531
+ }
532
+ transaction.push("prepare", "commit");
533
+ return transaction;
534
+ }
535
+ function updateWorkspaceBasisRefs(workspacePath, receipt) {
536
+ runWorkspaceRefUpdateBatch(
537
+ workspacePath,
538
+ workspaceBasisRefTransaction(receipt, listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)),
539
+ "Synchronize workspace hydration basis refs"
540
+ );
541
+ runWorkspaceRefUpdateBatch(
542
+ workspacePath,
543
+ listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).map((ref) => `delete ${ref}`),
544
+ "remove staged workspace hydration basis refs"
545
+ );
546
+ }
547
+ async function updateWorkspaceBasisRefsAsync(workspacePath, receipt) {
548
+ const stableRefs = (await gitAsync(workspacePath, ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_REF_PREFIX], "list workspace basis refs")).split("\n").filter(Boolean).sort();
549
+ await runWorkspaceRefUpdateBatchAsync(
550
+ workspacePath,
551
+ workspaceBasisRefTransaction(receipt, stableRefs),
552
+ "Synchronize workspace hydration basis refs"
553
+ );
554
+ const stagedRefs = (await gitAsync(
555
+ workspacePath,
556
+ ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
557
+ "list staged workspace basis refs"
558
+ )).split("\n").filter(Boolean).sort();
559
+ await runWorkspaceRefUpdateBatchAsync(
560
+ workspacePath,
561
+ stagedRefs.map((ref) => `delete ${ref}`),
562
+ "remove staged workspace hydration basis refs"
563
+ );
564
+ }
565
+ function assertCanonicalHydrationReceiptHeads(receipt) {
407
566
  for (const mount of receipt.mounts) {
408
567
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
409
568
  throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
410
569
  }
411
- if (!tryGit(workspacePath, ["cat-file", "-e", `${mount.head}^{commit}`])) {
412
- throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${mount.head}`);
413
- }
570
+ }
571
+ }
572
+ function throwMissingHydrationReceiptCommit(workspacePath, receipt) {
573
+ const missing = receipt.mounts.find(({ head }) => !tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`]));
574
+ throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${missing?.head ?? "unknown"}`);
575
+ }
576
+ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
577
+ assertCanonicalHydrationReceiptHeads(receipt);
578
+ if (!workspaceCommitsExist(workspacePath, receipt.mounts.map(({ head }) => head))) {
579
+ throwMissingHydrationReceiptCommit(workspacePath, receipt);
414
580
  }
415
581
  readHydratedWorkspaceReceipt(workspacePath);
582
+ writeHydratedWorkspaceReceiptCore(workspacePath, receipt);
583
+ }
584
+ async function updateHydratedWorkspaceReceiptAsync(workspacePath, receipt) {
585
+ assertCanonicalHydrationReceiptHeads(receipt);
586
+ if (!await workspaceCommitsExistAsync(workspacePath, receipt.mounts.map(({ head }) => head))) {
587
+ throwMissingHydrationReceiptCommit(workspacePath, receipt);
588
+ }
589
+ await readHydratedWorkspaceReceiptAsync(workspacePath);
590
+ await writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt);
591
+ }
592
+ function prepareHydratedWorkspaceReceiptWrite(workspacePath) {
416
593
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
417
594
  const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
418
595
  if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
@@ -429,13 +606,16 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
429
606
  if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
430
607
  throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
431
608
  }
432
- const temporaryPath = import_node_path.default.join(receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
433
- const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
609
+ return { receiptPath, receiptDirectory };
610
+ }
611
+ function stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix) {
434
612
  desiredWorkspaceBasisRefs(receipt);
435
- for (const mount of receipt.mounts) {
436
- const stagedRef = `${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)}`;
437
- git(workspacePath, ["update-ref", stagedRef, mount.head], "stage workspace hydration basis ref");
438
- }
613
+ return receipt.mounts.map(
614
+ (mount) => `update ${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)} ${mount.head}`
615
+ );
616
+ }
617
+ function publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths) {
618
+ const temporaryPath = import_node_path.default.join(paths.receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
439
619
  let descriptor;
440
620
  try {
441
621
  descriptor = import_node_fs.default.openSync(temporaryPath, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL, 384);
@@ -444,15 +624,36 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
444
624
  import_node_fs.default.fsyncSync(descriptor);
445
625
  import_node_fs.default.closeSync(descriptor);
446
626
  descriptor = void 0;
447
- import_node_fs.default.renameSync(temporaryPath, receiptPath);
448
- fsyncDirectory(receiptDirectory);
449
- updateWorkspaceBasisRefs(workspacePath, receipt);
627
+ import_node_fs.default.renameSync(temporaryPath, paths.receiptPath);
628
+ fsyncDirectory(paths.receiptDirectory);
450
629
  } catch (error) {
451
630
  if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
452
631
  import_node_fs.default.rmSync(temporaryPath, { force: true });
453
632
  throw error;
454
633
  }
455
634
  }
635
+ function writeHydratedWorkspaceReceiptCore(workspacePath, receipt) {
636
+ const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
637
+ const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
638
+ runWorkspaceRefUpdateBatch(
639
+ workspacePath,
640
+ stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
641
+ "stage workspace hydration basis refs"
642
+ );
643
+ publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
644
+ updateWorkspaceBasisRefs(workspacePath, receipt);
645
+ }
646
+ async function writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt) {
647
+ const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
648
+ const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
649
+ await runWorkspaceRefUpdateBatchAsync(
650
+ workspacePath,
651
+ stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
652
+ "stage workspace hydration basis refs"
653
+ );
654
+ publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
655
+ await updateWorkspaceBasisRefsAsync(workspacePath, receipt);
656
+ }
456
657
  function hydrationTransactionPath(workspacePath) {
457
658
  return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
458
659
  }
@@ -504,7 +705,7 @@ function fsyncTree(rootPath) {
504
705
  };
505
706
  syncEntry(rootPath);
506
707
  }
507
- function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, content) {
708
+ function parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, content) {
508
709
  let parsed;
509
710
  try {
510
711
  parsed = JSON.parse(content);
@@ -527,25 +728,34 @@ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, conte
527
728
  `) {
528
729
  throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
529
730
  }
530
- if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
531
- throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
532
- }
533
731
  return record;
534
732
  }
535
- function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
733
+ function readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath) {
536
734
  const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
537
735
  const status = lstatIfExists(recordPath);
538
736
  if (!status) return null;
539
737
  if (!status.isFile() || status.isSymbolicLink()) {
540
738
  throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
541
739
  }
542
- return parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, import_node_fs.default.readFileSync(recordPath, "utf8"));
740
+ return { record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, import_node_fs.default.readFileSync(recordPath, "utf8")), recordPath };
543
741
  }
544
- function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
545
- if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
546
- throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
742
+ function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
743
+ const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
744
+ if (!unverified) return null;
745
+ if (unverified.record.head && !tryGit(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
746
+ throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
547
747
  }
548
- readWorkspaceCheckoutDurabilityRecord(workspacePath);
748
+ return unverified.record;
749
+ }
750
+ async function readWorkspaceCheckoutDurabilityRecordAsync(workspacePath) {
751
+ const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
752
+ if (!unverified) return null;
753
+ if (unverified.record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
754
+ throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
755
+ }
756
+ return unverified.record;
757
+ }
758
+ function writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record) {
549
759
  const stateDirectory = workspacePrivateStateDirectory(workspacePath);
550
760
  const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
551
761
  const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
@@ -559,6 +769,20 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
559
769
  throw error;
560
770
  }
561
771
  }
772
+ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
773
+ if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
774
+ throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
775
+ }
776
+ readWorkspaceCheckoutDurabilityRecord(workspacePath);
777
+ writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
778
+ }
779
+ async function updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, record) {
780
+ if (record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
781
+ throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
782
+ }
783
+ await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
784
+ writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
785
+ }
562
786
  function fsyncWorkspaceCheckoutTree(workspacePath) {
563
787
  const status = import_node_fs.default.lstatSync(workspacePath);
564
788
  if (!status.isDirectory() || status.isSymbolicLink()) {
@@ -570,6 +794,40 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
570
794
  }
571
795
  fsyncDirectory(workspacePath);
572
796
  }
797
+ const WORKSPACE_FSYNC_YIELD_INTERVAL = 64;
798
+ async function fsyncWorkspaceCheckoutTreeYielding(workspacePath) {
799
+ const status = import_node_fs.default.lstatSync(workspacePath);
800
+ if (!status.isDirectory() || status.isSymbolicLink()) {
801
+ throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
802
+ }
803
+ let entriesSinceYield = 0;
804
+ const syncEntry = async (entryPath) => {
805
+ const entryStatus = import_node_fs.default.lstatSync(entryPath);
806
+ if (entryStatus.isSymbolicLink()) return;
807
+ if (entryStatus.isDirectory()) {
808
+ for (const name of import_node_fs.default.readdirSync(entryPath)) await syncEntry(import_node_path.default.join(entryPath, name));
809
+ fsyncDirectory(entryPath);
810
+ return;
811
+ }
812
+ if (!entryStatus.isFile()) return;
813
+ const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
814
+ try {
815
+ import_node_fs.default.fsyncSync(descriptor);
816
+ } finally {
817
+ import_node_fs.default.closeSync(descriptor);
818
+ }
819
+ entriesSinceYield += 1;
820
+ if (entriesSinceYield >= WORKSPACE_FSYNC_YIELD_INTERVAL) {
821
+ entriesSinceYield = 0;
822
+ await yieldToWorkerEventLoop();
823
+ }
824
+ };
825
+ for (const name of import_node_fs.default.readdirSync(workspacePath)) {
826
+ if (name === ".git") continue;
827
+ await syncEntry(import_node_path.default.join(workspacePath, name));
828
+ }
829
+ fsyncDirectory(workspacePath);
830
+ }
573
831
  function workspaceRebaseInProgress(workspacePath) {
574
832
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
575
833
  return import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-merge")) || import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-apply"));
@@ -577,8 +835,8 @@ function workspaceRebaseInProgress(workspacePath) {
577
835
  function workspaceResolutionInProgress(workspacePath) {
578
836
  return workspaceRebaseInProgress(workspacePath) || import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git", "MERGE_HEAD"));
579
837
  }
580
- function workspaceHasUnmergedEntries(workspacePath) {
581
- const result = gitResult(workspacePath, ["ls-files", "-u", "-z"]);
838
+ async function workspaceHasUnmergedEntries(workspacePath) {
839
+ const result = await gitResultAsync(workspacePath, ["ls-files", "-u", "-z"]);
582
840
  if (result.exitCode !== 0) {
583
841
  throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
584
842
  }
@@ -634,17 +892,21 @@ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
634
892
  const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
635
893
  return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
636
894
  }
895
+ async function workspaceCheckoutDurabilityIsCurrentAsync(workspacePath) {
896
+ const record = await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
897
+ return Boolean(record?.state === "durable" && record.head === await revParseAsync(workspacePath, "HEAD"));
898
+ }
637
899
  function beginWorkspaceCheckoutTransition(workspacePath) {
638
900
  recoverWorkspaceCheckoutDurability(workspacePath);
639
901
  const head = revParse(workspacePath, "HEAD");
640
902
  updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
641
903
  }
642
- function completeWorkspaceCheckoutTransition(workspacePath) {
643
- fsyncWorkspaceCheckoutTree(workspacePath);
644
- updateWorkspaceCheckoutDurabilityRecord(workspacePath, {
904
+ async function completeWorkspaceCheckoutTransition(workspacePath) {
905
+ await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
906
+ await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
645
907
  version: 1,
646
908
  state: "durable",
647
- head: revParse(workspacePath, "HEAD")
909
+ head: await revParseAsync(workspacePath, "HEAD")
648
910
  });
649
911
  }
650
912
  function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
@@ -658,11 +920,11 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
658
920
  }
659
921
  throw error;
660
922
  }
661
- function runWorkspaceCheckoutTransition(workspacePath, mutate) {
923
+ async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
662
924
  beginWorkspaceCheckoutTransition(workspacePath);
663
925
  try {
664
- mutate();
665
- completeWorkspaceCheckoutTransition(workspacePath);
926
+ await mutate();
927
+ await completeWorkspaceCheckoutTransition(workspacePath);
666
928
  } catch (error) {
667
929
  throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
668
930
  }
@@ -960,24 +1222,24 @@ function validateMounts(workspacePath, mounts) {
960
1222
  if (overlap) throw new Error(`Workspace mounts overlap: ${current} and ${overlap}`);
961
1223
  }
962
1224
  }
963
- function configureWorkspaceRepository(input) {
964
- if (tryGit(input.workspacePath, ["remote", "get-url", "origin"])) {
965
- git(input.workspacePath, ["remote", "set-url", "origin", input.remoteUrl], "configure workspace origin");
1225
+ async function configureWorkspaceRepository(input) {
1226
+ if (await tryGitAsync(input.workspacePath, ["remote", "get-url", "origin"])) {
1227
+ await gitAsync(input.workspacePath, ["remote", "set-url", "origin", input.remoteUrl], "configure workspace origin");
966
1228
  } else {
967
- git(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
1229
+ await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
968
1230
  }
969
- tryGit(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
970
- git(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
971
- git(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
1231
+ await tryGitAsync(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
1232
+ await gitAsync(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
1233
+ await gitAsync(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
972
1234
  if (input.credentialUsername) {
973
- git(
1235
+ await gitAsync(
974
1236
  input.workspacePath,
975
1237
  ["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.remoteUrl), input.credentialUsername],
976
1238
  "configure workspace credential username"
977
1239
  );
978
1240
  }
979
1241
  if (input.credentialHelper) {
980
- git(
1242
+ await gitAsync(
981
1243
  input.workspacePath,
982
1244
  ["config", "--local", "--add", "credential.helper", input.credentialHelper],
983
1245
  "configure workspace credential helper"
@@ -986,26 +1248,26 @@ function configureWorkspaceRepository(input) {
986
1248
  const name = input.gitIdentity.name.trim();
987
1249
  const email = input.gitIdentity.email.trim();
988
1250
  if (!name || !email) throw new Error("Workspace Git identity must include name and email");
989
- git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
990
- git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
991
- git(
1251
+ await gitAsync(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
1252
+ await gitAsync(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
1253
+ await gitAsync(
992
1254
  input.workspacePath,
993
1255
  ["config", "--local", "core.fsync", "committed,reference"],
994
1256
  "configure durable workspace commits and references"
995
1257
  );
996
- git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
1258
+ await gitAsync(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
997
1259
  }
998
- function configureExistingWorkspaceGitForRemediation(input) {
1260
+ async function configureExistingWorkspaceGitForRemediation(input) {
999
1261
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1000
1262
  const workspaceStatus = lstatIfExists(workspacePath);
1001
1263
  const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
1002
1264
  if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
1003
1265
  throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
1004
1266
  }
1005
- configureWorkspaceRepository({ ...input, workspacePath });
1267
+ await configureWorkspaceRepository({ ...input, workspacePath });
1006
1268
  }
1007
- function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
1008
- const result = gitResult(workspacePath, [
1269
+ async function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
1270
+ const result = await gitResultAsync(workspacePath, [
1009
1271
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
1010
1272
  "fetch",
1011
1273
  "--no-recurse-submodules",
@@ -1024,7 +1286,7 @@ ${result.stdout}`;
1024
1286
  }
1025
1287
  return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1026
1288
  }
1027
- function ensureWorkspaceGitClone(input) {
1289
+ async function ensureWorkspaceGitClone(input) {
1028
1290
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1029
1291
  const workspaceParent = import_node_path.default.dirname(workspacePath);
1030
1292
  const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
@@ -1064,18 +1326,18 @@ function ensureWorkspaceGitClone(input) {
1064
1326
  git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
1065
1327
  }
1066
1328
  }
1067
- configureWorkspaceRepository({ ...input, workspacePath });
1329
+ await configureWorkspaceRepository({ ...input, workspacePath });
1068
1330
  recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
1069
- const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1070
- const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
1071
- if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
1072
- updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
1331
+ const previousRemoteHead = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1332
+ const localHeadBeforeFetch = await revParseAsync(workspacePath, "HEAD");
1333
+ if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
1334
+ await updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
1073
1335
  }
1074
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1075
- let localHead = revParse(workspacePath, "HEAD");
1336
+ const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1337
+ let localHead = await revParseAsync(workspacePath, "HEAD");
1076
1338
  if (!localHead && remoteHead) {
1077
- runWorkspaceCheckoutTransition(workspacePath, () => {
1078
- git(
1339
+ await runWorkspaceCheckoutTransition(workspacePath, async () => {
1340
+ await gitAsync(
1079
1341
  workspacePath,
1080
1342
  ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
1081
1343
  "check out workspace main"
@@ -1084,12 +1346,12 @@ function ensureWorkspaceGitClone(input) {
1084
1346
  localHead = remoteHead;
1085
1347
  }
1086
1348
  if (localHead && remoteHead && localHead === remoteHead) {
1087
- updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1349
+ await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1088
1350
  } else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
1089
- runWorkspaceCheckoutTransition(workspacePath, () => {
1090
- git(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
1351
+ await runWorkspaceCheckoutTransition(workspacePath, async () => {
1352
+ await gitAsync(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
1091
1353
  });
1092
- updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1354
+ await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1093
1355
  localHead = remoteHead;
1094
1356
  }
1095
1357
  return { localHead, remoteHead };
@@ -1213,7 +1475,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
1213
1475
  }
1214
1476
  return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
1215
1477
  }
1216
- function hydrateWorkspaceGitMountsTransactionally(input) {
1478
+ async function hydrateWorkspaceGitMountsTransactionally(input) {
1217
1479
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1218
1480
  const durabilityMounts = input.durabilityMounts ?? input.mounts;
1219
1481
  const receiptMounts = input.receiptMounts ?? durabilityMounts;
@@ -1221,19 +1483,34 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1221
1483
  const candidateMounts = [
1222
1484
  ...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
1223
1485
  ];
1224
- const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
1225
- const requestedIds = new Set(input.mounts.map(({ id }) => id));
1226
- const requiredIds = new Set(requiredMounts.map(({ id }) => id));
1227
- const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
1228
- const advancedDurabilityMounts = durabilityMounts.filter(
1229
- ({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
1230
- );
1231
- const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
1232
- if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
1486
+ if (input.recordCurrentHead && !await workspaceCheckoutDurabilityIsCurrentAsync(workspacePath)) {
1233
1487
  throw new Error("Workspace checkout is not durably materialized at its current HEAD");
1234
1488
  }
1235
- const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
1236
- const targetReceipt = targetHead ? receiptWithMountsAtHead(readHydratedWorkspaceReceipt(workspacePath), targetHead, advancedDurabilityMounts, receiptMounts) : null;
1489
+ const targetHead = input.recordCurrentHead ? await revParseAsync(workspacePath, "HEAD") : null;
1490
+ const receiptBeforeTransaction = targetHead ? await readHydratedWorkspaceReceiptAsync(workspacePath) : null;
1491
+ const computeSelection = () => {
1492
+ const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
1493
+ const requestedIds = new Set(input.mounts.map(({ id }) => id));
1494
+ const requiredIds = new Set(requiredMounts.map(({ id }) => id));
1495
+ const hydrationMounts2 = input.mounts.filter(({ id }) => !busyMountIds.has(id));
1496
+ const advancedDurabilityMounts2 = durabilityMounts.filter(
1497
+ ({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
1498
+ );
1499
+ const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
1500
+ return {
1501
+ busyMountIds,
1502
+ hydrationMounts: hydrationMounts2,
1503
+ advancedDurabilityMounts: advancedDurabilityMounts2,
1504
+ skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
1505
+ targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, advancedDurabilityMounts2, receiptMounts) : null
1506
+ };
1507
+ };
1508
+ const noopCandidate = computeSelection();
1509
+ 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)) {
1510
+ return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
1511
+ }
1512
+ const selection = computeSelection();
1513
+ const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
1237
1514
  const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1238
1515
  try {
1239
1516
  const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
@@ -1247,7 +1524,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1247
1524
  removeHydrationTransaction(workspacePath);
1248
1525
  return {
1249
1526
  hydratedMountIds: hydration.hydratedMountIds,
1250
- skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort()
1527
+ skippedMountIds: selection.skippedMountIds
1251
1528
  };
1252
1529
  } catch (error) {
1253
1530
  const transactionPath = hydrationTransactionPath(workspacePath);
@@ -1266,7 +1543,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1266
1543
  throw error;
1267
1544
  }
1268
1545
  }
1269
- function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1546
+ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1270
1547
  const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
1271
1548
  if (!import_node_fs.default.existsSync(gitDirectory)) {
1272
1549
  return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
@@ -1278,16 +1555,18 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1278
1555
  ignoreBusy: options.ignoreBusy
1279
1556
  });
1280
1557
  }
1281
- function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1558
+ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1282
1559
  workspacePath = import_node_path.default.resolve(workspacePath);
1283
1560
  validateMounts(workspacePath, mounts);
1284
1561
  recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
1285
1562
  recoverPendingHydrationTransaction(workspacePath);
1286
- const currentHead = revParse(workspacePath, "HEAD");
1287
- const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
1288
- repairWorkspaceBasisRefs(workspacePath, currentReceipt);
1563
+ const currentHead = await revParseAsync(workspacePath, "HEAD");
1564
+ const currentReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1565
+ if (!await workspaceBasisRefsAreCurrentAsync(workspacePath, currentReceipt ?? { version: 3, mounts: [] })) {
1566
+ repairWorkspaceBasisRefs(workspacePath, currentReceipt);
1567
+ }
1289
1568
  if (!currentHead) return;
1290
- const outerHeadContainsMount = (mount) => tryGit(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
1569
+ const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
1291
1570
  const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
1292
1571
  const uncoveredBasisMounts = configuredBasisMounts.filter(
1293
1572
  (mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
@@ -1298,10 +1577,15 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1298
1577
  (mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
1299
1578
  );
1300
1579
  const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
1301
- const hydrationTargets = idleBasislessMounts.filter(
1302
- (mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
1303
- );
1304
- const hydration = hydrateWorkspaceGitMountsTransactionally({
1580
+ const hydrationTargets = [];
1581
+ for (const mount of idleBasislessMounts) {
1582
+ if (mount.deleteWhenSourceMissing) continue;
1583
+ if (mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) {
1584
+ continue;
1585
+ }
1586
+ if (await outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true) hydrationTargets.push(mount);
1587
+ }
1588
+ const hydration = await hydrateWorkspaceGitMountsTransactionally({
1305
1589
  workspacePath,
1306
1590
  mounts: hydrationTargets,
1307
1591
  durabilityMounts: idleBasislessMounts,
@@ -1312,27 +1596,31 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1312
1596
  });
1313
1597
  void hydration;
1314
1598
  }
1315
- function resetWorkspaceGit(input) {
1599
+ async function resetWorkspaceGit(input) {
1316
1600
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1317
1601
  validateMounts(workspacePath, input.mounts);
1318
1602
  const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
1319
1603
  if (busyResetMountIds.length > 0) {
1320
1604
  throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
1321
1605
  }
1322
- const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
1606
+ const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
1323
1607
  recoverPendingHydrationTransaction(workspacePath);
1324
1608
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
1325
1609
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
1326
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1610
+ const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1327
1611
  if (remoteHead) {
1328
- runWorkspaceCheckoutTransition(workspacePath, () => {
1329
- git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
1330
- git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
1612
+ await runWorkspaceCheckoutTransition(workspacePath, async () => {
1613
+ await gitAsync(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
1614
+ await gitAsync(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
1331
1615
  });
1332
1616
  } else if (revParse(workspacePath, "HEAD")) {
1333
- runWorkspaceCheckoutTransition(workspacePath, () => {
1334
- git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
1335
- git(workspacePath, ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })], "record empty workspace reset");
1617
+ await runWorkspaceCheckoutTransition(workspacePath, async () => {
1618
+ await gitAsync(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
1619
+ await gitAsync(
1620
+ workspacePath,
1621
+ ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })],
1622
+ "record empty workspace reset"
1623
+ );
1336
1624
  });
1337
1625
  }
1338
1626
  const selected = activeMounts(input.mounts);
@@ -1342,14 +1630,17 @@ function resetWorkspaceGit(input) {
1342
1630
  const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
1343
1631
  return import_node_fs.default.existsSync(outerPath);
1344
1632
  });
1633
+ const bootstrapMountIds = new Set(bootstrapMounts.map(({ id }) => id));
1634
+ const durablyAbsentMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode === "all");
1635
+ const uncoveredSkippedMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode !== "all");
1345
1636
  const hydrationMounts = [...selected.active, ...bootstrapMounts];
1346
1637
  const requiredLiveMounts = [
1347
1638
  ...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
1348
1639
  ];
1349
- const hydration = hydrateWorkspaceGitMountsTransactionally({
1640
+ const hydration = await hydrateWorkspaceGitMountsTransactionally({
1350
1641
  workspacePath,
1351
1642
  mounts: hydrationMounts,
1352
- durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
1643
+ durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
1353
1644
  receiptMounts: input.mounts,
1354
1645
  requiredMounts: requiredLiveMounts,
1355
1646
  recordCurrentHead: true
@@ -1365,11 +1656,11 @@ function resetWorkspaceGit(input) {
1365
1656
  remoteHead,
1366
1657
  discardedPaths,
1367
1658
  activeMountIds: hydration.hydratedMountIds,
1368
- skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1659
+ skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1369
1660
  };
1370
1661
  }
1371
- function stagedPaths(workspacePath) {
1372
- return git(workspacePath, ["diff", "--cached", "--name-only", "--no-renames", "-z"], "inspect staged workspace paths").split("\0").filter(Boolean).sort();
1662
+ async function stagedPaths(workspacePath) {
1663
+ return (await gitAsync(workspacePath, ["diff", "--cached", "--name-only", "--no-renames", "-z"], "inspect staged workspace paths")).split("\0").filter(Boolean).sort();
1373
1664
  }
1374
1665
  function emptyTreeHash(workspacePath) {
1375
1666
  const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "hash-object", "-t", "tree", "--stdin"], {
@@ -1383,9 +1674,9 @@ function emptyTreeHash(workspacePath) {
1383
1674
  throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
1384
1675
  return result.stdout.toString().trim();
1385
1676
  }
1386
- function changedPaths(workspacePath, baseRevision, headRevision) {
1677
+ async function changedPaths(workspacePath, baseRevision, headRevision) {
1387
1678
  const base = baseRevision ?? emptyTreeHash(workspacePath);
1388
- return git(workspacePath, ["diff", "--name-only", "--no-renames", "-z", base, headRevision], "inspect workspace commit paths").split("\0").filter(Boolean).sort();
1679
+ return (await gitAsync(workspacePath, ["diff", "--name-only", "--no-renames", "-z", base, headRevision], "inspect workspace commit paths")).split("\0").filter(Boolean).sort();
1389
1680
  }
1390
1681
  async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
1391
1682
  const base = baseRevision ?? emptyTreeHash(workspacePath);
@@ -1428,15 +1719,15 @@ function snapshotConflict(input) {
1428
1719
  git(input.workspacePath, ["update-ref", refs.remote, input.remoteHead], "snapshot remote workspace conflict head");
1429
1720
  return refs;
1430
1721
  }
1431
- function rebaseWorkspace(input) {
1722
+ async function rebaseWorkspace(input) {
1432
1723
  const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
1433
1724
  beginWorkspaceCheckoutTransition(input.workspacePath);
1434
- const rebase = gitResult(
1725
+ const rebase = await gitResultAsync(
1435
1726
  input.workspacePath,
1436
1727
  input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
1437
1728
  );
1438
1729
  if (rebase.exitCode === 0) {
1439
- completeWorkspaceCheckoutTransition(input.workspacePath);
1730
+ await completeWorkspaceCheckoutTransition(input.workspacePath);
1440
1731
  return { ok: true };
1441
1732
  }
1442
1733
  const conflicts = conflictPaths(input.workspacePath);
@@ -1445,7 +1736,7 @@ function rebaseWorkspace(input) {
1445
1736
  if (abort.exitCode !== 0) {
1446
1737
  throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
1447
1738
  }
1448
- completeWorkspaceCheckoutTransition(input.workspacePath);
1739
+ await completeWorkspaceCheckoutTransition(input.workspacePath);
1449
1740
  return {
1450
1741
  ok: false,
1451
1742
  conflictPaths: conflicts,
@@ -1453,13 +1744,13 @@ function rebaseWorkspace(input) {
1453
1744
  error: rebase.stderr || rebase.stdout || `Workspace rebase onto ${input.remoteHead} conflicted`
1454
1745
  };
1455
1746
  }
1456
- function synchronizeWithFetchedHead(input) {
1457
- const localHead = revParse(input.workspacePath, "HEAD");
1747
+ async function synchronizeWithFetchedHead(input) {
1748
+ const localHead = await revParseAsync(input.workspacePath, "HEAD");
1458
1749
  const remoteHead = input.remoteHead;
1459
1750
  if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
1460
1751
  if (!localHead) {
1461
- runWorkspaceCheckoutTransition(input.workspacePath, () => {
1462
- git(
1752
+ await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
1753
+ await gitAsync(
1463
1754
  input.workspacePath,
1464
1755
  ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
1465
1756
  "check out fetched workspace main"
@@ -1468,35 +1759,35 @@ function synchronizeWithFetchedHead(input) {
1468
1759
  return { kind: "ready", updated: true, rebased: false };
1469
1760
  }
1470
1761
  if (localHead === remoteHead) {
1471
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1762
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1472
1763
  return { kind: "ready", updated: false, rebased: false };
1473
1764
  }
1474
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
1475
- runWorkspaceCheckoutTransition(input.workspacePath, () => {
1476
- git(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
1765
+ if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
1766
+ await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
1767
+ await gitAsync(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
1477
1768
  });
1478
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1769
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1479
1770
  return { kind: "ready", updated: true, rebased: false };
1480
1771
  }
1481
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
1482
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1772
+ if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
1773
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1483
1774
  return { kind: "ready", updated: false, rebased: false };
1484
1775
  }
1485
- const integratedHead = revParse(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
1776
+ const integratedHead = await revParseAsync(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
1486
1777
  const truncatedHistoryBase = workspaceIsShallow(input.workspacePath) && integratedHead && tryGit(input.workspacePath, ["merge-base", "--is-ancestor", integratedHead, localHead]) ? integratedHead : void 0;
1487
1778
  if (workspaceIsShallow(input.workspacePath) && !truncatedHistoryBase) {
1488
1779
  throw new Error(
1489
1780
  "Workspace history was truncated before this clone's integrated base could be identified; reset the workspace clone before retrying"
1490
1781
  );
1491
1782
  }
1492
- const rebased = rebaseWorkspace({
1783
+ const rebased = await rebaseWorkspace({
1493
1784
  workspacePath: input.workspacePath,
1494
1785
  localHead,
1495
1786
  remoteHead,
1496
1787
  attemptId: input.attemptId,
1497
1788
  truncatedHistoryBase
1498
1789
  });
1499
- if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1790
+ if (rebased.ok) await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1500
1791
  return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
1501
1792
  }
1502
1793
  async function synchronizeWorkspaceGit(input) {
@@ -1507,7 +1798,7 @@ async function synchronizeWorkspaceGit(input) {
1507
1798
  validateMounts(workspacePath, input.mounts);
1508
1799
  const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
1509
1800
  const preserveResolutionInProgress = input.skipMountMirror === true;
1510
- const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1801
+ const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1511
1802
  assertWorkspaceRemediationAncestry({
1512
1803
  workspacePath,
1513
1804
  currentHead: initial.localHead,
@@ -1516,7 +1807,7 @@ async function synchronizeWorkspaceGit(input) {
1516
1807
  });
1517
1808
  const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
1518
1809
  const startingHead = initial.localHead;
1519
- const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
1810
+ const receiptBeforeInitialHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
1520
1811
  const deferredMountIds = /* @__PURE__ */ new Set();
1521
1812
  if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
1522
1813
  for (const mount of input.mounts) {
@@ -1525,7 +1816,7 @@ async function synchronizeWorkspaceGit(input) {
1525
1816
  }
1526
1817
  }
1527
1818
  }
1528
- recoverWorkspaceGitHydration(workspacePath, input.mounts, {
1819
+ await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
1529
1820
  ...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
1530
1821
  preserveResolutionInProgress,
1531
1822
  preserveStaleBases: true
@@ -1536,8 +1827,8 @@ async function synchronizeWorkspaceGit(input) {
1536
1827
  );
1537
1828
  const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
1538
1829
  const receiptRequiredLiveMounts = selected.active;
1539
- const projectionBasisHead = revParse(workspacePath, "HEAD");
1540
- const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
1830
+ const projectionBasisHead = await revParseAsync(workspacePath, "HEAD");
1831
+ const projectionBasisReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1541
1832
  const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
1542
1833
  const fastProjectionMounts = [];
1543
1834
  const mergedProjectionTrees = /* @__PURE__ */ new Map();
@@ -1578,6 +1869,10 @@ async function synchronizeWorkspaceGit(input) {
1578
1869
  projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
1579
1870
  continue;
1580
1871
  }
1872
+ if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
1873
+ cycleSkippedMountIds.add(mount.id);
1874
+ continue;
1875
+ }
1581
1876
  const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
1582
1877
  workspacePath,
1583
1878
  mount: {
@@ -1612,14 +1907,15 @@ async function synchronizeWorkspaceGit(input) {
1612
1907
  }
1613
1908
  mergedProjectionTrees.set(mount.id, merged.resultTree);
1614
1909
  mergedProjectionMounts.push(mount);
1910
+ await yieldToWorkerEventLoop();
1615
1911
  }
1616
1912
  }
1617
1913
  if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
1618
1914
  `);
1619
- const completedMountSelection = (requireCurrentHeadReceipt = false) => {
1620
- const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
1915
+ const completedMountSelection = async (requireCurrentHeadReceipt = false) => {
1916
+ const currentHeadBeforeHydration = await revParseAsync(workspacePath, "HEAD");
1621
1917
  const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
1622
- const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
1918
+ const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
1623
1919
  refreshCycleSkippedMounts(completionReceiptMounts);
1624
1920
  const mountIsAlreadyCovered = (mount) => Boolean(
1625
1921
  currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
@@ -1636,7 +1932,7 @@ async function synchronizeWorkspaceGit(input) {
1636
1932
  const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
1637
1933
  (mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
1638
1934
  );
1639
- const hydration = hydrateWorkspaceGitMountsTransactionally({
1935
+ const hydration = await hydrateWorkspaceGitMountsTransactionally({
1640
1936
  workspacePath,
1641
1937
  mounts: uncoveredCompletionMounts,
1642
1938
  durabilityMounts: uncoveredCompletionBasisMounts,
@@ -1647,8 +1943,8 @@ async function synchronizeWorkspaceGit(input) {
1647
1943
  for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
1648
1944
  refreshCycleSkippedMounts(completionReceiptMounts);
1649
1945
  if (requireCurrentHeadReceipt) {
1650
- const currentHead = revParse(workspacePath, "HEAD");
1651
- const receipt = readHydratedWorkspaceReceipt(workspacePath);
1946
+ const currentHead = await revParseAsync(workspacePath, "HEAD");
1947
+ const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1652
1948
  const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
1653
1949
  const idleMounts = completionReceiptMounts.filter(
1654
1950
  (mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
@@ -1660,17 +1956,17 @@ async function synchronizeWorkspaceGit(input) {
1660
1956
  return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
1661
1957
  };
1662
1958
  let newlyStagedPaths = [];
1663
- const stageAndCommitWorkspace = () => {
1664
- if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
1959
+ const stageAndCommitWorkspace = async () => {
1960
+ if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
1665
1961
  throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
1666
1962
  }
1667
- git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
1668
- newlyStagedPaths = stagedPaths(workspacePath);
1963
+ await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
1964
+ newlyStagedPaths = await stagedPaths(workspacePath);
1669
1965
  if (newlyStagedPaths.length > 0) {
1670
- if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
1966
+ if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
1671
1967
  throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
1672
1968
  }
1673
- git(
1969
+ await gitAsync(
1674
1970
  workspacePath,
1675
1971
  [
1676
1972
  "commit",
@@ -1681,64 +1977,77 @@ async function synchronizeWorkspaceGit(input) {
1681
1977
  );
1682
1978
  }
1683
1979
  };
1980
+ const projectedFastMounts = [];
1684
1981
  if (!input.skipMountMirror) {
1685
1982
  beginWorkspaceCheckoutTransition(workspacePath);
1686
1983
  try {
1687
1984
  const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
1688
1985
  const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
1689
1986
  const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
1690
- mirrorMountsToWorkspace(workspacePath, fastActiveMounts);
1987
+ for (const mount of fastActiveMounts) {
1988
+ if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
1989
+ cycleSkippedMountIds.add(mount.id);
1990
+ continue;
1991
+ }
1992
+ mirrorMountsToWorkspace(workspacePath, [mount]);
1993
+ projectedFastMounts.push(mount);
1994
+ await yieldToWorkerEventLoop();
1995
+ }
1691
1996
  removeWorkspaceMounts(workspacePath, fastTombstones);
1997
+ projectedFastMounts.push(...fastTombstones);
1692
1998
  for (const mount of mergedProjectionMounts) {
1693
1999
  (0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
1694
2000
  workspacePath,
1695
2001
  workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
1696
2002
  resultTree: mergedProjectionTrees.get(mount.id)
1697
2003
  });
2004
+ await yieldToWorkerEventLoop();
1698
2005
  }
1699
- const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
1700
2006
  restoreDeferredWorkspaceMountsFromHead(
1701
2007
  workspacePath,
1702
- input.mounts.filter(({ id }) => restoreFromHeadIds.has(id))
2008
+ input.mounts.filter(({ id }) => deferredMountIds.has(id))
1703
2009
  );
1704
- stageAndCommitWorkspace();
1705
- fsyncWorkspaceCheckoutTree(workspacePath);
2010
+ await stageAndCommitWorkspace();
2011
+ await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
1706
2012
  } catch (error) {
1707
2013
  throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
1708
2014
  }
1709
2015
  } else {
1710
- stageAndCommitWorkspace();
1711
- if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
2016
+ await stageAndCommitWorkspace();
2017
+ if (newlyStagedPaths.length > 0) await completeWorkspaceCheckoutTransition(workspacePath);
1712
2018
  }
1713
2019
  if (!input.skipMountMirror) {
1714
- const projectedHead = revParse(workspacePath, "HEAD");
2020
+ const projectedHead = await revParseAsync(workspacePath, "HEAD");
1715
2021
  if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
1716
2022
  const targetProjectionReceipt = receiptWithMountsAtHead(
1717
2023
  projectionBasisReceipt,
1718
2024
  projectedHead,
1719
- fastProjectionMounts,
2025
+ projectedFastMounts,
1720
2026
  allProjectionReceiptMounts
1721
2027
  );
1722
- const currentProjectionReceipt = readHydratedWorkspaceReceipt(workspacePath);
1723
- if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !workspaceBasisRefsAreCurrent(workspacePath, targetProjectionReceipt)) {
1724
- fsyncHydratedWorkspaceMounts(fastProjectionMounts);
1725
- updateHydratedWorkspaceReceipt(workspacePath, targetProjectionReceipt);
2028
+ const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
2029
+ if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
2030
+ for (const mount of projectedFastMounts) {
2031
+ fsyncHydratedWorkspaceMount(mount);
2032
+ await yieldToWorkerEventLoop();
2033
+ }
2034
+ await updateHydratedWorkspaceReceiptAsync(workspacePath, targetProjectionReceipt);
1726
2035
  }
1727
- completeWorkspaceCheckoutTransition(workspacePath);
2036
+ await completeWorkspaceCheckoutTransition(workspacePath);
1728
2037
  }
1729
- let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2038
+ let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1730
2039
  let rebaseCount = 0;
1731
2040
  let updated = false;
1732
2041
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
1733
2042
  assertWorkspaceRemediationAncestry({
1734
2043
  workspacePath,
1735
- currentHead: revParse(workspacePath, "HEAD"),
2044
+ currentHead: await revParseAsync(workspacePath, "HEAD"),
1736
2045
  remoteHead,
1737
2046
  requiredAncestorHeads
1738
2047
  });
1739
- const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
2048
+ const reconciled = await synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
1740
2049
  if (reconciled.kind === "conflict") {
1741
- const localHead2 = revParse(workspacePath, "HEAD");
2050
+ const localHead2 = await revParseAsync(workspacePath, "HEAD");
1742
2051
  const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
1743
2052
  return {
1744
2053
  outcome: "conflict_blocked",
@@ -1759,8 +2068,8 @@ async function synchronizeWorkspaceGit(input) {
1759
2068
  }
1760
2069
  updated ||= reconciled.updated;
1761
2070
  if (reconciled.rebased) rebaseCount += 1;
1762
- if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
1763
- const localHead = revParse(workspacePath, "HEAD");
2071
+ if (reconciled.updated || reconciled.rebased) await completedMountSelection(true);
2072
+ const localHead = await revParseAsync(workspacePath, "HEAD");
1764
2073
  if (!localHead) {
1765
2074
  const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
1766
2075
  return {
@@ -1777,7 +2086,7 @@ async function synchronizeWorkspaceGit(input) {
1777
2086
  ...projectionWarning ? { error: projectionWarning } : {}
1778
2087
  };
1779
2088
  }
1780
- const paths = changedPaths(workspacePath, remoteHead, localHead);
2089
+ const paths = await changedPaths(workspacePath, remoteHead, localHead);
1781
2090
  const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1782
2091
  input.assertStillAdmitted?.();
1783
2092
  if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
@@ -1798,7 +2107,7 @@ async function synchronizeWorkspaceGit(input) {
1798
2107
  }
1799
2108
  if (localHead === remoteHead) {
1800
2109
  assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1801
- const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
2110
+ const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
1802
2111
  await input.afterWorkspacePublished?.({
1803
2112
  publishedHead: localHead,
1804
2113
  activeMountIds: completedMounts.activeMountIds
@@ -1826,11 +2135,11 @@ async function synchronizeWorkspaceGit(input) {
1826
2135
  ];
1827
2136
  if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
1828
2137
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
1829
- const push = gitResult(workspacePath, pushArgs);
2138
+ const push = await gitResultAsync(workspacePath, pushArgs);
1830
2139
  if (push.exitCode === 0) {
1831
2140
  assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1832
- updateIntegratedWorkspaceHead(workspacePath, localHead);
1833
- const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
2141
+ await updateIntegratedWorkspaceHead(workspacePath, localHead);
2142
+ const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
1834
2143
  await input.afterWorkspacePublished?.({
1835
2144
  publishedHead: localHead,
1836
2145
  activeMountIds: completedMounts.activeMountIds
@@ -1854,7 +2163,7 @@ async function synchronizeWorkspaceGit(input) {
1854
2163
  ${push.stdout}`)) {
1855
2164
  throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
1856
2165
  }
1857
- remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2166
+ remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1858
2167
  }
1859
2168
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
1860
2169
  }
@@ -1881,5 +2190,6 @@ const workspaceGitSyncTestHarness = {
1881
2190
  resetWorkspaceGit,
1882
2191
  synchronizeWorkspaceGit,
1883
2192
  workspaceGitHydrationIsCurrent,
2193
+ workspaceGitMountsForRemediation,
1884
2194
  workspaceGitSyncTestHarness
1885
2195
  });