@ricsam/r5d-worker 0.0.137 → 0.0.139

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.
Files changed (236) hide show
  1. package/README.md +4 -94
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/mjs/main.mjs +46 -7543
  4. package/dist/mjs/package.json +1 -1
  5. package/dist/mjs/personal/action-scheduler.mjs +31 -0
  6. package/dist/mjs/personal/client.mjs +215 -0
  7. package/dist/mjs/personal/runtime.mjs +359 -0
  8. package/dist/mjs/personal/tcp.mjs +217 -0
  9. package/dist/mjs/runtime/adapter.mjs +42 -0
  10. package/dist/mjs/runtime/client.mjs +95 -0
  11. package/dist/mjs/runtime/daemon.mjs +131 -0
  12. package/dist/mjs/runtime/executor.mjs +436 -0
  13. package/dist/mjs/runtime/index.mjs +15 -0
  14. package/dist/mjs/runtime/protocol.mjs +139 -0
  15. package/dist/mjs/runtime/pty-host.mjs +156 -0
  16. package/dist/mjs/runtime/pty.mjs +127 -0
  17. package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
  18. package/dist/mjs/runtime/releases/grants.mjs +93 -0
  19. package/dist/mjs/runtime/releases/main.mjs +101 -0
  20. package/dist/mjs/runtime/releases/manager.mjs +541 -0
  21. package/dist/mjs/runtime/releases/process.mjs +88 -0
  22. package/dist/mjs/runtime/releases/retirement.mjs +18 -0
  23. package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
  24. package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
  25. package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
  26. package/dist/mjs/runtime/storage.mjs +156 -0
  27. package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
  28. package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
  29. package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
  30. package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
  31. package/dist/mjs/runtime/workspace/files.mjs +308 -0
  32. package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
  33. package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
  34. package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
  35. package/dist/types/main.d.ts +0 -1167
  36. package/dist/types/personal/action-scheduler.d.ts +23 -0
  37. package/dist/types/personal/client.d.ts +14 -0
  38. package/dist/types/personal/runtime.d.ts +121 -0
  39. package/dist/types/personal/tcp.d.ts +14 -0
  40. package/dist/types/runtime/client.d.ts +3 -1
  41. package/dist/types/runtime/executor.d.ts +6 -1
  42. package/dist/types/runtime/protocol.d.ts +176 -4
  43. package/dist/types/runtime/pty.d.ts +1 -0
  44. package/dist/types/runtime/releases/artifacts.d.ts +51 -0
  45. package/dist/types/runtime/releases/grants.d.ts +16 -0
  46. package/dist/types/runtime/releases/manager.d.ts +145 -0
  47. package/dist/types/runtime/releases/process.d.ts +14 -0
  48. package/dist/types/runtime/releases/retirement.d.ts +15 -0
  49. package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
  50. package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
  51. package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
  52. package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
  53. package/dist/types/runtime/workspace/authority.d.ts +338 -0
  54. package/dist/types/runtime/workspace/contracts.d.ts +86 -0
  55. package/dist/types/runtime/workspace/file-write.d.ts +14 -0
  56. package/dist/types/runtime/workspace/files.d.ts +21 -0
  57. package/dist/types/runtime/workspace/http-body.d.ts +9 -0
  58. package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
  59. package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
  60. package/package.json +8 -6
  61. package/dist/cjs/atomic-rename.cjs +0 -303
  62. package/dist/cjs/browser-upload.cjs +0 -73
  63. package/dist/cjs/cli-update.cjs +0 -108
  64. package/dist/cjs/command-launcher.cjs +0 -423
  65. package/dist/cjs/control-command-policy.cjs +0 -177
  66. package/dist/cjs/git-blob-hash.cjs +0 -41
  67. package/dist/cjs/git-identity.cjs +0 -39
  68. package/dist/cjs/git-process-environment.cjs +0 -133
  69. package/dist/cjs/inline-output.cjs +0 -62
  70. package/dist/cjs/main.cjs +0 -7517
  71. package/dist/cjs/managed-paths.cjs +0 -215
  72. package/dist/cjs/plan-lint.cjs +0 -165
  73. package/dist/cjs/plan-parser.cjs +0 -127
  74. package/dist/cjs/plan-paths.cjs +0 -53
  75. package/dist/cjs/port-forward-client.cjs +0 -206
  76. package/dist/cjs/process-tree.cjs +0 -117
  77. package/dist/cjs/project-checkout-garbage.cjs +0 -219
  78. package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
  79. package/dist/cjs/project-mirror-refs-token.cjs +0 -85
  80. package/dist/cjs/project-workspace-state.cjs +0 -972
  81. package/dist/cjs/project-worktrees.cjs +0 -1713
  82. package/dist/cjs/pty-output-coalescer.cjs +0 -87
  83. package/dist/cjs/recovery-journal-protocol.cjs +0 -56
  84. package/dist/cjs/recovery-journal-runtime.cjs +0 -133
  85. package/dist/cjs/recovery-journal-thread.cjs +0 -9
  86. package/dist/cjs/recovery-journal.cjs +0 -742
  87. package/dist/cjs/recovery-output-window.cjs +0 -77
  88. package/dist/cjs/recovery-protocol.cjs +0 -31
  89. package/dist/cjs/recovery-store.cjs +0 -252
  90. package/dist/cjs/registry-auth.cjs +0 -310
  91. package/dist/cjs/repository-transition-policy.cjs +0 -49
  92. package/dist/cjs/runtime-version.cjs +0 -88
  93. package/dist/cjs/session-file-mutations.cjs +0 -61
  94. package/dist/cjs/supervisor.cjs +0 -147
  95. package/dist/cjs/three-way-merge.cjs +0 -346
  96. package/dist/cjs/working-tree-mirror.cjs +0 -1872
  97. package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
  98. package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
  99. package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
  100. package/dist/cjs/workspace-command-targets.cjs +0 -63
  101. package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
  102. package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
  103. package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
  104. package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
  105. package/dist/cjs/workspace-git-sync.cjs +0 -3595
  106. package/dist/cjs/workspace-hydration-ledger.cjs +0 -83
  107. package/dist/cjs/workspace-hydration-merge.cjs +0 -448
  108. package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
  109. package/dist/cjs/workspace-incident-state.cjs +0 -37
  110. package/dist/cjs/workspace-merge-projection.cjs +0 -628
  111. package/dist/cjs/workspace-mount-boundary.cjs +0 -71
  112. package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
  113. package/dist/cjs/workspace-mutation-gate.cjs +0 -127
  114. package/dist/cjs/workspace-path-move.cjs +0 -195
  115. package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
  116. package/dist/cjs/workspace-project-config-policy.cjs +0 -53
  117. package/dist/cjs/workspace-projection-ledger.cjs +0 -81
  118. package/dist/cjs/workspace-publication-evidence.cjs +0 -46
  119. package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
  120. package/dist/mjs/atomic-rename.mjs +0 -261
  121. package/dist/mjs/browser-upload.mjs +0 -42
  122. package/dist/mjs/command-launcher.mjs +0 -393
  123. package/dist/mjs/control-command-policy.mjs +0 -146
  124. package/dist/mjs/git-blob-hash.mjs +0 -16
  125. package/dist/mjs/git-identity.mjs +0 -15
  126. package/dist/mjs/git-process-environment.mjs +0 -105
  127. package/dist/mjs/inline-output.mjs +0 -37
  128. package/dist/mjs/managed-paths.mjs +0 -174
  129. package/dist/mjs/plan-lint.mjs +0 -129
  130. package/dist/mjs/plan-parser.mjs +0 -94
  131. package/dist/mjs/plan-paths.mjs +0 -25
  132. package/dist/mjs/port-forward-client.mjs +0 -172
  133. package/dist/mjs/project-checkout-garbage.mjs +0 -178
  134. package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
  135. package/dist/mjs/project-mirror-refs-token.mjs +0 -53
  136. package/dist/mjs/project-workspace-state.mjs +0 -937
  137. package/dist/mjs/project-worktrees.mjs +0 -1657
  138. package/dist/mjs/pty-output-coalescer.mjs +0 -61
  139. package/dist/mjs/recovery-journal-protocol.mjs +0 -30
  140. package/dist/mjs/recovery-journal-runtime.mjs +0 -112
  141. package/dist/mjs/recovery-journal-thread.mjs +0 -8
  142. package/dist/mjs/recovery-journal.mjs +0 -694
  143. package/dist/mjs/recovery-output-window.mjs +0 -53
  144. package/dist/mjs/recovery-protocol.mjs +0 -6
  145. package/dist/mjs/recovery-store.mjs +0 -217
  146. package/dist/mjs/registry-auth.mjs +0 -269
  147. package/dist/mjs/repository-transition-policy.mjs +0 -22
  148. package/dist/mjs/runtime-version.mjs +0 -54
  149. package/dist/mjs/session-file-mutations.mjs +0 -37
  150. package/dist/mjs/supervisor.mjs +0 -114
  151. package/dist/mjs/three-way-merge.mjs +0 -318
  152. package/dist/mjs/working-tree-mirror.mjs +0 -1817
  153. package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
  154. package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
  155. package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
  156. package/dist/mjs/workspace-command-targets.mjs +0 -37
  157. package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
  158. package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
  159. package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
  160. package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
  161. package/dist/mjs/workspace-git-sync.mjs +0 -3553
  162. package/dist/mjs/workspace-hydration-ledger.mjs +0 -58
  163. package/dist/mjs/workspace-hydration-merge.mjs +0 -414
  164. package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
  165. package/dist/mjs/workspace-incident-state.mjs +0 -12
  166. package/dist/mjs/workspace-merge-projection.mjs +0 -594
  167. package/dist/mjs/workspace-mount-boundary.mjs +0 -37
  168. package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
  169. package/dist/mjs/workspace-mutation-gate.mjs +0 -103
  170. package/dist/mjs/workspace-path-move.mjs +0 -160
  171. package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
  172. package/dist/mjs/workspace-project-config-policy.mjs +0 -27
  173. package/dist/mjs/workspace-projection-ledger.mjs +0 -57
  174. package/dist/mjs/workspace-publication-evidence.mjs +0 -21
  175. package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
  176. package/dist/types/atomic-rename.d.ts +0 -78
  177. package/dist/types/browser-upload.d.ts +0 -7
  178. package/dist/types/command-launcher.d.ts +0 -104
  179. package/dist/types/control-command-policy.d.ts +0 -53
  180. package/dist/types/git-blob-hash.d.ts +0 -10
  181. package/dist/types/git-identity.d.ts +0 -9
  182. package/dist/types/git-process-environment.d.ts +0 -9
  183. package/dist/types/inline-output.d.ts +0 -3
  184. package/dist/types/managed-paths.d.ts +0 -19
  185. package/dist/types/plan-lint.d.ts +0 -16
  186. package/dist/types/plan-parser.d.ts +0 -38
  187. package/dist/types/plan-paths.d.ts +0 -36
  188. package/dist/types/port-forward-client.d.ts +0 -8
  189. package/dist/types/project-checkout-garbage.d.ts +0 -86
  190. package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
  191. package/dist/types/project-mirror-refs-token.d.ts +0 -53
  192. package/dist/types/project-workspace-state.d.ts +0 -188
  193. package/dist/types/project-worktrees.d.ts +0 -397
  194. package/dist/types/pty-output-coalescer.d.ts +0 -20
  195. package/dist/types/recovery-journal-protocol.d.ts +0 -35
  196. package/dist/types/recovery-journal-runtime.d.ts +0 -12
  197. package/dist/types/recovery-journal-stall-fixture.d.ts +0 -1
  198. package/dist/types/recovery-journal-thread.d.ts +0 -1
  199. package/dist/types/recovery-journal.d.ts +0 -248
  200. package/dist/types/recovery-output-window.d.ts +0 -19
  201. package/dist/types/recovery-protocol.d.ts +0 -59
  202. package/dist/types/recovery-store.d.ts +0 -52
  203. package/dist/types/registry-auth.d.ts +0 -47
  204. package/dist/types/repository-transition-policy.d.ts +0 -26
  205. package/dist/types/runtime-version.d.ts +0 -1
  206. package/dist/types/session-file-mutations.d.ts +0 -22
  207. package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
  208. package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
  209. package/dist/types/supervisor-signal-fixture.d.ts +0 -1
  210. package/dist/types/supervisor.d.ts +0 -23
  211. package/dist/types/three-way-merge.d.ts +0 -77
  212. package/dist/types/working-tree-mirror.d.ts +0 -424
  213. package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
  214. package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
  215. package/dist/types/workspace-command-sync-policy.d.ts +0 -57
  216. package/dist/types/workspace-command-targets.d.ts +0 -37
  217. package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
  218. package/dist/types/workspace-filesystem-executor.d.ts +0 -123
  219. package/dist/types/workspace-filesystem-job-types.d.ts +0 -302
  220. package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
  221. package/dist/types/workspace-git-sync.d.ts +0 -514
  222. package/dist/types/workspace-hydration-ledger.d.ts +0 -76
  223. package/dist/types/workspace-hydration-merge.d.ts +0 -103
  224. package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
  225. package/dist/types/workspace-incident-state.d.ts +0 -19
  226. package/dist/types/workspace-merge-projection.d.ts +0 -69
  227. package/dist/types/workspace-mount-boundary.d.ts +0 -10
  228. package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
  229. package/dist/types/workspace-mutation-gate.d.ts +0 -42
  230. package/dist/types/workspace-path-move.d.ts +0 -21
  231. package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
  232. package/dist/types/workspace-project-config-policy.d.ts +0 -48
  233. package/dist/types/workspace-projection-ledger.d.ts +0 -59
  234. package/dist/types/workspace-publication-evidence.d.ts +0 -17
  235. package/dist/types/workspace-sync-coalescer.d.ts +0 -28
  236. /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
