@ricsam/r5d-worker 0.0.86 → 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.
@@ -107,6 +107,71 @@ function revParse(workspacePath, revision) {
107
107
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
108
108
  return result.exitCode === 0 ? result.stdout : null;
109
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
+ }
110
175
  function requiredWorkspaceAncestorHeads(value) {
111
176
  if (value === void 0) return [];
112
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) {
@@ -130,8 +195,8 @@ function assertWorkspaceRemediationAncestry(input) {
130
195
  );
131
196
  }
132
197
  }
133
- function updateIntegratedWorkspaceHead(workspacePath, head) {
134
- 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");
135
200
  }
136
201
  function fsyncDirectory(directoryPath) {
137
202
  const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
@@ -235,16 +300,13 @@ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMou
235
300
  return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
236
301
  });
237
302
  }
238
- function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
303
+ function parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content) {
239
304
  const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
240
305
  if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
241
306
  `) {
242
- if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
243
- throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
244
- }
245
307
  const receipt2 = { version: 3, mounts: [] };
246
308
  Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
247
- return receipt2;
309
+ return { receipt: receipt2, headsToVerify: [legacyHead] };
248
310
  }
249
311
  let parsed;
250
312
  try {
@@ -312,14 +374,16 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
312
374
  }
313
375
  const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
314
376
  if (liftedV2Head) receiptHeads.add(liftedV2Head);
315
- for (const head of receiptHeads) {
316
- if (!tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`])) {
317
- throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
318
- }
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}`);
319
383
  }
320
384
  return receipt;
321
385
  }
322
- function readHydratedWorkspaceReceipt(workspacePath) {
386
+ function readHydratedWorkspaceReceiptUnverified(workspacePath) {
323
387
  const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
324
388
  const status = lstatIfExists(receiptPath);
325
389
  if (!status) return null;
@@ -327,7 +391,23 @@ function readHydratedWorkspaceReceipt(workspacePath) {
327
391
  throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
328
392
  }
329
393
  const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
330
- 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;
331
411
  }
332
412
  function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
333
413
  const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
@@ -374,49 +454,142 @@ function desiredWorkspaceBasisRefs(receipt) {
374
454
  }
375
455
  function workspaceBasisRefsMatch(workspacePath, receipt) {
376
456
  const desired = desiredWorkspaceBasisRefs(receipt);
377
- const actualRefs = listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX);
378
- if (actualRefs.length !== desired.size) return false;
379
- 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
+ });
380
468
  }
381
469
  function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
382
470
  return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
383
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
+ }
384
490
  function repairWorkspaceBasisRefs(workspacePath, receipt) {
385
491
  const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
386
492
  if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
387
493
  }
388
- function updateWorkspaceBasisRefs(workspacePath, receipt) {
389
- const desired = desiredWorkspaceBasisRefs(receipt);
390
- const transaction = ["start"];
391
- for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
392
- for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
393
- if (!desired.has(ref)) transaction.push(`delete ${ref}`);
394
- }
395
- transaction.push("prepare", "commit", "");
494
+ function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
495
+ if (instructions.length === 0) return;
396
496
  const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
397
497
  cwd: workspacePath,
398
- stdin: Buffer.from(transaction.join("\n")),
498
+ stdin: Buffer.from([...instructions, ""].join("\n")),
399
499
  stdout: "pipe",
400
500
  stderr: "pipe",
401
501
  env: (0, import_git_process_environment.workerGitProcessEnvironment)()
402
502
  });
403
503
  if (updated.exitCode !== 0) {
404
- 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}`}`);
405
505
  }
406
- for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) {
407
- 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}`}`);
408
523
  }
409
524
  }
410
- 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) {
411
566
  for (const mount of receipt.mounts) {
412
567
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
413
568
  throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
414
569
  }
415
- if (!tryGit(workspacePath, ["cat-file", "-e", `${mount.head}^{commit}`])) {
416
- throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${mount.head}`);
417
- }
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);
418
580
  }
419
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) {
420
593
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
421
594
  const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
422
595
  if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
@@ -433,13 +606,16 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
433
606
  if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
434
607
  throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
435
608
  }
