@ricsam/r5d-worker 0.0.132 → 0.0.134

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 (47) hide show
  1. package/dist/cjs/atomic-rename.cjs +303 -0
  2. package/dist/cjs/git-blob-hash.cjs +41 -0
  3. package/dist/cjs/main.cjs +187 -38
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/three-way-merge.cjs +346 -0
  6. package/dist/cjs/working-tree-mirror.cjs +1049 -64
  7. package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
  8. package/dist/cjs/workspace-command-targets.cjs +63 -0
  9. package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
  10. package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
  11. package/dist/cjs/workspace-git-sync.cjs +846 -61
  12. package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
  13. package/dist/cjs/workspace-hydration-merge.cjs +433 -0
  14. package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
  15. package/dist/cjs/workspace-merge-projection.cjs +81 -10
  16. package/dist/cjs/workspace-project-config-policy.cjs +19 -12
  17. package/dist/mjs/atomic-rename.mjs +261 -0
  18. package/dist/mjs/git-blob-hash.mjs +16 -0
  19. package/dist/mjs/main.mjs +196 -39
  20. package/dist/mjs/package.json +1 -1
  21. package/dist/mjs/three-way-merge.mjs +318 -0
  22. package/dist/mjs/working-tree-mirror.mjs +1035 -64
  23. package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
  24. package/dist/mjs/workspace-command-targets.mjs +37 -0
  25. package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
  26. package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
  27. package/dist/mjs/workspace-git-sync.mjs +854 -62
  28. package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
  29. package/dist/mjs/workspace-hydration-merge.mjs +399 -0
  30. package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
  31. package/dist/mjs/workspace-merge-projection.mjs +85 -11
  32. package/dist/mjs/workspace-project-config-policy.mjs +16 -10
  33. package/dist/types/atomic-rename.d.ts +78 -0
  34. package/dist/types/git-blob-hash.d.ts +10 -0
  35. package/dist/types/main.d.ts +21 -2
  36. package/dist/types/three-way-merge.d.ts +77 -0
  37. package/dist/types/working-tree-mirror.d.ts +270 -7
  38. package/dist/types/workspace-command-sync-policy.d.ts +12 -6
  39. package/dist/types/workspace-command-targets.d.ts +37 -0
  40. package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
  41. package/dist/types/workspace-git-sync.d.ts +125 -3
  42. package/dist/types/workspace-hydration-ledger.d.ts +43 -0
  43. package/dist/types/workspace-hydration-merge.d.ts +95 -0
  44. package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
  45. package/dist/types/workspace-merge-projection.d.ts +19 -1
  46. package/dist/types/workspace-project-config-policy.d.ts +17 -3
  47. package/package.json +2 -2
@@ -178,7 +178,7 @@ function hashRegularFile(workspacePath, sourcePath, entry) {
178
178
  if (pathStatus.dev !== after.dev || pathStatus.ino !== after.ino || !pathStatus.isFile() || pathStatus.isSymbolicLink()) {
179
179
  throw new Error(`Working-tree source changed while its merge projection was being synthesized: ${sourcePath}`);
180
180
  }
181
- return objectId;
181
+ return { objectId, projected: (0, import_working_tree_mirror.projectedFileEntry)(after) };
182
182
  } finally {
183
183
  import_node_fs.default.closeSync(descriptor);
184
184
  }