@@ -1,1817 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { createHash, randomUUID } from "node:crypto";
4
- import { workerGitProcessEnvironment } from "./git-process-environment.mjs";
5
- import { AtomicRenameError, atomicRenamePrimitivesFor, isAtomicRenameUnsupported } from "./atomic-rename.mjs";
6
- function projectedFileEntry(stat) {
7
- return { kind: "file", size: stat.size, mode: stat.mode, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs, ino: stat.ino };
8
- }
9
- function projectedFileEntryMatches(entry, stat) {
10
- return stat.isFile() && stat.ino === entry.ino && stat.size === entry.size && stat.mode === entry.mode && stat.mtimeMs === entry.mtimeMs && stat.ctimeMs === entry.ctimeMs;
11
- }
12
- function workingTreeStatsMatch(left, right) {
13
- 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;
14
- }
15
- function sameWorkingTreeInode(left, right) {
16
- return left.ino === right.ino && left.dev === right.dev;
17
- }
18
- function displacedEntryMatches(expected, displaced) {
19
- return sameWorkingTreeInode(expected, displaced) && displaced.mode === expected.mode && displaced.size === expected.size && displaced.mtimeMs === expected.mtimeMs;
20
- }
21
- class WorkingTreeTargetChangedError extends Error {
22
- constructor(relativePath, options = {}) {
23
- const retained = options.retained ?? [];
24
- super(
25
- `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(", ")}` : "")
26
- );
27
- this.relativePath = relativePath;
28
- this.name = "WorkingTreeTargetChangedError";
29
- this.retained = retained;
30
- }
31
- relativePath;
32
- retained;
33
- }
34
- let raceHook = null;
35
- function setWorkingTreeMirrorRaceHook(hook) {
36
- raceHook = hook;
37
- }
38
- function raceWindow(phase, relativePath, targetPath) {
39
- if (raceHook) raceHook({ phase, relativePath, targetPath });
40
- }
41
- const HFS_IGNORED_CODE_POINTS = /[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/gu;
42
- const MIRROR_TEMPORARY_FILE_PREFIX = ".r5d-working-tree-mirror-";
43
- const OBJECT_ID_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
44
- const GITLINK_STALE_HEADS_FILE = "gitlink-heads.json";
45
- const NON_RECURSIVE_GIT_CONFIG = [
46
- "-c",
47
- "submodule.recurse=false",
48
- "-c",
49
- "fetch.recurseSubmodules=false",
50
- "-c",
51
- "push.recurseSubmodules=false"
52
- ];
53
- function isPortableGitMetadataSegment(segment) {
54
- const hfsFolded = segment.replace(HFS_IGNORED_CODE_POINTS, "").toLowerCase();
55
- if (hfsFolded === ".git") return true;
56
- const ntfsFolded = segment.toLowerCase();
57
- return /^(?:\.git|git~1)[ .]*(?::|$)/u.test(ntfsFolded);
58
- }
59
- function isGitMetadataPath(relativePath) {
60
- return relativePath.split(/[\\/]/u).some(isPortableGitMetadataSegment);
61
- }
62
- function assertWorkingTreeHasNoPortableGitMetadataAliases(root) {
63
- root = path.resolve(root);
64
- const rootStatus = fs.lstatSync(root);
65
- if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
66
- throw new Error(`Working-tree root is not a regular directory: ${root}`);
67
- }
68
- const inspectDirectory = (directoryPath, relativeDirectory) => {
69
- for (const name of fs.readdirSync(directoryPath).sort()) {
70
- const relativePath = relativeDirectory ? path.posix.join(relativeDirectory, name) : name;
71
- if (name === ".git") continue;
72
- if (isGitMetadataPath(relativePath)) {
73
- throw new Error(`Working tree contains a portable Git metadata alias that cannot be moved safely: ${relativePath}`);
74
- }
75
- const entryPath = path.join(directoryPath, name);
76
- const status = fs.lstatSync(entryPath);
77
- if (status.isDirectory() && !status.isSymbolicLink()) inspectDirectory(entryPath, relativePath);
78
- }
79
- };
80
- inspectDirectory(root, "");
81
- }
82
- function normalizeRelativePath(value) {
83
- const normalized = value.split(path.sep).join(path.posix.sep).replace(/^\.\/+/, "").replace(/^\/+|\/+$/g, "");
84
- if (!normalized || normalized === "." || normalized.split("/").includes("..") || isGitMetadataPath(normalized)) return null;
85
- return normalized;
86
- }
87
- function assertInside(root, candidate) {
88
- const relative = path.relative(path.resolve(root), path.resolve(candidate));
89
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
90
- throw new Error(`Working-tree path escapes its root: ${candidate}`);
91
- }
92
- }
93
- function inspectRelativePathWithoutFollowingAncestors(root, relativePath) {
94
- const normalized = normalizeRelativePath(relativePath);
95
- if (!normalized) return { kind: "blocked", blockingPath: relativePath };
96
- let current = path.resolve(root);
97
- const segments = normalized.split("/");
98
- for (let index = 0; index < segments.length; index += 1) {
99
- current = path.join(current, segments[index]);
100
- assertInside(root, current);
101
- let stat;
102
- try {
103
- stat = fs.lstatSync(current);
104
- } catch (error) {
105
- if (error.code === "ENOENT") return { kind: "missing" };
106
- throw error;
107
- }
108
- if (index < segments.length - 1 && (!stat.isDirectory() || stat.isSymbolicLink())) {
109
- return { kind: "blocked", blockingPath: current };
110
- }
111
- if (index === segments.length - 1) return { kind: "entry", absolutePath: current, stat };
112
- }
113
- return { kind: "missing" };
114
- }
115
- function inspectWorkingTreePath(root, relativePath) {
116
- return inspectRelativePathWithoutFollowingAncestors(path.resolve(root), relativePath);
117
- }
118
- function inspectDirectoryRoot(root, options) {
119
- let stat;
120
- try {
121
- stat = fs.lstatSync(root);
122
- } catch (error) {
123
- if (options.allowMissing && error.code === "ENOENT") return false;
124
- throw error;
125
- }
126
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
127
- throw new Error(`${options.label} is not a regular directory: ${root}`);
128
- }
129
- return true;
130
- }
131
- function lstatIfExists(targetPath) {
132
- try {
133
- return fs.lstatSync(targetPath);
134
- } catch (error) {
135
- if (error.code === "ENOENT") return null;
136
- throw error;
137
- }
138
- }
139
- function nulRecords(content) {
140
- const records = [];
141
- let start = 0;
142
- for (let index = 0; index < content.length; index += 1) {
143
- if (content[index] !== 0) continue;
144
- if (index > start) records.push(content.subarray(start, index));
145
- start = index + 1;
146
- }
147
- if (start < content.length) records.push(content.subarray(start));
148
- return records;
149
- }
150
- function gitOutput(cwd, args, action, options = {}) {
151
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...args], {
152
- cwd,
153
- ...options.stdin ? { stdin: options.stdin } : {},
154
- stdout: "pipe",
155
- stderr: "pipe",
156
- env: options.environment ?? workerGitProcessEnvironment()
157
- });
158
- if (result.exitCode !== 0) {
159
- throw new Error(`${action}: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
160
- }
161
- return Buffer.from(result.stdout);
162
- }
163
- function listGitCheckout(root) {
164
- const output = gitOutput(
165
- root,
166
- ["ls-files", "-z", "-t", "--stage", "--cached", "--others", "--exclude-standard", "--", "."],
167
- "Inspect Git working tree"
168
- );
169
- const roots = /* @__PURE__ */ new Map();
170
- const indexRecords = [];
171
- for (const record of nulRecords(output)) {
172
- const text = record.toString();
173
- const other = /^\? (.*)$/su.exec(text);
174
- if (other) {
175
- const normalized2 = normalizeRelativePath(other[1]);
176
- if (normalized2 && !roots.has(normalized2)) roots.set(normalized2, other[1].endsWith("/"));
177
- continue;
178
- }
179
- const staged = /^[A-Z] ([0-7]{6}) ([0-9a-f]{40,64}) ([0-3])\t(.*)$/su.exec(text);
180
- if (!staged) continue;
181
- const normalized = normalizeRelativePath(staged[4]);
182
- if (!normalized) continue;
183
- indexRecords.push({ mode: staged[1], objectId: staged[2], stage: Number(staged[3]), relativePath: normalized });
184
- if (!roots.has(normalized)) roots.set(normalized, false);
185
- }
186
- return {
187
- roots: [...roots].map(([relativePath, nestedRepository]) => ({ relativePath, nestedRepository })).sort((left, right) => left.relativePath < right.relativePath ? -1 : left.relativePath > right.relativePath ? 1 : 0),
188
- indexRecords
189
- };
190
- }
191
- function populatedCheckoutHead(root, absolutePath) {
192
- if (!lstatIfExists(path.join(absolutePath, ".git"))) return null;
193
- const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "rev-parse", "--verify", "HEAD^{commit}"], {
194
- cwd: absolutePath,
195
- stdout: "pipe",
196
- stderr: "pipe",
197
- env: { ...workerGitProcessEnvironment(), GIT_CEILING_DIRECTORIES: root }
198
- });
199
- if (result.exitCode !== 0) return null;
200
- const head = result.stdout.toString().trim();
201
- return OBJECT_ID_PATTERN.test(head) ? head : null;
202
- }
203
- function checkoutGitDirectory(root) {
204
- const dotGit = path.join(root, ".git");
205
- const status = lstatIfExists(dotGit);
206
- if (!status) return null;
207
- if (status.isDirectory()) return dotGit;
208
- if (!status.isFile()) return null;
209
- const match = /^gitdir:[ \t]*(.+?)\s*$/u.exec(fs.readFileSync(dotGit, "utf8").split(/\r?\n/u)[0] ?? "");
210
- return match ? path.resolve(root, match[1]) : null;
211
- }
212
- function gitlinkStaleHeadsPath(root) {
213
- const gitDirectory = checkoutGitDirectory(root);
214
- return gitDirectory ? path.join(gitDirectory, "r5d", GITLINK_STALE_HEADS_FILE) : null;
215
- }
216
- function readGitlinkStaleHeads(root) {
217
- const heads = /* @__PURE__ */ new Map();
218
- const markerPath = gitlinkStaleHeadsPath(root);
219
- if (!markerPath || !lstatIfExists(markerPath)?.isFile()) return heads;
220
- let parsed;
221
- try {
222
- parsed = JSON.parse(fs.readFileSync(markerPath, "utf8"));
223
- } catch {
224
- return heads;
225
- }
226
- if (!parsed || typeof parsed !== "object" || parsed.version !== 1) return heads;
227
- const recorded = parsed.heads;
228
- if (!recorded || typeof recorded !== "object") return heads;
229
- for (const [relativePath, objectId] of Object.entries(recorded)) {
230
- const normalized = normalizeRelativePath(relativePath);
231
- if (normalized && typeof objectId === "string" && OBJECT_ID_PATTERN.test(objectId)) heads.set(normalized, objectId);
232
- }
233
- return heads;
234
- }
235
- function writeGitlinkStaleHeads(root, heads) {
236
- const markerPath = gitlinkStaleHeadsPath(root);
237
- if (!markerPath) return;
238
- const directory = path.dirname(markerPath);
239
- if (heads.size === 0) {
240
- if (!lstatIfExists(markerPath)) return;
241
- fs.rmSync(markerPath, { force: true });
242
- fsyncPath(directory, fs.constants.O_RDONLY);
243
- return;
244
- }
245
- const directoryStatus = lstatIfExists(directory);
246
- if (!directoryStatus) fs.mkdirSync(directory, { mode: 448 });
247
- else if (!directoryStatus.isDirectory() || directoryStatus.isSymbolicLink()) {
248
- throw new Error(`Checkout private state is not a regular directory: ${directory}`);
249
- }
250
- const temporaryPath = path.join(directory, `.${GITLINK_STALE_HEADS_FILE}.${randomUUID()}.tmp`);
251
- const descriptor = fs.openSync(temporaryPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
252
- try {
253
- fs.writeFileSync(descriptor, `${JSON.stringify({ version: 1, heads: Object.fromEntries([...heads].sort()) })}
254
- `);
255
- fs.fsyncSync(descriptor);
256
- } finally {
257
- fs.closeSync(descriptor);
258
- }
259
- fs.renameSync(temporaryPath, markerPath);
260
- fsyncPath(directory, fs.constants.O_RDONLY);
261
- }
262
- function addEntry(entries, root, relativePath, recurseDirectories, gitlinks, stats) {
263
- const normalized = normalizeRelativePath(relativePath);
264
- if (!normalized || entries.has(normalized)) return;
265
- const gitlink = gitlinks?.get(normalized);
266
- if (gitlink !== void 0) {
267
- entries.set(normalized, { kind: "gitlink", objectId: gitlink });
268
- return;
269
- }
270
- const inspected = inspectRelativePathWithoutFollowingAncestors(root, normalized);
271
- if (inspected.kind !== "entry") return;
272
- const { absolutePath, stat } = inspected;
273
- if (stat.isSymbolicLink()) {
274
- for (const existing of [...entries.keys()]) {
275
- if (existing.startsWith(`${normalized}/`)) {
276
- entries.delete(existing);
277
- stats?.delete(existing);
278
- }
279
- }
280
- entries.set(normalized, { kind: "symlink", mode: stat.mode, target: fs.readlinkSync(absolutePath) });
281
- stats?.set(normalized, stat);
282
- return;
283
- }
284
- if (stat.isFile()) {
285
- entries.set(normalized, { kind: "file", mode: stat.mode, size: stat.size });
286
- stats?.set(normalized, stat);
287
- return;
288
- }
289
- if (!stat.isDirectory()) return;
290
- entries.set(normalized, { kind: "directory", mode: stat.mode });
291
- stats?.set(normalized, stat);
292
- if (!recurseDirectories) return;
293
- for (const child of fs.readdirSync(absolutePath).sort()) {
294
- addEntry(entries, root, path.posix.join(normalized, child), true, gitlinks, stats);
295
- }
296
- }
297
- function addParentDirectories(entries, root, stats) {
298
- for (const relativePath of [...entries.keys()]) {
299
- let parent = path.posix.dirname(relativePath);
300
- while (parent !== ".") {
301
- if (!entries.has(parent)) {
302
- const inspected = inspectRelativePathWithoutFollowingAncestors(root, parent);
303
- if (inspected.kind !== "entry" || !inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) {
304
- throw new Error(`Working-tree entry has an unsafe parent directory: ${parent}`);
305
- }
306
- entries.set(parent, { kind: "directory", mode: inspected.stat.mode });
307
- stats?.set(parent, inspected.stat);
308
- }
309
- parent = path.posix.dirname(parent);
310
- }
311
- }
312
- }
313
- function inspectGitWorkingTree(root, stats) {
314
- const entries = /* @__PURE__ */ new Map();
315
- const listing = listGitCheckout(root);
316
- const indexGitlinks = /* @__PURE__ */ new Map();
317
- for (const record of listing.indexRecords) {
318
- if (record.mode === "160000" && !indexGitlinks.has(record.relativePath)) indexGitlinks.set(record.relativePath, record.objectId);
319
- }
320
- const submodules = /* @__PURE__ */ new Map();
321
- let staleHeads = null;
322
- for (const { relativePath, nestedRepository } of listing.roots) {
323
- if (entries.has(relativePath)) continue;
324
- const indexObjectId = indexGitlinks.get(relativePath);
325
- if (indexObjectId === void 0 && !nestedRepository) {
326
- addEntry(entries, root, relativePath, true, void 0, stats);
327
- continue;
328
- }
329
- const inspected = inspectRelativePathWithoutFollowingAncestors(root, relativePath);
330
- if (inspected.kind !== "entry") continue;
331
- if (!inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) {
332
- addEntry(entries, root, relativePath, true, void 0, stats);
333
- continue;
334
- }
335
- const head = populatedCheckoutHead(root, inspected.absolutePath);
336
- submodules.set(relativePath, head);
337
- let objectId = indexObjectId ?? head;
338
- if (head && indexObjectId !== void 0 && head !== indexObjectId) {
339
- staleHeads ??= readGitlinkStaleHeads(root);
340
- objectId = staleHeads.get(relativePath) === head ? indexObjectId : head;
341
- }
342
- if (objectId) {
343
- entries.set(relativePath, { kind: "gitlink", objectId });
344
- stats?.set(relativePath, inspected.stat);
345
- }
346
- }
347
- addParentDirectories(entries, root, stats);
348
- return { entries, indexRecords: listing.indexRecords, submodules };
349
- }
350
- function normalizedGitlinks(gitlinks) {
351
- const normalized = /* @__PURE__ */ new Map();
352
- for (const [relativePath, objectId] of gitlinks ?? []) {
353
- const relative = normalizeRelativePath(relativePath);
354
- if (!relative) throw new Error(`Invalid working-tree gitlink path: ${relativePath}`);
355
- if (!OBJECT_ID_PATTERN.test(objectId)) throw new Error(`Invalid working-tree gitlink object id for ${relativePath}: ${objectId}`);
356
- normalized.set(relative, objectId);
357
- }
358
- return normalized;
359
- }
360
- function inspectWorkingTree(root, mode, options = {}) {
361
- const entries = /* @__PURE__ */ new Map();
362
- root = path.resolve(root);
363
- if (!inspectDirectoryRoot(root, { allowMissing: true, label: "Working-tree root" })) return entries;
364
- if (mode === "git") return inspectGitWorkingTree(root, options.stats).entries;
365
- const gitlinks = normalizedGitlinks(options.gitlinks);
366
- for (const child of fs.readdirSync(root).sort()) addEntry(entries, root, child, true, gitlinks, options.stats);
367
- for (const [relativePath, objectId] of gitlinks) {
368
- if (entries.get(relativePath)?.kind !== "gitlink") {
369
- entries.set(relativePath, { kind: "gitlink", objectId });
370
- options.stats?.delete(relativePath);
371
- }
372
- }
373
- addParentDirectories(entries, root, options.stats);
374
- return entries;
375
- }
376
- function removeEntry(targetRoot, relativePath) {
377
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
378
- if (inspected.kind === "missing") return;
379
- if (inspected.kind === "blocked") {
380
- throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
381
- }
382
- fs.rmSync(inspected.absolutePath, { recursive: true, force: true });
383
- }
384
- function assertTargetExpectation(targetRoot, relativePath, guards) {
385
- if (!guards?.expectations?.has(relativePath)) return;
386
- const expected = guards.expectations.get(relativePath) ?? null;
387
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
388
- if (expected === null) {
389
- if (inspected.kind === "missing") return;
390
- throw new WorkingTreeTargetChangedError(relativePath);
391
- }
392
- if (expected.isDirectory() && !expected.isSymbolicLink()) {
393
- if (inspected.kind === "entry" && inspected.stat.isDirectory() && !inspected.stat.isSymbolicLink()) return;
394
- throw new WorkingTreeTargetChangedError(relativePath);
395
- }
396
- if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) throw new WorkingTreeTargetChangedError(relativePath);
397
- }
398
- const DISPLACED_RETENTION_DIRECTORY = "hydration-displaced";
399
- const RETAINED_NAME_MAX_ENCODED_LENGTH = 150;
400
- const YIELD_ATTEMPTS = 4;
401
- const passStates = /* @__PURE__ */ new WeakMap();
402
- function newPassState(targetRoot) {
403
- const retention = retentionDirectoryFor(targetRoot);
404
- const primitives = atomicRenamePrimitivesFor(retention ?? targetRoot);
405
- return { targetRoot, retention, primitives, exchangeUnavailable: primitives.support.exchange === "unavailable" };
406
- }
407
- function workingTreeAtomicRenameSupport(targetRoot) {
408
- return newPassState(path.resolve(targetRoot)).primitives.support;
409
- }
410
- function guardedPassState(guards, targetRoot) {
411
- let state = passStates.get(guards);
412
- if (!state) {
413
- state = newPassState(targetRoot);
414
- passStates.set(guards, state);
415
- }
416
- return state;
417
- }
418
- function workingTreeDisplacedRetentionDirectory(targetRoot) {
419
- const gitDirectory = checkoutGitDirectory(path.resolve(targetRoot));
420
- return gitDirectory ? path.join(gitDirectory, "r5d", DISPLACED_RETENTION_DIRECTORY) : null;
421
- }
422
- function retentionDirectoryFor(targetRoot) {
423
- const directory = workingTreeDisplacedRetentionDirectory(targetRoot);
424
- if (!directory) return null;
425
- try {
426
- fs.mkdirSync(directory, { recursive: true, mode: 448 });
427
- const status = fs.lstatSync(directory);
428
- if (!status.isDirectory() || status.isSymbolicLink()) return null;
429
- if (status.dev !== fs.lstatSync(targetRoot).dev) return null;
430
- return directory;
431
- } catch {
432
- return null;
433
- }
434
- }
435
- function encodeRetainedName(relativePath) {
436
- let encoded = "";
437
- for (const byte of Buffer.from(relativePath, "utf8")) {
438
- const isPlain = byte >= 48 && byte <= 57 || byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte === 45 || byte === 46 || byte === 95;
439
- encoded += isPlain ? String.fromCharCode(byte) : `%${byte.toString(16).padStart(2, "0")}`;
440
- }
441
- return encoded.length > RETAINED_NAME_MAX_ENCODED_LENGTH ? encoded.slice(0, RETAINED_NAME_MAX_ENCODED_LENGTH) : encoded;
442
- }
443
- function stagingPathFor(state, targetPath, relativePath) {
444
- if (state.retention) return path.join(state.retention, `${randomUUID()}.${encodeRetainedName(relativePath)}`);
445
- return path.join(path.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${randomUUID()}.tmp`);
446
- }
447
- function switchExpectation(targetRoot, relativePath, guards) {
448
- if (guards.expectations?.has(relativePath)) return guards.expectations.get(relativePath) ?? null;
449
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
450
- if (inspected.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${inspected.blockingPath}`);
451
- return inspected.kind === "entry" ? inspected.stat : null;
452
- }
453
- function settledAfter(targetPath, stagedStat, stagedContent) {
454
- const current = lstatIfExists(targetPath);
455
- return current && displacedEntryMatches(stagedStat, current) && entryContent(targetPath, current) === stagedContent ? current : stagedStat;
456
- }
457
- function entryContent(filePath, expected, expectedBlob) {
458
- try {
459
- if (expected.isSymbolicLink()) {
460
- const before = fs.lstatSync(filePath);
461
- if (!workingTreeStatsMatch(expected, before)) return null;
462
- const bytes = Buffer.from(fs.readlinkSync(filePath));
463
- if (!workingTreeStatsMatch(before, fs.lstatSync(filePath))) return null;
464
- const digest = createHash("sha256").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
465
- if (expectedBlob && (expectedBlob.length === 64 ? digest : createHash("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex")) !== expectedBlob)
466
- return null;
467
- return digest;
468
- }
469
- if (!expected.isFile()) return null;
470
- const descriptor = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
471
- try {
472
- const before = fs.fstatSync(descriptor);
473
- if (!workingTreeStatsMatch(expected, before)) return null;
474
- const hash = createHash("sha256").update(`blob ${before.size}\0`);
475
- const knownHash = expectedBlob && expectedBlob.length !== 64 ? createHash("sha1").update(`blob ${before.size}\0`) : null;
476
- const buffer = Buffer.allocUnsafe(64 * 1024);
477
- for (let offset = 0; offset < before.size; ) {
478
- const count = fs.readSync(descriptor, buffer, 0, Math.min(buffer.length, before.size - offset), offset);
479
- if (count <= 0) return null;
480
- const bytes = buffer.subarray(0, count);
481
- hash.update(bytes);
482
- knownHash?.update(bytes);
483
- offset += count;
484
- }
485
- if (!workingTreeStatsMatch(before, fs.fstatSync(descriptor)) || !workingTreeStatsMatch(before, fs.lstatSync(filePath))) return null;
486
- const digest = hash.digest("hex");
487
- if (expectedBlob && (knownHash ? knownHash.digest("hex") : digest) !== expectedBlob) return null;
488
- return digest;
489
- } finally {
490
- fs.closeSync(descriptor);
491
- }
492
- } catch (error) {
493
- if (isPathStateCode(error.code) || error.code === "ELOOP") return null;
494
- throw error;
495
- }
496
- }
497
- function fsyncRetainedEntry(retainedPath) {
498
- const status = fs.lstatSync(retainedPath);
499
- if (status.isDirectory() && !status.isSymbolicLink()) {
500
- for (const child of fs.readdirSync(retainedPath)) fsyncRetainedEntry(path.join(retainedPath, child));
501
- fsyncPath(retainedPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
502
- } else if (status.isFile()) {
503
- fsyncPath(retainedPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
504
- }
505
- fsyncPath(path.dirname(retainedPath), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
506
- }
507
- function retainForeign(journal, relativePath, retainedPath, reason) {
508
- fsyncRetainedEntry(retainedPath);
509
- const retained = { relativePath, path: retainedPath, reason };
510
- if (journal) (journal.retained ??= []).push(retained);
511
- return retained;
512
- }
513
- function isPathStateCode(code) {
514
- return code === "EEXIST" || code === "ENOENT" || code === "ENOTDIR" || code === "ENOTEMPTY" || code === "EISDIR";
515
- }
516
- function publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath) {
517
- if (stagedStat.isSymbolicLink() && state.primitives.support.noReplace === "link") {
518
- throw new WorkingTreeTargetChangedError(relativePath, { detail: "filesystem cannot publish a symlink with provable ownership" });
519
- }
520
- try {
521
- state.primitives.renameNoReplace(stagedPath, targetPath);
522
- } catch (error) {
523
- if (error instanceof AtomicRenameError && isPathStateCode(error.code)) throw new WorkingTreeTargetChangedError(relativePath);
524
- throw error;
525
- }
526
- return null;
527
- }
528
- function restoreDisplacedNoReplace(state, displacedPath, displaced, targetPath) {
529
- try {
530
- if (displaced.isSymbolicLink() && state.primitives.support.noReplace === "link") {
531
- return false;
532
- }
533
- state.primitives.renameNoReplace(displacedPath, targetPath);
534
- return true;
535
- } catch (error) {
536
- const code = error instanceof AtomicRenameError ? error.code : error.code;
537
- if (isPathStateCode(code) || code === "ENOTSUP") return false;
538
- throw error;
539
- }
540
- }
541
- function discardStaged(input, reason) {
542
- const current = lstatIfExists(input.stagedPath);
543
- if (!current) return null;
544
- if (input.onFailure === "unlink" && input.stagedContent && displacedEntryMatches(input.stagedStat, current) && entryContent(input.stagedPath, current) === input.stagedContent) {
545
- fs.rmSync(input.stagedPath, { force: true });
546
- return null;
547
- }
548
- return retainForeign(input.journal, input.relativePath, input.stagedPath, reason);
549
- }
550
- function discardStagedAndThrow(input, error) {
551
- const retained = discardStaged(input, "unrestorable");
552
- if (retained && error instanceof WorkingTreeTargetChangedError) {
553
- throw new WorkingTreeTargetChangedError(input.relativePath, { detail: error.message, retained: [...error.retained, retained] });
554
- }
555
- throw error;
556
- }
557
- function switchStagedEntry(input) {
558
- const { state, targetRoot, relativePath, targetPath, stagedPath, stagedStat, expected } = input;
559
- const stagedContent = input.stagedContent ?? entryContent(stagedPath, stagedStat);
560
- if (!stagedContent) {
561
- const retained = retainForeign(input.journal, relativePath, stagedPath, "unrestorable");
562
- throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
563
- }
564
- input.stagedContent = stagedContent;
565
- if (stagedStat.isSymbolicLink() && state.primitives.support.noReplace === "link") {
566
- discardStagedAndThrow(
567
- input,
568
- new WorkingTreeTargetChangedError(relativePath, { detail: "filesystem cannot publish a symlink with provable ownership" })
569
- );
570
- }
571
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
572
- if (inspected.kind === "blocked") {
573
- discardStagedAndThrow(input, new Error(`Refusing to copy through a symlink or non-directory: ${inspected.blockingPath}`));
574
- }
575
- if (expected === null) {
576
- if (inspected.kind !== "missing") {
577
- discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
578
- }
579
- raceWindow(input.phase, relativePath, targetPath);
580
- let created;
581
- try {
582
- created = publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath);
583
- } catch (error) {
584
- discardStagedAndThrow(input, error);
585
- }
586
- raceWindow("after_switch", relativePath, targetPath);
587
- return { after: created ?? settledAfter(targetPath, stagedStat, stagedContent), afterContent: stagedContent, displaced: null };
588
- }
589
- if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) {
590
- discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
591
- }
592
- const expectedContent = entryContent(targetPath, expected, input.expectedBlob);
593
- if (!expectedContent || input.expectedContent && input.expectedContent !== expectedContent) {
594
- discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath));
595
- }
596
- input.expectedContent = expectedContent;
597
- raceWindow(input.phase, relativePath, targetPath);
598
- if (!state.exchangeUnavailable) {
599
- try {
600
- state.primitives.exchange(stagedPath, targetPath);
601
- } catch (error) {
602
- if (isAtomicRenameUnsupported(error)) {
603
- state.exchangeUnavailable = true;
604
- return switchWithoutExchange(input);
605
- }
606
- if (error instanceof AtomicRenameError && isPathStateCode(error.code)) {
607
- discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath, { detail: `${error.code} at the switch` }));
608
- }
609
- discardStagedAndThrow(input, error);
610
- }
611
- raceWindow("after_switch", relativePath, targetPath);
612
- const displaced = fs.lstatSync(stagedPath);
613
- if (displacedEntryMatches(expected, displaced) && entryContent(stagedPath, displaced) === expectedContent) {
614
- return {
615
- after: settledAfter(targetPath, stagedStat, stagedContent),
616
- afterContent: stagedContent,
617
- displaced: { path: stagedPath, stat: displaced, content: expectedContent }
618
- };
619
- }
620
- return yieldDisplacedEntry(input, displaced);
621
- }
622
- return switchWithoutExchange(input);
623
- }
624
- function yieldDisplacedEntry(input, displaced) {
625
- const { state, relativePath, targetPath, stagedPath, stagedStat } = input;
626
- raceWindow("before_yield", relativePath, targetPath);
627
- let installedBefore = stagedStat;
628
- let toInstall = displaced;
629
- for (let attempt = 0; attempt < YIELD_ATTEMPTS; attempt += 1) {
630
- try {
631
- state.primitives.exchange(stagedPath, targetPath);
632
- } catch (error) {
633
- if (!(error instanceof AtomicRenameError && isPathStateCode(error.code))) throw error;
634
- if (restoreDisplacedNoReplace(state, stagedPath, toInstall, targetPath)) {
635
- throw new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" });
636
- }
637
- const retained2 = retainForeign(input.journal, relativePath, stagedPath, "later_writer");
638
- throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained2] });
639
- }
640
- const returned = fs.lstatSync(stagedPath);
641
- if (sameWorkingTreeInode(returned, installedBefore)) {
642
- if (sameWorkingTreeInode(returned, stagedStat)) {
643
- discardStagedAndThrow(
644
- input,
645
- new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" })
646
- );
647
- }
648
- const retained2 = retainForeign(input.journal, relativePath, stagedPath, "later_writer");
649
- throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained2] });
650
- }
651
- installedBefore = toInstall;
652
- toInstall = returned;
653
- }
654
- const retained = retainForeign(input.journal, relativePath, stagedPath, "unrestorable");
655
- throw new WorkingTreeTargetChangedError(relativePath, { detail: "the path kept changing while the pass yielded", retained: [retained] });
656
- }
657
- function switchWithoutExchange(input) {
658
- const { state, relativePath, targetPath, stagedPath, stagedStat, expected } = input;
659
- const park = stagingPathFor(state, targetPath, relativePath);
660
- try {
661
- fs.renameSync(targetPath, park);
662
- } catch (error) {
663
- if (isPathStateCode(error.code))
664
- discardStagedAndThrow(input, new WorkingTreeTargetChangedError(relativePath, { detail: "removed before the switch" }));
665
- discardStagedAndThrow(input, error);
666
- }
667
- raceWindow("after_switch", relativePath, targetPath);
668
- const displaced = fs.lstatSync(park);
669
- if (displacedEntryMatches(expected, displaced) && entryContent(park, displaced) === input.expectedContent) {
670
- let created;
671
- try {
672
- created = publishNoReplace(state, stagedPath, stagedStat, relativePath, targetPath);
673
- } catch (error) {
674
- if (error instanceof WorkingTreeTargetChangedError) {
675
- const retained2 = retainForeign(input.journal, relativePath, park, "reappeared");
676
- discardStagedAndThrow(
677
- input,
678
- new WorkingTreeTargetChangedError(relativePath, {
679
- detail: "an entry was created while the path was being replaced",
680
- retained: [retained2]
681
- })
682
- );
683
- }
684
- if (!restoreDisplacedNoReplace(state, park, displaced, targetPath)) retainForeign(input.journal, relativePath, park, "unrestorable");
685
- discardStagedAndThrow(input, error);
686
- }
687
- return {
688
- after: created ?? settledAfter(targetPath, stagedStat, input.stagedContent),
689
- afterContent: input.stagedContent,
690
- displaced: { path: park, stat: displaced, content: input.expectedContent }
691
- };
692
- }
693
- raceWindow("before_yield", relativePath, targetPath);
694
- const stagedRetained = discardStaged(input, "unrestorable");
695
- if (restoreDisplacedNoReplace(state, park, displaced, targetPath)) {
696
- throw new WorkingTreeTargetChangedError(relativePath, {
697
- detail: "another writer replaced the entry; its bytes were kept",
698
- retained: stagedRetained ? [stagedRetained] : []
699
- });
700
- }
701
- const retained = retainForeign(input.journal, relativePath, park, "later_writer");
702
- throw new WorkingTreeTargetChangedError(relativePath, { retained: [...stagedRetained ? [stagedRetained] : [], retained] });
703
- }
704
- function removeSwitchedEntry(input) {
705
- const { state, targetRoot, relativePath, targetPath, expected } = input;
706
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
707
- if (inspected.kind === "blocked") {
708
- throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
709
- }
710
- if (inspected.kind !== "entry" || !workingTreeStatsMatch(expected, inspected.stat)) throw new WorkingTreeTargetChangedError(relativePath);
711
- const content = entryContent(targetPath, expected, input.expectedBlob);
712
- if (!content || input.expectedContent && content !== input.expectedContent) throw new WorkingTreeTargetChangedError(relativePath);
713
- raceWindow(input.phase, relativePath, targetPath);
714
- const park = stagingPathFor(state, targetPath, relativePath);
715
- try {
716
- fs.renameSync(targetPath, park);
717
- } catch (error) {
718
- if (isPathStateCode(error.code))
719
- throw new WorkingTreeTargetChangedError(relativePath, { detail: "removed before the switch" });
720
- throw error;
721
- }
722
- raceWindow("after_remove", relativePath, targetPath);
723
- const displaced = fs.lstatSync(park);
724
- if (displacedEntryMatches(expected, displaced) && entryContent(park, displaced) === content)
725
- return { path: park, stat: displaced, content };
726
- raceWindow("before_yield", relativePath, targetPath);
727
- if (restoreDisplacedNoReplace(state, park, displaced, targetPath)) {
728
- throw new WorkingTreeTargetChangedError(relativePath, { detail: "another writer replaced the entry; its bytes were kept" });
729
- }
730
- const retained = retainForeign(input.journal, relativePath, park, "later_writer");
731
- throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
732
- }
733
- function removeEntryGuarded(targetRoot, relativePath, replaced, guards) {
734
- if (!guards) {
735
- removeEntry(targetRoot, relativePath);
736
- return;
737
- }
738
- const state = guardedPassState(guards, targetRoot);
739
- const expected = switchExpectation(targetRoot, relativePath, guards);
740
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
741
- assertInside(targetRoot, targetPath);
742
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
743
- if (inspected.kind === "blocked") {
744
- throw new Error(`Refusing to remove a working-tree path through a symlink or non-directory: ${inspected.blockingPath}`);
745
- }
746
- if (expected === null) {
747
- if (inspected.kind !== "missing") throw new WorkingTreeTargetChangedError(relativePath);
748
- return;
749
- }
750
- if (expected.isDirectory() && !expected.isSymbolicLink()) {
751
- if (inspected.kind === "entry") {
752
- if (!inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) throw new WorkingTreeTargetChangedError(relativePath);
753
- raceWindow("before_remove", relativePath, targetPath);
754
- try {
755
- fs.rmdirSync(inspected.absolutePath);
756
- } catch (error) {
757
- const code = error.code;
758
- if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOTDIR") throw new WorkingTreeTargetChangedError(relativePath);
759
- if (code !== "ENOENT") throw error;
760
- }
761
- }
762
- if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
763
- if (replaced) guards.journal?.entries.push({ relativePath, replaced, after: null });
764
- return;
765
- }
766
- const displaced = removeSwitchedEntry({
767
- state,
768
- targetRoot,
769
- relativePath,
770
- targetPath,
771
- expected,
772
- expectedBlob: guards.preBlobs?.get(relativePath),
773
- journal: guards.journal,
774
- phase: "before_remove"
775
- });
776
- if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
777
- const recorded = replaced ?? treeEntryFromStat(displaced.stat, displaced.path);
778
- if (guards.journal && recorded) guards.journal.entries.push({ relativePath, replaced: recorded, after: null, displaced });
779
- else releaseRetainedEntry(void 0, relativePath, displaced);
780
- }
781
- function ensureDirectoryGuarded(targetRoot, relativePath, guards) {
782
- if (!guards) {
783
- ensureDirectory(targetRoot, relativePath, 493);
784
- return;
785
- }
786
- const normalized = normalizeRelativePath(relativePath);
787
- if (!normalized) throw new Error(`Invalid working-tree directory path: ${relativePath}`);
788
- let current = path.resolve(targetRoot);
789
- let relative = "";
790
- for (const segment of normalized.split("/")) {
791
- current = path.join(current, segment);
792
- relative = relative ? `${relative}/${segment}` : segment;
793
- assertInside(targetRoot, current);
794
- const stat = lstatIfExists(current);
795
- if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) throw new WorkingTreeTargetChangedError(relative);
796
- if (stat) continue;
797
- try {
798
- fs.mkdirSync(current, { mode: 493 });
799
- } catch (error) {
800
- if (error.code !== "EEXIST") throw error;
801
- const now = lstatIfExists(current);
802
- if (!now || !now.isDirectory() || now.isSymbolicLink()) throw new WorkingTreeTargetChangedError(relative);
803
- }
804
- }
805
- }
806
- function replaceWithSymlink(targetRoot, relativePath, target) {
807
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
808
- assertInside(targetRoot, targetPath);
809
- const temporaryPath = path.join(path.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${randomUUID()}.tmp`);
810
- fs.symlinkSync(target, temporaryPath);
811
- try {
812
- const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
813
- if (current.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${current.blockingPath}`);
814
- if (current.kind === "entry" && current.stat.isDirectory() && !current.stat.isSymbolicLink()) removeEntry(targetRoot, relativePath);
815
- fs.renameSync(temporaryPath, targetPath);
816
- } catch (error) {
817
- fs.rmSync(temporaryPath, { force: true });
818
- throw error;
819
- }
820
- }
821
- function ensureDirectory(targetRoot, relativePath, mode) {
822
- const normalized = normalizeRelativePath(relativePath);
823
- if (!normalized) throw new Error(`Invalid working-tree directory path: ${relativePath}`);
824
- let current = path.resolve(targetRoot);
825
- const segments = normalized.split("/");
826
- for (let index = 0; index < segments.length; index += 1) {
827
- current = path.join(current, segments[index]);
828
- assertInside(targetRoot, current);
829
- let stat = null;
830
- try {
831
- stat = fs.lstatSync(current);
832
- } catch (error) {
833
- if (error.code !== "ENOENT") throw error;
834
- }
835
- if (stat && (!stat.isDirectory() || stat.isSymbolicLink())) {
836
- fs.rmSync(current, { recursive: true, force: true });
837
- stat = null;
838
- }
839
- if (!stat) fs.mkdirSync(current, { mode: index === segments.length - 1 ? mode & 511 : 493 });
840
- }
841
- }
842
- function filesEqual(leftPath, rightPath, entry) {
843
- let rightStat;
844
- try {
845
- rightStat = fs.lstatSync(rightPath);
846
- } catch {
847
- return false;
848
- }
849
- if (!rightStat.isFile() || rightStat.size !== entry.size || (rightStat.mode & 511) !== (entry.mode & 511)) return false;
850
- let left;
851
- let right;
852
- try {
853
- left = fs.openSync(leftPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
854
- right = fs.openSync(rightPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
855
- } catch {
856
- if (left !== void 0) fs.closeSync(left);
857
- if (right !== void 0) fs.closeSync(right);
858
- return false;
859
- }
860
- const leftBuffer = Buffer.allocUnsafe(64 * 1024);
861
- const rightBuffer = Buffer.allocUnsafe(64 * 1024);
862
- try {
863
- let offset = 0;
864
- while (offset < entry.size) {
865
- const length = Math.min(leftBuffer.length, entry.size - offset);
866
- const leftRead = fs.readSync(left, leftBuffer, 0, length, offset);
867
- const rightRead = fs.readSync(right, rightBuffer, 0, length, offset);
868
- if (leftRead <= 0 || rightRead <= 0) return false;
869
- if (leftRead !== rightRead || !leftBuffer.subarray(0, leftRead).equals(rightBuffer.subarray(0, rightRead))) return false;
870
- offset += leftRead;
871
- }
872
- return true;
873
- } finally {
874
- fs.closeSync(left);
875
- fs.closeSync(right);
876
- }
877
- }
878
- function stageRegularFile(sourcePath, stagedPath, entry, durability) {
879
- const source = fs.openSync(sourcePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
880
- let target;
881
- try {
882
- const sourceStat = fs.fstatSync(source);
883
- if (!sourceStat.isFile() || sourceStat.size !== entry.size || (sourceStat.mode & 511) !== (entry.mode & 511)) {
884
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
885
- }
886
- target = fs.openSync(
887
- stagedPath,
888
- fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW,
889
- entry.mode & 511
890
- );
891
- const buffer = Buffer.allocUnsafe(64 * 1024);
892
- let offset = 0;
893
- while (offset < sourceStat.size) {
894
- const bytesRead = fs.readSync(source, buffer, 0, Math.min(buffer.length, sourceStat.size - offset), offset);
895
- if (bytesRead <= 0) throw new Error(`Working-tree source ended while it was being mirrored: ${sourcePath}`);
896
- let written = 0;
897
- while (written < bytesRead) {
898
- written += fs.writeSync(target, buffer, written, bytesRead - written, offset + written);
899
- }
900
- offset += bytesRead;
901
- }
902
- fs.fchmodSync(target, entry.mode & 511);
903
- if (durability === "per_entry") fs.fsyncSync(target);
904
- const finalSourceStat = fs.fstatSync(source);
905
- if (!finalSourceStat.isFile() || finalSourceStat.size !== sourceStat.size || (finalSourceStat.mode & 511) !== (sourceStat.mode & 511) || finalSourceStat.mtimeMs !== sourceStat.mtimeMs || finalSourceStat.ctimeMs !== sourceStat.ctimeMs) {
906
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
907
- }
908
- const stagedStat = fs.fstatSync(target);
909
- fs.closeSync(target);
910
- target = void 0;
911
- return { sourceStat: finalSourceStat, stagedStat };
912
- } catch (error) {
913
- if (target !== void 0) {
914
- fs.closeSync(target);
915
- target = void 0;
916
- }
917
- fs.rmSync(stagedPath, { force: true });
918
- throw error;
919
- } finally {
920
- if (target !== void 0) fs.closeSync(target);
921
- fs.closeSync(source);
922
- }
923
- }
924
- function writeStagedContent(stagedPath, override, durability) {
925
- const target = fs.openSync(
926
- stagedPath,
927
- fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW,
928
- override.mode & 511
929
- );
930
- try {
931
- let written = 0;
932
- while (written < override.content.length) {
933
- written += fs.writeSync(target, override.content, written, override.content.length - written, written);
934
- }
935
- fs.fchmodSync(target, override.mode & 511);
936
- if (durability === "per_entry") fs.fsyncSync(target);
937
- return fs.fstatSync(target);
938
- } catch (error) {
939
- fs.rmSync(stagedPath, { force: true });
940
- throw error;
941
- } finally {
942
- fs.closeSync(target);
943
- }
944
- }
945
- function copyRegularFile(sourcePath, targetPath, entry, durability) {
946
- const temporaryPath = path.join(path.dirname(targetPath), `${MIRROR_TEMPORARY_FILE_PREFIX}${randomUUID()}.tmp`);
947
- const { sourceStat } = stageRegularFile(sourcePath, temporaryPath, entry, durability);
948
- try {
949
- fs.renameSync(temporaryPath, targetPath);
950
- } catch (error) {
951
- fs.rmSync(temporaryPath, { force: true });
952
- throw error;
953
- }
954
- if (durability === "per_entry") fsyncPath(path.dirname(targetPath), fs.constants.O_RDONLY);
955
- return sourceStat;
956
- }
957
- function copyEntry(sourceRoot, targetRoot, relativePath, entry, durability) {
958
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
959
- assertInside(targetRoot, targetPath);
960
- const parent = path.posix.dirname(relativePath);
961
- if (parent !== ".") ensureDirectory(targetRoot, parent, 493);
962
- const source = inspectRelativePathWithoutFollowingAncestors(sourceRoot, relativePath);
963
- if (source.kind !== "entry") {
964
- throw new Error(`Working-tree source became unsafe while it was being mirrored: ${relativePath}`);
965
- }
966
- const sourcePath = source.absolutePath;
967
- if (entry.kind === "directory") {
968
- if (!source.stat.isDirectory() || source.stat.isSymbolicLink()) {
969
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
970
- }
971
- ensureDirectory(targetRoot, relativePath, entry.mode);
972
- return null;
973
- }
974
- if (entry.kind === "symlink") {
975
- if (!source.stat.isSymbolicLink() || fs.readlinkSync(sourcePath) !== entry.target) {
976
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
977
- }
978
- let unchanged = false;
979
- try {
980
- unchanged = fs.lstatSync(targetPath).isSymbolicLink() && fs.readlinkSync(targetPath) === entry.target;
981
- } catch {
982
- unchanged = false;
983
- }
984
- if (!unchanged) replaceWithSymlink(targetRoot, relativePath, entry.target);
985
- return { kind: "symlink", target: entry.target };
986
- }
987
- if (entry.kind === "gitlink") {
988
- throw new Error(`Working-tree gitlink cannot be copied as content: ${relativePath}`);
989
- }
990
- if (!source.stat.isFile() || source.stat.isSymbolicLink() || source.stat.size !== entry.size) {
991
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
992
- }
993
- if (filesEqual(sourcePath, targetPath, entry)) {
994
- const settled = fs.lstatSync(sourcePath);
995
- if (!settled.isFile() || settled.ino !== source.stat.ino || settled.size !== source.stat.size || settled.mode !== source.stat.mode) {
996
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
997
- }
998
- if (settled.mtimeMs !== source.stat.mtimeMs || settled.ctimeMs !== source.stat.ctimeMs) {
999
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1000
- }
1001
- return projectedFileEntry(settled);
1002
- }
1003
- const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1004
- if (target.kind === "blocked") {
1005
- throw new Error(`Refusing to copy through a symlink or non-directory: ${target.blockingPath}`);
1006
- }
1007
- if (target.kind === "entry" && !target.stat.isFile()) removeEntry(targetRoot, relativePath);
1008
- return projectedFileEntry(copyRegularFile(sourcePath, targetPath, entry, durability));
1009
- }
1010
- function copyEntryGuarded(sourceRoot, targetRoot, relativePath, entry, durability, guards) {
1011
- const state = guardedPassState(guards, targetRoot);
1012
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
1013
- assertInside(targetRoot, targetPath);
1014
- const parent = path.posix.dirname(relativePath);
1015
- if (parent !== ".") ensureDirectoryGuarded(targetRoot, parent, guards);
1016
- const override = guards.overrides?.get(relativePath);
1017
- if (override && entry.kind !== "file") throw new Error(`Working-tree override must replace a regular file: ${relativePath}`);
1018
- let sourcePath = null;
1019
- let sourceStat = null;
1020
- if (!override) {
1021
- const source = inspectRelativePathWithoutFollowingAncestors(sourceRoot, relativePath);
1022
- if (source.kind !== "entry") throw new Error(`Working-tree source became unsafe while it was being mirrored: ${relativePath}`);
1023
- sourcePath = source.absolutePath;
1024
- sourceStat = source.stat;
1025
- }
1026
- if (entry.kind === "directory") {
1027
- if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) {
1028
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1029
- }
1030
- const target2 = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1031
- if (target2.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${target2.blockingPath}`);
1032
- if (target2.kind === "entry" && target2.stat.isDirectory() && !target2.stat.isSymbolicLink()) return null;
1033
- if (target2.kind === "entry") {
1034
- if (guards.expectations && !guards.expectations.has(relativePath)) throw new WorkingTreeTargetChangedError(relativePath);
1035
- removeEntryGuarded(targetRoot, relativePath, treeEntryFromStat(target2.stat, target2.absolutePath), guards);
1036
- } else assertTargetExpectation(targetRoot, relativePath, guards);
1037
- createDirectoryGuarded(targetRoot, relativePath, entry.mode, guards);
1038
- return null;
1039
- }
1040
- if (entry.kind === "gitlink") {
1041
- throw new Error(`Working-tree gitlink cannot be copied as content: ${relativePath}`);
1042
- }
1043
- const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1044
- if (target.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${target.blockingPath}`);
1045
- if (entry.kind === "symlink") {
1046
- if (!sourceStat.isSymbolicLink() || fs.readlinkSync(sourcePath) !== entry.target) {
1047
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1048
- }
1049
- if (target.kind === "entry" && target.stat.isSymbolicLink() && fs.readlinkSync(target.absolutePath) === entry.target) {
1050
- return { kind: "symlink", target: entry.target };
1051
- }
1052
- } else if (!override) {
1053
- if (!sourceStat.isFile() || sourceStat.isSymbolicLink() || sourceStat.size !== entry.size) {
1054
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1055
- }
1056
- if (filesEqual(sourcePath, targetPath, entry)) {
1057
- const settled = fs.lstatSync(sourcePath);
1058
- if (!settled.isFile() || settled.ino !== sourceStat.ino || settled.size !== sourceStat.size || settled.mode !== sourceStat.mode) {
1059
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1060
- }
1061
- if (settled.mtimeMs !== sourceStat.mtimeMs || settled.ctimeMs !== sourceStat.ctimeMs) {
1062
- throw new Error(`Working-tree source changed while it was being mirrored: ${sourcePath}`);
1063
- }
1064
- return projectedFileEntry(settled);
1065
- }
1066
- }
1067
- let replaced = target.kind === "entry" ? treeEntryFromStat(target.stat, target.absolutePath) : null;
1068
- let expected = switchExpectation(targetRoot, relativePath, guards);
1069
- if (target.kind === "entry" && target.stat.isDirectory() && !target.stat.isSymbolicLink()) {
1070
- removeEntryGuarded(targetRoot, relativePath, replaced, guards);
1071
- replaced = null;
1072
- expected = null;
1073
- }
1074
- const stagedPath = stagingPathFor(state, targetPath, relativePath);
1075
- let stagedStat;
1076
- let projected = null;
1077
- if (entry.kind === "symlink") {
1078
- fs.symlinkSync(entry.target, stagedPath);
1079
- stagedStat = fs.lstatSync(stagedPath);
1080
- projected = { kind: "symlink", target: entry.target };
1081
- } else if (override) {
1082
- stagedStat = writeStagedContent(stagedPath, override, durability);
1083
- } else {
1084
- const staged = stageRegularFile(sourcePath, stagedPath, entry, durability);
1085
- stagedStat = staged.stagedStat;
1086
- projected = projectedFileEntry(staged.sourceStat);
1087
- }
1088
- const outcome = switchStagedEntry({
1089
- state,
1090
- targetRoot,
1091
- relativePath,
1092
- targetPath,
1093
- stagedPath,
1094
- stagedStat,
1095
- expected,
1096
- expectedBlob: guards.preBlobs?.get(relativePath),
1097
- journal: guards.journal,
1098
- phase: "before_switch",
1099
- onFailure: "unlink"
1100
- });
1101
- if (durability === "per_entry") fsyncPath(path.dirname(targetPath), fs.constants.O_RDONLY);
1102
- if (guards.journal)
1103
- guards.journal.entries.push({
1104
- relativePath,
1105
- replaced,
1106
- after: outcome.after,
1107
- afterContent: outcome.afterContent,
1108
- ...outcome.displaced ? { displaced: outcome.displaced } : {}
1109
- });
1110
- else if (outcome.displaced) releaseRetainedEntry(void 0, relativePath, outcome.displaced);
1111
- if (guards.expectations?.has(relativePath)) guards.expectations.set(relativePath, outcome.after);
1112
- return projected;
1113
- }
1114
- function createDirectoryGuarded(targetRoot, relativePath, mode, guards) {
1115
- if (!guards) {
1116
- ensureDirectory(targetRoot, relativePath, mode);
1117
- return;
1118
- }
1119
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
1120
- assertInside(targetRoot, targetPath);
1121
- try {
1122
- fs.mkdirSync(targetPath, { mode: mode & 511 });
1123
- } catch (error) {
1124
- if (isPathStateCode(error.code)) throw new WorkingTreeTargetChangedError(relativePath);
1125
- throw error;
1126
- }
1127
- if (!guards.journal) return;
1128
- const created = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1129
- if (created.kind !== "entry" || !created.stat.isDirectory() || created.stat.isSymbolicLink()) {
1130
- throw new WorkingTreeTargetChangedError(relativePath);
1131
- }
1132
- guards.journal.entries.push({ relativePath, replaced: null, after: created.stat });
1133
- }
1134
- function retainedEntryIntact(retained, current) {
1135
- 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;
1136
- }
1137
- function settleRetainedEntries(journal, targetRoot) {
1138
- raceWindow("before_settle", "", targetRoot);
1139
- for (const entry of journal.entries) {
1140
- if (!entry.displaced) continue;
1141
- const current = lstatIfExists(entry.displaced.path);
1142
- if (!current || !retainedEntryIntact(entry.displaced, current)) {
1143
- throw new WorkingTreeTargetChangedError(entry.relativePath, {
1144
- detail: "written through a descriptor opened before hydration replaced the path"
1145
- });
1146
- }
1147
- }
1148
- releaseRetainedEntries(journal);
1149
- }
1150
- function releaseRetainedEntries(journal) {
1151
- for (const entry of journal.entries) {
1152
- if (!entry.displaced) continue;
1153
- releaseRetainedEntry(journal, entry.relativePath, entry.displaced);
1154
- delete entry.displaced;
1155
- }
1156
- }
1157
- function releaseRetainedEntry(journal, relativePath, displaced) {
1158
- const current = lstatIfExists(displaced.path);
1159
- if (!current) return;
1160
- if (!retainedEntryIntact(displaced, current)) {
1161
- const retained = retainForeign(journal, relativePath, displaced.path, "unrestorable");
1162
- if (!journal) throw new WorkingTreeTargetChangedError(relativePath, { retained: [retained] });
1163
- return;
1164
- }
1165
- fs.rmSync(displaced.path, { force: true });
1166
- }
1167
- function inspectMirrorTarget(targetRoot, deletionMode, stats) {
1168
- if (deletionMode !== "git") return { existing: inspectWorkingTree(targetRoot, "all", { stats }), git: null };
1169
- const inspection = inspectGitWorkingTree(targetRoot, stats);
1170
- const occupied = /* @__PURE__ */ new Set();
1171
- for (const relativePath of inspection.submodules.keys()) {
1172
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1173
- if (inspected.kind === "entry" && inspected.stat.isDirectory() && fs.readdirSync(inspected.absolutePath).length > 0) {
1174
- occupied.add(relativePath);
1175
- }
1176
- }
1177
- return { existing: inspection.entries, git: { indexRecords: inspection.indexRecords, submodules: inspection.submodules, occupied } };
1178
- }
1179
- function shieldedDesiredPaths(desired, git) {
1180
- const shielded = /* @__PURE__ */ new Set();
1181
- if (git.submodules.size === 0) return shielded;
1182
- const roots = new Set(git.submodules.keys());
1183
- for (const [relativePath, entry] of desired) {
1184
- if (hasAncestorIn(relativePath, roots)) {
1185
- shielded.add(relativePath);
1186
- continue;
1187
- }
1188
- if (!roots.has(relativePath) || entry.kind === "gitlink") continue;
1189
- if (entry.kind === "directory" || git.occupied.has(relativePath)) shielded.add(relativePath);
1190
- }
1191
- return shielded;
1192
- }
1193
- function directoryShieldsSubmodule(relativePath, entry, git) {
1194
- if (!git || entry.kind !== "directory") return false;
1195
- const prefix = `${relativePath}/`;
1196
- for (const root of git.submodules.keys()) if (root.startsWith(prefix)) return true;
1197
- return false;
1198
- }
1199
- function planIndexChanges(desired, shielded, git) {
1200
- const removed = [];
1201
- const written = [];
1202
- const desiredGitlinks = /* @__PURE__ */ new Map();
1203
- for (const [relativePath, entry] of desired) {
1204
- if (entry.kind === "gitlink" && !shielded.has(relativePath)) desiredGitlinks.set(relativePath, entry.objectId);
1205
- }
1206
- const desiredGitlinkRoots = new Set(desiredGitlinks.keys());
1207
- const currentGitlinks = /* @__PURE__ */ new Map();
1208
- for (const record of git.indexRecords) {
1209
- const desiredObjectId = desiredGitlinks.get(record.relativePath);
1210
- if (record.mode === "160000") {
1211
- if (record.stage === 0) currentGitlinks.set(record.relativePath, record.objectId);
1212
- if (desiredObjectId !== void 0) {
1213
- if (desiredObjectId !== record.objectId) removed.push(record);
1214
- continue;
1215
- }
1216
- const desiredEntry = desired.get(record.relativePath);
1217
- if (desiredEntry?.kind !== "directory" && desiredEntry?.kind !== "gitlink" && !git.occupied.has(record.relativePath)) {
1218
- removed.push(record);
1219
- }
1220
- continue;
1221
- }
1222
- if (desiredObjectId !== void 0 || hasAncestorIn(record.relativePath, desiredGitlinkRoots)) removed.push(record);
1223
- }
1224
- for (const [relativePath, objectId] of desiredGitlinks) {
1225
- if (currentGitlinks.get(relativePath) !== objectId) written.push({ relativePath, objectId });
1226
- }
1227
- return { removed, written };
1228
- }
1229
- function indexInfoRecord(record) {
1230
- const stage = record.stage === 0 ? "" : ` ${record.stage}`;
1231
- return Buffer.concat([Buffer.from(`${record.mode} ${record.objectId}${stage} `), Buffer.from(record.relativePath), Buffer.from([0])]);
1232
- }
1233
- function updateIndex(targetRoot, changes) {
1234
- if (changes.drop.length === 0 && changes.put.length === 0) return;
1235
- const feed = Buffer.concat([
1236
- ...changes.drop.map(
1237
- ({ relativePath, objectId }) => indexInfoRecord({ mode: "0", objectId: "0".repeat(objectId.length), stage: 0, relativePath })
1238
- ),
1239
- ...changes.put.map(indexInfoRecord)
1240
- ]);
1241
- gitOutput(
1242
- targetRoot,
1243
- ["-c", "core.fsync=index", "-c", "core.fsyncMethod=fsync", "update-index", "-z", "--index-info"],
1244
- "Update Git working-tree index",
1245
- { stdin: feed }
1246
- );
1247
- const gitDirectory = checkoutGitDirectory(targetRoot);
1248
- if (!gitDirectory) return;
1249
- const indexPath = path.join(gitDirectory, "index");
1250
- if (lstatIfExists(indexPath)?.isFile()) fsyncPath(indexPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
1251
- fsyncPath(gitDirectory, fs.constants.O_RDONLY);
1252
- }
1253
- function recordGitlinkStaleHeads(targetRoot, desired, shielded, git) {
1254
- const heads = readGitlinkStaleHeads(targetRoot);
1255
- let changed = false;
1256
- for (const [relativePath, head] of git.submodules) {
1257
- const entry = desired.get(relativePath);
1258
- const next = entry?.kind === "gitlink" && !shielded.has(relativePath) && head && head !== entry.objectId ? head : null;
1259
- if ((heads.get(relativePath) ?? null) === next) continue;
1260
- if (next) heads.set(relativePath, next);
1261
- else heads.delete(relativePath);
1262
- changed = true;
1263
- }
1264
- for (const relativePath of [...heads.keys()]) {
1265
- if (git.submodules.has(relativePath)) continue;
1266
- heads.delete(relativePath);
1267
- changed = true;
1268
- }
1269
- if (changed) writeGitlinkStaleHeads(targetRoot, heads);
1270
- }
1271
- function prepareWorkingTreeMirror(input) {
1272
- const sourceRoot = path.resolve(input.sourceRoot);
1273
- const targetRoot = path.resolve(input.targetRoot);
1274
- if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
1275
- fs.mkdirSync(targetRoot, { recursive: true });
1276
- }
1277
- inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
1278
- const desired = inspectWorkingTree(sourceRoot, input.sourceMode, { gitlinks: input.sourceGitlinks });
1279
- const existingStats = /* @__PURE__ */ new Map();
1280
- const target = inspectMirrorTarget(targetRoot, input.deletionMode, existingStats);
1281
- const shielded = target.git ? shieldedDesiredPaths(desired, target.git) : /* @__PURE__ */ new Set();
1282
- return {
1283
- sourceRoot,
1284
- targetRoot,
1285
- deletionMode: input.deletionMode,
1286
- desired,
1287
- existing: target.existing,
1288
- existingStats,
1289
- git: target.git,
1290
- shielded
1291
- };
1292
- }
1293
- function applyWorkingTreeMirror(prepared, options = {}) {
1294
- const { sourceRoot, targetRoot, desired, existing, shielded } = prepared;
1295
- const durability = options.durability ?? "per_entry";
1296
- const guards = options.guards;
1297
- const selection = options.selection;
1298
- const target = { existing, git: prepared.git };
1299
- const indexDesired = selection?.desiredForIndex ?? desired;
1300
- const index = target.git ? planIndexChanges(indexDesired, shielded, target.git) : null;
1301
- const neededDirectories = /* @__PURE__ */ new Set();
1302
- if (selection) {
1303
- for (const relativePath of selection.write) {
1304
- let parent = path.posix.dirname(relativePath);
1305
- while (parent !== ".") {
1306
- neededDirectories.add(parent);
1307
- parent = path.posix.dirname(parent);
1308
- }
1309
- }
1310
- }
1311
- for (const relativePath of [...existing.keys()].sort(deepestFirst)) {
1312
- if (selection ? !selection.remove.has(relativePath) : desired.has(relativePath)) continue;
1313
- const entry = existing.get(relativePath);
1314
- if (entry.kind === "gitlink" || directoryShieldsSubmodule(relativePath, entry, target.git)) continue;
1315
- if (selection && entry.kind === "directory") {
1316
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1317
- if (inspected.kind !== "entry" || !inspected.stat.isDirectory() || inspected.stat.isSymbolicLink()) continue;
1318
- try {
1319
- fs.rmdirSync(inspected.absolutePath);
1320
- } catch (error) {
1321
- const code = error.code;
1322
- if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOENT") continue;
1323
- throw error;
1324
- }
1325
- if (guards?.expectations?.has(relativePath)) guards.expectations.set(relativePath, null);
1326
- guards?.journal?.entries.push({ relativePath, replaced: entry, after: null });
1327
- continue;
1328
- }
1329
- removeEntryGuarded(targetRoot, relativePath, entry, guards);
1330
- }
1331
- const gitlinks = [];
1332
- for (const [relativePath, entry] of [...desired.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
1333
- if (shielded.has(relativePath)) continue;
1334
- if (selection && entry.kind === "directory" && !neededDirectories.has(relativePath)) continue;
1335
- if (selection && entry.kind !== "directory" && !selection.write.has(relativePath)) continue;
1336
- if (entry.kind === "gitlink") {
1337
- if (guards) {
1338
- const parent = path.posix.dirname(relativePath);
1339
- if (parent !== ".") ensureDirectoryGuarded(targetRoot, parent, guards);
1340
- const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1341
- if (current.kind === "blocked") throw new Error(`Refusing to copy through a symlink or non-directory: ${current.blockingPath}`);
1342
- if (current.kind === "entry" && (!current.stat.isDirectory() || current.stat.isSymbolicLink())) {
1343
- if (guards.expectations && !guards.expectations.has(relativePath)) throw new WorkingTreeTargetChangedError(relativePath);
1344
- removeEntryGuarded(targetRoot, relativePath, treeEntryFromStat(current.stat, current.absolutePath), guards);
1345
- createDirectoryGuarded(targetRoot, relativePath, 493, guards);
1346
- } else if (current.kind !== "entry") {
1347
- assertTargetExpectation(targetRoot, relativePath, guards);
1348
- createDirectoryGuarded(targetRoot, relativePath, 493, guards);
1349
- }
1350
- } else {
1351
- ensureDirectory(targetRoot, relativePath, 493);
1352
- }
1353
- gitlinks.push({ relativePath, objectId: entry.objectId });
1354
- options.projected?.set(relativePath, { kind: "gitlink", objectId: entry.objectId });
1355
- continue;
1356
- }
1357
- const projected = guards ? copyEntryGuarded(sourceRoot, targetRoot, relativePath, entry, durability, guards) : copyEntry(sourceRoot, targetRoot, relativePath, entry, durability);
1358
- if (projected && options.projected) options.projected.set(relativePath, projected);
1359
- }
1360
- if (guards?.journal) settleRetainedEntries(guards.journal, targetRoot);
1361
- if (index && target.git) {
1362
- updateIndex(targetRoot, { drop: index.removed, put: index.written.map((gitlink) => ({ mode: "160000", stage: 0, ...gitlink })) });
1363
- if (guards?.journal && (index.removed.length > 0 || index.written.length > 0)) guards.journal.indexApplied = index;
1364
- recordGitlinkStaleHeads(targetRoot, indexDesired, shielded, target.git);
1365
- }
1366
- return {
1367
- paths: [...desired.keys()].filter((relativePath) => !shielded.has(relativePath) && desired.get(relativePath)?.kind !== "directory").sort(),
1368
- gitlinks
1369
- };
1370
- }
1371
- function mirrorWorkingTree(input) {
1372
- return applyWorkingTreeMirror(prepareWorkingTreeMirror(input), { durability: input.durability, projected: input.projected });
1373
- }
1374
- function normalizeWorkingTreeRelativePath(value) {
1375
- return normalizeRelativePath(value);
1376
- }
1377
- const MIRROR_TEMPORARY_FILE_PATTERN = /^\.r5d-working-tree-mirror-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/u;
1378
- function relativeDepth(relativePath) {
1379
- return relativePath.split("/").length;
1380
- }
1381
- function shallowestFirst(left, right) {
1382
- return relativeDepth(left) - relativeDepth(right) || left.localeCompare(right);
1383
- }
1384
- function deepestFirst(left, right) {
1385
- return relativeDepth(right) - relativeDepth(left) || left.localeCompare(right);
1386
- }
1387
- function hasAncestorIn(relativePath, ancestors) {
1388
- let parent = path.posix.dirname(relativePath);
1389
- while (parent !== ".") {
1390
- if (ancestors.has(parent)) return true;
1391
- parent = path.posix.dirname(parent);
1392
- }
1393
- return false;
1394
- }
1395
- function treeEntryFromStat(stat, absolutePath) {
1396
- if (stat.isSymbolicLink()) return { kind: "symlink", mode: stat.mode, target: fs.readlinkSync(absolutePath) };
1397
- if (stat.isFile()) return { kind: "file", mode: stat.mode, size: stat.size };
1398
- if (stat.isDirectory()) return { kind: "directory", mode: stat.mode };
1399
- return null;
1400
- }
1401
- function fsyncPath(targetPath, flags) {
1402
- const descriptor = fs.openSync(targetPath, flags);
1403
- try {
1404
- fs.fsyncSync(descriptor);
1405
- } finally {
1406
- fs.closeSync(descriptor);
1407
- }
1408
- }
1409
- function planWorkingTreeMirror(input) {
1410
- return planPreparedWorkingTreeMirror(prepareWorkingTreeMirror(input));
1411
- }
1412
- function planPreparedWorkingTreeMirror(prepared, options = {}) {
1413
- const { sourceRoot, targetRoot, existing, shielded } = prepared;
1414
- const inspectedDesired = prepared.desired;
1415
- const selection = options.selection;
1416
- const target = { existing, git: prepared.git };
1417
- const desired = new Map(
1418
- [...inspectedDesired].filter(
1419
- ([relativePath, entry]) => !shielded.has(relativePath) && (!selection || entry.kind === "directory" || selection.write.has(relativePath))
1420
- )
1421
- );
1422
- const replaced = /* @__PURE__ */ new Map();
1423
- const absent = [];
1424
- for (const [relativePath, entry] of existing) {
1425
- if (selection ? !selection.remove.has(relativePath) : inspectedDesired.has(relativePath)) continue;
1426
- if (entry.kind === "gitlink") continue;
1427
- if (directoryShieldsSubmodule(relativePath, entry, target.git)) continue;
1428
- replaced.set(relativePath, entry);
1429
- }
1430
- for (const [relativePath, entry] of desired) {
1431
- const targetEntry = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1432
- if (targetEntry.kind !== "entry") {
1433
- absent.push(relativePath);
1434
- continue;
1435
- }
1436
- const { absolutePath, stat } = targetEntry;
1437
- if (options.overrides?.has(relativePath)) {
1438
- } else if (entry.kind === "directory" || entry.kind === "gitlink") {
1439
- if (stat.isDirectory()) continue;
1440
- } else if (entry.kind === "symlink") {
1441
- if (stat.isSymbolicLink() && fs.readlinkSync(absolutePath) === entry.target) continue;
1442
- } else if (stat.isFile() && filesEqual(path.join(sourceRoot, ...relativePath.split("/")), absolutePath, entry)) {
1443
- continue;
1444
- }
1445
- const current = treeEntryFromStat(stat, absolutePath);
1446
- if (current) replaced.set(relativePath, current);
1447
- else absent.push(relativePath);
1448
- }
1449
- const replacedDirectories = new Set(
1450
- [...replaced].filter(([, entry]) => entry.kind === "directory").map(([relativePath]) => relativePath)
1451
- );
1452
- for (const relativePath of [...replaced.keys()]) {
1453
- if (hasAncestorIn(relativePath, replacedDirectories)) replaced.delete(relativePath);
1454
- }
1455
- return {
1456
- desired,
1457
- replaced,
1458
- absent: absent.filter((relativePath) => !hasAncestorIn(relativePath, replacedDirectories)).sort(shallowestFirst),
1459
- index: target.git ? planIndexChanges(selection?.desiredForIndex ?? inspectedDesired, shielded, target.git) : { removed: [], written: [] }
1460
- };
1461
- }
1462
- function rollbackWorkingTreeMirrorJournal(input) {
1463
- const targetRoot = path.resolve(input.targetRoot);
1464
- const snapshotRoot = path.resolve(input.snapshotRoot);
1465
- const { journal } = input;
1466
- const state = newPassState(targetRoot);
1467
- const touched = /* @__PURE__ */ new Set();
1468
- const abandoned = [];
1469
- const restoredTrees = /* @__PURE__ */ new Set();
1470
- const targetPathOf = (relativePath) => {
1471
- const targetPath = path.join(targetRoot, ...relativePath.split("/"));
1472
- assertInside(targetRoot, targetPath);
1473
- return targetPath;
1474
- };
1475
- const stageFromSnapshot = (relativePath, replaced) => {
1476
- const captured = inspectRelativePathWithoutFollowingAncestors(snapshotRoot, relativePath);
1477
- if (captured.kind !== "entry") throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
1478
- const stagedPath = stagingPathFor(state, targetPathOf(relativePath), relativePath);
1479
- if (replaced.kind === "symlink") {
1480
- if (!captured.stat.isSymbolicLink()) throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
1481
- fs.symlinkSync(fs.readlinkSync(captured.absolutePath), stagedPath);
1482
- return { path: stagedPath, stat: fs.lstatSync(stagedPath) };
1483
- }
1484
- if (!captured.stat.isFile()) throw new Error(`Working-tree snapshot does not hold the replaced entry: ${relativePath}`);
1485
- return { path: stagedPath, stat: stageRegularFile(captured.absolutePath, stagedPath, replaced, "per_entry").stagedStat };
1486
- };
1487
- const restoreReplaced = (entry, expected) => {
1488
- const replaced = entry.replaced;
1489
- const { relativePath } = entry;
1490
- if (replaced.kind === "gitlink") return true;
1491
- if (replaced.kind === "directory") return restoreCapturedDirectory(relativePath, replaced);
1492
- let staged;
1493
- let onFailure = "unlink";
1494
- if (entry.displaced && lstatIfExists(entry.displaced.path)) {
1495
- const current = fs.lstatSync(entry.displaced.path);
1496
- if (!sameWorkingTreeInode(current, entry.displaced.stat))
1497
- throw new Error(`Retained working-tree entry was replaced: ${entry.displaced.path}`);
1498
- if (!retainedEntryIntact(entry.displaced, current)) onFailure = "retain";
1499
- staged = { path: entry.displaced.path, stat: current };
1500
- } else {
1501
- staged = stageFromSnapshot(relativePath, replaced);
1502
- }
1503
- try {
1504
- const outcome = switchStagedEntry({
1505
- state,
1506
- targetRoot,
1507
- relativePath,
1508
- targetPath: targetPathOf(relativePath),
1509
- stagedPath: staged.path,
1510
- stagedStat: staged.stat,
1511
- expected,
1512
- expectedContent: expected === null ? void 0 : entry.afterContent,
1513
- journal,
1514
- phase: "before_rollback_switch",
1515
- onFailure
1516
- });
1517
- if (outcome.displaced) releaseRetainedEntry(journal, relativePath, outcome.displaced);
1518
- delete entry.displaced;
1519
- return true;
1520
- } catch (error) {
1521
- if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
1522
- if (onFailure === "retain") delete entry.displaced;
1523
- else if (entry.displaced && !lstatIfExists(entry.displaced.path)) delete entry.displaced;
1524
- return false;
1525
- }
1526
- };
1527
- const restoreCapturedDirectory = (relativePath, replaced) => {
1528
- const captured = inspectRelativePathWithoutFollowingAncestors(snapshotRoot, relativePath);
1529
- if (captured.kind !== "entry" || !captured.stat.isDirectory()) {
1530
- throw new Error(`Working-tree snapshot does not hold the captured directory: ${relativePath}`);
1531
- }
1532
- try {
1533
- fs.mkdirSync(targetPathOf(relativePath), { mode: replaced.mode & 511 });
1534
- } catch (error) {
1535
- if (isPathStateCode(error.code)) return false;
1536
- throw error;
1537
- }
1538
- let complete = true;
1539
- const children = inspectWorkingTree(captured.absolutePath, "all");
1540
- for (const [childRelative, child] of [...children.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
1541
- const childPath = path.posix.join(relativePath, childRelative);
1542
- if (child.kind === "gitlink") continue;
1543
- if (child.kind === "directory") {
1544
- try {
1545
- fs.mkdirSync(targetPathOf(childPath), { mode: child.mode & 511 });
1546
- } catch (error) {
1547
- if (!isPathStateCode(error.code)) throw error;
1548
- const now = inspectRelativePathWithoutFollowingAncestors(targetRoot, childPath);
1549
- if (now.kind !== "entry" || !now.stat.isDirectory() || now.stat.isSymbolicLink()) {
1550
- abandoned.push(childPath);
1551
- complete = false;
1552
- }
1553
- }
1554
- continue;
1555
- }
1556
- const staged = stageFromSnapshot(childPath, child);
1557
- try {
1558
- switchStagedEntry({
1559
- state,
1560
- targetRoot,
1561
- relativePath: childPath,
1562
- targetPath: targetPathOf(childPath),
1563
- stagedPath: staged.path,
1564
- stagedStat: staged.stat,
1565
- expected: null,
1566
- journal,
1567
- phase: "before_rollback_switch",
1568
- onFailure: "unlink"
1569
- });
1570
- } catch (error) {
1571
- if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
1572
- abandoned.push(childPath);
1573
- complete = false;
1574
- }
1575
- }
1576
- restoredTrees.add(relativePath);
1577
- return complete;
1578
- };
1579
- for (const entry of [...journal.entries].reverse()) {
1580
- const { relativePath } = entry;
1581
- if (hasAncestorIn(relativePath, restoredTrees)) {
1582
- touched.add(relativePath);
1583
- continue;
1584
- }
1585
- const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1586
- if (current.kind === "blocked") {
1587
- abandoned.push(relativePath);
1588
- continue;
1589
- }
1590
- if (entry.after === null) {
1591
- if (current.kind !== "missing") {
1592
- abandoned.push(relativePath);
1593
- continue;
1594
- }
1595
- if (entry.replaced && !restoreReplaced(entry, null)) {
1596
- abandoned.push(relativePath);
1597
- continue;
1598
- }
1599
- touched.add(relativePath);
1600
- continue;
1601
- }
1602
- if (current.kind !== "entry" || !workingTreeStatsMatch(entry.after, current.stat) || entry.afterContent !== void 0 && entryContent(current.absolutePath, current.stat) !== entry.afterContent) {
1603
- if (!(current.kind === "entry" && entry.after.isDirectory() && current.stat.isDirectory())) {
1604
- abandoned.push(relativePath);
1605
- continue;
1606
- }
1607
- }
1608
- if (current.stat.isDirectory() && !current.stat.isSymbolicLink()) {
1609
- try {
1610
- fs.rmdirSync(current.absolutePath);
1611
- } catch (error) {
1612
- const code = error.code;
1613
- if (code === "ENOTEMPTY" || code === "EEXIST" || code === "ENOTDIR") {
1614
- abandoned.push(relativePath);
1615
- continue;
1616
- }
1617
- if (code !== "ENOENT") throw error;
1618
- }
1619
- if (entry.replaced && !restoreReplaced(entry, null)) {
1620
- abandoned.push(relativePath);
1621
- continue;
1622
- }
1623
- touched.add(relativePath);
1624
- continue;
1625
- }
1626
- if (entry.replaced) {
1627
- if (!restoreReplaced(entry, entry.after)) {
1628
- abandoned.push(relativePath);
1629
- continue;
1630
- }
1631
- touched.add(relativePath);
1632
- continue;
1633
- }
1634
- try {
1635
- const displaced = removeSwitchedEntry({
1636
- state,
1637
- targetRoot,
1638
- relativePath,
1639
- targetPath: targetPathOf(relativePath),
1640
- expected: entry.after,
1641
- expectedContent: entry.afterContent,
1642
- journal,
1643
- phase: "before_rollback_remove"
1644
- });
1645
- releaseRetainedEntry(journal, relativePath, displaced);
1646
- } catch (error) {
1647
- if (!(error instanceof WorkingTreeTargetChangedError)) throw error;
1648
- abandoned.push(relativePath);
1649
- continue;
1650
- }
1651
- touched.add(relativePath);
1652
- }
1653
- releaseRetainedEntries(journal);
1654
- if (journal.indexApplied) {
1655
- updateIndex(targetRoot, { drop: journal.indexApplied.written, put: journal.indexApplied.removed });
1656
- }
1657
- return {
1658
- paths: [...touched].sort(shallowestFirst),
1659
- abandoned: [...new Set(abandoned)].sort(shallowestFirst),
1660
- ...journal.retained && journal.retained.length > 0 ? { retained: [...journal.retained] } : {}
1661
- };
1662
- }
1663
- function captureWorkingTreeMirrorPlan(input) {
1664
- const targetRoot = path.resolve(input.targetRoot);
1665
- const snapshotRoot = path.resolve(input.snapshotRoot);
1666
- const durability = input.durability ?? "per_entry";
1667
- inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
1668
- if (!inspectDirectoryRoot(snapshotRoot, { allowMissing: true, label: "Working-tree snapshot root" })) {
1669
- fs.mkdirSync(snapshotRoot, { recursive: true });
1670
- }
1671
- const trees = [];
1672
- for (const [relativePath, entry] of [...input.plan.replaced.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
1673
- if (entry.kind === "gitlink") continue;
1674
- if (entry.kind !== "directory") {
1675
- copyEntry(targetRoot, snapshotRoot, relativePath, entry, durability);
1676
- continue;
1677
- }
1678
- const source = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1679
- if (source.kind !== "entry" || !source.stat.isDirectory()) {
1680
- throw new Error(`Working-tree source changed while it was being mirrored: ${path.join(targetRoot, ...relativePath.split("/"))}`);
1681
- }
1682
- ensureDirectory(snapshotRoot, relativePath, entry.mode);
1683
- mirrorWorkingTree({
1684
- sourceRoot: source.absolutePath,
1685
- targetRoot: path.join(snapshotRoot, ...relativePath.split("/")),
1686
- sourceMode: "all",
1687
- deletionMode: "all",
1688
- durability
1689
- });
1690
- trees.push(relativePath);
1691
- }
1692
- const { index } = input.plan;
1693
- return {
1694
- absent: [...input.plan.absent].sort(shallowestFirst),
1695
- trees: trees.sort(shallowestFirst),
1696
- ...index.removed.length > 0 || index.written.length > 0 ? { index } : {}
1697
- };
1698
- }
1699
- function restoreWorkingTreeMirrorScope(input) {
1700
- const snapshotRoot = path.resolve(input.snapshotRoot);
1701
- const targetRoot = path.resolve(input.targetRoot);
1702
- inspectDirectoryRoot(snapshotRoot, { allowMissing: false, label: "Working-tree snapshot root" });
1703
- if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
1704
- fs.mkdirSync(targetRoot, { recursive: true });
1705
- }
1706
- const normalize = (relativePath) => {
1707
- const normalized = normalizeRelativePath(relativePath);
1708
- if (!normalized) throw new Error(`Invalid working-tree scope path: ${relativePath}`);
1709
- return normalized;
1710
- };
1711
- const absent = input.scope.absent.map(normalize);
1712
- const trees = new Set(input.scope.trees.map(normalize));
1713
- const captured = inspectWorkingTree(snapshotRoot, "all");
1714
- for (const tree of trees) {
1715
- if (captured.get(tree)?.kind !== "directory") {
1716
- throw new Error(`Working-tree snapshot does not hold the captured directory: ${tree}`);
1717
- }
1718
- }
1719
- const touched = /* @__PURE__ */ new Set();
1720
- for (const relativePath of [...absent].sort(deepestFirst)) {
1721
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1722
- if (inspected.kind === "entry" && inspected.stat.isDirectory() && lstatIfExists(path.join(inspected.absolutePath, ".git"))) continue;
1723
- if (inspected.kind === "entry") fs.rmSync(inspected.absolutePath, { recursive: true, force: true });
1724
- touched.add(relativePath);
1725
- }
1726
- const writtenDirectories = /* @__PURE__ */ new Set();
1727
- for (const relativePath of absent) writtenDirectories.add(path.posix.dirname(relativePath));
1728
- for (const [relativePath, entry] of captured) {
1729
- if (entry.kind !== "directory" && !hasAncestorIn(relativePath, trees)) writtenDirectories.add(path.posix.dirname(relativePath));
1730
- }
1731
- for (const directory of writtenDirectories) {
1732
- let absoluteDirectory = targetRoot;
1733
- if (directory !== ".") {
1734
- const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, directory);
1735
- if (inspected.kind !== "entry" || !inspected.stat.isDirectory()) continue;
1736
- absoluteDirectory = inspected.absolutePath;
1737
- }
1738
- for (const name of fs.readdirSync(absoluteDirectory)) {
1739
- if (!MIRROR_TEMPORARY_FILE_PATTERN.test(name)) continue;
1740
- const temporaryPath = path.join(absoluteDirectory, name);
1741
- const temporaryStatus = fs.lstatSync(temporaryPath);
1742
- if (!temporaryStatus.isFile() && !temporaryStatus.isSymbolicLink()) continue;
1743
- fs.rmSync(temporaryPath, { force: true });
1744
- touched.add(directory === "." ? name : path.posix.join(directory, name));
1745
- }
1746
- }
1747
- for (const [relativePath, entry] of [...captured.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
1748
- touched.add(relativePath);
1749
- if (hasAncestorIn(relativePath, trees)) continue;
1750
- if (!trees.has(relativePath)) {
1751
- copyEntry(snapshotRoot, targetRoot, relativePath, entry, "per_entry");
1752
- continue;
1753
- }
1754
- const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
1755
- if (current.kind === "blocked") {
1756
- throw new Error(`Refusing to restore a working-tree directory through a symlink or non-directory: ${current.blockingPath}`);
1757
- }
1758
- if (current.kind === "entry" && !current.stat.isDirectory()) {
1759
- fs.rmSync(current.absolutePath, { recursive: true, force: true });
1760
- }
1761
- ensureDirectory(targetRoot, relativePath, entry.kind === "directory" ? entry.mode : 493);
1762
- mirrorWorkingTree({
1763
- sourceRoot: path.join(snapshotRoot, ...relativePath.split("/")),
1764
- targetRoot: path.join(targetRoot, ...relativePath.split("/")),
1765
- sourceMode: "all",
1766
- deletionMode: "all"
1767
- });
1768
- }
1769
- if (input.scope.index) updateIndex(targetRoot, { drop: input.scope.index.written, put: input.scope.index.removed });
1770
- return { paths: [...touched].sort(shallowestFirst) };
1771
- }
1772
- function fsyncWorkingTreePaths(root, relativePaths) {
1773
- root = path.resolve(root);
1774
- inspectDirectoryRoot(root, { allowMissing: false, label: "Working-tree root" });
1775
- const directories = /* @__PURE__ */ new Set(["."]);
1776
- for (const relativePath of relativePaths) {
1777
- const normalized = normalizeRelativePath(relativePath);
1778
- if (!normalized) throw new Error(`Invalid working-tree path: ${relativePath}`);
1779
- directories.add(path.posix.dirname(normalized));
1780
- const inspected = inspectRelativePathWithoutFollowingAncestors(root, normalized);
1781
- if (inspected.kind !== "entry") continue;
1782
- if (inspected.stat.isDirectory()) directories.add(normalized);
1783
- else if (inspected.stat.isFile()) fsyncPath(inspected.absolutePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
1784
- }
1785
- for (const directory of directories) {
1786
- if (directory === ".") {
1787
- fsyncPath(root, fs.constants.O_RDONLY);
1788
- continue;
1789
- }
1790
- const inspected = inspectRelativePathWithoutFollowingAncestors(root, directory);
1791
- if (inspected.kind === "entry" && inspected.stat.isDirectory()) fsyncPath(inspected.absolutePath, fs.constants.O_RDONLY);
1792
- }
1793
- }
1794
- export {
1795
- WorkingTreeTargetChangedError,
1796
- applyWorkingTreeMirror,
1797
- assertWorkingTreeHasNoPortableGitMetadataAliases,
1798
- captureWorkingTreeMirrorPlan,
1799
- displacedEntryMatches,
1800
- fsyncWorkingTreePaths,
1801
- inspectWorkingTree,
1802
- inspectWorkingTreePath,
1803
- mirrorWorkingTree,
1804
- normalizeWorkingTreeRelativePath,
1805
- planPreparedWorkingTreeMirror,
1806
- planWorkingTreeMirror,
1807
- prepareWorkingTreeMirror,
1808
- projectedFileEntry,
1809
- projectedFileEntryMatches,
1810
- restoreWorkingTreeMirrorScope,
1811
- rollbackWorkingTreeMirrorJournal,
1812
- sameWorkingTreeInode,
1813
- setWorkingTreeMirrorRaceHook,
1814
- workingTreeAtomicRenameSupport,
1815
- workingTreeDisplacedRetentionDirectory,
1816
- workingTreeStatsMatch
1817
- };