@ricsam/r5d-worker 0.0.96 → 0.0.99

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.96",
3
+ "version": "0.0.99",
4
4
  "type": "commonjs"
5
5
  }
@@ -29,8 +29,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var working_tree_mirror_exports = {};
30
30
  __export(working_tree_mirror_exports, {
31
31
  assertWorkingTreeHasNoPortableGitMetadataAliases: () => assertWorkingTreeHasNoPortableGitMetadataAliases,
32
+ captureWorkingTreeMirrorPlan: () => captureWorkingTreeMirrorPlan,
33
+ fsyncWorkingTreePaths: () => fsyncWorkingTreePaths,
32
34
  inspectWorkingTree: () => inspectWorkingTree,
33
- mirrorWorkingTree: () => mirrorWorkingTree
35
+ mirrorWorkingTree: () => mirrorWorkingTree,
36
+ normalizeWorkingTreeRelativePath: () => normalizeWorkingTreeRelativePath,
37
+ planWorkingTreeMirror: () => planWorkingTreeMirror,
38
+ restoreWorkingTreeMirrorScope: () => restoreWorkingTreeMirrorScope
34
39
  });
35
40
  module.exports = __toCommonJS(working_tree_mirror_exports);
36
41
  var import_node_fs = __toESM(require("node:fs"), 1);
@@ -186,14 +191,55 @@ function addParentDirectories(entries, root) {
186
191
  }
187
192
  }
188
193
  }