436
- const temporaryPath = import_node_path.default.join(receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
437
- const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
609
+ return { receiptPath, receiptDirectory };
610
+ }
611
+ function stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix) {
438
612
  desiredWorkspaceBasisRefs(receipt);
439
- for (const mount of receipt.mounts) {
440
- const stagedRef = `${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)}`;
441
- git(workspacePath, ["update-ref", stagedRef, mount.head], "stage workspace hydration basis ref");
442
- }
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`);
443
619
  let descriptor;
444
620
  try {
445
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);
@@ -448,15 +624,36 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
448
624
  import_node_fs.default.fsyncSync(descriptor);
449
625
  import_node_fs.default.closeSync(descriptor);
450
626
  descriptor = void 0;
451
- import_node_fs.default.renameSync(temporaryPath, receiptPath);
452
- fsyncDirectory(receiptDirectory);
453
- updateWorkspaceBasisRefs(workspacePath, receipt);
627
+ import_node_fs.default.renameSync(temporaryPath, paths.receiptPath);
628
+ fsyncDirectory(paths.receiptDirectory);
454
629
  } catch (error) {
455
630
  if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
456
631
  import_node_fs.default.rmSync(temporaryPath, { force: true });
457
632
  throw error;
458
633
  }
459
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
+ }
460
657
  function hydrationTransactionPath(workspacePath) {
461
658
  return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
462
659
  }
@@ -508,7 +705,7 @@ function fsyncTree(rootPath) {
508
705
  };
509
706
  syncEntry(rootPath);
510
707
  }
511
- function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, content) {
708
+ function parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, content) {
512
709
  let parsed;
513
710
  try {
514
711
  parsed = JSON.parse(content);
@@ -531,25 +728,34 @@ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, conte
531
728
  `) {
532
729
  throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
533
730
  }
534
- if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
535
- throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
536
- }
537
731
  return record;
538
732
  }
539
- function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
733
+ function readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath) {
540
734
  const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
541
735
  const status = lstatIfExists(recordPath);
542
736
  if (!status) return null;
543
737
  if (!status.isFile() || status.isSymbolicLink()) {
544
738
  throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
545
739
  }
546
- return parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, import_node_fs.default.readFileSync(recordPath, "utf8"));
740
+ return { record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, import_node_fs.default.readFileSync(recordPath, "utf8")), recordPath };
547
741
  }
