@ricsam/r5d-worker 0.0.132 → 0.0.133
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/atomic-rename.cjs +303 -0
- package/dist/cjs/git-blob-hash.cjs +41 -0
- package/dist/cjs/main.cjs +187 -38
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/three-way-merge.cjs +346 -0
- package/dist/cjs/working-tree-mirror.cjs +1049 -64
- package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
- package/dist/cjs/workspace-command-targets.cjs +63 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
- package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
- package/dist/cjs/workspace-git-sync.cjs +846 -61
- package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
- package/dist/cjs/workspace-hydration-merge.cjs +433 -0
- package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
- package/dist/cjs/workspace-merge-projection.cjs +81 -10
- package/dist/cjs/workspace-project-config-policy.cjs +19 -12
- package/dist/mjs/atomic-rename.mjs +261 -0
- package/dist/mjs/git-blob-hash.mjs +16 -0
- package/dist/mjs/main.mjs +196 -39
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/three-way-merge.mjs +318 -0
- package/dist/mjs/working-tree-mirror.mjs +1035 -64
- package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
- package/dist/mjs/workspace-command-targets.mjs +37 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
- package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
- package/dist/mjs/workspace-git-sync.mjs +854 -62
- package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
- package/dist/mjs/workspace-hydration-merge.mjs +399 -0
- package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
- package/dist/mjs/workspace-merge-projection.mjs +85 -11
- package/dist/mjs/workspace-project-config-policy.mjs +16 -10
- package/dist/types/atomic-rename.d.ts +78 -0
- package/dist/types/git-blob-hash.d.ts +10 -0
- package/dist/types/main.d.ts +21 -2
- package/dist/types/three-way-merge.d.ts +77 -0
- package/dist/types/working-tree-mirror.d.ts +270 -7
- package/dist/types/workspace-command-sync-policy.d.ts +12 -6
- package/dist/types/workspace-command-targets.d.ts +37 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
- package/dist/types/workspace-git-sync.d.ts +125 -3
- package/dist/types/workspace-hydration-ledger.d.ts +43 -0
- package/dist/types/workspace-hydration-merge.d.ts +95 -0
- package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
- package/dist/types/workspace-merge-projection.d.ts +19 -1
- package/dist/types/workspace-project-config-policy.d.ts +17 -3
- package/package.json +2 -2
|
@@ -28,20 +28,70 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var working_tree_mirror_exports = {};
|
|
30
30
|
__export(working_tree_mirror_exports, {
|
|
31
|
+
WorkingTreeTargetChangedError: () => WorkingTreeTargetChangedError,
|
|
32
|
+
applyWorkingTreeMirror: () => applyWorkingTreeMirror,
|
|
31
33
|
assertWorkingTreeHasNoPortableGitMetadataAliases: () => assertWorkingTreeHasNoPortableGitMetadataAliases,
|
|
32
34
|
captureWorkingTreeMirrorPlan: () => captureWorkingTreeMirrorPlan,
|
|
35
|
+
displacedEntryMatches: () => displacedEntryMatches,
|
|
33
36
|
fsyncWorkingTreePaths: () => fsyncWorkingTreePaths,
|
|
34
37
|
inspectWorkingTree: () => inspectWorkingTree,
|
|
38
|
+
inspectWorkingTreePath: () => inspectWorkingTreePath,
|
|
35
39
|
mirrorWorkingTree: () => mirrorWorkingTree,
|
|
36
40
|
normalizeWorkingTreeRelativePath: () => normalizeWorkingTreeRelativePath,
|
|
41
|
+
planPreparedWorkingTreeMirror: () => planPreparedWorkingTreeMirror,
|
|
37
42
|
planWorkingTreeMirror: () => planWorkingTreeMirror,
|
|
38
|
-
|
|
43
|
+
prepareWorkingTreeMirror: () => prepareWorkingTreeMirror,
|
|
44
|
+
projectedFileEntry: () => projectedFileEntry,
|
|
45
|
+
projectedFileEntryMatches: () => projectedFileEntryMatches,
|
|
46
|
+
restoreWorkingTreeMirrorScope: () => restoreWorkingTreeMirrorScope,
|
|
47
|
+
rollbackWorkingTreeMirrorJournal: () => rollbackWorkingTreeMirrorJournal,
|
|
48
|
+
sameWorkingTreeInode: () => sameWorkingTreeInode,
|
|
49
|
+
setWorkingTreeMirrorRaceHook: () => setWorkingTreeMirrorRaceHook,
|
|
50
|
+
workingTreeAtomicRenameSupport: () => workingTreeAtomicRenameSupport,
|
|
51
|
+
workingTreeDisplacedRetentionDirectory: () => workingTreeDisplacedRetentionDirectory,
|
|
52
|
+
workingTreeStatsMatch: () => workingTreeStatsMatch
|
|
39
53
|
});
|
|
40
54
|
module.exports = __toCommonJS(working_tree_mirror_exports);
|
|
41
55
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
42
56
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
43
57
|
var import_node_crypto = require("node:crypto");
|
|
44
58
|
var import_git_process_environment = require("./git-process-environment.cjs");
|
|
59
|
+
var import_atomic_rename = require("./atomic-rename.cjs");
|
|
60
|
+
function projectedFileEntry(stat) {
|
|
61
|
+
return { kind: "file", size: stat.size, mode: stat.mode, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs, ino: stat.ino };
|
|
62
|
+
}
|
|
63
|
+
function projectedFileEntryMatches(entry, stat) {
|
|
64
|
+
return stat.isFile() && stat.ino === entry.ino && stat.size === entry.size && stat.mode === entry.mode && stat.mtimeMs === entry.mtimeMs && stat.ctimeMs === entry.ctimeMs;
|
|
65
|
+
}
|
|
66
|
+
function workingTreeStatsMatch(left, right) {
|
|
67
|
+
return left.isFile() === right.isFile() && left.isDirectory() === right.isDirectory() && left.isSymbolicLink() === right.isSymbolicLink() && left.ino === right.ino && left.dev === right.dev && left.size === right.size && left.mode === right.mode && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
68
|
+
}
|
|
69
|
+
function sameWorkingTreeInode(left, right) {
|
|
70
|
+
return left.ino === right.ino && left.dev === right.dev;
|
|
71
|
+
}
|
|
72
|
+
function displacedEntryMatches(expected, displaced) {
|
|
73
|
+
return sameWorkingTreeInode(expected, displaced) && displaced.mode === expected.mode && displaced.size === expected.size && displaced.mtimeMs === expected.mtimeMs;
|
|
74
|
+
}
|
|
75
|
+
class WorkingTreeTargetChangedError extends Error {
|
|
76
|
+
constructor(relativePath, options = {}) {
|
|
77
|
+
const retained = options.retained ?? [];
|
|
78
|
+
super(
|
|
79
|
+
`Working-tree target changed while it was being hydrated: ${relativePath}` + (options.detail ? ` (${options.detail})` : "") + (retained.length > 0 ? `; displaced entries retained at ${retained.map(({ path: retainedPath }) => retainedPath).join(", ")}` : "")
|
|
80
|
+
);
|
|
81
|
+
this.relativePath = relativePath;
|
|
82
|
+
this.name = "WorkingTreeTargetChangedError";
|
|
83
|
+
this.retained = retained;
|
|
84
|
+
}
|
|
85
|
+
relativePath;
|
|
86
|
+
retained;
|
|
87
|
+
}
|
|
88
|
+
let raceHook = null;
|
|
89
|
+
function setWorkingTreeMirrorRaceHook(hook) {
|
|
90
|
+
raceHook = hook;
|
|
91
|
+
}
|
|
92
|
+
function raceWindow(phase, relativePath, targetPath) {
|
|
93
|
+
if (raceHook) raceHook({ phase, relativePath, targetPath });
|
|
94
|
+
}
|
|
45
95
|
const HFS_IGNORED_CODE_POINTS = /[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/gu;
|
|
46
96
|
const MIRROR_TEMPORARY_FILE_PREFIX = ".r5d-working-tree-mirror-";
|
|
47
97
|
const OBJECT_ID_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
|
|
@@ -116,6 +166,9 @@ function inspectRelativePathWithoutFollowingAncestors(root, relativePath) {
|
|
|
116
166
|
}
|
|
117
167
|
return { kind: "missing" };
|
|
118
168
|
}
|
|
169
|
+
function inspectWorkingTreePath(root, relativePath) {
|
|
170
|
+
return inspectRelativePathWithoutFollowingAncestors(import_node_path.default.resolve(root), relativePath);
|
|
171
|
+
}
|
|
119
172
|
function inspectDirectoryRoot(root, options) {
|
|
120
173
|
let stat;
|
|
121
174
|
try {
|
|
@@ -260,7 +313,7 @@ function writeGitlinkStaleHeads(root, heads) {
|
|
|
260
313
|
import_node_fs.default.renameSync(temporaryPath, markerPath);
|
|
261
314
|
fsyncPath(directory, import_node_fs.default.constants.O_RDONLY);
|
|
262
315
|
}
|
|
263
|
-
function addEntry(entries, root, relativePath, recurseDirectories, gitlinks) {
|
|
316
|
+
function addEntry(entries, root, relativePath, recurseDirectories, gitlinks, stats) {
|
|
264
317
|
const normalized = normalizeRelativePath(relativePath);
|
|
265
318
|
if (!normalized || entries.has(normalized)) return;
|
|
266
319
|
const gitlink = gitlinks?.get(normalized);
|
|
@@ -273,23 +326,29 @@ function addEntry(entries, root, relativePath, recurseDirectories, gitlinks) {
|
|
|
273
326
|
const { absolutePath, stat } = inspected;
|
|
274
327
|
if (stat.isSymbolicLink()) {
|
|
275
328
|
for (const existing of [...entries.keys()]) {
|
|
276
|
-
if (existing.startsWith(`${normalized}/`))
|
|
329
|
+
if (existing.startsWith(`${normalized}/`)) {
|
|
330
|
+
entries.delete(existing);
|
|
331
|
+
stats?.delete(existing);
|
|
332
|
+
}
|
|
277
333
|
}
|
|
278
334
|
entries.set(normalized, { kind: "symlink", mode: stat.mode, target: import_node_fs.default.readlinkSync(absolutePath) });
|
|
335
|
+
stats?.set(normalized, stat);
|
|
279
336
|
return;
|
|
280
337
|
}
|
|
281
338
|
if (stat.isFile()) {
|
|
282
339
|
entries.set(normalized, { kind: "file", mode: stat.mode, size: stat.size });
|
|
340
|
+
stats?.set(normalized, stat);
|
|
283
341
|
return;
|
|
284
342
|
}
|
|
285
343
|
if (!stat.isDirectory()) return;
|
|
286
344
|
entries.set(normalized, { kind: "directory", mode: stat.mode });
|
|
345
|
+
stats?.set(normalized, stat);
|
|
287
346
|
if (!recurseDirectories) return;
|
|
288
347
|
for (const child of import_node_fs.default.readdirSync(absolutePath).sort()) {
|
|
289
|
-
addEntry(entries, root, import_node_path.default.posix.join(normalized, child), true, gitlinks);
|
|
348
|
+
addEntry(entries, root, import_node_path.default.posix.join(normalized, child), true, gitlinks, stats);
|
|
290
349
|
}
|
|
291
350
|
}
|
|
292
|
-
function addParentDirectories(entries, root) {
|
|
351
|
+
function addParentDirectories(entries, root, stats) {
|
|
293
352
|
for (const relativePath of [...entries.keys()]) {
|
|
294
353
|
let parent = import_node_path.default.posix.dirname(relativePath);
|
|
295
354
|
while (parent !== ".") {
|
|
@@ -299,12 +358,13 @@ function addParentDirectories(entries, root) {
|
|
|
299
358
|
throw new Error(`Working-tree entry has an unsafe parent directory: ${parent}`);
|
|
300
359
|
}
|
|
301
360
|
entries.set(parent, { kind: "directory", mode: inspected.stat.mode });
|
|
361
|
+
stats?.set(parent, inspected.stat);
|
|
302
362
|
}
|
|
303
363
|
parent = import_node_path.default.posix.dirname(parent);
|
|
304
364
|
}
|
|
305
365
|
}
|
|
306
366
|
}
|
|
307
|
-
function inspectGitWorkingTree(root) {
|
|
367
|
+
function inspectGitWorkingTree(root, stats) {
|
|
308
368
|
const entries = /* @__PURE__ */ new Map();
|
|
309
369
|
const listing = listGitCheckout(root);
|
|
310
370
|
const indexGitlinks = /* @__PURE__ */ new Map();
|
|
@@ -317,13 +377,13 @@ function inspectGitWorkingTree(root) {
|
|
|
317
377
|
if (entries.has(relativePath)) continue;
|
|
318
378
|
const indexObjectId = indexGitlinks.get(relativePath);
|
|
319
379
|
if (indexObjectId === void 0 && !nestedRepository) {
|
|
320
|
-
addEntry(entries, root, relativePath, true);
|
|
380
|
+
addEntry(entries, root, relativePath, true, void 0, stats);
|
|
321
381
|
continue;
|
|
322
382
|
}
|
|
323
383
|
const inspected = inspectRelativePathWithoutFollowingAncestors(root, relativePath);
|
|
324
384
|
if (inspected.kind !== "entry") continue;
|
|
325
385
|
if (!inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) {
|
|
326
|
-
addEntry(entries, root, relativePath, true);
|
|
386
|
+
addEntry(entries, root, relativePath, true, void 0, stats);
|
|
327
387
|
continue;
|
|
328
388
|
}
|
|
329
389
|
const head = populatedCheckoutHead(root, inspected.absolutePath);
|
|
@@ -333,9 +393,12 @@ function inspectGitWorkingTree(root) {
|
|
|
333
393
|
staleHeads ??= readGitlinkStaleHeads(root);
|
|
334
394
|
objectId = staleHeads.get(relativePath) === head ? indexObjectId : head;
|
|
335
395
|
}
|
|
336
|
-
if (objectId)
|
|
396
|
+
if (objectId) {
|
|
397
|
+
entries.set(relativePath, { kind: "gitlink", objectId });
|
|
398
|
+
stats?.set(relativePath, inspected.stat);
|
|
399
|
+
}
|
|
337
400
|
}
|
|
338
|
-
addParentDirectories(entries, root);
|
|
401
|
+
addParentDirectories(entries, root, stats);
|
|
339
402
|
return { entries, indexRecords: listing.indexRecords, submodules };
|
|
340
403
|
}
|
|
341
404
|
function normalizedGitlinks(gitlinks) {
|
|
@@ -352,13 +415,16 @@ function inspectWorkingTree(root, mode, options = {}) {
|
|
|
352
415
|
const entries = /* @__PURE__ */ new Map();
|
|
353
416
|
root = import_node_path.default.resolve(root);
|
|
354
417
|
if (!inspectDirectoryRoot(root, { allowMissing: true, label: "Working-tree root" })) return entries;
|
|
355
|
-
if (mode === "git") return inspectGitWorkingTree(root).entries;
|
|
418
|
+
if (mode === "git") return inspectGitWorkingTree(root, options.stats).entries;
|
|
356
419
|
const gitlinks = normalizedGitlinks(options.gitlinks);
|
|
357
|
-
for (const child of import_node_fs.default.readdirSync(root).sort()) addEntry(entries, root, child, true, gitlinks);
|
|
420
|
+
for (const child of import_node_fs.default.readdirSync(root).sort()) addEntry(entries, root, child, true, gitlinks, options.stats);
|
|
358
421
|
for (const [relativePath, objectId] of gitlinks) {
|
|
359
|
-
if (entries.get(relativePath)?.kind !== "gitlink")
|
|
422
|
+
if (entries.get(relativePath)?.kind !== "gitlink") {
|
|
423
|
+
entries.set(relativePath, { kind: "gitlink", objectId });
|
|
424
|
+
options.stats?.delete(relativePath);
|
|
425
|
+
}
|
|
360
426
|
}
|
|
361
|
-
addParentDirectories(entries, root);
|
|
427
|
+
addParentDirectories(entries, root, options.stats);
|
|
362
428
|
return entries;
|
|
363
429
|
}
|
|
364
430
|
function removeEntry(targetRoot, relativePath) {
|
|
@@ -369,6 +435,443 @@ function removeEntry(targetRoot, relativePath) {
|
|
|
369
435
|
}
|
|
370
436
|
import_node_fs.default.rmSync(inspected.absolutePath, { recursive: true, force: true });
|
|
371
437
|
}
|
|
438
|
+
function assertTargetExpectation(targetRoot, relativePath, guards) {
|
|
439
|
+
if (!guards?.expectations?.has(relativePath)) return;
|
|
440
|
+
const expected = guards.expectations.get(relativePath) ?? null;
|
|
441
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
442
|
+
if (expected === null) {
|
|
443
|
+
if (inspected.kind === "missing") return;
|
|
444
|
+
throw new WorkingTreeTargetChangedError(relativePath);
|
|
445
|
+
}
|
|
446
|
+
if (expected.isDirectory() && !expected.isSymbolicLink()) {
|
|
447
|
+
if (inspected.kind === "entry" && inspected.stat.isDirectory() && !inspected.stat.isSymbolicLink()) return;
|
|
448
|
+
throw new WorkingTreeTargetChangedError(relativePath);
|
|
449
|
+
}
|
|
450
|
+
if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
451
|
+
}
|
|
452
|
+
const DISPLACED_RETENTION_DIRECTORY = "hydration-displaced";
|
|
453
|
+
const RETAINED_NAME_MAX_ENCODED_LENGTH = 150;
|
|
454
|
+
const YIELD_ATTEMPTS = 4;
|
|
455
|
+
const passStates = /* @__PURE__ */ new WeakMap();
|
|
456
|
+
function newPassState(targetRoot) {
|
|
457
|
+
const retention = retentionDirectoryFor(targetRoot);
|
|
458
|
+
const primitives = (0, import_atomic_rename.atomicRenamePrimitivesFor)(retention ?? targetRoot);
|
|
459
|
+
return { targetRoot, retention, primitives, exchangeUnavailable: primitives.support.exchange === "unavailable" };
|
|
460
|
+
}
|
|
461
|
+
function workingTreeAtomicRenameSupport(targetRoot) {
|
|
462
|
+
return newPassState(import_node_path.default.resolve(targetRoot)).primitives.support;
|
|
463
|
+
}
|
|
464
|
+
function guardedPassState(guards, targetRoot) {
|
|
465
|
+
let state = passStates.get(guards);
|
|
466
|
+
if (!state) {
|
|
467
|
+
state = newPassState(targetRoot);
|
|
468
|
+
passStates.set(guards, state);
|
|
469
|
+
}
|
|
470
|
+
return state;
|
|
471
|
+
}
|
|
472
|
+
function workingTreeDisplacedRetentionDirectory(targetRoot) {
|
|
473
|
+
const gitDirectory = checkoutGitDirectory(import_node_path.default.resolve(targetRoot));
|
|
474
|
+
return gitDirectory ? import_node_path.default.join(gitDirectory, "r5d", DISPLACED_RETENTION_DIRECTORY) : null;
|
|
475
|
+
}
|
|
476
|
+
function retentionDirectoryFor(targetRoot) {
|
|
477
|
+
const directory = workingTreeDisplacedRetentionDirectory(targetRoot);
|
|
478
|
+
if (!directory) return null;
|
|
479
|
+
try {
|
|
480
|
+
import_node_fs.default.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
481
|
+
const status = import_node_fs.default.lstatSync(directory);
|
|
482
|
+
if (!status.isDirectory() || status.isSymbolicLink()) return null;
|
|
483
|
+
if (status.dev !== import_node_fs.default.lstatSync(targetRoot).dev) return null;
|
|
484
|
+
return directory;
|
|
485
|
+
} catch {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function encodeRetainedName(relativePath) {
|
|
490
|
+
let encoded = "";
|
|
491
|
+
for (const byte of Buffer.from(relativePath, "utf8")) {
|
|
492
|
+
const isPlain = byte >= 48 && byte <= 57 || byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte === 45 || byte === 46 || byte === 95;
|
|
493
|
+
encoded += isPlain ? String.fromCharCode(byte) : `%${byte.toString(16).padStart(2, "0")}`;
|
|
494
|
+
}
|
|
495
|
+
return encoded.length > RETAINED_NAME_MAX_ENCODED_LENGTH ? encoded.slice(0, RETAINED_NAME_MAX_ENCODED_LENGTH) : encoded;
|
|
496
|
+
}
|
|
497
|
+
function stagingPathFor(state, targetPath, relativePath) {
|
|
498
|
+
if (state.retention) return import_node_path.default.join(state.retention, `${(0, import_node_crypto.randomUUID)()}.${encodeRetainedName(relativePath)}`);
|
|
499
|
+
return import_node_path.default.join(import_node_path.default.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
500
|
+
}
|
|
501
|
+
function switchExpectation(targetRoot, relativePath, guards) {
|
|
502
|
+
if (guards.expectations?.has(relativePath)) return guards.expectations.get(relativePath) ?? null;
|
|
503
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
504
|
+
if (inspected.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${inspected.blockingPath}`);
|
|
505
|
+
return inspected.kind === "entry" ? inspected.stat : null;
|
|
506
|
+
}
|
|
507
|
+
function settledAfter(targetPath, stagedStat, stagedContent) {
|
|
508
|
+
const current = lstatIfExists(targetPath);
|
|
509
|
+
return current && displacedEntryMatches(stagedStat, current) && entryContent(targetPath, current) === stagedContent ? current : stagedStat;
|
|
510
|
+
}
|
|
511
|
+
function entryContent(filePath, expected, expectedBlob) {
|
|
512
|
+
try {
|
|
513
|
+
if (expected.isSymbolicLink()) {
|
|
514
|
+
const before = import_node_fs.default.lstatSync(filePath);
|
|
515
|
+
if (!workingTreeStatsMatch(expected, before)) return null;
|
|
516
|
+
const bytes = Buffer.from(import_node_fs.default.readlinkSync(filePath));
|
|
517
|
+
if (!workingTreeStatsMatch(before, import_node_fs.default.lstatSync(filePath))) return null;
|
|
518
|
+
const digest = (0, import_node_crypto.createHash)("sha256").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
|
|
519
|
+
if (expectedBlob && (expectedBlob.length === 64 ? digest : (0, import_node_crypto.createHash)("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex")) !== expectedBlob)
|
|
520
|
+
return null;
|
|
521
|
+
return digest;
|
|
522
|
+
}
|
|
523
|
+
if (!expected.isFile()) return null;
|
|
524
|
+
const descriptor = import_node_fs.default.openSync(filePath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
525
|
+
try {
|
|
526
|
+
const before = import_node_fs.default.fstatSync(descriptor);
|
|
527
|
+
if (!workingTreeStatsMatch(expected, before)) return null;
|
|
528
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(`blob ${before.size}\0`);
|
|
529
|
+
const knownHash = expectedBlob && expectedBlob.length !== 64 ? (0, import_node_crypto.createHash)("sha1").update(`blob ${before.size}\0`) : null;
|
|
530
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
531
|
+
for (let offset = 0; offset < before.size; ) {
|
|
532
|
+
const count = import_node_fs.default.readSync(descriptor, buffer, 0, Math.min(buffer.length, before.size - offset), offset);
|
|
533
|
+
if (count <= 0) return null;
|
|
534
|
+
const bytes = buffer.subarray(0, count);
|
|
535
|
+
hash.update(bytes);
|
|
536
|
+
knownHash?.update(bytes);
|
|
537
|
+
offset += count;
|
|
538
|
+
}
|
|
539
|
+
if (!workingTreeStatsMatch(before, import_node_fs.default.fstatSync(descriptor)) || !workingTreeStatsMatch(before, import_node_fs.default.lstatSync(filePath))) return null;
|
|
540
|
+
const digest = hash.digest("hex");
|
|
541
|
+
if (expectedBlob && (knownHash ? knownHash.digest("hex") : digest) !== expectedBlob) return null;
|
|
542
|
+
return digest;
|
|
543
|
+
} finally {
|
|
544
|
+
import_node_fs.default.closeSync(descriptor);
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (isPathStateCode(error.code) || error.code === "ELOOP") return null;
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function fsyncRetainedEntry(retainedPath) {
|
|
552
|
+
const status = import_node_fs.default.lstatSync(retainedPath);
|
|
553
|
+
if (status.isDirectory() && !status.isSymbolicLink()) {
|
|
554
|
+
for (const child of import_node_fs.default.readdirSync(retainedPath)) fsyncRetainedEntry(import_node_path.default.join(retainedPath, child));
|
|
555
|
+
fsyncPath(retainedPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
556
|
+
} else if (status.isFile()) {
|
|
557
|
+
fsyncPath(retainedPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
558
|
+
}
|
|
559
|
+
fsyncPath(import_node_path.default.dirname(retainedPath), import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
560
|
+
}
|
|
561
|
+
function retainForeign(journal, relativePath, retainedPath, reason) {
|
|
562
|
+
fsyncRetainedEntry(retainedPath);
|
|
563
|
+
const retained = { relativePath, path: retainedPath, reason };
|
|
564
|
+
if (journal) (journal.retained ??= []).push(retained);
|
|
565
|
+
return retained;
|
|
566
|
+
}
|
|
567
|
+
function isPathStateCode(code) {
|
|
568
|
+
return code === "EEXIST" || code === "ENOENT" || code === "ENOTDIR" || code === "ENOTEMPTY" || code === "EISDIR";
|
|
569
|
+
}
|
|
570
|
+
function publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath) {
|
|
571
|
+
if (stagedStat.isSymbolicLink() && state.primitives.support.noReplace === "link") {
|
|
572
|
+
throw new WorkingTreeTargetChangedError(relativePath, { detail: "filesystem cannot publish a symlink with provable ownership" });
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
state.primitives.renameNoReplace(stagedPath, targetPath);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (error instanceof import_atomic_rename.AtomicRenameError && isPathStateCode(error.code)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
function restoreDisplacedNoReplace(state, displacedPath, displaced, targetPath) {
|
|
583
|
+
try {
|
|
584
|
+
if (displaced.isSymbolicLink() && state.primitives.support.noReplace === "link") {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
state.primitives.renameNoReplace(displacedPath, targetPath);
|
|
588
|
+
return true;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const code = error instanceof import_atomic_rename.AtomicRenameError ? error.code : error.code;
|
|
591
|
+
if (isPathStateCode(code) || code === "ENOTSUP") return false;
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function discardStaged(input, reason) {
|
|
596
|
+
const current = lstatIfExists(input.stagedPath);
|
|
597
|
+
if (!current) return null;
|
|
598
|
+
if (input.onFailure === "unlink" && input.stagedContent && displacedEntryMatches(input.stagedStat, current) && entryContent(input.stagedPath, current) === input.stagedContent) {
|
|
599
|
+
import_node_fs.default.rmSync(input.stagedPath, { force: true });
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
return retainForeign(input.journal, input.relativePath, input.stagedPath, reason);
|
|
603
|
+
}
|
|
604
|
+
function discardStagedAndThrow(input, error) {
|
|
605
|
+
const retained = discardStaged(input, "unrestorable");
|
|
606
|
+
if (retained && error instanceof WorkingTreeTargetChangedError) {
|
|
607
|
+
throw new WorkingTreeTargetChangedError(input.relativePath, { detail: error.message, retained: [...error.retained, retained] });
|
|
608
|
+
}
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
function switchStagedEntry(input) {
|
|
612
|
+
const { state, targetRoot, relativePath, targetPath, stagedPath, stagedStat, expected } = input;
|
|
613
|
+
const stagedContent = input.stagedContent ?? entryContent(stagedPath, stagedStat);
|
|
614
|
+
if (!stagedContent) {
|
|
615
|
+
const retained = retainForeign(input.journal, relativePath, stagedPath, "unrestorable");
|
|
616
|
+
throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
|
|
617
|
+
}
|
|
618
|
+
input.stagedContent = stagedContent;
|
|
619
|
+
if (stagedStat.isSymbolicLink() && state.primitives.support.noReplace === "link") {
|
|
620
|
+
discardStagedAndThrow(
|
|
621
|
+
input,
|
|
622
|
+
new WorkingTreeTargetChangedError(relativePath, { detail: "filesystem cannot publish a symlink with provable ownership" })
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
626
|
+
if (inspected.kind === "blocked") {
|
|
627
|
+
discardStagedAndThrow(input, new Error(`Refusing to copy through a symlink or non-directory: ${inspected.blockingPath}`));
|
|
628
|
+
}
|
|
629
|
+
if (expected === null) {
|
|
630
|
+
if (inspected.kind !== "missing") {
|
|
631
|
+
discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
|
|
632
|
+
}
|
|
633
|
+
raceWindow(input.phase, relativePath, targetPath);
|
|
634
|
+
let created;
|
|
635
|
+
try {
|
|
636
|
+
created = publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
discardStagedAndThrow(input, error);
|
|
639
|
+
}
|
|
640
|
+
raceWindow("after_switch", relativePath, targetPath);
|
|
641
|
+
return { after: created ?? settledAfter(targetPath, stagedStat, stagedContent), afterContent: stagedContent, displaced: null };
|
|
642
|
+
}
|
|
643
|
+
if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) {
|
|
644
|
+
discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
|
|
645
|
+
}
|
|
646
|
+
const expectedContent = entryContent(targetPath, expected, input.expectedBlob);
|
|
647
|
+
if (!expectedContent || input.expectedContent && input.expectedContent !== expectedContent) {
|
|
648
|
+
discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
|
|
649
|
+
}
|
|
650
|
+
input.expectedContent = expectedContent;
|
|
651
|
+
raceWindow(input.phase, relativePath, targetPath);
|
|
652
|
+
if (!state.exchangeUnavailable) {
|
|
653
|
+
try {
|
|
654
|
+
state.primitives.exchange(stagedPath, targetPath);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if ((0, import_atomic_rename.isAtomicRenameUnsupported)(error)) {
|
|
657
|
+
state.exchangeUnavailable = true;
|
|
658
|
+
return switchWithoutExchange(input);
|
|
659
|
+
}
|
|
660
|
+
if (error instanceof import_atomic_rename.AtomicRenameError && isPathStateCode(error.code)) {
|
|
661
|
+
discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath, { detail: `${error.code} at the switch` }));
|
|
662
|
+
}
|
|
663
|
+
discardStagedAndThrow(input, error);
|
|
664
|
+
}
|
|
665
|
+
raceWindow("after_switch", relativePath, targetPath);
|
|
666
|
+
const displaced = import_node_fs.default.lstatSync(stagedPath);
|
|
667
|
+
if (displacedEntryMatches(expected, displaced) && entryContent(stagedPath, displaced) === expectedContent) {
|
|
668
|
+
return {
|
|
669
|
+
after: settledAfter(targetPath, stagedStat, stagedContent),
|
|
670
|
+
afterContent: stagedContent,
|
|
671
|
+
displaced: { path: stagedPath, stat: displaced, content: expectedContent }
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return yieldDisplacedEntry(input, displaced);
|
|
675
|
+
}
|
|
676
|
+
return switchWithoutExchange(input);
|
|
677
|
+
}
|
|
678
|
+
function yieldDisplacedEntry(input, displaced) {
|
|
679
|
+
const { state, relativePath, targetPath, stagedPath, stagedStat } = input;
|
|
680
|
+
raceWindow("before_yield", relativePath, targetPath);
|
|
681
|
+
let installedBefore = stagedStat;
|
|
682
|
+
let toInstall = displaced;
|
|
683
|
+
for (let attempt = 0; attempt < YIELD_ATTEMPTS; attempt += 1) {
|
|
684
|
+
try {
|
|
685
|
+
state.primitives.exchange(stagedPath, targetPath);
|
|
686
|
+
} catch (error) {
|
|
687
|
+
if (!(error instanceof import_atomic_rename.AtomicRenameError && isPathStateCode(error.code))) throw error;
|
|
688
|
+
if (restoreDisplacedNoReplace(state, stagedPath, toInstall, targetPath)) {
|
|
689
|
+
throw new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" });
|
|
690
|
+
}
|
|
691
|
+
const retained2 = retainForeign(input.journal, relativePath, stagedPath, "later_writer");
|
|
692
|
+
throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained2] });
|
|
693
|
+
}
|
|
694
|
+
const returned = import_node_fs.default.lstatSync(stagedPath);
|
|
695
|
+
if (sameWorkingTreeInode(returned, installedBefore)) {
|
|
696
|
+
if (sameWorkingTreeInode(returned, stagedStat)) {
|
|
697
|
+
discardStagedAndThrow(
|
|
698
|
+
input,
|
|
699
|
+
new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" })
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const retained2 = retainForeign(input.journal, relativePath, stagedPath, "later_writer");
|
|
703
|
+
throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained2] });
|
|
704
|
+
}
|
|
705
|
+
installedBefore = toInstall;
|
|
706
|
+
toInstall = returned;
|
|
707
|
+
}
|
|
708
|
+
const retained = retainForeign(input.journal, relativePath, stagedPath, "unrestorable");
|
|
709
|
+
throw new WorkingTreeTargetChangedError(relativePath, { detail: "the path kept changing while the pass yielded", retained: [retained] });
|
|
710
|
+
}
|
|
711
|
+
function switchWithoutExchange(input) {
|
|
712
|
+
const { state, relativePath, targetPath, stagedPath, stagedStat, expected } = input;
|
|
713
|
+
const park = stagingPathFor(state, targetPath, relativePath);
|
|
714
|
+
try {
|
|
715
|
+
import_node_fs.default.renameSync(targetPath, park);
|
|
716
|
+
} catch (error) {
|
|
717
|
+
if (isPathStateCode(error.code))
|
|
718
|
+
discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath, { detail: "removed before the switch" }));
|
|
719
|
+
discardStagedAndThrow(input, error);
|
|
720
|
+
}
|
|
721
|
+
raceWindow("after_switch", relativePath, targetPath);
|
|
722
|
+
const displaced = import_node_fs.default.lstatSync(park);
|
|
723
|
+
if (displacedEntryMatches(expected, displaced) && entryContent(park, displaced) === input.expectedContent) {
|
|
724
|
+
let created;
|
|
725
|
+
try {
|
|
726
|
+
created = publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (error instanceof WorkingTreeTargetChangedError) {
|
|
729
|
+
const retained2 = retainForeign(input.journal, relativePath, park, "reappeared");
|
|
730
|
+
discardStagedAndThrow(
|
|
731
|
+
input,
|
|
732
|
+
new WorkingTreeTargetChangedError(relativePath, {
|
|
733
|
+
detail: "an entry was created while the path was being replaced",
|
|
734
|
+
retained: [retained2]
|
|
735
|
+
})
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
if (!restoreDisplacedNoReplace(state, park, displaced, targetPath)) retainForeign(input.journal, relativePath, park, "unrestorable");
|
|
739
|
+
discardStagedAndThrow(input, error);
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
after: created ?? settledAfter(targetPath, stagedStat, input.stagedContent),
|
|
743
|
+
afterContent: input.stagedContent,
|
|
744
|
+
displaced: { path: park, stat: displaced, content: input.expectedContent }
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
raceWindow("before_yield", relativePath, targetPath);
|
|
748
|
+
const stagedRetained = discardStaged(input, "unrestorable");
|
|
749
|
+
if (restoreDisplacedNoReplace(state, park, displaced, targetPath)) {
|
|
750
|
+
throw new WorkingTreeTargetChangedError(relativePath, {
|
|
751
|
+
detail: "another writer replaced the entry; its bytes were kept",
|
|
752
|
+
retained: stagedRetained ? [stagedRetained] : []
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
const retained = retainForeign(input.journal, relativePath, park, "later_writer");
|
|
756
|
+
throw new WorkingTreeTargetChangedError(relativePath, { retained: [...stagedRetained ? [stagedRetained] : [], retained] });
|
|
757
|
+
}
|
|
758
|
+
function removeSwitchedEntry(input) {
|
|
759
|
+
const { state, targetRoot, relativePath, targetPath, expected } = input;
|
|
760
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
761
|
+
if (inspected.kind === "blocked") {
|
|
762
|
+
throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
|
|
763
|
+
}
|
|
764
|
+
if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
765
|
+
const content = entryContent(targetPath, expected, input.expectedBlob);
|
|
766
|
+
if (!content || input.expectedContent && content !== input.expectedContent) throw new WorkingTreeTargetChangedError(relativePath);
|
|
767
|
+
raceWindow(input.phase, relativePath, targetPath);
|
|
768
|
+
const park = stagingPathFor(state, targetPath, relativePath);
|
|
769
|
+
try {
|
|
770
|
+
import_node_fs.default.renameSync(targetPath, park);
|
|
771
|
+
} catch (error) {
|
|
772
|
+
if (isPathStateCode(error.code))
|
|
773
|
+
throw new WorkingTreeTargetChangedError(relativePath, { detail: "removed before the switch" });
|
|
774
|
+
throw error;
|
|
775
|
+
}
|
|
776
|
+
raceWindow("after_remove", relativePath, targetPath);
|
|
777
|
+
const displaced = import_node_fs.default.lstatSync(park);
|
|
778
|
+
if (displacedEntryMatches(expected, displaced) && entryContent(park, displaced) === content)
|
|
779
|
+
return { path: park, stat: displaced, content };
|
|
780
|
+
raceWindow("before_yield", relativePath, targetPath);
|
|
781
|
+
if (restoreDisplacedNoReplace(state, park, displaced, targetPath)) {
|
|
782
|
+
throw new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" });
|
|
783
|
+
}
|
|
784
|
+
const retained = retainForeign(input.journal, relativePath, park, "later_writer");
|
|
785
|
+
throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
|
|
786
|
+
}
|
|
787
|
+
function removeEntryGuarded(targetRoot, relativePath, replaced, guards) {
|
|
788
|
+
if (!guards) {
|
|
789
|
+
removeEntry(targetRoot, relativePath);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const state = guardedPassState(guards, targetRoot);
|
|
793
|
+
const expected = switchExpectation(targetRoot, relativePath, guards);
|
|
794
|
+
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
795
|
+
assertInside(targetRoot, targetPath);
|
|
796
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
797
|
+
if (inspected.kind === "blocked") {
|
|
798
|
+
throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
|
|
799
|
+
}
|
|
800
|
+
if (expected === null) {
|
|
801
|
+
if (inspected.kind !== "missing") throw new WorkingTreeTargetChangedError(relativePath);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (expected.isDirectory() && !expected.isSymbolicLink()) {
|
|
805
|
+
if (inspected.kind === "entry") {
|
|
806
|
+
if (!inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) throw new WorkingTreeTargetChangedError(relativePath);
|
|
807
|
+
raceWindow("before_remove", relativePath, targetPath);
|
|
808
|
+
try {
|
|
809
|
+
import_node_fs.default.rmdirSync(inspected.absolutePath);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
const code = error.code;
|
|
812
|
+
if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOTDIR") throw new WorkingTreeTargetChangedError(relativePath);
|
|
813
|
+
if (code !== "ENOENT") throw error;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
|
|
817
|
+
if (replaced) guards.journal?.entries.push({ relativePath, replaced, after: null });
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const displaced = removeSwitchedEntry({
|
|
821
|
+
state,
|
|
822
|
+
targetRoot,
|
|
823
|
+
relativePath,
|
|
824
|
+
targetPath,
|
|
825
|
+
expected,
|
|
826
|
+
expectedBlob: guards.preBlobs?.get(relativePath),
|
|
827
|
+
journal: guards.journal,
|
|
828
|
+
phase: "before_remove"
|
|
829
|
+
});
|
|
830
|
+
if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
|
|
831
|
+
const recorded = replaced ?? treeEntryFromStat(displaced.stat, displaced.path);
|
|
832
|
+
if (guards.journal && recorded) guards.journal.entries.push({ relativePath, replaced: recorded, after: null, displaced });
|
|
833
|
+
else releaseRetainedEntry(void 0, relativePath, displaced);
|
|
834
|
+
}
|
|
835
|
+
function ensureDirectoryGuarded(targetRoot, relativePath, guards) {
|
|
836
|
+
if (!guards) {
|
|
837
|
+
ensureDirectory(targetRoot, relativePath, 493);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
841
|
+
if (!normalized) throw new Error(`Invalid working-tree directory path: ${relativePath}`);
|
|
842
|
+
let current = import_node_path.default.resolve(targetRoot);
|
|
843
|
+
let relative = "";
|
|
844
|
+
for (const segment of normalized.split("/")) {
|
|
845
|
+
current = import_node_path.default.join(current, segment);
|
|
846
|
+
relative = relative ? `${relative}/${segment}` : segment;
|
|
847
|
+
assertInside(targetRoot, current);
|
|
848
|
+
const stat = lstatIfExists(current);
|
|
849
|
+
if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) throw new WorkingTreeTargetChangedError(relative);
|
|
850
|
+
if (stat) continue;
|
|
851
|
+
try {
|
|
852
|
+
import_node_fs.default.mkdirSync(current, { mode: 493 });
|
|
853
|
+
} catch (error) {
|
|
854
|
+
if (error.code !== "EEXIST") throw error;
|
|
855
|
+
const now = lstatIfExists(current);
|
|
856
|
+
if (!now || !now.isDirectory() || now.isSymbolicLink()) throw new WorkingTreeTargetChangedError(relative);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function replaceWithSymlink(targetRoot, relativePath, target) {
|
|
861
|
+
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
862
|
+
assertInside(targetRoot, targetPath);
|
|
863
|
+
const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
864
|
+
import_node_fs.default.symlinkSync(target, temporaryPath);
|
|
865
|
+
try {
|
|
866
|
+
const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
867
|
+
if (current.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${current.blockingPath}`);
|
|
868
|
+
if (current.kind === "entry" && current.stat.isDirectory() && !current.stat.isSymbolicLink()) removeEntry(targetRoot, relativePath);
|
|
869
|
+
import_node_fs.default.renameSync(temporaryPath, targetPath);
|
|
870
|
+
} catch (error) {
|
|
871
|
+
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
872
|
+
throw error;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
372
875
|
function ensureDirectory(targetRoot, relativePath, mode) {
|
|
373
876
|
const normalized = normalizeRelativePath(relativePath);
|
|
374
877
|
if (!normalized) throw new Error(`Invalid working-tree directory path: ${relativePath}`);
|
|
@@ -426,9 +929,8 @@ function filesEqual(leftPath, rightPath, entry) {
|
|
|
426
929
|
import_node_fs.default.closeSync(right);
|
|
427
930
|
}
|
|
428
931
|
}
|
|
429
|
-
function
|
|
932
|
+
function stageRegularFile(sourcePath, stagedPath, entry, durability) {
|
|
430
933
|
const source = import_node_fs.default.openSync(sourcePath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
431
|
-
const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
432
934
|
let target;
|
|
433
935
|
try {
|
|
434
936
|
const sourceStat = import_node_fs.default.fstatSync(source);
|
|
@@ -436,7 +938,7 @@ function copyRegularFile(sourcePath, targetPath, entry, durability) {
|
|
|
436
938
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
437
939
|
}
|
|
438
940
|
target = import_node_fs.default.openSync(
|
|
439
|
-
|
|
941
|
+
stagedPath,
|
|
440
942
|
import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL | import_node_fs.default.constants.O_NOFOLLOW,
|
|
441
943
|
entry.mode & 511
|
|
442
944
|
);
|
|
@@ -457,22 +959,54 @@ function copyRegularFile(sourcePath, targetPath, entry, durability) {
|
|
|
457
959
|
if (!finalSourceStat.isFile() || finalSourceStat.size !== sourceStat.size || (finalSourceStat.mode & 511) !== (sourceStat.mode & 511) || finalSourceStat.mtimeMs !== sourceStat.mtimeMs || finalSourceStat.ctimeMs !== sourceStat.ctimeMs) {
|
|
458
960
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
459
961
|
}
|
|
962
|
+
const stagedStat = import_node_fs.default.fstatSync(target);
|
|
460
963
|
import_node_fs.default.closeSync(target);
|
|
461
964
|
target = void 0;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
} finally {
|
|
468
|
-
import_node_fs.default.closeSync(parent);
|
|
469
|
-
}
|
|
965
|
+
return { sourceStat: finalSourceStat, stagedStat };
|
|
966
|
+
} catch (error) {
|
|
967
|
+
if (target !== void 0) {
|
|
968
|
+
import_node_fs.default.closeSync(target);
|
|
969
|
+
target = void 0;
|
|
470
970
|
}
|
|
971
|
+
import_node_fs.default.rmSync(stagedPath, { force: true });
|
|
972
|
+
throw error;
|
|
471
973
|
} finally {
|
|
472
974
|
if (target !== void 0) import_node_fs.default.closeSync(target);
|
|
473
975
|
import_node_fs.default.closeSync(source);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function writeStagedContent(stagedPath, override, durability) {
|
|
979
|
+
const target = import_node_fs.default.openSync(
|
|
980
|
+
stagedPath,
|
|
981
|
+
import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL | import_node_fs.default.constants.O_NOFOLLOW,
|
|
982
|
+
override.mode & 511
|
|
983
|
+
);
|
|
984
|
+
try {
|
|
985
|
+
let written = 0;
|
|
986
|
+
while (written < override.content.length) {
|
|
987
|
+
written += import_node_fs.default.writeSync(target, override.content, written, override.content.length - written, written);
|
|
988
|
+
}
|
|
989
|
+
import_node_fs.default.fchmodSync(target, override.mode & 511);
|
|
990
|
+
if (durability === "per_entry") import_node_fs.default.fsyncSync(target);
|
|
991
|
+
return import_node_fs.default.fstatSync(target);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
import_node_fs.default.rmSync(stagedPath, { force: true });
|
|
994
|
+
throw error;
|
|
995
|
+
} finally {
|
|
996
|
+
import_node_fs.default.closeSync(target);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function copyRegularFile(sourcePath, targetPath, entry, durability) {
|
|
1000
|
+
const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
1001
|
+
const { sourceStat } = stageRegularFile(sourcePath, temporaryPath, entry, durability);
|
|
1002
|
+
try {
|
|
1003
|
+
import_node_fs.default.renameSync(temporaryPath, targetPath);
|
|
1004
|
+
} catch (error) {
|
|
474
1005
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
1006
|
+
throw error;
|
|
475
1007
|
}
|
|
1008
|
+
if (durability === "per_entry") fsyncPath(import_node_path.default.dirname(targetPath), import_node_fs.default.constants.O_RDONLY);
|
|
1009
|
+
return sourceStat;
|
|
476
1010
|
}
|
|
477
1011
|
function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
478
1012
|
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
@@ -489,7 +1023,7 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
|
489
1023
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
490
1024
|
}
|
|
491
1025
|
ensureDirectory(targetRoot, relativePath, entry.mode);
|
|
492
|
-
return;
|
|
1026
|
+
return null;
|
|
493
1027
|
}
|
|
494
1028
|
if (entry.kind === "symlink") {
|
|
495
1029
|
if (!source.stat.isSymbolicLink() || import_node_fs.default.readlinkSync(sourcePath) !== entry.target) {
|
|
@@ -501,11 +1035,8 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
|
501
1035
|
} catch {
|
|
502
1036
|
unchanged = false;
|
|
503
1037
|
}
|
|
504
|
-
if (!unchanged)
|
|
505
|
-
|
|
506
|
-
import_node_fs.default.symlinkSync(entry.target, targetPath);
|
|
507
|
-
}
|
|
508
|
-
return;
|
|
1038
|
+
if (!unchanged) replaceWithSymlink(targetRoot, relativePath, entry.target);
|
|
1039
|
+
return { kind: "symlink", target: entry.target };
|
|
509
1040
|
}
|
|
510
1041
|
if (entry.kind === "gitlink") {
|
|
511
1042
|
throw new Error(`Working-tree gitlink cannot be copied as content: ${relativePath}`);
|
|
@@ -513,19 +1044,183 @@ function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
|
|
|
513
1044
|
if (!source.stat.isFile() || source.stat.isSymbolicLink() || source.stat.size !== entry.size) {
|
|
514
1045
|
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
515
1046
|
}
|
|
516
|
-
if (
|
|
517
|
-
const
|
|
518
|
-
if (
|
|
519
|
-
throw new Error(`
|
|
1047
|
+
if (filesEqual(sourcePath, targetPath, entry)) {
|
|
1048
|
+
const settled = import_node_fs.default.lstatSync(sourcePath);
|
|
1049
|
+
if (!settled.isFile() || settled.ino !== source.stat.ino || settled.size !== source.stat.size || settled.mode !== source.stat.mode) {
|
|
1050
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
520
1051
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1052
|
+
if (settled.mtimeMs !== source.stat.mtimeMs || settled.ctimeMs !== source.stat.ctimeMs) {
|
|
1053
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1054
|
+
}
|
|
1055
|
+
return projectedFileEntry(settled);
|
|
1056
|
+
}
|
|
1057
|
+
const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1058
|
+
if (target.kind === "blocked") {
|
|
1059
|
+
throw new Error(`Refusing to copy through a symlink or non-directory: ${target.blockingPath}`);
|
|
524
1060
|
}
|
|
1061
|
+
if (target.kind === "entry" && !target.stat.isFile()) removeEntry(targetRoot, relativePath);
|
|
1062
|
+
return projectedFileEntry(copyRegularFile(sourcePath, targetPath, entry, durability));
|
|
525
1063
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
const
|
|
1064
|
+
function copyEntryGuarded(sourceRoot, targetRoot, relativePath, entry, durability, guards) {
|
|
1065
|
+
const state = guardedPassState(guards, targetRoot);
|
|
1066
|
+
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
1067
|
+
assertInside(targetRoot, targetPath);
|
|
1068
|
+
const parent = import_node_path.default.posix.dirname(relativePath);
|
|
1069
|
+
if (parent !== ".") ensureDirectoryGuarded(targetRoot, parent, guards);
|
|
1070
|
+
const override = guards.overrides?.get(relativePath);
|
|
1071
|
+
if (override && entry.kind !== "file") throw new Error(`Working-tree override must replace a regular file: ${relativePath}`);
|
|
1072
|
+
let sourcePath = null;
|
|
1073
|
+
let sourceStat = null;
|
|
1074
|
+
if (!override) {
|
|
1075
|
+
const source = inspectRelativePathWithoutFollowingAncestors(sourceRoot, relativePath);
|
|
1076
|
+
if (source.kind !== "entry") throw new Error(`Working-tree source became unsafe while it was being mirrored: ${relativePath}`);
|
|
1077
|
+
sourcePath = source.absolutePath;
|
|
1078
|
+
sourceStat = source.stat;
|
|
1079
|
+
}
|
|
1080
|
+
if (entry.kind === "directory") {
|
|
1081
|
+
if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) {
|
|
1082
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1083
|
+
}
|
|
1084
|
+
const target2 = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1085
|
+
if (target2.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${target2.blockingPath}`);
|
|
1086
|
+
if (target2.kind === "entry" && target2.stat.isDirectory() && !target2.stat.isSymbolicLink()) return null;
|
|
1087
|
+
if (target2.kind === "entry") {
|
|
1088
|
+
if (guards.expectations && !guards.expectations.has(relativePath)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
1089
|
+
removeEntryGuarded(targetRoot, relativePath, treeEntryFromStat(target2.stat, target2.absolutePath), guards);
|
|
1090
|
+
} else assertTargetExpectation(targetRoot, relativePath, guards);
|
|
1091
|
+
createDirectoryGuarded(targetRoot, relativePath, entry.mode, guards);
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
if (entry.kind === "gitlink") {
|
|
1095
|
+
throw new Error(`Working-tree gitlink cannot be copied as content: ${relativePath}`);
|
|
1096
|
+
}
|
|
1097
|
+
const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1098
|
+
if (target.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${target.blockingPath}`);
|
|
1099
|
+
if (entry.kind === "symlink") {
|
|
1100
|
+
if (!sourceStat.isSymbolicLink() || import_node_fs.default.readlinkSync(sourcePath) !== entry.target) {
|
|
1101
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1102
|
+
}
|
|
1103
|
+
if (target.kind === "entry" && target.stat.isSymbolicLink() && import_node_fs.default.readlinkSync(target.absolutePath) === entry.target) {
|
|
1104
|
+
return { kind: "symlink", target: entry.target };
|
|
1105
|
+
}
|
|
1106
|
+
} else if (!override) {
|
|
1107
|
+
if (!sourceStat.isFile() || sourceStat.isSymbolicLink() || sourceStat.size !== entry.size) {
|
|
1108
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1109
|
+
}
|
|
1110
|
+
if (filesEqual(sourcePath, targetPath, entry)) {
|
|
1111
|
+
const settled = import_node_fs.default.lstatSync(sourcePath);
|
|
1112
|
+
if (!settled.isFile() || settled.ino !== sourceStat.ino || settled.size !== sourceStat.size || settled.mode !== sourceStat.mode) {
|
|
1113
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (settled.mtimeMs !== sourceStat.mtimeMs || settled.ctimeMs !== sourceStat.ctimeMs) {
|
|
1116
|
+
throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
|
|
1117
|
+
}
|
|
1118
|
+
return projectedFileEntry(settled);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
let replaced = target.kind === "entry" ? treeEntryFromStat(target.stat, target.absolutePath) : null;
|
|
1122
|
+
let expected = switchExpectation(targetRoot, relativePath, guards);
|
|
1123
|
+
if (target.kind === "entry" && target.stat.isDirectory() && !target.stat.isSymbolicLink()) {
|
|
1124
|
+
removeEntryGuarded(targetRoot, relativePath, replaced, guards);
|
|
1125
|
+
replaced = null;
|
|
1126
|
+
expected = null;
|
|
1127
|
+
}
|
|
1128
|
+
const stagedPath = stagingPathFor(state, targetPath, relativePath);
|
|
1129
|
+
let stagedStat;
|
|
1130
|
+
let projected = null;
|
|
1131
|
+
if (entry.kind === "symlink") {
|
|
1132
|
+
import_node_fs.default.symlinkSync(entry.target, stagedPath);
|
|
1133
|
+
stagedStat = import_node_fs.default.lstatSync(stagedPath);
|
|
1134
|
+
projected = { kind: "symlink", target: entry.target };
|
|
1135
|
+
} else if (override) {
|
|
1136
|
+
stagedStat = writeStagedContent(stagedPath, override, durability);
|
|
1137
|
+
} else {
|
|
1138
|
+
const staged = stageRegularFile(sourcePath, stagedPath, entry, durability);
|
|
1139
|
+
stagedStat = staged.stagedStat;
|
|
1140
|
+
projected = projectedFileEntry(staged.sourceStat);
|
|
1141
|
+
}
|
|
1142
|
+
const outcome = switchStagedEntry({
|
|
1143
|
+
state,
|
|
1144
|
+
targetRoot,
|
|
1145
|
+
relativePath,
|
|
1146
|
+
targetPath,
|
|
1147
|
+
stagedPath,
|
|
1148
|
+
stagedStat,
|
|
1149
|
+
expected,
|
|
1150
|
+
expectedBlob: guards.preBlobs?.get(relativePath),
|
|
1151
|
+
journal: guards.journal,
|
|
1152
|
+
phase: "before_switch",
|
|
1153
|
+
onFailure: "unlink"
|
|
1154
|
+
});
|
|
1155
|
+
if (durability === "per_entry") fsyncPath(import_node_path.default.dirname(targetPath), import_node_fs.default.constants.O_RDONLY);
|
|
1156
|
+
if (guards.journal)
|
|
1157
|
+
guards.journal.entries.push({
|
|
1158
|
+
relativePath,
|
|
1159
|
+
replaced,
|
|
1160
|
+
after: outcome.after,
|
|
1161
|
+
afterContent: outcome.afterContent,
|
|
1162
|
+
...outcome.displaced ? { displaced: outcome.displaced } : {}
|
|
1163
|
+
});
|
|
1164
|
+
else if (outcome.displaced) releaseRetainedEntry(void 0, relativePath, outcome.displaced);
|
|
1165
|
+
if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, outcome.after);
|
|
1166
|
+
return projected;
|
|
1167
|
+
}
|
|
1168
|
+
function createDirectoryGuarded(targetRoot, relativePath, mode, guards) {
|
|
1169
|
+
if (!guards) {
|
|
1170
|
+
ensureDirectory(targetRoot, relativePath, mode);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
1174
|
+
assertInside(targetRoot, targetPath);
|
|
1175
|
+
try {
|
|
1176
|
+
import_node_fs.default.mkdirSync(targetPath, { mode: mode & 511 });
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
if (isPathStateCode(error.code)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
1179
|
+
throw error;
|
|
1180
|
+
}
|
|
1181
|
+
if (!guards.journal) return;
|
|
1182
|
+
const created = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1183
|
+
if (created.kind !== "entry" || !created.stat.isDirectory() || created.stat.isSymbolicLink()) {
|
|
1184
|
+
throw new WorkingTreeTargetChangedError(relativePath);
|
|
1185
|
+
}
|
|
1186
|
+
guards.journal.entries.push({ relativePath, replaced: null, after: created.stat });
|
|
1187
|
+
}
|
|
1188
|
+
function retainedEntryIntact(retained, current) {
|
|
1189
|
+
return retained.content !== void 0 && sameWorkingTreeInode(retained.stat, current) && current.mode === retained.stat.mode && current.size === retained.stat.size && current.mtimeMs === retained.stat.mtimeMs && current.ctimeMs === retained.stat.ctimeMs && entryContent(retained.path, current) === retained.content;
|
|
1190
|
+
}
|
|
1191
|
+
function settleRetainedEntries(journal, targetRoot) {
|
|
1192
|
+
raceWindow("before_settle", "", targetRoot);
|
|
1193
|
+
for (const entry of journal.entries) {
|
|
1194
|
+
if (!entry.displaced) continue;
|
|
1195
|
+
const current = lstatIfExists(entry.displaced.path);
|
|
1196
|
+
if (!current || !retainedEntryIntact(entry.displaced, current)) {
|
|
1197
|
+
throw new WorkingTreeTargetChangedError(entry.relativePath, {
|
|
1198
|
+
detail: "written through a descriptor opened before hydration replaced the path"
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
releaseRetainedEntries(journal);
|
|
1203
|
+
}
|
|
1204
|
+
function releaseRetainedEntries(journal) {
|
|
1205
|
+
for (const entry of journal.entries) {
|
|
1206
|
+
if (!entry.displaced) continue;
|
|
1207
|
+
releaseRetainedEntry(journal, entry.relativePath, entry.displaced);
|
|
1208
|
+
delete entry.displaced;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function releaseRetainedEntry(journal, relativePath, displaced) {
|
|
1212
|
+
const current = lstatIfExists(displaced.path);
|
|
1213
|
+
if (!current) return;
|
|
1214
|
+
if (!retainedEntryIntact(displaced, current)) {
|
|
1215
|
+
const retained = retainForeign(journal, relativePath, displaced.path, "unrestorable");
|
|
1216
|
+
if (!journal) throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
import_node_fs.default.rmSync(displaced.path, { force: true });
|
|
1220
|
+
}
|
|
1221
|
+
function inspectMirrorTarget(targetRoot, deletionMode, stats) {
|
|
1222
|
+
if (deletionMode !== "git") return { existing: inspectWorkingTree(targetRoot, "all", { stats }), git: null };
|
|
1223
|
+
const inspection = inspectGitWorkingTree(targetRoot, stats);
|
|
529
1224
|
const occupied = /* @__PURE__ */ new Set();
|
|
530
1225
|
for (const relativePath of inspection.submodules.keys()) {
|
|
531
1226
|
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
@@ -627,43 +1322,109 @@ function recordGitlinkStaleHeads(targetRoot, desired, shielded, git) {
|
|
|
627
1322
|
}
|
|
628
1323
|
if (changed) writeGitlinkStaleHeads(targetRoot, heads);
|
|
629
1324
|
}
|
|
630
|
-
function
|
|
1325
|
+
function prepareWorkingTreeMirror(input) {
|
|
631
1326
|
const sourceRoot = import_node_path.default.resolve(input.sourceRoot);
|
|
632
1327
|
const targetRoot = import_node_path.default.resolve(input.targetRoot);
|
|
633
|
-
const durability = input.durability ?? "per_entry";
|
|
634
1328
|
if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
|
|
635
1329
|
import_node_fs.default.mkdirSync(targetRoot, { recursive: true });
|
|
636
1330
|
}
|
|
637
1331
|
inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
|
|
638
1332
|
const desired = inspectWorkingTree(sourceRoot, input.sourceMode, { gitlinks: input.sourceGitlinks });
|
|
639
|
-
const
|
|
1333
|
+
const existingStats = /* @__PURE__ */ new Map();
|
|
1334
|
+
const target = inspectMirrorTarget(targetRoot, input.deletionMode, existingStats);
|
|
640
1335
|
const shielded = target.git ? shieldedDesiredPaths(desired, target.git) : /* @__PURE__ */ new Set();
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1336
|
+
return {
|
|
1337
|
+
sourceRoot,
|
|
1338
|
+
targetRoot,
|
|
1339
|
+
deletionMode: input.deletionMode,
|
|
1340
|
+
desired,
|
|
1341
|
+
existing: target.existing,
|
|
1342
|
+
existingStats,
|
|
1343
|
+
git: target.git,
|
|
1344
|
+
shielded
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function applyWorkingTreeMirror(prepared, options = {}) {
|
|
1348
|
+
const { sourceRoot, targetRoot, desired, existing, shielded } = prepared;
|
|
1349
|
+
const durability = options.durability ?? "per_entry";
|
|
1350
|
+
const guards = options.guards;
|
|
1351
|
+
const selection = options.selection;
|
|
1352
|
+
const target = { existing, git: prepared.git };
|
|
1353
|
+
const indexDesired = selection?.desiredForIndex ?? desired;
|
|
1354
|
+
const index = target.git ? planIndexChanges(indexDesired, shielded, target.git) : null;
|
|
1355
|
+
const neededDirectories = /* @__PURE__ */ new Set();
|
|
1356
|
+
if (selection) {
|
|
1357
|
+
for (const relativePath of selection.write) {
|
|
1358
|
+
let parent = import_node_path.default.posix.dirname(relativePath);
|
|
1359
|
+
while (parent !== ".") {
|
|
1360
|
+
neededDirectories.add(parent);
|
|
1361
|
+
parent = import_node_path.default.posix.dirname(parent);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
for (const relativePath of [...existing.keys()].sort(deepestFirst)) {
|
|
1366
|
+
if (selection ? !selection.remove.has(relativePath) : desired.has(relativePath)) continue;
|
|
1367
|
+
const entry = existing.get(relativePath);
|
|
645
1368
|
if (entry.kind === "gitlink" || directoryShieldsSubmodule(relativePath, entry, target.git)) continue;
|
|
646
|
-
|
|
1369
|
+
if (selection && entry.kind === "directory") {
|
|
1370
|
+
const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1371
|
+
if (inspected.kind !== "entry" || !inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) continue;
|
|
1372
|
+
try {
|
|
1373
|
+
import_node_fs.default.rmdirSync(inspected.absolutePath);
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
const code = error.code;
|
|
1376
|
+
if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOENT") continue;
|
|
1377
|
+
throw error;
|
|
1378
|
+
}
|
|
1379
|
+
if (guards?.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
|
|
1380
|
+
guards?.journal?.entries.push({ relativePath, replaced: entry, after: null });
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
removeEntryGuarded(targetRoot, relativePath, entry, guards);
|
|
647
1384
|
}
|
|
648
1385
|
const gitlinks = [];
|
|
649
1386
|
for (const [relativePath, entry] of [...desired.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
|
|
650
1387
|
if (shielded.has(relativePath)) continue;
|
|
1388
|
+
if (selection && entry.kind === "directory" && !neededDirectories.has(relativePath)) continue;
|
|
1389
|
+
if (selection && entry.kind !== "directory" && !selection.write.has(relativePath)) continue;
|
|
651
1390
|
if (entry.kind === "gitlink") {
|
|
652
|
-
|
|
1391
|
+
if (guards) {
|
|
1392
|
+
const parent = import_node_path.default.posix.dirname(relativePath);
|
|
1393
|
+
if (parent !== ".") ensureDirectoryGuarded(targetRoot, parent, guards);
|
|
1394
|
+
const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1395
|
+
if (current.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${current.blockingPath}`);
|
|
1396
|
+
if (current.kind === "entry" && (!current.stat.isDirectory() || current.stat.isSymbolicLink())) {
|
|
1397
|
+
if (guards.expectations && !guards.expectations.has(relativePath)) throw new WorkingTreeTargetChangedError(relativePath);
|
|
1398
|
+
removeEntryGuarded(targetRoot, relativePath, treeEntryFromStat(current.stat, current.absolutePath), guards);
|
|
1399
|
+
createDirectoryGuarded(targetRoot, relativePath, 493, guards);
|
|
1400
|
+
} else if (current.kind !== "entry") {
|
|
1401
|
+
assertTargetExpectation(targetRoot, relativePath, guards);
|
|
1402
|
+
createDirectoryGuarded(targetRoot, relativePath, 493, guards);
|
|
1403
|
+
}
|
|
1404
|
+
} else {
|
|
1405
|
+
ensureDirectory(targetRoot, relativePath, 493);
|
|
1406
|
+
}
|
|
653
1407
|
gitlinks.push({ relativePath, objectId: entry.objectId });
|
|
1408
|
+
options.projected?.set(relativePath, { kind: "gitlink", objectId: entry.objectId });
|
|
654
1409
|
continue;
|
|
655
1410
|
}
|
|
656
|
-
copyEntry(sourceRoot, targetRoot, relativePath, entry, durability);
|
|
1411
|
+
const projected = guards ? copyEntryGuarded(sourceRoot, targetRoot, relativePath, entry, durability, guards) : copyEntry(sourceRoot, targetRoot, relativePath, entry, durability);
|
|
1412
|
+
if (projected && options.projected) options.projected.set(relativePath, projected);
|
|
657
1413
|
}
|
|
1414
|
+
if (guards?.journal) settleRetainedEntries(guards.journal, targetRoot);
|
|
658
1415
|
if (index && target.git) {
|
|
659
1416
|
updateIndex(targetRoot, { drop: index.removed, put: index.written.map((gitlink) => ({ mode: "160000", stage: 0, ...gitlink })) });
|
|
660
|
-
|
|
1417
|
+
if (guards?.journal && (index.removed.length > 0 || index.written.length > 0)) guards.journal.indexApplied = index;
|
|
1418
|
+
recordGitlinkStaleHeads(targetRoot, indexDesired, shielded, target.git);
|
|
661
1419
|
}
|
|
662
1420
|
return {
|
|
663
1421
|
paths: [...desired.keys()].filter((relativePath) => !shielded.has(relativePath) && desired.get(relativePath)?.kind !== "directory").sort(),
|
|
664
1422
|
gitlinks
|
|
665
1423
|
};
|
|
666
1424
|
}
|
|
1425
|
+
function mirrorWorkingTree(input) {
|
|
1426
|
+
return applyWorkingTreeMirror(prepareWorkingTreeMirror(input), { durability: input.durability, projected: input.projected });
|
|
1427
|
+
}
|
|
667
1428
|
function normalizeWorkingTreeRelativePath(value) {
|
|
668
1429
|
return normalizeRelativePath(value);
|
|
669
1430
|
}
|
|
@@ -700,16 +1461,23 @@ function fsyncPath(targetPath, flags) {
|
|
|
700
1461
|
}
|
|
701
1462
|
}
|
|
702
1463
|
function planWorkingTreeMirror(input) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
const
|
|
708
|
-
const
|
|
1464
|
+
return planPreparedWorkingTreeMirror(prepareWorkingTreeMirror(input));
|
|
1465
|
+
}
|
|
1466
|
+
function planPreparedWorkingTreeMirror(prepared, options = {}) {
|
|
1467
|
+
const { sourceRoot, targetRoot, existing, shielded } = prepared;
|
|
1468
|
+
const inspectedDesired = prepared.desired;
|
|
1469
|
+
const selection = options.selection;
|
|
1470
|
+
const target = { existing, git: prepared.git };
|
|
1471
|
+
const desired = new Map(
|
|
1472
|
+
[...inspectedDesired].filter(
|
|
1473
|
+
([relativePath, entry]) => !shielded.has(relativePath) && (!selection || entry.kind === "directory" || selection.write.has(relativePath))
|
|
1474
|
+
)
|
|
1475
|
+
);
|
|
709
1476
|
const replaced = /* @__PURE__ */ new Map();
|
|
710
1477
|
const absent = [];
|
|
711
|
-
for (const [relativePath, entry] of
|
|
712
|
-
if (
|
|
1478
|
+
for (const [relativePath, entry] of existing) {
|
|
1479
|
+
if (selection ? !selection.remove.has(relativePath) : inspectedDesired.has(relativePath)) continue;
|
|
1480
|
+
if (entry.kind === "gitlink") continue;
|
|
713
1481
|
if (directoryShieldsSubmodule(relativePath, entry, target.git)) continue;
|
|
714
1482
|
replaced.set(relativePath, entry);
|
|
715
1483
|
}
|
|
@@ -720,7 +1488,8 @@ function planWorkingTreeMirror(input) {
|
|
|
720
1488
|
continue;
|
|
721
1489
|
}
|
|
722
1490
|
const { absolutePath, stat } = targetEntry;
|
|
723
|
-
if (
|
|
1491
|
+
if (options.overrides?.has(relativePath)) {
|
|
1492
|
+
} else if (entry.kind === "directory" || entry.kind === "gitlink") {
|
|
724
1493
|
if (stat.isDirectory()) continue;
|
|
725
1494
|
} else if (entry.kind === "symlink") {
|
|
726
1495
|
if (stat.isSymbolicLink() && import_node_fs.default.readlinkSync(absolutePath) === entry.target) continue;
|
|
@@ -741,7 +1510,208 @@ function planWorkingTreeMirror(input) {
|
|
|
741
1510
|
desired,
|
|
742
1511
|
replaced,
|
|
743
1512
|
absent: absent.filter((relativePath) => !hasAncestorIn(relativePath, replacedDirectories)).sort(shallowestFirst),
|
|
744
|
-
index: target.git ? planIndexChanges(inspectedDesired, shielded, target.git) : { removed: [], written: [] }
|
|
1513
|
+
index: target.git ? planIndexChanges(selection?.desiredForIndex ?? inspectedDesired, shielded, target.git) : { removed: [], written: [] }
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
function rollbackWorkingTreeMirrorJournal(input) {
|
|
1517
|
+
const targetRoot = import_node_path.default.resolve(input.targetRoot);
|
|
1518
|
+
const snapshotRoot = import_node_path.default.resolve(input.snapshotRoot);
|
|
1519
|
+
const { journal } = input;
|
|
1520
|
+
const state = newPassState(targetRoot);
|
|
1521
|
+
const touched = /* @__PURE__ */ new Set();
|
|
1522
|
+
const abandoned = [];
|
|
1523
|
+
const restoredTrees = /* @__PURE__ */ new Set();
|
|
1524
|
+
const targetPathOf = (relativePath) => {
|
|
1525
|
+
const targetPath = import_node_path.default.join(targetRoot, ...relativePath.split("/"));
|
|
1526
|
+
assertInside(targetRoot, targetPath);
|
|
1527
|
+
return targetPath;
|
|
1528
|
+
};
|
|
1529
|
+
const stageFromSnapshot = (relativePath, replaced) => {
|
|
1530
|
+
const captured = inspectRelativePathWithoutFollowingAncestors(snapshotRoot, relativePath);
|
|
1531
|
+
if (captured.kind !== "entry") throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
|
|
1532
|
+
const stagedPath = stagingPathFor(state, targetPathOf(relativePath), relativePath);
|
|
1533
|
+
if (replaced.kind === "symlink") {
|
|
1534
|
+
if (!captured.stat.isSymbolicLink()) throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
|
|
1535
|
+
import_node_fs.default.symlinkSync(import_node_fs.default.readlinkSync(captured.absolutePath), stagedPath);
|
|
1536
|
+
return { path: stagedPath, stat: import_node_fs.default.lstatSync(stagedPath) };
|
|
1537
|
+
}
|
|
1538
|
+
if (!captured.stat.isFile()) throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
|
|
1539
|
+
return { path: stagedPath, stat: stageRegularFile(captured.absolutePath, stagedPath, replaced, "per_entry").stagedStat };
|
|
1540
|
+
};
|
|
1541
|
+
const restoreReplaced = (entry, expected) => {
|
|
1542
|
+
const replaced = entry.replaced;
|
|
1543
|
+
const { relativePath } = entry;
|
|
1544
|
+
if (replaced.kind === "gitlink") return true;
|
|
1545
|
+
if (replaced.kind === "directory") return restoreCapturedDirectory(relativePath, replaced);
|
|
1546
|
+
let staged;
|
|
1547
|
+
let onFailure = "unlink";
|
|
1548
|
+
if (entry.displaced && lstatIfExists(entry.displaced.path)) {
|
|
1549
|
+
const current = import_node_fs.default.lstatSync(entry.displaced.path);
|
|
1550
|
+
if (!sameWorkingTreeInode(current, entry.displaced.stat))
|
|
1551
|
+
throw new Error(`Retained working-tree entry was replaced: ${entry.displaced.path}`);
|
|
1552
|
+
if (!retainedEntryIntact(entry.displaced, current)) onFailure = "retain";
|
|
1553
|
+
staged = { path: entry.displaced.path, stat: current };
|
|
1554
|
+
} else {
|
|
1555
|
+
staged = stageFromSnapshot(relativePath, replaced);
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
const outcome = switchStagedEntry({
|
|
1559
|
+
state,
|
|
1560
|
+
targetRoot,
|
|
1561
|
+
relativePath,
|
|
1562
|
+
targetPath: targetPathOf(relativePath),
|
|
1563
|
+
stagedPath: staged.path,
|
|
1564
|
+
stagedStat: staged.stat,
|
|
1565
|
+
expected,
|
|
1566
|
+
expectedContent: expected === null ? void 0 : entry.afterContent,
|
|
1567
|
+
journal,
|
|
1568
|
+
phase: "before_rollback_switch",
|
|
1569
|
+
onFailure
|
|
1570
|
+
});
|
|
1571
|
+
if (outcome.displaced) releaseRetainedEntry(journal, relativePath, outcome.displaced);
|
|
1572
|
+
delete entry.displaced;
|
|
1573
|
+
return true;
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
|
|
1576
|
+
if (onFailure === "retain") delete entry.displaced;
|
|
1577
|
+
else if (entry.displaced && !lstatIfExists(entry.displaced.path)) delete entry.displaced;
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
const restoreCapturedDirectory = (relativePath, replaced) => {
|
|
1582
|
+
const captured = inspectRelativePathWithoutFollowingAncestors(snapshotRoot, relativePath);
|
|
1583
|
+
if (captured.kind !== "entry" || !captured.stat.isDirectory()) {
|
|
1584
|
+
throw new Error(`Working-tree snapshot does not hold the captured directory: ${relativePath}`);
|
|
1585
|
+
}
|
|
1586
|
+
try {
|
|
1587
|
+
import_node_fs.default.mkdirSync(targetPathOf(relativePath), { mode: replaced.mode & 511 });
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
if (isPathStateCode(error.code)) return false;
|
|
1590
|
+
throw error;
|
|
1591
|
+
}
|
|
1592
|
+
let complete = true;
|
|
1593
|
+
const children = inspectWorkingTree(captured.absolutePath, "all");
|
|
1594
|
+
for (const [childRelative, child] of [...children.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
|
|
1595
|
+
const childPath = import_node_path.default.posix.join(relativePath, childRelative);
|
|
1596
|
+
if (child.kind === "gitlink") continue;
|
|
1597
|
+
if (child.kind === "directory") {
|
|
1598
|
+
try {
|
|
1599
|
+
import_node_fs.default.mkdirSync(targetPathOf(childPath), { mode: child.mode & 511 });
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
if (!isPathStateCode(error.code)) throw error;
|
|
1602
|
+
const now = inspectRelativePathWithoutFollowingAncestors(targetRoot, childPath);
|
|
1603
|
+
if (now.kind !== "entry" || !now.stat.isDirectory() || now.stat.isSymbolicLink()) {
|
|
1604
|
+
abandoned.push(childPath);
|
|
1605
|
+
complete = false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
const staged = stageFromSnapshot(childPath, child);
|
|
1611
|
+
try {
|
|
1612
|
+
switchStagedEntry({
|
|
1613
|
+
state,
|
|
1614
|
+
targetRoot,
|
|
1615
|
+
relativePath: childPath,
|
|
1616
|
+
targetPath: targetPathOf(childPath),
|
|
1617
|
+
stagedPath: staged.path,
|
|
1618
|
+
stagedStat: staged.stat,
|
|
1619
|
+
expected: null,
|
|
1620
|
+
journal,
|
|
1621
|
+
phase: "before_rollback_switch",
|
|
1622
|
+
onFailure: "unlink"
|
|
1623
|
+
});
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
|
|
1626
|
+
abandoned.push(childPath);
|
|
1627
|
+
complete = false;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
restoredTrees.add(relativePath);
|
|
1631
|
+
return complete;
|
|
1632
|
+
};
|
|
1633
|
+
for (const entry of [...journal.entries].reverse()) {
|
|
1634
|
+
const { relativePath } = entry;
|
|
1635
|
+
if (hasAncestorIn(relativePath, restoredTrees)) {
|
|
1636
|
+
touched.add(relativePath);
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
|
|
1640
|
+
if (current.kind === "blocked") {
|
|
1641
|
+
abandoned.push(relativePath);
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (entry.after === null) {
|
|
1645
|
+
if (current.kind !== "missing") {
|
|
1646
|
+
abandoned.push(relativePath);
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (entry.replaced && !restoreReplaced(entry, null)) {
|
|
1650
|
+
abandoned.push(relativePath);
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
touched.add(relativePath);
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
if (current.kind !== "entry" || !workingTreeStatsMatch(entry.after, current.stat) || entry.afterContent !== void 0 && entryContent(current.absolutePath, current.stat) !== entry.afterContent) {
|
|
1657
|
+
if (!(current.kind === "entry" && entry.after.isDirectory() && current.stat.isDirectory())) {
|
|
1658
|
+
abandoned.push(relativePath);
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (current.stat.isDirectory() && !current.stat.isSymbolicLink()) {
|
|
1663
|
+
try {
|
|
1664
|
+
import_node_fs.default.rmdirSync(current.absolutePath);
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
const code = error.code;
|
|
1667
|
+
if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOTDIR") {
|
|
1668
|
+
abandoned.push(relativePath);
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (code !== "ENOENT") throw error;
|
|
1672
|
+
}
|
|
1673
|
+
if (entry.replaced && !restoreReplaced(entry, null)) {
|
|
1674
|
+
abandoned.push(relativePath);
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
touched.add(relativePath);
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
if (entry.replaced) {
|
|
1681
|
+
if (!restoreReplaced(entry, entry.after)) {
|
|
1682
|
+
abandoned.push(relativePath);
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
touched.add(relativePath);
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
try {
|
|
1689
|
+
const displaced = removeSwitchedEntry({
|
|
1690
|
+
state,
|
|
1691
|
+
targetRoot,
|
|
1692
|
+
relativePath,
|
|
1693
|
+
targetPath: targetPathOf(relativePath),
|
|
1694
|
+
expected: entry.after,
|
|
1695
|
+
expectedContent: entry.afterContent,
|
|
1696
|
+
journal,
|
|
1697
|
+
phase: "before_rollback_remove"
|
|
1698
|
+
});
|
|
1699
|
+
releaseRetainedEntry(journal, relativePath, displaced);
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
|
|
1702
|
+
abandoned.push(relativePath);
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
touched.add(relativePath);
|
|
1706
|
+
}
|
|
1707
|
+
releaseRetainedEntries(journal);
|
|
1708
|
+
if (journal.indexApplied) {
|
|
1709
|
+
updateIndex(targetRoot, { drop: journal.indexApplied.written, put: journal.indexApplied.removed });
|
|
1710
|
+
}
|
|
1711
|
+
return {
|
|
1712
|
+
paths: [...touched].sort(shallowestFirst),
|
|
1713
|
+
abandoned: [...new Set(abandoned)].sort(shallowestFirst),
|
|
1714
|
+
...journal.retained && journal.retained.length > 0 ? { retained: [...journal.retained] } : {}
|
|
745
1715
|
};
|
|
746
1716
|
}
|
|
747
1717
|
function captureWorkingTreeMirrorPlan(input) {
|
|
@@ -822,7 +1792,8 @@ function restoreWorkingTreeMirrorScope(input) {
|
|
|
822
1792
|
for (const name of import_node_fs.default.readdirSync(absoluteDirectory)) {
|
|
823
1793
|
if (!MIRROR_TEMPORARY_FILE_PATTERN.test(name)) continue;
|
|
824
1794
|
const temporaryPath = import_node_path.default.join(absoluteDirectory, name);
|
|
825
|
-
|
|
1795
|
+
const temporaryStatus = import_node_fs.default.lstatSync(temporaryPath);
|
|
1796
|
+
if (!temporaryStatus.isFile() && !temporaryStatus.isSymbolicLink()) continue;
|
|
826
1797
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
827
1798
|
touched.add(directory === "." ? name : import_node_path.default.posix.join(directory, name));
|
|
828
1799
|
}
|
|
@@ -876,12 +1847,26 @@ function fsyncWorkingTreePaths(root, relativePaths) {
|
|
|
876
1847
|
}
|
|
877
1848
|
// Annotate the CommonJS export names for ESM import in node:
|
|
878
1849
|
0 && (module.exports = {
|
|
1850
|
+
WorkingTreeTargetChangedError,
|
|
1851
|
+
applyWorkingTreeMirror,
|
|
879
1852
|
assertWorkingTreeHasNoPortableGitMetadataAliases,
|
|
880
1853
|
captureWorkingTreeMirrorPlan,
|
|
1854
|
+
displacedEntryMatches,
|
|
881
1855
|
fsyncWorkingTreePaths,
|
|
882
1856
|
inspectWorkingTree,
|
|
1857
|
+
inspectWorkingTreePath,
|
|
883
1858
|
mirrorWorkingTree,
|
|
884
1859
|
normalizeWorkingTreeRelativePath,
|
|
1860
|
+
planPreparedWorkingTreeMirror,
|
|
885
1861
|
planWorkingTreeMirror,
|
|
886
|
-
|
|
1862
|
+
prepareWorkingTreeMirror,
|
|
1863
|
+
projectedFileEntry,
|
|
1864
|
+
projectedFileEntryMatches,
|
|
1865
|
+
restoreWorkingTreeMirrorScope,
|
|
1866
|
+
rollbackWorkingTreeMirrorJournal,
|
|
1867
|
+
sameWorkingTreeInode,
|
|
1868
|
+
setWorkingTreeMirrorRaceHook,
|
|
1869
|
+
workingTreeAtomicRenameSupport,
|
|
1870
|
+
workingTreeDisplacedRetentionDirectory,
|
|
1871
|
+
workingTreeStatsMatch
|
|
887
1872
|
});
|