@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.
- package/dist/cjs/atomic-rename.cjs +303 -0
- package/dist/cjs/git-blob-hash.cjs +41 -0
- package/dist/cjs/main.cjs +187 -38
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/three-way-merge.cjs +346 -0
- package/dist/cjs/working-tree-mirror.cjs +1049 -64
- package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
- package/dist/cjs/workspace-command-targets.cjs +63 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
- package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
- package/dist/cjs/workspace-git-sync.cjs +846 -61
- package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
- package/dist/cjs/workspace-hydration-merge.cjs +433 -0
- package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
- package/dist/cjs/workspace-merge-projection.cjs +81 -10
- package/dist/cjs/workspace-project-config-policy.cjs +19 -12
- package/dist/mjs/atomic-rename.mjs +261 -0
- package/dist/mjs/git-blob-hash.mjs +16 -0
- package/dist/mjs/main.mjs +196 -39
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/three-way-merge.mjs +318 -0
- package/dist/mjs/working-tree-mirror.mjs +1035 -64
- package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
- package/dist/mjs/workspace-command-targets.mjs +37 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
- package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
- package/dist/mjs/workspace-git-sync.mjs +854 -62
- package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
- package/dist/mjs/workspace-hydration-merge.mjs +399 -0
- package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
- package/dist/mjs/workspace-merge-projection.mjs +85 -11
- package/dist/mjs/workspace-project-config-policy.mjs +16 -10
- package/dist/types/atomic-rename.d.ts +78 -0
- package/dist/types/git-blob-hash.d.ts +10 -0
- package/dist/types/main.d.ts +21 -2
- package/dist/types/three-way-merge.d.ts +77 -0
- package/dist/types/working-tree-mirror.d.ts +270 -7
- package/dist/types/workspace-command-sync-policy.d.ts +12 -6
- package/dist/types/workspace-command-targets.d.ts +37 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
- package/dist/types/workspace-git-sync.d.ts +125 -3
- package/dist/types/workspace-hydration-ledger.d.ts +43 -0
- package/dist/types/workspace-hydration-merge.d.ts +95 -0
- package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
- package/dist/types/workspace-merge-projection.d.ts +19 -1
- package/dist/types/workspace-project-config-policy.d.ts +17 -3
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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].
|
|
34
|
+
return [...busyProjectIds].flatMap((projectId) => {
|
|
34
35
|
const current = currentById.get(projectId);
|
|
35
36
|
const incoming = incomingById.get(projectId);
|
|
36
|
-
if (!current && !incoming) return
|
|
37
|
-
if (!current
|
|
38
|
-
if (!
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
+
};
|