@ricsam/r5d-worker 0.0.79 → 0.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +38 -5
- package/dist/cjs/working-tree-mirror.cjs +13 -10
- package/dist/cjs/workspace-git-sync.cjs +10 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +38 -5
- package/dist/mjs/working-tree-mirror.mjs +13 -10
- package/dist/mjs/workspace-git-sync.mjs +10 -1
- package/dist/types/working-tree-mirror.d.ts +7 -0
- package/package.json +1 -1
package/dist/cjs/package.json
CHANGED
|
@@ -389,6 +389,7 @@ function copyCheckoutTree(sourceRoot, targetRoot) {
|
|
|
389
389
|
dereference: false,
|
|
390
390
|
errorOnExist: true,
|
|
391
391
|
force: false,
|
|
392
|
+
mode: import_node_fs.default.constants.COPYFILE_FICLONE,
|
|
392
393
|
preserveTimestamps: true,
|
|
393
394
|
verbatimSymlinks: true
|
|
394
395
|
});
|
|
@@ -769,6 +770,7 @@ function reconcileLinkedLayout(input) {
|
|
|
769
770
|
tryGit(primaryPath, ["worktree", "prune"]);
|
|
770
771
|
fetchProjectHeads({ ...input, primaryPath });
|
|
771
772
|
const mirrorHeads = /* @__PURE__ */ new Map();
|
|
773
|
+
const mutatedBranches = /* @__PURE__ */ new Set();
|
|
772
774
|
for (const branch of input.branches) {
|
|
773
775
|
const checkoutPath = branchPath(input.projectRoot, branch.branchName);
|
|
774
776
|
const resolved = seedForBranch({
|
|
@@ -779,16 +781,22 @@ function reconcileLinkedLayout(input) {
|
|
|
779
781
|
});
|
|
780
782
|
mirrorHeads.set(branch.branchName, resolved.mirrorHead);
|
|
781
783
|
if (branch.branchName === input.primaryBranchName || commonGitDirectory(checkoutPath) === commonGitDirectory(primaryPath)) {
|
|
784
|
+
const currentHead = git(checkoutPath, ["rev-parse", "HEAD"], `resolve linked worktree head ${branch.branchName}`);
|
|
785
|
+
if (currentHead === resolved.seed) continue;
|
|
786
|
+
input.beforeDestructiveMutation();
|
|
787
|
+
mutatedBranches.add(branch.branchName);
|
|
782
788
|
preserveHead(checkoutPath);
|
|
783
789
|
git(checkoutPath, ["reset", "--hard", resolved.seed], `anchor linked worktree ${branch.branchName}`);
|
|
784
790
|
continue;
|
|
785
791
|
}
|
|
792
|
+
input.beforeDestructiveMutation();
|
|
793
|
+
mutatedBranches.add(branch.branchName);
|
|
786
794
|
import_node_fs.default.rmSync(checkoutPath, { recursive: true, force: true });
|
|
787
795
|
git(primaryPath, ["branch", "-f", branch.branchName, resolved.seed], `anchor project branch ${branch.branchName}`);
|
|
788
796
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
|
|
789
797
|
git(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
|
|
790
798
|
}
|
|
791
|
-
return mirrorHeads;
|
|
799
|
+
return { mirrorHeads, mutatedBranches };
|
|
792
800
|
}
|
|
793
801
|
function aheadBehind(checkoutPath, branchName, primaryBranchName, defaultBranch) {
|
|
794
802
|
const originBranchName = projectOriginBranchName({ branchName, primaryBranchName, defaultBranch });
|
|
@@ -805,7 +813,13 @@ function ensureProjectWorktrees(input) {
|
|
|
805
813
|
if (!input.branches.some(({ branchName }) => branchName === input.primaryBranchName)) {
|
|
806
814
|
throw new Error(`Primary project branch ${input.primaryBranchName} is missing from the workspace configuration`);
|
|
807
815
|
}
|
|
808
|
-
const
|
|
816
|
+
const snapshotStorageRoot = import_node_path.default.resolve(input.snapshotRoot ?? import_node_os.default.tmpdir());
|
|
817
|
+
assertNoOutstandingSnapshotForProject(snapshotStorageRoot, projectRoot);
|
|
818
|
+
let snapshot;
|
|
819
|
+
const ensureSnapshot = () => {
|
|
820
|
+
snapshot ??= snapshotBranchTrees(projectRoot, input.branches, snapshotStorageRoot);
|
|
821
|
+
return snapshot;
|
|
822
|
+
};
|
|
809
823
|
try {
|
|
810
824
|
import_node_fs.default.mkdirSync(projectRoot, { recursive: true });
|
|
811
825
|
const linked = hasCompatibleLinkedProjectWorktreeLayout({
|
|
@@ -813,9 +827,27 @@ function ensureProjectWorktrees(input) {
|
|
|
813
827
|
primaryBranchName: input.primaryBranchName,
|
|
814
828
|
branches: input.branches
|
|
815
829
|
});
|
|
816
|
-
|
|
830
|
+
let mirrorHeads;
|
|
831
|
+
let mutatedBranches;
|
|
832
|
+
if (linked) {
|
|
833
|
+
({ mirrorHeads, mutatedBranches } = reconcileLinkedLayout({
|
|
834
|
+
...input,
|
|
835
|
+
projectRoot,
|
|
836
|
+
defaultBranch,
|
|
837
|
+
beforeDestructiveMutation: ensureSnapshot
|
|
838
|
+
}));
|
|
839
|
+
} else {
|
|
840
|
+
ensureSnapshot();
|
|
841
|
+
mirrorHeads = initializeLinkedLayout({ ...input, projectRoot, defaultBranch });
|
|
842
|
+
mutatedBranches = new Set(input.branches.map(({ branchName }) => branchName));
|
|
843
|
+
}
|
|
844
|
+
const preparedSnapshot = snapshot;
|
|
845
|
+
if (mutatedBranches.size > 0 && !preparedSnapshot) {
|
|
846
|
+
throw new Error("Project worktree mutation started without a prepared recovery snapshot");
|
|
847
|
+
}
|
|
817
848
|
for (const branch of input.branches) {
|
|
818
|
-
|
|
849
|
+
if (!mutatedBranches.has(branch.branchName)) continue;
|
|
850
|
+
const snapshotPath = preparedSnapshot?.paths.get(branch.branchName);
|
|
819
851
|
if (!snapshotPath) continue;
|
|
820
852
|
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
821
853
|
sourceRoot: snapshotPath,
|
|
@@ -835,9 +867,10 @@ function ensureProjectWorktrees(input) {
|
|
|
835
867
|
...divergence
|
|
836
868
|
};
|
|
837
869
|
}).sort((left, right) => left.branchName.localeCompare(right.branchName));
|
|
838
|
-
persistReconciledProjectAndConsumeSnapshot(snapshot);
|
|
870
|
+
if (snapshot) persistReconciledProjectAndConsumeSnapshot(snapshot);
|
|
839
871
|
return states;
|
|
840
872
|
} catch (error) {
|
|
873
|
+
if (!snapshot) throw error;
|
|
841
874
|
try {
|
|
842
875
|
rollbackProjectWorktreeSnapshot(snapshot);
|
|
843
876
|
} catch (recoveryError) {
|
|
@@ -264,7 +264,7 @@ function filesEqual(leftPath, rightPath, entry) {
|
|
|
264
264
|
import_node_fs.default.closeSync(right);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
function copyRegularFile(sourcePath, targetPath, entry) {
|
|
267
|
+
function copyRegularFile(sourcePath, targetPath, entry, durability) {
|
|
268
268
|
const source = import_node_fs.default.openSync(sourcePath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
269
269
|
const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
270
270
|
let target;
|
|
@@ -290,7 +290,7 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
290
290
|
offset += bytesRead;
|
|
291
291
|
}
|
|
292
292
|
import_node_fs.default.fchmodSync(target, entry.mode & 511);
|
|
293
|
-
import_node_fs.default.fsyncSync(target);
|
|
293
|
+
if (durability === "per_entry") import_node_fs.default.fsyncSync(target);
|
|
294
294
|
const finalSourceStat = import_node_fs.default.fstatSync(source);
|
|
295
295
|
if (!finalSourceStat.isFile() || finalSourceStat.size !== sourceStat.size || (finalSourceStat.mode & 511) !== (sourceStat.mode & 511) || finalSourceStat.mtimeMs !== sourceStat.mtimeMs || finalSourceStat.ctimeMs !== sourceStat.ctimeMs) {
|
|
296
296
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
@@ -298,11 +298,13 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
298
298
|
import_node_fs.default.closeSync(target);
|
|
299
299
|
target = void 0;
|
|
300
300
|
import_node_fs.default.renameSync(temporaryPath, targetPath);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
301
|
+
if (durability === "per_entry") {
|
|
302
|
+
const parent = import_node_fs.default.openSync(import_node_path.default.dirname(targetPath), import_node_fs.default.constants.O_RDONLY);
|
|
303
|
+
try {
|
|
304
|
+
import_node_fs.default.fsyncSync(parent);
|
|
305
|
+
} finally {
|
|
306
|
+
import_node_fs.default.closeSync(parent);
|
|
307
|
+
}
|
|
306
308
|
}
|
|
307
309
|
} finally {
|
|
308
310
|
if (target !== void 0) import_node_fs.default.closeSync(target);
|
|
@@ -310,7 +312,7 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
310
312
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
311
313
|
}
|
|
312
314
|
}
|
|
313
|
-
function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
315
|
+
function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
314
316
|
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
315
317
|
assertInside(targetRoot, targetPath);
|
|
316
318
|
const parent = import_node_path.default.posix.dirname(relativePath);
|
|
@@ -353,12 +355,13 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
|
353
355
|
}
|
|
354
356
|
const targetStat = target.kind === "entry" ? target.stat : null;
|
|
355
357
|
if (targetStat && !targetStat.isFile()) removeEntry(targetRoot, relativePath);
|
|
356
|
-
copyRegularFile(sourcePath, targetPath, entry);
|
|
358
|
+
copyRegularFile(sourcePath, targetPath, entry, durability);
|
|
357
359
|
}
|
|
358
360
|
}
|
|
359
361
|
function mirrorWorkingTree(input) {
|
|
360
362
|
const sourceRoot = import_node_path.default.resolve(input.sourceRoot);
|
|
361
363
|
const targetRoot = import_node_path.default.resolve(input.targetRoot);
|
|
364
|
+
const durability = input.durability ?? "per_entry";
|
|
362
365
|
if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
|
|
363
366
|
import_node_fs.default.mkdirSync(targetRoot, { recursive: true });
|
|
364
367
|
}
|
|
@@ -371,7 +374,7 @@ function mirrorWorkingTree(input) {
|
|
|
371
374
|
for (const [relativePath, entry] of [...desired.entries()].sort(
|
|
372
375
|
([left], [right]) => left.split("/").length - right.split("/").length || left.localeCompare(right)
|
|
373
376
|
)) {
|
|
374
|
-
copyEntry(sourceRoot, targetRoot, relativePath, entry);
|
|
377
|
+
copyEntry(sourceRoot, targetRoot, relativePath, entry, durability);
|
|
375
378
|
}
|
|
376
379
|
return { paths: [...desired.keys()].filter((relativePath) => desired.get(relativePath)?.kind !== "directory").sort() };
|
|
377
380
|
}
|
|
@@ -645,7 +645,16 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
|
645
645
|
const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
|
|
646
646
|
import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
|
|
647
647
|
if (sourceStatus) {
|
|
648
|
-
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
648
|
+
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
649
|
+
sourceRoot: sourcePath,
|
|
650
|
+
targetRoot: snapshotPath,
|
|
651
|
+
sourceMode: "all",
|
|
652
|
+
deletionMode: "all",
|
|
653
|
+
// This tree is still private and the source authority is untouched.
|
|
654
|
+
// The single recursive barrier below makes the complete snapshot
|
|
655
|
+
// durable before its directory entry is published or hydration starts.
|
|
656
|
+
durability: "deferred_private_staging"
|
|
657
|
+
});
|
|
649
658
|
}
|
|
650
659
|
return {
|
|
651
660
|
id: mount.id,
|
package/dist/mjs/package.json
CHANGED
|
@@ -336,6 +336,7 @@ function copyCheckoutTree(sourceRoot, targetRoot) {
|
|
|
336
336
|
dereference: false,
|
|
337
337
|
errorOnExist: true,
|
|
338
338
|
force: false,
|
|
339
|
+
mode: fs.constants.COPYFILE_FICLONE,
|
|
339
340
|
preserveTimestamps: true,
|
|
340
341
|
verbatimSymlinks: true
|
|
341
342
|
});
|
|
@@ -716,6 +717,7 @@ function reconcileLinkedLayout(input) {
|
|
|
716
717
|
tryGit(primaryPath, ["worktree", "prune"]);
|
|
717
718
|
fetchProjectHeads({ ...input, primaryPath });
|
|
718
719
|
const mirrorHeads = /* @__PURE__ */ new Map();
|
|
720
|
+
const mutatedBranches = /* @__PURE__ */ new Set();
|
|
719
721
|
for (const branch of input.branches) {
|
|
720
722
|
const checkoutPath = branchPath(input.projectRoot, branch.branchName);
|
|
721
723
|
const resolved = seedForBranch({
|
|
@@ -726,16 +728,22 @@ function reconcileLinkedLayout(input) {
|
|
|
726
728
|
});
|
|
727
729
|
mirrorHeads.set(branch.branchName, resolved.mirrorHead);
|
|
728
730
|
if (branch.branchName === input.primaryBranchName || commonGitDirectory(checkoutPath) === commonGitDirectory(primaryPath)) {
|
|
731
|
+
const currentHead = git(checkoutPath, ["rev-parse", "HEAD"], `resolve linked worktree head ${branch.branchName}`);
|
|
732
|
+
if (currentHead === resolved.seed) continue;
|
|
733
|
+
input.beforeDestructiveMutation();
|
|
734
|
+
mutatedBranches.add(branch.branchName);
|
|
729
735
|
preserveHead(checkoutPath);
|
|
730
736
|
git(checkoutPath, ["reset", "--hard", resolved.seed], `anchor linked worktree ${branch.branchName}`);
|
|
731
737
|
continue;
|
|
732
738
|
}
|
|
739
|
+
input.beforeDestructiveMutation();
|
|
740
|
+
mutatedBranches.add(branch.branchName);
|
|
733
741
|
fs.rmSync(checkoutPath, { recursive: true, force: true });
|
|
734
742
|
git(primaryPath, ["branch", "-f", branch.branchName, resolved.seed], `anchor project branch ${branch.branchName}`);
|
|
735
743
|
fs.mkdirSync(path.dirname(checkoutPath), { recursive: true });
|
|
736
744
|
git(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
|
|
737
745
|
}
|
|
738
|
-
return mirrorHeads;
|
|
746
|
+
return { mirrorHeads, mutatedBranches };
|
|
739
747
|
}
|
|
740
748
|
function aheadBehind(checkoutPath, branchName, primaryBranchName, defaultBranch) {
|
|
741
749
|
const originBranchName = projectOriginBranchName({ branchName, primaryBranchName, defaultBranch });
|
|
@@ -752,7 +760,13 @@ function ensureProjectWorktrees(input) {
|
|
|
752
760
|
if (!input.branches.some(({ branchName }) => branchName === input.primaryBranchName)) {
|
|
753
761
|
throw new Error(`Primary project branch ${input.primaryBranchName} is missing from the workspace configuration`);
|
|
754
762
|
}
|
|
755
|
-
const
|
|
763
|
+
const snapshotStorageRoot = path.resolve(input.snapshotRoot ?? os.tmpdir());
|
|
764
|
+
assertNoOutstandingSnapshotForProject(snapshotStorageRoot, projectRoot);
|
|
765
|
+
let snapshot;
|
|
766
|
+
const ensureSnapshot = () => {
|
|
767
|
+
snapshot ??= snapshotBranchTrees(projectRoot, input.branches, snapshotStorageRoot);
|
|
768
|
+
return snapshot;
|
|
769
|
+
};
|
|
756
770
|
try {
|
|
757
771
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
758
772
|
const linked = hasCompatibleLinkedProjectWorktreeLayout({
|
|
@@ -760,9 +774,27 @@ function ensureProjectWorktrees(input) {
|
|
|
760
774
|
primaryBranchName: input.primaryBranchName,
|
|
761
775
|
branches: input.branches
|
|
762
776
|
});
|
|
763
|
-
|
|
777
|
+
let mirrorHeads;
|
|
778
|
+
let mutatedBranches;
|
|
779
|
+
if (linked) {
|
|
780
|
+
({ mirrorHeads, mutatedBranches } = reconcileLinkedLayout({
|
|
781
|
+
...input,
|
|
782
|
+
projectRoot,
|
|
783
|
+
defaultBranch,
|
|
784
|
+
beforeDestructiveMutation: ensureSnapshot
|
|
785
|
+
}));
|
|
786
|
+
} else {
|
|
787
|
+
ensureSnapshot();
|
|
788
|
+
mirrorHeads = initializeLinkedLayout({ ...input, projectRoot, defaultBranch });
|
|
789
|
+
mutatedBranches = new Set(input.branches.map(({ branchName }) => branchName));
|
|
790
|
+
}
|
|
791
|
+
const preparedSnapshot = snapshot;
|
|
792
|
+
if (mutatedBranches.size > 0 && !preparedSnapshot) {
|
|
793
|
+
throw new Error("Project worktree mutation started without a prepared recovery snapshot");
|
|
794
|
+
}
|
|
764
795
|
for (const branch of input.branches) {
|
|
765
|
-
|
|
796
|
+
if (!mutatedBranches.has(branch.branchName)) continue;
|
|
797
|
+
const snapshotPath = preparedSnapshot?.paths.get(branch.branchName);
|
|
766
798
|
if (!snapshotPath) continue;
|
|
767
799
|
mirrorWorkingTree({
|
|
768
800
|
sourceRoot: snapshotPath,
|
|
@@ -782,9 +814,10 @@ function ensureProjectWorktrees(input) {
|
|
|
782
814
|
...divergence
|
|
783
815
|
};
|
|
784
816
|
}).sort((left, right) => left.branchName.localeCompare(right.branchName));
|
|
785
|
-
persistReconciledProjectAndConsumeSnapshot(snapshot);
|
|
817
|
+
if (snapshot) persistReconciledProjectAndConsumeSnapshot(snapshot);
|
|
786
818
|
return states;
|
|
787
819
|
} catch (error) {
|
|
820
|
+
if (!snapshot) throw error;
|
|
788
821
|
try {
|
|
789
822
|
rollbackProjectWorktreeSnapshot(snapshot);
|
|
790
823
|
} catch (recoveryError) {
|
|
@@ -229,7 +229,7 @@ function filesEqual(leftPath, rightPath, entry) {
|
|
|
229
229
|
fs.closeSync(right);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
function copyRegularFile(sourcePath, targetPath, entry) {
|
|
232
|
+
function copyRegularFile(sourcePath, targetPath, entry, durability) {
|
|
233
233
|
const source = fs.openSync(sourcePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
234
234
|
const temporaryPath = path.join(path.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${randomUUID()}.tmp`);
|
|
235
235
|
let target;
|
|
@@ -255,7 +255,7 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
255
255
|
offset += bytesRead;
|
|
256
256
|
}
|
|
257
257
|
fs.fchmodSync(target, entry.mode & 511);
|
|
258
|
-
fs.fsyncSync(target);
|
|
258
|
+
if (durability === "per_entry") fs.fsyncSync(target);
|
|
259
259
|
const finalSourceStat = fs.fstatSync(source);
|
|
260
260
|
if (!finalSourceStat.isFile() || finalSourceStat.size !== sourceStat.size || (finalSourceStat.mode & 511) !== (sourceStat.mode & 511) || finalSourceStat.mtimeMs !== sourceStat.mtimeMs || finalSourceStat.ctimeMs !== sourceStat.ctimeMs) {
|
|
261
261
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
@@ -263,11 +263,13 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
263
263
|
fs.closeSync(target);
|
|
264
264
|
target = void 0;
|
|
265
265
|
fs.renameSync(temporaryPath, targetPath);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
if (durability === "per_entry") {
|
|
267
|
+
const parent = fs.openSync(path.dirname(targetPath), fs.constants.O_RDONLY);
|
|
268
|
+
try {
|
|
269
|
+
fs.fsyncSync(parent);
|
|
270
|
+
} finally {
|
|
271
|
+
fs.closeSync(parent);
|
|
272
|
+
}
|
|
271
273
|
}
|
|
272
274
|
} finally {
|
|
273
275
|
if (target !== void 0) fs.closeSync(target);
|
|
@@ -275,7 +277,7 @@ function copyRegularFile(sourcePath, targetPath, entry) {
|
|
|
275
277
|
fs.rmSync(temporaryPath, { force: true });
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
|
-
function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
280
|
+
function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
279
281
|
const targetPath = path.join(targetRoot, ...relativePath.split("/"));
|
|
280
282
|
assertInside(targetRoot, targetPath);
|
|
281
283
|
const parent = path.posix.dirname(relativePath);
|
|
@@ -318,12 +320,13 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry) {
|
|
|
318
320
|
}
|
|
319
321
|
const targetStat = target.kind === "entry" ? target.stat : null;
|
|
320
322
|
if (targetStat && !targetStat.isFile()) removeEntry(targetRoot, relativePath);
|
|
321
|
-
copyRegularFile(sourcePath, targetPath, entry);
|
|
323
|
+
copyRegularFile(sourcePath, targetPath, entry, durability);
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
326
|
function mirrorWorkingTree(input) {
|
|
325
327
|
const sourceRoot = path.resolve(input.sourceRoot);
|
|
326
328
|
const targetRoot = path.resolve(input.targetRoot);
|
|
329
|
+
const durability = input.durability ?? "per_entry";
|
|
327
330
|
if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
|
|
328
331
|
fs.mkdirSync(targetRoot, { recursive: true });
|
|
329
332
|
}
|
|
@@ -336,7 +339,7 @@ function mirrorWorkingTree(input) {
|
|
|
336
339
|
for (const [relativePath, entry] of [...desired.entries()].sort(
|
|
337
340
|
([left], [right]) => left.split("/").length - right.split("/").length || left.localeCompare(right)
|
|
338
341
|
)) {
|
|
339
|
-
copyEntry(sourceRoot, targetRoot, relativePath, entry);
|
|
342
|
+
copyEntry(sourceRoot, targetRoot, relativePath, entry, durability);
|
|
340
343
|
}
|
|
341
344
|
return { paths: [...desired.keys()].filter((relativePath) => desired.get(relativePath)?.kind !== "directory").sort() };
|
|
342
345
|
}
|
|
@@ -600,7 +600,16 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
|
600
600
|
const snapshotPath = path.join(snapshotRoot, snapshotName);
|
|
601
601
|
fs.mkdirSync(snapshotPath, { mode: 448 });
|
|
602
602
|
if (sourceStatus) {
|
|
603
|
-
mirrorWorkingTree({
|
|
603
|
+
mirrorWorkingTree({
|
|
604
|
+
sourceRoot: sourcePath,
|
|
605
|
+
targetRoot: snapshotPath,
|
|
606
|
+
sourceMode: "all",
|
|
607
|
+
deletionMode: "all",
|
|
608
|
+
// This tree is still private and the source authority is untouched.
|
|
609
|
+
// The single recursive barrier below makes the complete snapshot
|
|
610
|
+
// durable before its directory entry is published or hydration starts.
|
|
611
|
+
durability: "deferred_private_staging"
|
|
612
|
+
});
|
|
604
613
|
}
|
|
605
614
|
return {
|
|
606
615
|
id: mount.id,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type WorkingTreeSourceMode = "all" | "git";
|
|
2
2
|
export type WorkingTreeDeletionMode = "all" | "git";
|
|
3
|
+
export type WorkingTreeMirrorDurability = "per_entry" | "deferred_private_staging";
|
|
3
4
|
type TreeEntry = {
|
|
4
5
|
kind: "directory";
|
|
5
6
|
mode: number;
|
|
@@ -25,6 +26,12 @@ export declare function mirrorWorkingTree(input: {
|
|
|
25
26
|
targetRoot: string;
|
|
26
27
|
sourceMode: WorkingTreeSourceMode;
|
|
27
28
|
deletionMode: WorkingTreeDeletionMode;
|
|
29
|
+
/**
|
|
30
|
+
* Defer destination fsync only while building an unpublished private tree.
|
|
31
|
+
* The caller must durably fsync that complete tree before publishing it or
|
|
32
|
+
* mutating the source authority.
|
|
33
|
+
*/
|
|
34
|
+
durability?: WorkingTreeMirrorDurability;
|
|
28
35
|
}): {
|
|
29
36
|
paths: string[];
|
|
30
37
|
};
|