548
- function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
549
- if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
550
- 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}`);
551
747
  }
552
- 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) {
553
759
  const stateDirectory = workspacePrivateStateDirectory(workspacePath);
554
760
  const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
555
761
  const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
@@ -563,6 +769,20 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
563
769
  throw error;
564
770
  }
565
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
+ }
566
786
  function fsyncWorkspaceCheckoutTree(workspacePath) {
567
787
  const status = import_node_fs.default.lstatSync(workspacePath);
568
788
  if (!status.isDirectory() || status.isSymbolicLink()) {
@@ -574,6 +794,40 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
574
794
  }
575
795
  fsyncDirectory(workspacePath);
576
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
+ }
577
831
  function workspaceRebaseInProgress(workspacePath) {
578
832
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
579
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"));
@@ -581,8 +835,8 @@ function workspaceRebaseInProgress(workspacePath) {
581
835
  function workspaceResolutionInProgress(workspacePath) {
582
836
  return workspaceRebaseInProgress(workspacePath) || import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git", "MERGE_HEAD"));
583
837
  }
584
- function workspaceHasUnmergedEntries(workspacePath) {
585
- const result = gitResult(workspacePath, ["ls-files", "-u", "-z"]);
838
+ async function workspaceHasUnmergedEntries(workspacePath) {
839
+ const result = await gitResultAsync(workspacePath, ["ls-files", "-u", "-z"]);
586
840
  if (result.exitCode !== 0) {
587
841
  throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
588
842
  }
@@ -638,17 +892,21 @@ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
638
892
  const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
639
893
  return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
640
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
+ }
641
899
  function beginWorkspaceCheckoutTransition(workspacePath) {
642
900
  recoverWorkspaceCheckoutDurability(workspacePath);
643
901
  const head = revParse(workspacePath, "HEAD");
644
902
  updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
645
903
  }
646
- function completeWorkspaceCheckoutTransition(workspacePath) {
647
- fsyncWorkspaceCheckoutTree(workspacePath);
648
- updateWorkspaceCheckoutDurabilityRecord(workspacePath, {
904
+ async function completeWorkspaceCheckoutTransition(workspacePath) {
905
+ await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
906
+ await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
649
907
  version: 1,
650
908
  state: "durable",
651
- head: revParse(workspacePath, "HEAD")
909
+ head: await revParseAsync(workspacePath, "HEAD")
652
910
  });
653
911
  }
654
912
  function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
@@ -662,11 +920,11 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
662
920
  }
663
921
  throw error;
664
922
  }
665
- function runWorkspaceCheckoutTransition(workspacePath, mutate) {
923
+ async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
666
924
  beginWorkspaceCheckoutTransition(workspacePath);
667
925
  try {
668
- mutate();
669
- completeWorkspaceCheckoutTransition(workspacePath);
926
+ await mutate();
927
+ await completeWorkspaceCheckoutTransition(workspacePath);
670
928
  } catch (error) {
671
929
  throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
672
930
  }
@@ -964,24 +1222,24 @@ function validateMounts(workspacePath, mounts) {
964
1222
  if (overlap) throw new Error(`Workspace mounts overlap: ${current} and ${overlap}`);
965
1223
  }
966
1224
  }
967
- function configureWorkspaceRepository(input) {
968
- if (tryGit(input.workspacePath, ["remote", "get-url", "origin"])) {
969
- 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");
970
1228
  } else {
971
- git(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
1229
+ await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
972
1230
  }
973
- tryGit(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
974
- git(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
975
- 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");
976
1234
  if (input.credentialUsername) {
977
- git(
1235
+ await gitAsync(
978
1236
  input.workspacePath,
979
1237
  ["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.remoteUrl), input.credentialUsername],
980
1238
  "configure workspace credential username"
981
1239
  );
982
1240
  }
983
1241
  if (input.credentialHelper) {
984
- git(
1242
+ await gitAsync(
985
1243
  input.workspacePath,
986
1244
  ["config", "--local", "--add", "credential.helper", input.credentialHelper],
987
1245
  "configure workspace credential helper"
@@ -990,26 +1248,26 @@ function configureWorkspaceRepository(input) {
990
1248
  const name = input.gitIdentity.name.trim();
991
1249
  const email = input.gitIdentity.email.trim();
992
1250
  if (!name || !email) throw new Error("Workspace Git identity must include name and email");
993
- git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
994
- git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
995
- 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(
996
1254
  input.workspacePath,
997
1255
  ["config", "--local", "core.fsync", "committed,reference"],
998
1256
  "configure durable workspace commits and references"
999
1257
  );
1000
- 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");
1001
1259
  }
1002
- function configureExistingWorkspaceGitForRemediation(input) {
1260
+ async function configureExistingWorkspaceGitForRemediation(input) {
1003
1261
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1004
1262
  const workspaceStatus = lstatIfExists(workspacePath);
1005
1263
  const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
1006
1264
  if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
1007
1265
  throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
1008
1266
  }
1009
- configureWorkspaceRepository({ ...input, workspacePath });
1267
+ await configureWorkspaceRepository({ ...input, workspacePath });
1010
1268
  }
1011
- function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
1012
- const result = gitResult(workspacePath, [
1269
+ async function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
1270
+ const result = await gitResultAsync(workspacePath, [
1013
1271
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
1014
1272
  "fetch",
1015
1273
  "--no-recurse-submodules",
@@ -1028,7 +1286,7 @@ ${result.stdout}`;
1028
1286
  }
1029
1287
  return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1030
1288
  }
1031
- function ensureWorkspaceGitClone(input) {
1289
+ async function ensureWorkspaceGitClone(input) {
1032
1290
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1033
1291
  const workspaceParent = import_node_path.default.dirname(workspacePath);
1034
1292
  const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
@@ -1068,18 +1326,18 @@ function ensureWorkspaceGitClone(input) {
1068
1326
  git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
1069
1327
  }
1070
1328
  }
1071
- configureWorkspaceRepository({ ...input, workspacePath });
1329
+ await configureWorkspaceRepository({ ...input, workspacePath });
1072
1330
  recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
1073
- const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1074
- const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
1075
- if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
1076
- 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);
1077
1335
  }