@@ -211,6 +211,8 @@ function synthesizeMountTree(input) {
211
211
  const entries = (0, import_working_tree_mirror.inspectWorkingTree)(sourceRoot, input.sourceMode);
212
212
  const indexPath = temporaryIndexPath(input.workspacePath);
213
213
  const environment = temporaryIndexEnvironment(indexPath);
214
+ const projectedFiles = {};
215
+ const blobs = /* @__PURE__ */ new Map();
214
216
  try {
215
217
  git(input.workspacePath, ["read-tree", "--empty"], "initialize workspace projection index", { environment });
216
218
  const records = [];
@@ -218,10 +220,20 @@ function synthesizeMountTree(input) {
218
220
  if (entry.kind === "directory") continue;
219
221
  if (entry.kind === "gitlink") {
220
222
  records.push(indexInfoRecord("160000", entry.objectId, relativePath));
223
+ projectedFiles[relativePath] = { kind: "gitlink", objectId: entry.objectId };
221
224
  continue;
222
225
  }
223
226
  const sourcePath = sourceEntryPath(sourceRoot, relativePath);
224
- const objectId = entry.kind === "file" ? hashRegularFile(input.workspacePath, sourcePath, entry) : hashSymlink(input.workspacePath, sourcePath, entry);
227
+ let objectId;
228
+ if (entry.kind === "file") {
229
+ const hashed = hashRegularFile(input.workspacePath, sourcePath, entry);
230
+ objectId = hashed.objectId;
231
+ projectedFiles[relativePath] = hashed.projected;
232
+ } else {
233
+ objectId = hashSymlink(input.workspacePath, sourcePath, entry);
234
+ projectedFiles[relativePath] = { kind: "symlink", target: entry.target };
235
+ }
236
+ blobs.set(relativePath, objectId);
225
237
  records.push(indexInfoRecord(indexMode(entry), objectId, relativePath));
226
238
  }
227
239
  if (records.length > 0) {
@@ -230,14 +242,54 @@ function synthesizeMountTree(input) {
230
242
  stdin: Buffer.concat(records)
231
243
  });
232
244
  }
233
- return requireObjectId(
245
+ const tree = requireObjectId(
234
246
  gitText(input.workspacePath, ["write-tree"], "write workspace projection mount tree", { environment }),
235
247
  "Workspace projection mount tree"
236
248
  );
249
+ return { tree, projectedFiles, blobs };
237
250
  } finally {
238
251
  removeTemporaryIndex(indexPath);
239
252
  }
240
253
  }
254
+ function staleRewritePaths(input) {
255
+ const entries = Object.entries(input.staleRewriteBlobs ?? {});
256
+ if (entries.length === 0) return { stale: [], cleared: [] };
257
+ const probed = gitText(input.workspacePath, ["cat-file", "--batch-check"], "resolve workspace projection basis subtree", {
258
+ stdin: Buffer.from(`${input.basisHead}:${input.workspaceRelativePath}
259
+ `)
260
+ });
261
+ const basisSubtree = /^([0-9a-f]{40,64}) tree \d+$/u.exec(probed);
262
+ if (!basisSubtree && !/ missing$/u.test(probed))
263
+ throw new Error(`Resolve workspace projection basis subtree: unexpected answer ${probed}`);
264
+ const changed = /* @__PURE__ */ new Set();
265
+ if (!basisSubtree) {
266
+ for (const relativePath of input.blobs.keys()) changed.add(relativePath);
267
+ } else {
268
+ const listing = git(
269
+ input.workspacePath,
270
+ ["diff-tree", "-r", "-z", "--no-renames", "--raw", basisSubtree[1], input.mountTree],
271
+ "compare workspace projection with its basis"
272
+ );
273
+ const records = nulRecords(listing);
274
+ for (let index = 0; index + 1 < records.length; index += 2) changed.add(records[index + 1].toString());
275
+ }
276
+ const stale = [];
277
+ const cleared = [];
278
+ for (const [relativePath, blob] of entries) {
279
+ const synthesized = input.blobs.get(relativePath);
280
+ if (synthesized === void 0) {
281
+ cleared.push(relativePath);
282
+ continue;
283
+ }
284
+ if (synthesized === blob) {
285
+ if (changed.has(relativePath)) stale.push(relativePath);
286
+ else cleared.push(relativePath);
287
+ continue;
288
+ }
289
+ if (changed.has(relativePath)) cleared.push(relativePath);
290
+ }
291
+ return { stale: stale.sort(), cleared: cleared.sort() };
292
+ }
241
293
  function nulRecords(content) {
242
294
  const records = [];
243
295
  let start = 0;
@@ -285,23 +337,32 @@ function graftMountTree(input) {
285
337
  }
286
338
  }