189
- function inspectWorkingTree(root, mode) {
194
+ function gitlinkPaths(root) {
195
+ const result = Bun.spawnSync(
196
+ [
197
+ "git",
198
+ "-c",
199
+ "submodule.recurse=false",
200
+ "-c",
201
+ "fetch.recurseSubmodules=false",
202
+ "-c",
203
+ "push.recurseSubmodules=false",
204
+ "ls-files",
205
+ "-z",
206
+ "--stage",
207
+ "--",
208
+ "."
209
+ ],
210
+ {
211
+ cwd: root,
212
+ stdout: "pipe",
213
+ stderr: "pipe",
214
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
215
+ }
216
+ );
217
+ if (result.exitCode !== 0) {
218
+ throw new Error(`Inspect Git index gitlinks: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
219
+ }
220
+ const gitlinks = /* @__PURE__ */ new Set();
221
+ for (const record of result.stdout.toString().split("\0")) {
222
+ const match = /^160000 [0-9a-f]+ [0-3]\t(.+)$/su.exec(record);
223
+ if (!match) continue;
224
+ const normalized = normalizeRelativePath(match[1]);
225
+ if (normalized) gitlinks.add(normalized);
226
+ }
227
+ return gitlinks;
228
+ }
229
+ function directoryIsEmpty(root, relativePath) {
230
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, relativePath);
231
+ return inspected.kind === "entry" && inspected.stat.isDirectory() && import_node_fs.default.readdirSync(inspected.absolutePath).length === 0;
232
+ }
233
+ function inspectWorkingTree(root, mode, options = {}) {
190
234
  const entries = /* @__PURE__ */ new Map();
191
235
  root = import_node_path.default.resolve(root);
192
236
  if (!inspectDirectoryRoot(root, { allowMissing: true, label: "Working-tree root" })) return entries;
193
237
  if (mode === "all") {
194
238
  for (const child of import_node_fs.default.readdirSync(root).sort()) addEntry(entries, root, child, true);
195
239
  } else {
240
+ const omittedGitlinks = options.omitUnpopulatedGitlinks ? gitlinkPaths(root) : null;
196
241
  for (const relativePath of gitEligibleRoots(root)) {
242
+ if (omittedGitlinks?.has(relativePath) && directoryIsEmpty(root, relativePath)) continue;
197
243
  addEntry(entries, root, relativePath, true);
198
244
  }
199
245
  }
@@ -367,7 +413,9 @@ function mirrorWorkingTree(input) {
367
413
  }
368
414
  inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
369
415
  const desired = inspectWorkingTree(sourceRoot, input.sourceMode);
370
- const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all");
416
+ const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all", {
417
+ omitUnpopulatedGitlinks: input.deletionMode === "git"
418
+ });
371
419
  for (const relativePath of [...existing.keys()].sort((left, right) => right.split("/").length - left.split("/").length)) {
372
420
  if (!desired.has(relativePath)) removeEntry(targetRoot, relativePath);
373
421
  }
@@ -378,9 +426,213 @@ function mirrorWorkingTree(input) {
378
426
  }
379
427
  return { paths: [...desired.keys()].filter((relativePath) => desired.get(relativePath)?.kind !== "directory").sort() };
380
428
  }
429
+ function normalizeWorkingTreeRelativePath(value) {
430
+ return normalizeRelativePath(value);
431
+ }
432
+ const MIRROR_TEMPORARY_FILE_PATTERN = /^\.r5d-working-tree-mirror-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/u;
433
+ function relativeDepth(relativePath) {
434
+ return relativePath.split("/").length;
435
+ }
436
+ function shallowestFirst(left, right) {
437
+ return relativeDepth(left) - relativeDepth(right) || left.localeCompare(right);
438
+ }
439
+ function deepestFirst(left, right) {
440
+ return relativeDepth(right) - relativeDepth(left) || left.localeCompare(right);
441
+ }
442
+ function hasAncestorIn(relativePath, ancestors) {
443
+ let parent = import_node_path.default.posix.dirname(relativePath);
444
+ while (parent !== ".") {
445
+ if (ancestors.has(parent)) return true;
446
+ parent = import_node_path.default.posix.dirname(parent);
447
+ }
448
+ return false;
449
+ }
450
+ function treeEntryFromStat(stat, absolutePath) {
451
+ if (stat.isSymbolicLink()) return { kind: "symlink", mode: stat.mode, target: import_node_fs.default.readlinkSync(absolutePath) };
452
+ if (stat.isFile()) return { kind: "file", mode: stat.mode, size: stat.size };
453
+ if (stat.isDirectory()) return { kind: "directory", mode: stat.mode };
454
+ return null;
455
+ }
456
+ function fsyncPath(targetPath, flags) {
457
+ const descriptor = import_node_fs.default.openSync(targetPath, flags);
458
+ try {
459
+ import_node_fs.default.fsyncSync(descriptor);
460
+ } finally {
461
+ import_node_fs.default.closeSync(descriptor);
462
+ }
463
+ }
464
+ function planWorkingTreeMirror(input) {
465
+ const sourceRoot = import_node_path.default.resolve(input.sourceRoot);
466
+ const targetRoot = import_node_path.default.resolve(input.targetRoot);
467
+ const desired = inspectWorkingTree(sourceRoot, input.sourceMode);
468
+ const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all", {
469
+ omitUnpopulatedGitlinks: input.deletionMode === "git"
470
+ });
471
+ const replaced = /* @__PURE__ */ new Map();
472
+ const absent = [];
473
+ for (const [relativePath, entry] of existing) {
474
+ if (!desired.has(relativePath)) replaced.set(relativePath, entry);
475
+ }
476
+ for (const [relativePath, entry] of desired) {
477
+ const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
478
+ if (target.kind !== "entry") {
479
+ absent.push(relativePath);
480
+ continue;
481
+ }
482
+ const { absolutePath, stat } = target;
483
+ if (entry.kind === "directory") {
484
+ if (stat.isDirectory()) continue;
485
+ } else if (entry.kind === "symlink") {
486
+ if (stat.isSymbolicLink() && import_node_fs.default.readlinkSync(absolutePath) === entry.target) continue;
487
+ } else if (stat.isFile() && filesEqual(import_node_path.default.join(sourceRoot, ...relativePath.split("/")), absolutePath, entry)) {
488
+ continue;
489
+ }
490
+ const current = treeEntryFromStat(stat, absolutePath);
491
+ if (current) replaced.set(relativePath, current);
492
+ else absent.push(relativePath);
493
+ }
494
+ const replacedDirectories = new Set(
495
+ [...replaced].filter(([, entry]) => entry.kind === "directory").map(([relativePath]) => relativePath)
496
+ );
497
+ for (const relativePath of [...replaced.keys()]) {
498
+ if (hasAncestorIn(relativePath, replacedDirectories)) replaced.delete(relativePath);
499
+ }
500
+ return {
501
+ desired,
502
+ replaced,
503
+ absent: absent.filter((relativePath) => !hasAncestorIn(relativePath, replacedDirectories)).sort(shallowestFirst)
504
+ };
505
+ }
506
+ function captureWorkingTreeMirrorPlan(input) {
507
+ const targetRoot = import_node_path.default.resolve(input.targetRoot);
508
+ const snapshotRoot = import_node_path.default.resolve(input.snapshotRoot);
509
+ const durability = input.durability ?? "per_entry";
510
+ inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
511
+ if (!inspectDirectoryRoot(snapshotRoot, { allowMissing: true, label: "Working-tree snapshot root" })) {
512
+ import_node_fs.default.mkdirSync(snapshotRoot, { recursive: true });
513
+ }
514
+ const trees = [];
515
+ for (const [relativePath, entry] of [...input.plan.replaced.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
516
+ if (entry.kind !== "directory") {
517
+ copyEntry(targetRoot, snapshotRoot, relativePath, entry, durability);
518
+ continue;
519
+ }
520
+ const source = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
521
+ if (source.kind !== "entry" || !source.stat.isDirectory()) {
522
+ throw new Error(`Working-tree source changed while it was being mirrored: ${import_node_path.default.join(targetRoot, ...relativePath.split("/"))}`);
523
+ }
524
+ ensureDirectory(snapshotRoot, relativePath, entry.mode);
525
+ mirrorWorkingTree({
526
+ sourceRoot: source.absolutePath,
527
+ targetRoot: import_node_path.default.join(snapshotRoot, ...relativePath.split("/")),
528
+ sourceMode: "all",
529
+ deletionMode: "all",
530
+ durability
531
+ });
532
+ trees.push(relativePath);
533
+ }
534
+ return { absent: [...input.plan.absent].sort(shallowestFirst), trees: trees.sort(shallowestFirst) };
535
+ }
536
+ function restoreWorkingTreeMirrorScope(input) {
537
+ const snapshotRoot = import_node_path.default.resolve(input.snapshotRoot);
538
+ const targetRoot = import_node_path.default.resolve(input.targetRoot);
539
+ inspectDirectoryRoot(snapshotRoot, { allowMissing: false, label: "Working-tree snapshot root" });
540
+ if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
541
+ import_node_fs.default.mkdirSync(targetRoot, { recursive: true });
542
+ }
543
+ const normalize = (relativePath) => {
544
+ const normalized = normalizeRelativePath(relativePath);
545
+ if (!normalized) throw new Error(`Invalid working-tree scope path: ${relativePath}`);
546
+ return normalized;
547
+ };
548
+ const absent = input.scope.absent.map(normalize);
549
+ const trees = new Set(input.scope.trees.map(normalize));
550
+ const captured = inspectWorkingTree(snapshotRoot, "all");
551
+ for (const tree of trees) {
552
+ if (captured.get(tree)?.kind !== "directory") {
553
+ throw new Error(`Working-tree snapshot does not hold the captured directory: ${tree}`);
554
+ }
555
+ }
556
+ const touched = /* @__PURE__ */ new Set();
557
+ for (const relativePath of [...absent].sort(deepestFirst)) {
558
+ const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
559
+ if (inspected.kind === "entry") import_node_fs.default.rmSync(inspected.absolutePath, { recursive: true, force: true });
560
+ touched.add(relativePath);
561
+ }
562
+ const writtenDirectories = /* @__PURE__ */ new Set();
563
+ for (const relativePath of absent) writtenDirectories.add(import_node_path.default.posix.dirname(relativePath));
564
+ for (const [relativePath, entry] of captured) {
565
+ if (entry.kind !== "directory" && !hasAncestorIn(relativePath, trees)) writtenDirectories.add(import_node_path.default.posix.dirname(relativePath));
566
+ }
567
+ for (const directory of writtenDirectories) {
568
+ let absoluteDirectory = targetRoot;
569
+ if (directory !== ".") {
570
+ const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, directory);
571
+ if (inspected.kind !== "entry" || !inspected.stat.isDirectory()) continue;
572
+ absoluteDirectory = inspected.absolutePath;
573
+ }
574
+ for (const name of import_node_fs.default.readdirSync(absoluteDirectory)) {
575
+ if (!MIRROR_TEMPORARY_FILE_PATTERN.test(name)) continue;
576
+ const temporaryPath = import_node_path.default.join(absoluteDirectory, name);
577
+ if (!import_node_fs.default.lstatSync(temporaryPath).isFile()) continue;
578
+ import_node_fs.default.rmSync(temporaryPath, { force: true });
579
+ touched.add(directory === "." ? name : import_node_path.default.posix.join(directory, name));
580
+ }
581
+ }
582
+ for (const [relativePath, entry] of [...captured.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
583
+ touched.add(relativePath);
584
+ if (hasAncestorIn(relativePath, trees)) continue;
585
+ if (!trees.has(relativePath)) {
586
+ copyEntry(snapshotRoot, targetRoot, relativePath, entry, "per_entry");
587
+ continue;
588
+ }
589
+ const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
590
+ if (current.kind === "blocked") {
591
+ throw new Error(`Refusing to restore a working-tree directory through a symlink or non-directory: ${current.blockingPath}`);
592
+ }
593
+ if (current.kind === "entry" && !current.stat.isDirectory()) {
594
+ import_node_fs.default.rmSync(current.absolutePath, { recursive: true, force: true });
595
+ }
596
+ ensureDirectory(targetRoot, relativePath, entry.mode);
597
+ mirrorWorkingTree({
598
+ sourceRoot: import_node_path.default.join(snapshotRoot, ...relativePath.split("/")),
599
+ targetRoot: import_node_path.default.join(targetRoot, ...relativePath.split("/")),
600
+ sourceMode: "all",
601
+ deletionMode: "all"
602
+ });
603
+ }
604
+ return { paths: [...touched].sort(shallowestFirst) };
605
+ }
606
+ function fsyncWorkingTreePaths(root, relativePaths) {
607
+ root = import_node_path.default.resolve(root);
608
+ inspectDirectoryRoot(root, { allowMissing: false, label: "Working-tree root" });
609
+ const directories = /* @__PURE__ */ new Set(["."]);
610
+ for (const relativePath of relativePaths) {
611
+ const normalized = normalizeRelativePath(relativePath);
612
+ if (!normalized) throw new Error(`Invalid working-tree path: ${relativePath}`);
613
+ directories.add(import_node_path.default.posix.dirname(normalized));
614
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, normalized);
615
+ if (inspected.kind !== "entry") continue;
616
+ if (inspected.stat.isDirectory()) directories.add(normalized);
617
+ else if (inspected.stat.isFile()) fsyncPath(inspected.absolutePath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
618
+ }
619
+ for (const directory of directories) {
620
+ if (directory === ".") {
621
+ fsyncPath(root, import_node_fs.default.constants.O_RDONLY);
622
+ continue;
623
+ }
624
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, directory);
625
+ if (inspected.kind === "entry" && inspected.stat.isDirectory()) fsyncPath(inspected.absolutePath, import_node_fs.default.constants.O_RDONLY);
626
+ }
627
+ }
381
628
  // Annotate the CommonJS export names for ESM import in node:
382
629
  0 && (module.exports = {
383
630
  assertWorkingTreeHasNoPortableGitMetadataAliases,
631
+ captureWorkingTreeMirrorPlan,
632
+ fsyncWorkingTreePaths,
384
633
  inspectWorkingTree,
385
- mirrorWorkingTree
634
+ mirrorWorkingTree,
635
+ normalizeWorkingTreeRelativePath,
636
+ planWorkingTreeMirror,
637
+ restoreWorkingTreeMirrorScope
386
638
  });
@@ -954,7 +954,7 @@ function parseHydrationTransactionManifest(transactionPath) {
954
954
  throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
955
955
  }
956
956
  const candidate = parsed;
957
- if (candidate.version !== 2 && candidate.version !== 3 || !Array.isArray(candidate.mounts)) {
957
+ if (candidate.version !== 2 && candidate.version !== 3 && candidate.version !== 4 || !Array.isArray(candidate.mounts)) {
958
958
  throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
959
959
  }
960
960
  const parseReceipt = (rawReceipt) => {
@@ -967,6 +967,25 @@ function parseHydrationTransactionManifest(transactionPath) {
967
967
  };
968
968
  const targetReceipt = parseReceipt(candidate.targetReceipt);
969
969
  const receiptBefore = parseReceipt(candidate.receiptBefore);
970
+ const parseScope = (rawScope, existed) => {
971
+ if (rawScope === void 0) return void 0;
972
+ if (candidate.version !== 4 || !existed || !rawScope || typeof rawScope !== "object") {
973
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
974
+ }
975
+ const parsePaths = (rawPaths) => {
976
+ if (!Array.isArray(rawPaths)) throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
977
+ const seen = /* @__PURE__ */ new Set();
978
+ return rawPaths.map((rawPath) => {
979
+ if (typeof rawPath !== "string" || (0, import_working_tree_mirror.normalizeWorkingTreeRelativePath)(rawPath) !== rawPath || seen.has(rawPath)) {
980
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
981
+ }
982
+ seen.add(rawPath);
983
+ return rawPath;
984
+ });
985
+ };
986
+ const { absent, trees } = rawScope;
987
+ return { absent: parsePaths(absent), trees: parsePaths(trees) };
988
+ };
970
989
  const seenIds = /* @__PURE__ */ new Set();
971
990
  const seenSnapshots = /* @__PURE__ */ new Set();
972
991
  const mounts = candidate.mounts.map((rawMount) => {
@@ -983,10 +1002,20 @@ function parseHydrationTransactionManifest(transactionPath) {
983
1002
  }
984
1003
  seenIds.add(mount.id);
985
1004
  seenSnapshots.add(mount.snapshotName);
986
- return mount;
1005
+ const scope = parseScope(rawMount.scope, mount.existed);
1006
+ return {
1007
+ id: mount.id,
1008
+ incarnationKey: mount.incarnationKey,
1009
+ sourcePath: mount.sourcePath,
1010
+ durabilityRootPath: mount.durabilityRootPath,
1011
+ workspaceRelativePath: mount.workspaceRelativePath,
1012
+ existed: mount.existed,
1013
+ snapshotName: mount.snapshotName,
1014
+ ...scope ? { scope } : {}
1015
+ };
987
1016
  });
988
1017
  return {
989
- version: 3,
1018
+ version: candidate.version === 4 ? 4 : 3,
990
1019
  targetReceipt,
991
1020
  receiptBefore,
992
1021
  mounts
@@ -1008,6 +1037,7 @@ function hydrationTransactionCommitted(transactionPath) {
1008
1037
  }
1009
1038
  function restoreHydrationTransaction(workspacePath, manifest) {
1010
1039
  const transactionPath = hydrationTransactionPath(workspacePath);
1040
+ const durabilityScopes = /* @__PURE__ */ new Map();
1011
1041
  for (const mount of manifest.mounts) {
1012
1042
  (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
1013
1043
  trustedRoot: mount.durabilityRootPath,
@@ -1023,6 +1053,11 @@ function restoreHydrationTransaction(workspacePath, manifest) {
1023
1053
  if (!snapshotStatus.isDirectory() || snapshotStatus.isSymbolicLink()) {
1024
1054
  throw new Error(`Workspace hydration snapshot is not a regular directory: ${snapshotPath}`);
1025
1055
  }
1056
+ if (mount.scope) {
1057
+ const restored = (0, import_working_tree_mirror.restoreWorkingTreeMirrorScope)({ snapshotRoot: snapshotPath, targetRoot: mount.sourcePath, scope: mount.scope });
1058
+ durabilityScopes.set(mount.id, restored.paths);
1059
+ continue;
1060
+ }
1026
1061
  (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: snapshotPath, targetRoot: mount.sourcePath, sourceMode: "all", deletionMode: "all" });
1027
1062
  }
1028
1063
  fsyncHydratedWorkspaceMounts(
@@ -1031,7 +1066,8 @@ function restoreHydrationTransaction(workspacePath, manifest) {
1031
1066
  hydrationIncarnationKey: mount.incarnationKey,
1032
1067
  sourceMode: "all",
1033
1068
  hydrateDeletionMode: "all"
1034
- }))
1069
+ })),
1070
+ durabilityScopes
1035
1071
  );
1036
1072
  }
1037
1073
  function recoverPendingHydrationTransaction(workspacePath) {
@@ -1075,57 +1111,80 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
1075
1111
  const transactionPath = hydrationTransactionPath(workspacePath);
1076
1112
  const stagingPath = import_node_path.default.join(stateDirectory, `.workspace-hydration-transaction.${process.pid}.${crypto.randomUUID()}.tmp`);
1077
1113
  import_node_fs.default.mkdirSync(stagingPath, { mode: 448 });
1114
+ const durabilityScopes = /* @__PURE__ */ new Map();
1078
1115
  try {
1079
1116
  const snapshotRoot = import_node_path.default.join(stagingPath, "mounts");
1080
1117
  import_node_fs.default.mkdirSync(snapshotRoot, { mode: 448 });
1081
- const manifest = {
1082
- version: 3,
1083
- targetReceipt,
1084
- receiptBefore: readHydratedWorkspaceReceipt(workspacePath),
1085
- mounts: mounts.map((mount, index) => {
1086
- const sourcePath = import_node_path.default.resolve(mount.sourcePath);
1087
- const durabilityRootPath = import_node_path.default.resolve(mount.durabilityRootPath);
1118
+ const receiptBefore = readHydratedWorkspaceReceipt(workspacePath);
1119
+ const manifestMounts = mounts.map((mount, index) => {
1120
+ const sourcePath = import_node_path.default.resolve(mount.sourcePath);
1121
+ const durabilityRootPath = import_node_path.default.resolve(mount.durabilityRootPath);
1122
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
1123
+ trustedRoot: durabilityRootPath,
1124
+ candidate: sourcePath,
1125
+ label: `Workspace hydration snapshot mount ${mount.id}`
1126
+ });
1127
+ const sourceStatus = lstatIfExists(sourcePath);
1128
+ if (sourceStatus && (!sourceStatus.isDirectory() || sourceStatus.isSymbolicLink())) {
1129
+ throw new Error(`Workspace hydration snapshot source is not a regular directory: ${sourcePath}`);
1130
+ }
1131
+ const snapshotName = String(index);
1132
+ const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
1133
+ import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
1134
+ let scope;
1135
+ if (sourceStatus && mount.hydrateDeletionMode === "git") {
1136
+ const outerPath = workspaceMountOuterPath(workspacePath, mount);
1088
1137
  (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
1089
- trustedRoot: durabilityRootPath,
1090
- candidate: sourcePath,
1091
- label: `Workspace hydration snapshot mount ${mount.id}`
1138
+ trustedRoot: workspacePath,
1139
+ candidate: outerPath,
1140
+ label: `Workspace mount ${mount.id} outer source`
1092
1141
  });
1093
- const sourceStatus = lstatIfExists(sourcePath);
1094
- if (sourceStatus && (!sourceStatus.isDirectory() || sourceStatus.isSymbolicLink())) {
1095
- throw new Error(`Workspace hydration snapshot source is not a regular directory: ${sourcePath}`);
1096
- }
1097
- const snapshotName = String(index);
1098
- const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
1099
- import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
1100
- if (sourceStatus) {
1101
- (0, import_working_tree_mirror.mirrorWorkingTree)({
1102
- sourceRoot: sourcePath,
1103
- targetRoot: snapshotPath,
1104
- sourceMode: "all",
1105
- deletionMode: "all",
1106
- // This tree is still private and the source authority is untouched.
1107
- // The single recursive barrier below makes the complete snapshot
1108
- // durable before its directory entry is published or hydration starts.
1109
- durability: "deferred_private_staging"
1110
- });
1111
- }
1112
- return {
1113
- id: mount.id,
1114
- incarnationKey: mount.hydrationIncarnationKey,
1115
- sourcePath,
1116
- durabilityRootPath,
1117
- workspaceRelativePath: mount.workspaceRelativePath,
1118
- existed: Boolean(sourceStatus),
1119
- snapshotName
1120
- };
1121
- })
1142
+ const plan = (0, import_working_tree_mirror.planWorkingTreeMirror)({ sourceRoot: outerPath, targetRoot: sourcePath, sourceMode: "all", deletionMode: "git" });
1143
+ scope = (0, import_working_tree_mirror.captureWorkingTreeMirrorPlan)({
1144
+ plan,
1145
+ targetRoot: sourcePath,
1146
+ snapshotRoot: snapshotPath,
1147
+ // Same private-staging deferral as the complete snapshot below.
1148
+ durability: "deferred_private_staging"
1149
+ });
1150
+ durabilityScopes.set(mount.id, [...plan.desired.keys()]);
1151
+ } else if (sourceStatus) {
1152
+ (0, import_working_tree_mirror.mirrorWorkingTree)({
1153
+ sourceRoot: sourcePath,
1154
+ targetRoot: snapshotPath,
1155
+ sourceMode: "all",
1156
+ deletionMode: "all",
1157
+ // This tree is still private and the source authority is untouched.
1158
+ // The single recursive barrier below makes the complete snapshot
1159
+ // durable before its directory entry is published or hydration starts.
1160
+ durability: "deferred_private_staging"
1161
+ });
1162
+ }
1163
+ return {
1164
+ id: mount.id,
1165
+ incarnationKey: mount.hydrationIncarnationKey,
1166
+ sourcePath,
1167
+ durabilityRootPath,
1168
+ workspaceRelativePath: mount.workspaceRelativePath,
1169
+ existed: Boolean(sourceStatus),
1170
+ snapshotName,
1171
+ ...scope ? { scope } : {}
1172
+ };
1173
+ });
1174
+ const manifest = {
1175
+ // Older workers reject version 4, so a scoped snapshot can never be
1176
+ // replayed as a complete copy after a downgrade.
1177
+ version: manifestMounts.some((mount) => mount.scope) ? 4 : 3,
1178
+ targetReceipt,
1179
+ receiptBefore,
1180
+ mounts: manifestMounts
1122
1181
  };
1123
1182
  writePrivateFileDurably(import_node_path.default.join(stagingPath, "manifest.json"), `${JSON.stringify(manifest)}
1124
1183
  `);
1125
1184
  fsyncTree(stagingPath);
1126
1185
  import_node_fs.default.renameSync(stagingPath, transactionPath);
1127
1186
  fsyncDirectory(stateDirectory);
1128
- return manifest;
1187
+ return { manifest, durabilityScopes };
1129
1188
  } catch (error) {
1130
1189
  import_node_fs.default.rmSync(stagingPath, { recursive: true, force: true });
1131
1190
  fsyncDirectory(stateDirectory);
@@ -1137,7 +1196,11 @@ function markHydrationTransactionDurable(workspacePath) {
1137
1196
  writePrivateFileDurably(import_node_path.default.join(transactionPath, "committed"), "committed\n");
1138
1197
  fsyncDirectory(transactionPath);
1139
1198
  }
1140
- function fsyncHydratedWorkspaceMount(mount) {
1199
+ function gitVisibleDurabilityPaths(mount, sourcePath, sourceStatus) {
1200
+ if (mount.hydrateDeletionMode !== "git" || !sourceStatus.isDirectory() || !lstatIfExists(import_node_path.default.join(sourcePath, ".git"))) return null;
1201
+ return [...(0, import_working_tree_mirror.inspectWorkingTree)(sourcePath, "git").keys()];
1202
+ }
1203
+ function fsyncHydratedWorkspaceMount(mount, scope) {
1141
1204
  (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
1142
1205
  trustedRoot: mount.durabilityRootPath,
1143
1206
  candidate: mount.sourcePath,
@@ -1162,7 +1225,17 @@ function fsyncHydratedWorkspaceMount(mount) {
1162
1225
  import_node_fs.default.closeSync(descriptor);
1163
1226
  }
1164
1227
  };
1165
- if (import_node_fs.default.existsSync(mount.sourcePath)) syncEntry(mount.sourcePath, true);
1228
+ const sourcePath = import_node_path.default.resolve(mount.sourcePath);
1229
+ const sourceStatus = lstatIfExists(sourcePath);
1230
+ if (sourceStatus) {
1231
+ const paths = scope ?? gitVisibleDurabilityPaths(mount, sourcePath, sourceStatus);
1232
+ if (paths) {
1233
+ if (!sourceStatus.isDirectory()) throw new Error(`Workspace mount ${mount.id} source is not a regular directory: ${sourcePath}`);
1234
+ (0, import_working_tree_mirror.fsyncWorkingTreePaths)(sourcePath, paths);
1235
+ } else {
1236
+ syncEntry(sourcePath, true);
1237
+ }
1238
+ }
1166
1239
  const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
1167
1240
  let boundaryParent = import_node_path.default.dirname(import_node_path.default.resolve(mount.sourcePath));
1168
1241
  while (true) {
@@ -1181,8 +1254,8 @@ function fsyncHydratedWorkspaceMount(mount) {
1181
1254
  boundaryParent = nextParent;
1182
1255
  }
1183
1256
  }
1184
- function fsyncHydratedWorkspaceMounts(mounts) {
1185
- for (const mount of mounts) fsyncHydratedWorkspaceMount(mount);
1257
+ function fsyncHydratedWorkspaceMounts(mounts, scopes = /* @__PURE__ */ new Map()) {
1258
+ for (const mount of mounts) fsyncHydratedWorkspaceMount(mount, scopes.get(mount.id));
1186
1259
  }
1187
1260
  function workspaceIsShallow(workspacePath) {
1188
1261
  return gitResult(workspacePath, ["rev-parse", "--is-shallow-repository"]).stdout === "true";
@@ -1431,6 +1504,9 @@ function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
1431
1504
  }
1432
1505
  }
1433
1506
  }
1507
+ function workspaceMountOuterPath(workspacePath, mount) {
1508
+ return import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
1509
+ }
1434
1510
  function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
1435
1511
  const hydratedMountIds = [];
1436
1512
  const skippedMountIds = [];
@@ -1439,7 +1515,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
1439
1515
  skippedMountIds.push(mount.id);
1440
1516
  continue;
1441
1517
  }
1442
- const sourceRoot = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
1518
+ const sourceRoot = workspaceMountOuterPath(workspacePath, mount);
1443
1519
  (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
1444
1520
  trustedRoot: workspacePath,
1445
1521
  candidate: sourceRoot,
@@ -1545,14 +1621,14 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
1545
1621
  }
1546
1622
  const selection = computeSelection();
1547
1623
  const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
1548
- const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1624
+ const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1549
1625
  try {
1550
1626
  const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
1551
1627
  const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
1552
1628
  if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
1553
1629
  throw new Error("Workspace hydration did not include every required mount");
1554
1630
  }
1555
- fsyncHydratedWorkspaceMounts(advancedDurabilityMounts);
1631
+ fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
1556
1632
  markHydrationTransactionDurable(workspacePath);
1557
1633
  if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
1558
1634
  removeHydrationTransaction(workspacePath);
@@ -206,6 +206,14 @@ function indexMode(entry) {
206
206
  function indexInfoRecord(mode, objectId, relativePath) {
207
207
  return Buffer.concat([Buffer.from(`${mode} ${objectId} `), Buffer.from(relativePath), Buffer.from([0])]);
208
208
  }
209
+ function basisGitlinks(workspacePath, basis) {
210
+ const listed = gitResult(workspacePath, ["ls-tree", "-r", "-z", `${basis.basisHead}:${basis.workspaceRelativePath}`]);
211
+ if (listed.exitCode !== 0) return [];
212
+ return nulRecords(listed.stdout).flatMap((record) => {
213
+ const match = /^160000 commit ([0-9a-f]{40,64})\t(.+)$/su.exec(record.toString());
214
+ return match ? [{ objectId: match[1], relativePath: match[2] }] : [];
215
+ });
216
+ }
209
217
  function synthesizeMountTree(input) {
210
218
  const sourceRoot = import_node_path.default.resolve(input.sourcePath);
211
219
  const entries = (0, import_working_tree_mirror.inspectWorkingTree)(sourceRoot, input.sourceMode);
@@ -220,6 +228,15 @@ function synthesizeMountTree(input) {
220
228
  const objectId = entry.kind === "file" ? hashRegularFile(input.workspacePath, sourcePath, entry) : hashSymlink(input.workspacePath, sourcePath, entry);
221
229
  records.push(indexInfoRecord(indexMode(entry), objectId, relativePath));
222
230
  }
231
+ if (input.gitlinkBasis && input.sourceMode === "git") {
232
+ for (const gitlink of basisGitlinks(input.workspacePath, input.gitlinkBasis)) {
233
+ const populatedPrefix = `${gitlink.relativePath}/`;
234
+ const populated = [...entries.keys()].some((relativePath) => relativePath.startsWith(populatedPrefix));
235
+ if (entries.get(gitlink.relativePath)?.kind === "directory" && !populated) {
236
+ records.push(indexInfoRecord("160000", gitlink.objectId, gitlink.relativePath));
237
+ }
238
+ }
239
+ }
223
240
  if (records.length > 0) {
224
241
  git(input.workspacePath, ["update-index", "-z", "--index-info"], "populate workspace projection index", {
225
242
  environment,
@@ -284,7 +301,8 @@ function synthesizeOursCommit(input) {
284
301
  const mountTree = synthesizeMountTree({
285
302
  workspacePath: input.workspacePath,
286
303
  sourcePath: input.mount.sourcePath,
287
- sourceMode: input.mount.sourceMode
304
+ sourceMode: input.mount.sourceMode,
305
+ gitlinkBasis: { basisHead: input.basisHead, workspaceRelativePath: input.workspaceRelativePath }
288
306
  });
289
307
  const rootTree = graftMountTree({
290
308
  workspacePath: input.workspacePath,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.96",
3
+ "version": "0.0.99",
4
4
  "type": "module"
5
5
  }