1078
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1079
- let localHead = revParse(workspacePath, "HEAD");
1336
+ const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1337
+ let localHead = await revParseAsync(workspacePath, "HEAD");
1080
1338
  if (!localHead && remoteHead) {
1081
- runWorkspaceCheckoutTransition(workspacePath, () => {
1082
- git(
1339
+ await runWorkspaceCheckoutTransition(workspacePath, async () => {
1340
+ await gitAsync(
1083
1341
  workspacePath,
1084
1342
  ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
1085
1343
  "check out workspace main"
@@ -1088,12 +1346,12 @@ function ensureWorkspaceGitClone(input) {
1088
1346
  localHead = remoteHead;
1089
1347
  }
1090
1348
  if (localHead && remoteHead && localHead === remoteHead) {
1091
- updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1349
+ await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1092
1350
  } else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
1093
- runWorkspaceCheckoutTransition(workspacePath, () => {
1094
- 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");
1095
1353
  });
1096
- updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1354
+ await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1097
1355
  localHead = remoteHead;
1098
1356
  }
1099
1357
  return { localHead, remoteHead };
@@ -1217,7 +1475,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
1217
1475
  }
1218
1476
  return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
1219
1477
  }
1220
- function hydrateWorkspaceGitMountsTransactionally(input) {
1478
+ async function hydrateWorkspaceGitMountsTransactionally(input) {
1221
1479
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1222
1480
  const durabilityMounts = input.durabilityMounts ?? input.mounts;
1223
1481
  const receiptMounts = input.receiptMounts ?? durabilityMounts;
@@ -1225,19 +1483,34 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1225
1483
  const candidateMounts = [
1226
1484
  ...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
1227
1485
  ];
1228
- const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
1229
- const requestedIds = new Set(input.mounts.map(({ id }) => id));
1230
- const requiredIds = new Set(requiredMounts.map(({ id }) => id));
1231
- const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
1232
- const advancedDurabilityMounts = durabilityMounts.filter(
1233
- ({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
1234
- );
1235
- const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
1236
- if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
1486
+ if (input.recordCurrentHead && !await workspaceCheckoutDurabilityIsCurrentAsync(workspacePath)) {
1237
1487
  throw new Error("Workspace checkout is not durably materialized at its current HEAD");
1238
1488
  }
1239
- const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
1240
- 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;
1241
1514
  const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1242
1515
  try {
1243
1516
  const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
@@ -1251,7 +1524,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1251
1524
  removeHydrationTransaction(workspacePath);
1252
1525
  return {
1253
1526
  hydratedMountIds: hydration.hydratedMountIds,
1254
- skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort()
1527
+ skippedMountIds: selection.skippedMountIds
1255
1528
  };
1256
1529
  } catch (error) {
1257
1530
  const transactionPath = hydrationTransactionPath(workspacePath);
@@ -1270,7 +1543,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
1270
1543
  throw error;
1271
1544
  }
1272
1545
  }
1273
- function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1546
+ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1274
1547
  const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
1275
1548
  if (!import_node_fs.default.existsSync(gitDirectory)) {
1276
1549
  return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
@@ -1282,16 +1555,18 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1282
1555
  ignoreBusy: options.ignoreBusy
1283
1556
  });
1284
1557
  }
1285
- function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1558
+ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1286
1559
  workspacePath = import_node_path.default.resolve(workspacePath);
1287
1560
  validateMounts(workspacePath, mounts);
1288
1561
  recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
1289
1562
  recoverPendingHydrationTransaction(workspacePath);
1290
- const currentHead = revParse(workspacePath, "HEAD");
1291
- const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
1292
- 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
+ }
1293
1568
  if (!currentHead) return;
1294
- 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)}`]);
1295
1570
  const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
1296
1571
  const uncoveredBasisMounts = configuredBasisMounts.filter(
1297
1572
  (mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
@@ -1302,10 +1577,15 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1302
1577
  (mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
1303
1578
  );
1304
1579
  const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
1305
- const hydrationTargets = idleBasislessMounts.filter(
1306
- (mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
1307
- );
1308
- 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({
1309
1589
  workspacePath,
1310
1590
  mounts: hydrationTargets,
1311
1591
  durabilityMounts: idleBasislessMounts,
@@ -1316,27 +1596,31 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1316
1596
  });
1317
1597
  void hydration;
1318
1598
  }
1319
- function resetWorkspaceGit(input) {
1599
+ async function resetWorkspaceGit(input) {
1320
1600
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1321
1601
  validateMounts(workspacePath, input.mounts);
1322
1602
  const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
1323
1603
  if (busyResetMountIds.length > 0) {
1324
1604
  throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
1325
1605
  }
1326
- const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
1606
+ const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
1327
1607
  recoverPendingHydrationTransaction(workspacePath);
1328
1608
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
1329
1609
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
1330
- const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1610
+ const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1331
1611
  if (remoteHead) {
1332
- runWorkspaceCheckoutTransition(workspacePath, () => {
1333
- git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
1334
- 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");
1335
1615
  });
1336
1616
  } else if (revParse(workspacePath, "HEAD")) {
1337
- runWorkspaceCheckoutTransition(workspacePath, () => {
1338
- git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
1339
- 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
+ );
1340
1624
  });
1341
1625
  }
1342
1626
  const selected = activeMounts(input.mounts);
@@ -1353,7 +1637,7 @@ function resetWorkspaceGit(input) {
1353
1637
  const requiredLiveMounts = [
1354
1638
  ...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
1355
1639
  ];
1356
- const hydration = hydrateWorkspaceGitMountsTransactionally({
1640
+ const hydration = await hydrateWorkspaceGitMountsTransactionally({
1357
1641
  workspacePath,
1358
1642
  mounts: hydrationMounts,
1359
1643
  durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
@@ -1375,8 +1659,8 @@ function resetWorkspaceGit(input) {
1375
1659
  skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1376
1660
  };
1377
1661
  }
1378
- function stagedPaths(workspacePath) {
1379
- 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();
1380
1664
  }
1381
1665
  function emptyTreeHash(workspacePath) {
1382
1666
  const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "hash-object", "-t", "tree", "--stdin"], {
@@ -1390,9 +1674,9 @@ function emptyTreeHash(workspacePath) {
1390
1674
  throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
1391
1675
  return result.stdout.toString().trim();
1392
1676
  }
1393
- function changedPaths(workspacePath, baseRevision, headRevision) {
1677
+ async function changedPaths(workspacePath, baseRevision, headRevision) {
1394
1678
  const base = baseRevision ?? emptyTreeHash(workspacePath);
1395
- 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();
1396
1680
  }
1397
1681
  async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
1398
1682
  const base = baseRevision ?? emptyTreeHash(workspacePath);
@@ -1435,15 +1719,15 @@ function snapshotConflict(input) {
1435
1719
  git(input.workspacePath, ["update-ref", refs.remote, input.remoteHead], "snapshot remote workspace conflict head");
1436
1720
  return refs;
1437
1721
  }
1438
- function rebaseWorkspace(input) {
1722
+ async function rebaseWorkspace(input) {
1439
1723
  const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
1440
1724
  beginWorkspaceCheckoutTransition(input.workspacePath);
1441
- const rebase = gitResult(
1725
+ const rebase = await gitResultAsync(
1442
1726
  input.workspacePath,
1443
1727
  input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
1444
1728
  );
1445
1729
  if (rebase.exitCode === 0) {
1446
- completeWorkspaceCheckoutTransition(input.workspacePath);
1730
+ await completeWorkspaceCheckoutTransition(input.workspacePath);
1447
1731
  return { ok: true };
1448
1732
  }
1449
1733
  const conflicts = conflictPaths(input.workspacePath);
@@ -1452,7 +1736,7 @@ function rebaseWorkspace(input) {
1452
1736
  if (abort.exitCode !== 0) {
1453
1737
  throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
1454
1738
  }
1455
- completeWorkspaceCheckoutTransition(input.workspacePath);
1739
+ await completeWorkspaceCheckoutTransition(input.workspacePath);
1456
1740
  return {
1457
1741
  ok: false,
1458
1742
  conflictPaths: conflicts,
@@ -1460,13 +1744,13 @@ function rebaseWorkspace(input) {
1460
1744
  error: rebase.stderr || rebase.stdout || `Workspace rebase onto ${input.remoteHead} conflicted`
1461
1745
  };
1462
1746
  }
1463
- function synchronizeWithFetchedHead(input) {
1464
- const localHead = revParse(input.workspacePath, "HEAD");
1747
+ async function synchronizeWithFetchedHead(input) {
1748
+ const localHead = await revParseAsync(input.workspacePath, "HEAD");
1465
1749
  const remoteHead = input.remoteHead;
1466
1750
  if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
1467
1751
  if (!localHead) {
1468
- runWorkspaceCheckoutTransition(input.workspacePath, () => {
1469
- git(
1752
+ await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
1753
+ await gitAsync(
1470
1754
  input.workspacePath,
1471
1755
  ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
1472
1756
  "check out fetched workspace main"
@@ -1475,35 +1759,35 @@ function synchronizeWithFetchedHead(input) {
1475
1759
  return { kind: "ready", updated: true, rebased: false };
1476
1760
  }
1477
1761
  if (localHead === remoteHead) {
1478
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1762
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1479
1763
  return { kind: "ready", updated: false, rebased: false };
1480
1764
  }
1481
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
1482
- runWorkspaceCheckoutTransition(input.workspacePath, () => {
1483
- 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");
1484
1768
  });
1485
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1769
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1486
1770
  return { kind: "ready", updated: true, rebased: false };
1487
1771
  }
1488
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
1489
- updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1772
+ if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
1773
+ await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1490
1774
  return { kind: "ready", updated: false, rebased: false };
1491
1775
  }
1492
- const integratedHead = revParse(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
1776
+ const integratedHead = await revParseAsync(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
1493
1777
  const truncatedHistoryBase = workspaceIsShallow(input.workspacePath) && integratedHead && tryGit(input.workspacePath, ["merge-base", "--is-ancestor", integratedHead, localHead]) ? integratedHead : void 0;
1494
1778
  if (workspaceIsShallow(input.workspacePath) && !truncatedHistoryBase) {
1495
1779
  throw new Error(
1496
1780
  "Workspace history was truncated before this clone's integrated base could be identified; reset the workspace clone before retrying"
1497
1781
  );
1498
1782
  }
1499
- const rebased = rebaseWorkspace({
1783
+ const rebased = await rebaseWorkspace({
1500
1784
  workspacePath: input.workspacePath,
1501
1785
  localHead,
1502
1786
  remoteHead,
1503
1787
  attemptId: input.attemptId,
1504
1788
  truncatedHistoryBase
1505
1789
  });
1506
- if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1790
+ if (rebased.ok) await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
1507
1791
  return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
1508
1792
  }
1509
1793
  async function synchronizeWorkspaceGit(input) {
@@ -1514,7 +1798,7 @@ async function synchronizeWorkspaceGit(input) {
1514
1798
  validateMounts(workspacePath, input.mounts);
1515
1799
  const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
1516
1800
  const preserveResolutionInProgress = input.skipMountMirror === true;
1517
- const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1801
+ const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1518
1802
  assertWorkspaceRemediationAncestry({
1519
1803
  workspacePath,
1520
1804
  currentHead: initial.localHead,
@@ -1523,7 +1807,7 @@ async function synchronizeWorkspaceGit(input) {
1523
1807
  });
1524
1808
  const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
1525
1809
  const startingHead = initial.localHead;
1526
- const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
1810
+ const receiptBeforeInitialHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
1527
1811
  const deferredMountIds = /* @__PURE__ */ new Set();
1528
1812
  if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
1529
1813
  for (const mount of input.mounts) {
@@ -1532,7 +1816,7 @@ async function synchronizeWorkspaceGit(input) {
1532
1816
  }
1533
1817
  }
1534
1818
  }
1535
- recoverWorkspaceGitHydration(workspacePath, input.mounts, {
1819
+ await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
1536
1820
  ...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
1537
1821
  preserveResolutionInProgress,
1538
1822
  preserveStaleBases: true
@@ -1543,8 +1827,8 @@ async function synchronizeWorkspaceGit(input) {
1543
1827
  );
1544
1828
  const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
1545
1829
  const receiptRequiredLiveMounts = selected.active;
1546
- const projectionBasisHead = revParse(workspacePath, "HEAD");
1547
- const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
1830
+ const projectionBasisHead = await revParseAsync(workspacePath, "HEAD");
1831
+ const projectionBasisReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1548
1832
  const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
1549
1833
  const fastProjectionMounts = [];
1550
1834
  const mergedProjectionTrees = /* @__PURE__ */ new Map();
@@ -1585,6 +1869,10 @@ async function synchronizeWorkspaceGit(input) {
1585
1869
  projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
1586
1870
  continue;
1587
1871
  }
1872
+ if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
1873
+ cycleSkippedMountIds.add(mount.id);
1874
+ continue;
1875
+ }
1588
1876
  const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
1589
1877
  workspacePath,
1590
1878
  mount: {
@@ -1619,14 +1907,15 @@ async function synchronizeWorkspaceGit(input) {
1619
1907
  }
1620
1908
  mergedProjectionTrees.set(mount.id, merged.resultTree);
1621
1909
  mergedProjectionMounts.push(mount);
1910
+ await yieldToWorkerEventLoop();
1622
1911
  }
1623
1912
  }
1624
1913
  if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
1625
1914
  `);
1626
- const completedMountSelection = (requireCurrentHeadReceipt = false) => {
1627
- const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
1915
+ const completedMountSelection = async (requireCurrentHeadReceipt = false) => {
1916
+ const currentHeadBeforeHydration = await revParseAsync(workspacePath, "HEAD");
1628
1917
  const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
1629
- const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
1918
+ const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
1630
1919
  refreshCycleSkippedMounts(completionReceiptMounts);
1631
1920
  const mountIsAlreadyCovered = (mount) => Boolean(
1632
1921
  currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
@@ -1643,7 +1932,7 @@ async function synchronizeWorkspaceGit(input) {
1643
1932
  const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
1644
1933
  (mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
1645
1934
  );
1646
- const hydration = hydrateWorkspaceGitMountsTransactionally({
1935
+ const hydration = await hydrateWorkspaceGitMountsTransactionally({
1647
1936
  workspacePath,
1648
1937
  mounts: uncoveredCompletionMounts,
1649
1938
  durabilityMounts: uncoveredCompletionBasisMounts,
@@ -1654,8 +1943,8 @@ async function synchronizeWorkspaceGit(input) {
1654
1943
  for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
1655
1944
  refreshCycleSkippedMounts(completionReceiptMounts);
1656
1945
  if (requireCurrentHeadReceipt) {
1657
- const currentHead = revParse(workspacePath, "HEAD");
1658
- const receipt = readHydratedWorkspaceReceipt(workspacePath);
1946
+ const currentHead = await revParseAsync(workspacePath, "HEAD");
1947
+ const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1659
1948
  const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
1660
1949
  const idleMounts = completionReceiptMounts.filter(
1661
1950
  (mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
@@ -1667,17 +1956,17 @@ async function synchronizeWorkspaceGit(input) {
1667
1956
  return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
1668
1957
  };
1669
1958
  let newlyStagedPaths = [];
1670
- const stageAndCommitWorkspace = () => {
1671
- if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
1959
+ const stageAndCommitWorkspace = async () => {
1960
+ if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
1672
1961
  throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
1673
1962
  }
1674
- git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
1675
- newlyStagedPaths = stagedPaths(workspacePath);
1963
+ await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
1964
+ newlyStagedPaths = await stagedPaths(workspacePath);
1676
1965
  if (newlyStagedPaths.length > 0) {
1677
- if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
1966
+ if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
1678
1967
  throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
1679
1968
  }
1680
- git(
1969
+ await gitAsync(
1681
1970
  workspacePath,
1682
1971
  [
1683
1972
  "commit",
@@ -1688,64 +1977,77 @@ async function synchronizeWorkspaceGit(input) {
1688
1977
  );
1689
1978
  }
1690
1979
  };
1980
+ const projectedFastMounts = [];
1691
1981
  if (!input.skipMountMirror) {
1692
1982
  beginWorkspaceCheckoutTransition(workspacePath);
1693
1983
  try {
1694
1984
  const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
1695
1985
  const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
1696
1986
  const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
1697
- 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
+ }
1698
1996
  removeWorkspaceMounts(workspacePath, fastTombstones);
1997
+ projectedFastMounts.push(...fastTombstones);
1699
1998
  for (const mount of mergedProjectionMounts) {
1700
1999
  (0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
1701
2000
  workspacePath,
1702
2001
  workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
1703
2002
  resultTree: mergedProjectionTrees.get(mount.id)
1704
2003
  });
2004
+ await yieldToWorkerEventLoop();
1705
2005
  }
1706
- const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
1707
2006
  restoreDeferredWorkspaceMountsFromHead(
1708
2007
  workspacePath,
1709
- input.mounts.filter(({ id }) => restoreFromHeadIds.has(id))
2008
+ input.mounts.filter(({ id }) => deferredMountIds.has(id))
1710
2009
  );
1711
- stageAndCommitWorkspace();
1712
- fsyncWorkspaceCheckoutTree(workspacePath);
2010
+ await stageAndCommitWorkspace();
2011
+ await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
1713
2012
  } catch (error) {
1714
2013
  throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
1715
2014
  }
1716
2015
  } else {
1717
- stageAndCommitWorkspace();
1718
- if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
2016
+ await stageAndCommitWorkspace();
2017
+ if (newlyStagedPaths.length > 0) await completeWorkspaceCheckoutTransition(workspacePath);
1719
2018
  }
1720
2019
  if (!input.skipMountMirror) {
1721
- const projectedHead = revParse(workspacePath, "HEAD");
2020
+ const projectedHead = await revParseAsync(workspacePath, "HEAD");
1722
2021
  if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
1723
2022
  const targetProjectionReceipt = receiptWithMountsAtHead(
1724
2023
  projectionBasisReceipt,
1725
2024
  projectedHead,
1726
- fastProjectionMounts,
2025
+ projectedFastMounts,
1727
2026
  allProjectionReceiptMounts
1728
2027
  );
1729
- const currentProjectionReceipt = readHydratedWorkspaceReceipt(workspacePath);
1730
- if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !workspaceBasisRefsAreCurrent(workspacePath, targetProjectionReceipt)) {
1731
- fsyncHydratedWorkspaceMounts(fastProjectionMounts);
1732
- 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);
1733
2035
  }
1734
- completeWorkspaceCheckoutTransition(workspacePath);
2036
+ await completeWorkspaceCheckoutTransition(workspacePath);
1735
2037
  }
1736
- let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2038
+ let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1737
2039
  let rebaseCount = 0;
1738
2040
  let updated = false;
1739
2041
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
1740
2042
  assertWorkspaceRemediationAncestry({
1741
2043
  workspacePath,
1742
- currentHead: revParse(workspacePath, "HEAD"),
2044
+ currentHead: await revParseAsync(workspacePath, "HEAD"),
1743
2045
  remoteHead,
1744
2046
  requiredAncestorHeads
1745
2047
  });
1746
- const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
2048
+ const reconciled = await synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
1747
2049
  if (reconciled.kind === "conflict") {
1748
- const localHead2 = revParse(workspacePath, "HEAD");
2050
+ const localHead2 = await revParseAsync(workspacePath, "HEAD");
1749
2051
  const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
1750
2052
  return {
1751
2053
  outcome: "conflict_blocked",
@@ -1766,8 +2068,8 @@ async function synchronizeWorkspaceGit(input) {
1766
2068
  }
1767
2069
  updated ||= reconciled.updated;
1768
2070
  if (reconciled.rebased) rebaseCount += 1;
1769
- if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
1770
- const localHead = revParse(workspacePath, "HEAD");
2071
+ if (reconciled.updated || reconciled.rebased) await completedMountSelection(true);
2072
+ const localHead = await revParseAsync(workspacePath, "HEAD");
1771
2073
  if (!localHead) {
1772
2074
  const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
1773
2075
  return {
@@ -1784,7 +2086,7 @@ async function synchronizeWorkspaceGit(input) {
1784
2086
  ...projectionWarning ? { error: projectionWarning } : {}
1785
2087
  };
1786
2088
  }
1787
- const paths = changedPaths(workspacePath, remoteHead, localHead);
2089
+ const paths = await changedPaths(workspacePath, remoteHead, localHead);
1788
2090
  const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1789
2091
  input.assertStillAdmitted?.();
1790
2092
  if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
@@ -1805,7 +2107,7 @@ async function synchronizeWorkspaceGit(input) {
1805
2107
  }
1806
2108
  if (localHead === remoteHead) {
1807
2109
  assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1808
- const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
2110
+ const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
1809
2111
  await input.afterWorkspacePublished?.({
1810
2112
  publishedHead: localHead,
1811
2113
  activeMountIds: completedMounts.activeMountIds
@@ -1833,11 +2135,11 @@ async function synchronizeWorkspaceGit(input) {
1833
2135
  ];
1834
2136
  if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
1835
2137
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
1836
- const push = gitResult(workspacePath, pushArgs);
2138
+ const push = await gitResultAsync(workspacePath, pushArgs);
1837
2139
  if (push.exitCode === 0) {
1838
2140
  assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1839
- updateIntegratedWorkspaceHead(workspacePath, localHead);
1840
- const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
2141
+ await updateIntegratedWorkspaceHead(workspacePath, localHead);
2142
+ const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
1841
2143
  await input.afterWorkspacePublished?.({
1842
2144
  publishedHead: localHead,
1843
2145
  activeMountIds: completedMounts.activeMountIds
@@ -1861,7 +2163,7 @@ async function synchronizeWorkspaceGit(input) {
1861
2163
  ${push.stdout}`)) {
1862
2164
  throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
1863
2165
  }
1864
- remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2166
+ remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1865
2167
  }
1866
2168
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
1867
2169
  }