287
339
  function synthesizeOursCommit(input) {
288
- const mountTree = synthesizeMountTree({
340
+ const synthesized = synthesizeMountTree({
289
341
  workspacePath: input.workspacePath,
290
342
  sourcePath: input.mount.sourcePath,
291
343
  sourceMode: input.mount.sourceMode
292
344
  });
345
+ const detected = staleRewritePaths({
346
+ workspacePath: input.workspacePath,
347
+ workspaceRelativePath: input.workspaceRelativePath,
348
+ basisHead: input.basisHead,
349
+ mountTree: synthesized.tree,
350
+ blobs: synthesized.blobs,
351
+ staleRewriteBlobs: input.staleRewriteBlobs
352
+ });
353
+ if (detected.stale.length > 0) return { staleRewritePaths: detected.stale, staleRewriteCleared: detected.cleared };
293
354
  const rootTree = graftMountTree({
294
355
  workspacePath: input.workspacePath,
295
356
  workspaceRelativePath: input.workspaceRelativePath,
296
357
  basisHead: input.basisHead,
297
- mountTree
358
+ mountTree: synthesized.tree
298
359
  });
299
360
  const message = JSON.stringify({
300
361
  type: "workspace_projection_basis",
301
362
  mountId: input.mount.id,
302
363
  attemptId: input.attemptId
303
364
  });
304
- return requireObjectId(
365
+ const oursCommit = requireObjectId(
305
366
  gitText(
306
367
  input.workspacePath,
307
368
  ["commit-tree", rootTree, "-p", input.basisHead, "-m", message],
@@ -309,6 +370,7 @@ function synthesizeOursCommit(input) {
309
370
  ),
310
371
  `Synthesized workspace projection commit for mount ${input.mount.id}`
311
372
  );
373
+ return { oursCommit, projectedFiles: synthesized.projectedFiles, staleRewriteCleared: detected.cleared };
312
374
  }
313
375
  function mergeWorkspaceProjectionMount(input) {
314
376
  const support = workspaceMergeProjectionSupport();
@@ -317,13 +379,19 @@ function mergeWorkspaceProjectionMount(input) {
317
379
  const workspaceRelativePath = normalizeWorkspaceRelativePath(input.mount.workspaceRelativePath);
318
380
  const basisHead = requireCommit(workspacePath, input.basisHead, `workspace projection basis for mount ${input.mount.id}`);
319
381
  const currentHead = requireCommit(workspacePath, input.currentHead, "current workspace projection head");
320
- const oursCommit = synthesizeOursCommit({
382
+ const synthesized = synthesizeOursCommit({
321
383
  workspacePath,
322
384
  mount: input.mount,
323
385
  workspaceRelativePath,
324
386
  basisHead,
325
- attemptId: input.attemptId
387
+ attemptId: input.attemptId,
388
+ ...input.staleRewriteBlobs ? { staleRewriteBlobs: input.staleRewriteBlobs } : {}
326
389
  });
390
+ if ("staleRewritePaths" in synthesized) {
391
+ return { kind: "stale_rewrite", paths: synthesized.staleRewritePaths, staleRewriteCleared: synthesized.staleRewriteCleared };
392
+ }
393
+ const { oursCommit, projectedFiles, staleRewriteCleared } = synthesized;
394
+ const readAtMs = Date.now();
327
395
  const merged = gitResult(workspacePath, [
328
396
  "merge-tree",
329
397
  "--write-tree",
@@ -341,13 +409,16 @@ function mergeWorkspaceProjectionMount(input) {
341
409
  const records = nulRecords(merged.stdout);
342
410
  const resultTree = records.shift()?.toString() ?? "";
343
411
  requireObjectId(resultTree, `Merged workspace projection tree for mount ${input.mount.id}`);
344
- if (merged.exitCode === 0) return { kind: "clean", resultTree, oursCommit };
412
+ if (merged.exitCode === 0) return { kind: "clean", resultTree, oursCommit, projectedFiles, readAtMs, staleRewriteCleared };
345
413
  const conflictPaths = records.map((record) => record.toString()).sort();
346
414
  return {
347
415
  kind: "conflict",
348
416
  oursCommit,
349
417
  conflictPaths,
350
- error: `Workspace projection for mount ${input.mount.id} conflicted with the current workspace head`
418
+ error: `Workspace projection for mount ${input.mount.id} conflicted with the current workspace head`,
419
+ projectedFiles,
420
+ readAtMs,
421
+ staleRewriteCleared
351
422
  };
352
423
  }
353
424
  function materializeWorkspaceProjectionTree(input) {
@@ -18,29 +18,36 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var workspace_project_config_policy_exports = {};
20
20
  __export(workspace_project_config_policy_exports, {
21
- busyProjectConfigurationChangeIds: () => busyProjectConfigurationChangeIds,
22
- deferredProjectConfigurationPendingBranches: () => deferredProjectConfigurationPendingBranches
21
+ busyProjectConfigurationChanges: () => busyProjectConfigurationChanges,
22
+ deferredProjectConfigurationPendingBranches: () => deferredProjectConfigurationPendingBranches,
23
+ describeBusyProjectConfigurationChanges: () => describeBusyProjectConfigurationChanges
23
24
  });
24
25
  module.exports = __toCommonJS(workspace_project_config_policy_exports);
25
26
  function deferredProjectConfigurationPendingBranches(projects) {
26
- return projects.flatMap((project) => project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
27
+ return projects.filter((project) => !project.executionDisabled).flatMap((project) => project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
27
28
  }
28
- function busyProjectConfigurationChangeIds(input) {
29
+ function busyProjectConfigurationChanges(input) {
29
30
  const currentById = new Map(input.currentProjects.map((project) => [project.projectId, project]));
30
31
  const incomingById = new Map(input.incomingProjects.map((project) => [project.projectId, project]));
31
32
  const targets = [...input.activeTargets];
32
33
  const busyProjectIds = targets.some((target) => target.type === "workspace" && target.rootProfile === "visible_projects") ? /* @__PURE__ */ new Set([...currentById.keys(), ...incomingById.keys()]) : new Set(targets.flatMap((target) => target.type === "project" ? [target.projectId] : []));
33
- return [...busyProjectIds].filter((projectId) => {
34
+ return [...busyProjectIds].flatMap((projectId) => {
34
35
  const current = currentById.get(projectId);
35
36
  const incoming = incomingById.get(projectId);
36
- if (!current && !incoming) return false;
37
- if (!current || !incoming) return true;
38
- if (!input.currentProjectReady(current)) return true;
39
- return input.currentFingerprint(current) !== input.incomingFingerprint(incoming);
40
- }).sort();
37
+ if (!current && !incoming) return [];
38
+ if (!current) return [{ projectId, reason: "added" }];
39
+ if (!incoming) return [{ projectId, reason: "removed" }];
40
+ if (!input.currentProjectReady(current)) return [{ projectId, reason: "initializing" }];
41
+ if (input.currentFingerprint(current) !== input.incomingFingerprint(incoming)) return [{ projectId, reason: "changed" }];
42
+ return [];
43
+ }).sort((left, right) => left.projectId.localeCompare(right.projectId));
44
+ }
45
+ function describeBusyProjectConfigurationChanges(changes) {
46
+ return changes.map(({ projectId, reason }) => `${projectId} (${reason})`).join(", ");
41
47
  }
42
48
  // Annotate the CommonJS export names for ESM import in node:
43
49
  0 && (module.exports = {
44
- busyProjectConfigurationChangeIds,
45
- deferredProjectConfigurationPendingBranches
50
+ busyProjectConfigurationChanges,
51
+ deferredProjectConfigurationPendingBranches,
52
+ describeBusyProjectConfigurationChanges
46
53
  });
@@ -0,0 +1,261 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ class AtomicRenameError extends Error {
6
+ constructor(syscall, code, errno, path2, dest) {
7
+ super(`${code}: ${syscall} failed, ${path2} -> ${dest}`);
8
+ this.syscall = syscall;
9
+ this.code = code;
10
+ this.errno = errno;
11
+ this.path = path2;
12
+ this.dest = dest;
13
+ this.name = "AtomicRenameError";
14
+ }
15
+ syscall;
16
+ code;
17
+ errno;
18
+ path;
19
+ dest;
20
+ }
21
+ const UNSUPPORTED_CODES = /* @__PURE__ */ new Set(["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM"]);
22
+ function isAtomicRenameUnsupported(error) {
23
+ return error instanceof AtomicRenameError && UNSUPPORTED_CODES.has(error.code);
24
+ }
25
+ const LINUX_AT_FDCWD = -100;
26
+ const LINUX_RENAME_NOREPLACE = 1;
27
+ const LINUX_RENAME_EXCHANGE = 2;
28
+ const DARWIN_RENAME_SWAP = 2;
29
+ const DARWIN_RENAME_EXCL = 4;
30
+ const LINUX_SYS_RENAMEAT2 = { x64: 316, arm64: 276 };
31
+ const PROBE_PREFIX = ".r5d-atomic-rename-probe-";
32
+ let loaded = null;
33
+ let override = null;
34
+ const errnoNames = /* @__PURE__ */ new Map();
35
+ for (const [name, value] of Object.entries(os.constants.errno)) errnoNames.set(value, name);
36
+ function errnoCode(errno) {
37
+ return errnoNames.get(errno) ?? `E${errno}`;
38
+ }
39
+ function cString(value) {
40
+ return Buffer.from(`${value}\0`, "utf8");
41
+ }
42
+ function loadNative() {
43
+ if (loaded) return loaded;
44
+ loaded = { calls: null, detail: "not attempted" };
45
+ try {
46
+ const ffi = require("bun:ffi");
47
+ const { dlopen, FFIType, ptr, read } = ffi;
48
+ if (process.platform === "darwin") {
49
+ const lib = dlopen("libSystem.B.dylib", {
50
+ renamex_np: { args: [FFIType.ptr, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
51
+ __error: { args: [], returns: FFIType.ptr }
52
+ });
53
+ loaded = {
54
+ calls: {
55
+ rename2: (from, to, flags) => {
56
+ const fromBuffer = cString(from);
57
+ const toBuffer = cString(to);
58
+ return lib.symbols.renamex_np(ptr(fromBuffer), ptr(toBuffer), flags);
59
+ },
60
+ errno: () => read.i32(lib.symbols.__error(), 0),
61
+ exchangeFlag: DARWIN_RENAME_SWAP,
62
+ noReplaceFlag: DARWIN_RENAME_EXCL,
63
+ backend: "renamex_np"
64
+ }
65
+ };
66
+ return loaded;
67
+ }
68
+ if (process.platform === "linux") {
69
+ const errnoLib = dlopen("libc.so.6", { __errno_location: { args: [], returns: FFIType.ptr } });
70
+ const errno = () => read.i32(errnoLib.symbols.__errno_location(), 0);
71
+ try {
72
+ const lib = dlopen("libc.so.6", {
73
+ renameat2: { args: [FFIType.i32, FFIType.ptr, FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 }
74
+ });
75
+ loaded = {
76
+ calls: {
77
+ rename2: (from, to, flags) => {
78
+ const fromBuffer = cString(from);
79
+ const toBuffer = cString(to);
80
+ return lib.symbols.renameat2(LINUX_AT_FDCWD, ptr(fromBuffer), LINUX_AT_FDCWD, ptr(toBuffer), flags);
81
+ },
82
+ errno,
83
+ exchangeFlag: LINUX_RENAME_EXCHANGE,
84
+ noReplaceFlag: LINUX_RENAME_NOREPLACE,
85
+ backend: "renameat2"
86
+ }
87
+ };
88
+ return loaded;
89
+ } catch (error) {
90
+ const number = LINUX_SYS_RENAMEAT2[process.arch];
91
+ if (number === void 0) {
92
+ loaded = { calls: null, detail: `renameat2 symbol unavailable and no syscall number for ${process.arch}: ${String(error)}` };
93
+ return loaded;
94
+ }
95
+ const lib = dlopen("libc.so.6", {
96
+ syscall: {
97
+ args: [FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64],
98
+ returns: FFIType.i64
99
+ }
100
+ });
101
+ loaded = {
102
+ calls: {
103
+ rename2: (from, to, flags) => {
104
+ const fromBuffer = cString(from);
105
+ const toBuffer = cString(to);
106
+ return Number(
107
+ lib.symbols.syscall(number, LINUX_AT_FDCWD, Number(ptr(fromBuffer)), LINUX_AT_FDCWD, Number(ptr(toBuffer)), flags)
108
+ );
109
+ },
110
+ errno,
111
+ exchangeFlag: LINUX_RENAME_EXCHANGE,
112
+ noReplaceFlag: LINUX_RENAME_NOREPLACE,
113
+ backend: "syscall"
114
+ }
115
+ };
116
+ return loaded;
117
+ }
118
+ }
119
+ loaded = { calls: null, detail: `no atomic exchange primitive on ${process.platform}` };
120
+ } catch (error) {
121
+ loaded = { calls: null, detail: `bun:ffi unavailable: ${error instanceof Error ? error.message : String(error)}` };
122
+ }
123
+ return loaded;
124
+ }
125
+ function applyOverride(support) {
126
+ if (override?.exchange !== void 0) support.exchange = override.exchange;
127
+ if (override?.noReplace !== void 0) support.noReplace = override.noReplace;
128
+ return support;
129
+ }
130
+ function atomicRenameSupport() {
131
+ const native = loadNative();
132
+ return applyOverride(
133
+ native.calls ? { platform: process.platform, exchange: native.calls.backend, noReplace: native.calls.backend } : { platform: process.platform, exchange: "unavailable", noReplace: "link", detail: native.detail }
134
+ );
135
+ }
136
+ function overrideAtomicRenameSupportForTests(value) {
137
+ override = value;
138
+ }
139
+ function resetAtomicRenameProbesForTests() {
140
+ probes.clear();
141
+ }
142
+ function call(native, syscall, from, to, flags) {
143
+ if (native.rename2(from, to, flags) === 0) return;
144
+ const errno = native.errno();
145
+ throw new AtomicRenameError(syscall, errnoCode(errno), errno, from, to);
146
+ }
147
+ function exchangeWith(support, a, b) {
148
+ const native = loadNative().calls;
149
+ if (support.exchange === "unavailable" || !native) {
150
+ throw new AtomicRenameError("exchange", "ENOSYS", os.constants.errno.ENOSYS, a, b);
151
+ }
152
+ call(native, support.exchange, a, b, native.exchangeFlag);
153
+ }
154
+ function renameNoReplaceWith(support, from, to) {
155
+ const native = loadNative().calls;
156
+ if (support.noReplace !== "link" && native) {
157
+ call(native, support.noReplace, from, to, native.noReplaceFlag);
158
+ return;
159
+ }
160
+ const source = fs.lstatSync(from);
161
+ if (!source.isFile()) throw new AtomicRenameError("link", "ENOTSUP", os.constants.errno.ENOTSUP, from, to);
162
+ try {
163
+ fs.linkSync(from, to);
164
+ } catch (error) {
165
+ const code = error.code ?? "EIO";
166
+ throw new AtomicRenameError("link", code, error.errno ?? 0, from, to);
167
+ }
168
+ fs.unlinkSync(from);
169
+ }
170
+ function exchangePaths(a, b) {
171
+ exchangeWith(atomicRenameSupport(), a, b);
172
+ }
173
+ function renameNoReplace(from, to) {
174
+ renameNoReplaceWith(atomicRenameSupport(), from, to);
175
+ }
176
+ const probes = /* @__PURE__ */ new Map();
177
+ function withoutTemporaries(directory, run) {
178
+ const token = randomUUID();
179
+ const a = path.join(directory, `${PROBE_PREFIX}${token}-a`);
180
+ const b = path.join(directory, `${PROBE_PREFIX}${token}-b`);
181
+ try {
182
+ run(a, b);
183
+ } finally {
184
+ fs.rmSync(a, { force: true });
185
+ fs.rmSync(b, { force: true });
186
+ }
187
+ }
188
+ function createProbeFile(filePath, content) {
189
+ const descriptor = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
190
+ try {
191
+ fs.writeSync(descriptor, content);
192
+ return fs.fstatSync(descriptor);
193
+ } finally {
194
+ fs.closeSync(descriptor);
195
+ }
196
+ }
197
+ function probeAtomicRenameSupport(directory) {
198
+ const native = atomicRenameSupport();
199
+ const dev = fs.lstatSync(directory).dev;
200
+ const cached = probes.get(dev);
201
+ if (cached) return applyOverride({ ...cached });
202
+ const probed = { ...native };
203
+ if (native.exchange !== "unavailable") {
204
+ withoutTemporaries(directory, (a, b) => {
205
+ const statA = createProbeFile(a, "a");
206
+ const statB = createProbeFile(b, "b");
207
+ let swapped = false;
208
+ let detail;
209
+ try {
210
+ exchangeWith(native, a, b);
211
+ const afterA = fs.lstatSync(a);
212
+ const afterB = fs.lstatSync(b);
213
+ swapped = afterA.ino === statB.ino && afterB.ino === statA.ino;
214
+ if (!swapped) detail = `exchange flag ignored by the filesystem holding ${directory}`;
215
+ } catch (error) {
216
+ detail = `exchange rejected by the filesystem holding ${directory}: ${error instanceof Error ? error.message : String(error)}`;
217
+ }
218
+ if (!swapped) {
219
+ probed.exchange = "unavailable";
220
+ probed.detail = detail;
221
+ }
222
+ });
223
+ }
224
+ if (native.noReplace !== "link") {
225
+ withoutTemporaries(directory, (a, b) => {
226
+ createProbeFile(a, "a");
227
+ createProbeFile(b, "b");
228
+ let refused = false;
229
+ try {
230
+ renameNoReplaceWith(native, a, b);
231
+ } catch (error) {
232
+ refused = error instanceof AtomicRenameError && error.code === "EEXIST";
233
+ }
234
+ if (!refused) {
235
+ probed.noReplace = "link";
236
+ probed.detail = `${probed.detail ? `${probed.detail}; ` : ""}no-replace flag ignored by the filesystem holding ${directory}`;
237
+ }
238
+ });
239
+ }
240
+ probes.set(dev, probed);
241
+ return applyOverride({ ...probed });
242
+ }
243
+ function atomicRenamePrimitivesFor(directory) {
244
+ const support = probeAtomicRenameSupport(directory);
245
+ return {
246
+ support,
247
+ exchange: (a, b) => exchangeWith(support, a, b),
248
+ renameNoReplace: (from, to) => renameNoReplaceWith(support, from, to)
249
+ };
250
+ }
251
+ export {
252
+ AtomicRenameError,
253
+ atomicRenamePrimitivesFor,
254
+ atomicRenameSupport,
255
+ exchangePaths,
256
+ isAtomicRenameUnsupported,
257
+ overrideAtomicRenameSupportForTests,
258
+ probeAtomicRenameSupport,
259
+ renameNoReplace,
260
+ resetAtomicRenameProbesForTests
261
+ };
@@ -0,0 +1,16 @@
1
+ import { createHash } from "node:crypto";
2
+ function gitObjectHashAlgorithmFor(objectId) {
3
+ if (/^[0-9a-f]{40}$/u.test(objectId)) return "sha1";
4
+ if (/^[0-9a-f]{64}$/u.test(objectId)) return "sha256";
5
+ return null;
6
+ }
7
+ function gitBlobHash(content, algorithm) {
8
+ const hash = createHash(algorithm);
9
+ hash.update(`blob ${content.length}\0`);
10
+ hash.update(content);
11
+ return hash.digest("hex");
12
+ }
13
+ export {
14
+ gitBlobHash,
15
+ gitObjectHashAlgorithmFor
16
+ };