@ricsam/r5d-worker 0.0.86 → 0.0.88
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/main.cjs +101 -66
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +183 -104
- package/dist/cjs/supervisor.cjs +2 -1
- package/dist/cjs/workspace-git-sync.cjs +491 -189
- package/dist/mjs/main.mjs +101 -66
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +183 -104
- package/dist/mjs/supervisor.mjs +2 -1
- package/dist/mjs/workspace-git-sync.mjs +491 -189
- package/dist/types/project-worktrees.d.ts +12 -12
- package/dist/types/workspace-automatic-sync-policy.d.ts +7 -4
- package/dist/types/workspace-git-sync.d.ts +9 -9
- package/package.json +1 -1
|
@@ -63,6 +63,71 @@ function revParse(workspacePath, revision) {
|
|
|
63
63
|
const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
|
|
64
64
|
return result.exitCode === 0 ? result.stdout : null;
|
|
65
65
|
}
|
|
66
|
+
async function gitResultAsync(cwd, args) {
|
|
67
|
+
const subprocess = Bun.spawn(gitCommandArgs(args), {
|
|
68
|
+
cwd,
|
|
69
|
+
stdin: "ignore",
|
|
70
|
+
stdout: "pipe",
|
|
71
|
+
stderr: "pipe",
|
|
72
|
+
env: workerGitProcessEnvironment()
|
|
73
|
+
});
|
|
74
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
75
|
+
new Response(subprocess.stdout).text(),
|
|
76
|
+
new Response(subprocess.stderr).text(),
|
|
77
|
+
subprocess.exited
|
|
78
|
+
]);
|
|
79
|
+
return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
80
|
+
}
|
|
81
|
+
async function gitAsync(cwd, args, action) {
|
|
82
|
+
const result = await gitResultAsync(cwd, args);
|
|
83
|
+
if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
84
|
+
return result.stdout;
|
|
85
|
+
}
|
|
86
|
+
async function tryGitAsync(cwd, args) {
|
|
87
|
+
return (await gitResultAsync(cwd, args)).exitCode === 0;
|
|
88
|
+
}
|
|
89
|
+
async function revParseAsync(workspacePath, revision) {
|
|
90
|
+
const result = await gitResultAsync(workspacePath, ["rev-parse", "--verify", revision]);
|
|
91
|
+
return result.exitCode === 0 ? result.stdout : null;
|
|
92
|
+
}
|
|
93
|
+
function yieldToWorkerEventLoop() {
|
|
94
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
95
|
+
}
|
|
96
|
+
function workspaceCommitsExist(workspacePath, heads) {
|
|
97
|
+
const uniqueHeads = [...new Set(heads)];
|
|
98
|
+
if (uniqueHeads.length === 0) return true;
|
|
99
|
+
const batch = Bun.spawnSync(gitCommandArgs(["cat-file", "--batch-check"]), {
|
|
100
|
+
cwd: workspacePath,
|
|
101
|
+
stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
|
|
102
|
+
`).join("")),
|
|
103
|
+
stdout: "pipe",
|
|
104
|
+
stderr: "pipe",
|
|
105
|
+
env: workerGitProcessEnvironment()
|
|
106
|
+
});
|
|
107
|
+
if (batch.exitCode !== 0) return false;
|
|
108
|
+
const lines = batch.stdout.toString().split("\n").filter(Boolean);
|
|
109
|
+
return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
|
|
110
|
+
}
|
|
111
|
+
async function workspaceCommitsExistAsync(workspacePath, heads) {
|
|
112
|
+
const uniqueHeads = [...new Set(heads)];
|
|
113
|
+
if (uniqueHeads.length === 0) return true;
|
|
114
|
+
const subprocess = Bun.spawn(gitCommandArgs(["cat-file", "--batch-check"]), {
|
|
115
|
+
cwd: workspacePath,
|
|
116
|
+
stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
|
|
117
|
+
`).join("")),
|
|
118
|
+
stdout: "pipe",
|
|
119
|
+
stderr: "pipe",
|
|
120
|
+
env: workerGitProcessEnvironment()
|
|
121
|
+
});
|
|
122
|
+
const [stdout, , exitCode] = await Promise.all([
|
|
123
|
+
new Response(subprocess.stdout).text(),
|
|
124
|
+
new Response(subprocess.stderr).text(),
|
|
125
|
+
subprocess.exited
|
|
126
|
+
]);
|
|
127
|
+
if (exitCode !== 0) return false;
|
|
128
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
129
|
+
return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
|
|
130
|
+
}
|
|
66
131
|
function requiredWorkspaceAncestorHeads(value) {
|
|
67
132
|
if (value === void 0) return [];
|
|
68
133
|
if (!Array.isArray(value) || value.length === 0 || value.some((head) => typeof head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head)) || new Set(value).size !== value.length) {
|
|
@@ -86,8 +151,8 @@ function assertWorkspaceRemediationAncestry(input) {
|
|
|
86
151
|
);
|
|
87
152
|
}
|
|
88
153
|
}
|
|
89
|
-
function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
90
|
-
|
|
154
|
+
async function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
155
|
+
await gitAsync(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
|
|
91
156
|
}
|
|
92
157
|
function fsyncDirectory(directoryPath) {
|
|
93
158
|
const descriptor = fs.openSync(directoryPath, fs.constants.O_RDONLY);
|
|
@@ -191,16 +256,13 @@ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMou
|
|
|
191
256
|
return fs.existsSync(mount.sourcePath) || fs.existsSync(outerPath) || mount.sourceMode === "all";
|
|
192
257
|
});
|
|
193
258
|
}
|
|
194
|
-
function
|
|
259
|
+
function parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content) {
|
|
195
260
|
const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
|
|
196
261
|
if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
|
|
197
262
|
`) {
|
|
198
|
-
if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
|
|
199
|
-
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
200
|
-
}
|
|
201
263
|
const receipt2 = { version: 3, mounts: [] };
|
|
202
264
|
Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
|
|
203
|
-
return receipt2;
|
|
265
|
+
return { receipt: receipt2, headsToVerify: [legacyHead] };
|
|
204
266
|
}
|
|
205
267
|
let parsed;
|
|
206
268
|
try {
|
|
@@ -268,14 +330,16 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
268
330
|
}
|
|
269
331
|
const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
|
|
270
332
|
if (liftedV2Head) receiptHeads.add(liftedV2Head);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
333
|
+
return { receipt, headsToVerify: [...receiptHeads] };
|
|
334
|
+
}
|
|
335
|
+
function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
|
|
336
|
+
const { receipt, headsToVerify } = parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content);
|
|
337
|
+
if (!workspaceCommitsExist(workspacePath, headsToVerify)) {
|
|
338
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
275
339
|
}
|
|
276
340
|
return receipt;
|
|
277
341
|
}
|
|
278
|
-
function
|
|
342
|
+
function readHydratedWorkspaceReceiptUnverified(workspacePath) {
|
|
279
343
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
280
344
|
const status = lstatIfExists(receiptPath);
|
|
281
345
|
if (!status) return null;
|
|
@@ -283,7 +347,23 @@ function readHydratedWorkspaceReceipt(workspacePath) {
|
|
|
283
347
|
throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
|
|
284
348
|
}
|
|
285
349
|
const content = fs.readFileSync(receiptPath, "utf8");
|
|
286
|
-
return
|
|
350
|
+
return { ...parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content), receiptPath };
|
|
351
|
+
}
|
|
352
|
+
function readHydratedWorkspaceReceipt(workspacePath) {
|
|
353
|
+
const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
|
|
354
|
+
if (!unverified) return null;
|
|
355
|
+
if (!workspaceCommitsExist(workspacePath, unverified.headsToVerify)) {
|
|
356
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
|
|
357
|
+
}
|
|
358
|
+
return unverified.receipt;
|
|
359
|
+
}
|
|
360
|
+
async function readHydratedWorkspaceReceiptAsync(workspacePath) {
|
|
361
|
+
const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
|
|
362
|
+
if (!unverified) return null;
|
|
363
|
+
if (!await workspaceCommitsExistAsync(workspacePath, unverified.headsToVerify)) {
|
|
364
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
|
|
365
|
+
}
|
|
366
|
+
return unverified.receipt;
|
|
287
367
|
}
|
|
288
368
|
function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
289
369
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
@@ -330,49 +410,142 @@ function desiredWorkspaceBasisRefs(receipt) {
|
|
|
330
410
|
}
|
|
331
411
|
function workspaceBasisRefsMatch(workspacePath, receipt) {
|
|
332
412
|
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
413
|
+
const actual = git(
|
|
414
|
+
workspacePath,
|
|
415
|
+
["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX],
|
|
416
|
+
`list ${WORKSPACE_GIT_BASIS_REF_PREFIX} refs`
|
|
417
|
+
).split("\n").filter(Boolean);
|
|
418
|
+
if (actual.length !== desired.size) return false;
|
|
419
|
+
return actual.every((line) => {
|
|
420
|
+
const separator = line.lastIndexOf(" ");
|
|
421
|
+
if (separator <= 0) return false;
|
|
422
|
+
return desired.get(line.slice(0, separator)) === line.slice(separator + 1);
|
|
423
|
+
});
|
|
336
424
|
}
|
|
337
425
|
function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
|
|
338
426
|
return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
|
|
339
427
|
}
|
|
428
|
+
async function workspaceBasisRefsAreCurrentAsync(workspacePath, receipt) {
|
|
429
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
430
|
+
const listing = await gitAsync(
|
|
431
|
+
workspacePath,
|
|
432
|
+
["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
|
|
433
|
+
"list workspace hydration basis refs"
|
|
434
|
+
);
|
|
435
|
+
const stable = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const line of listing.split("\n").filter(Boolean)) {
|
|
437
|
+
const separator = line.lastIndexOf(" ");
|
|
438
|
+
if (separator <= 0) return false;
|
|
439
|
+
const ref = line.slice(0, separator);
|
|
440
|
+
if (ref.startsWith(WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) return false;
|
|
441
|
+
stable.set(ref, line.slice(separator + 1));
|
|
442
|
+
}
|
|
443
|
+
if (stable.size !== desired.size) return false;
|
|
444
|
+
return [...stable].every(([ref, head]) => desired.get(ref) === head);
|
|
445
|
+
}
|
|
340
446
|
function repairWorkspaceBasisRefs(workspacePath, receipt) {
|
|
341
447
|
const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
|
|
342
448
|
if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
|
|
343
449
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
const transaction = ["start"];
|
|
347
|
-
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
348
|
-
for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
|
|
349
|
-
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
350
|
-
}
|
|
351
|
-
transaction.push("prepare", "commit", "");
|
|
450
|
+
function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
|
|
451
|
+
if (instructions.length === 0) return;
|
|
352
452
|
const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
353
453
|
cwd: workspacePath,
|
|
354
|
-
stdin: Buffer.from(
|
|
454
|
+
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
355
455
|
stdout: "pipe",
|
|
356
456
|
stderr: "pipe",
|
|
357
457
|
env: workerGitProcessEnvironment()
|
|
358
458
|
});
|
|
359
459
|
if (updated.exitCode !== 0) {
|
|
360
|
-
throw new Error(
|
|
460
|
+
throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
|
|
361
461
|
}
|
|
362
|
-
|
|
363
|
-
|
|
462
|
+
}
|
|
463
|
+
async function runWorkspaceRefUpdateBatchAsync(workspacePath, instructions, action) {
|
|
464
|
+
if (instructions.length === 0) return;
|
|
465
|
+
const subprocess = Bun.spawn(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
466
|
+
cwd: workspacePath,
|
|
467
|
+
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
468
|
+
stdout: "pipe",
|
|
469
|
+
stderr: "pipe",
|
|
470
|
+
env: workerGitProcessEnvironment()
|
|
471
|
+
});
|
|
472
|
+
const [, stderr, exitCode] = await Promise.all([
|
|
473
|
+
new Response(subprocess.stdout).text(),
|
|
474
|
+
new Response(subprocess.stderr).text(),
|
|
475
|
+
subprocess.exited
|
|
476
|
+
]);
|
|
477
|
+
if (exitCode !== 0) {
|
|
478
|
+
throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
|
|
364
479
|
}
|
|
365
480
|
}
|
|
366
|
-
function
|
|
481
|
+
function workspaceBasisRefTransaction(receipt, stableRefs) {
|
|
482
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
483
|
+
const transaction = ["start"];
|
|
484
|
+
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
485
|
+
for (const ref of stableRefs) {
|
|
486
|
+
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
487
|
+
}
|
|
488
|
+
transaction.push("prepare", "commit");
|
|
489
|
+
return transaction;
|
|
490
|
+
}
|
|
491
|
+
function updateWorkspaceBasisRefs(workspacePath, receipt) {
|
|
492
|
+
runWorkspaceRefUpdateBatch(
|
|
493
|
+
workspacePath,
|
|
494
|
+
workspaceBasisRefTransaction(receipt, listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)),
|
|
495
|
+
"Synchronize workspace hydration basis refs"
|
|
496
|
+
);
|
|
497
|
+
runWorkspaceRefUpdateBatch(
|
|
498
|
+
workspacePath,
|
|
499
|
+
listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).map((ref) => `delete ${ref}`),
|
|
500
|
+
"remove staged workspace hydration basis refs"
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
async function updateWorkspaceBasisRefsAsync(workspacePath, receipt) {
|
|
504
|
+
const stableRefs = (await gitAsync(workspacePath, ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_REF_PREFIX], "list workspace basis refs")).split("\n").filter(Boolean).sort();
|
|
505
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
506
|
+
workspacePath,
|
|
507
|
+
workspaceBasisRefTransaction(receipt, stableRefs),
|
|
508
|
+
"Synchronize workspace hydration basis refs"
|
|
509
|
+
);
|
|
510
|
+
const stagedRefs = (await gitAsync(
|
|
511
|
+
workspacePath,
|
|
512
|
+
["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
|
|
513
|
+
"list staged workspace basis refs"
|
|
514
|
+
)).split("\n").filter(Boolean).sort();
|
|
515
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
516
|
+
workspacePath,
|
|
517
|
+
stagedRefs.map((ref) => `delete ${ref}`),
|
|
518
|
+
"remove staged workspace hydration basis refs"
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
function assertCanonicalHydrationReceiptHeads(receipt) {
|
|
367
522
|
for (const mount of receipt.mounts) {
|
|
368
523
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
|
|
369
524
|
throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
|
|
370
525
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function throwMissingHydrationReceiptCommit(workspacePath, receipt) {
|
|
529
|
+
const missing = receipt.mounts.find(({ head }) => !tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`]));
|
|
530
|
+
throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${missing?.head ?? "unknown"}`);
|
|
531
|
+
}
|
|
532
|
+
function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
533
|
+
assertCanonicalHydrationReceiptHeads(receipt);
|
|
534
|
+
if (!workspaceCommitsExist(workspacePath, receipt.mounts.map(({ head }) => head))) {
|
|
535
|
+
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
374
536
|
}
|
|
375
537
|
readHydratedWorkspaceReceipt(workspacePath);
|
|
538
|
+
writeHydratedWorkspaceReceiptCore(workspacePath, receipt);
|
|
539
|
+
}
|
|
540
|
+
async function updateHydratedWorkspaceReceiptAsync(workspacePath, receipt) {
|
|
541
|
+
assertCanonicalHydrationReceiptHeads(receipt);
|
|
542
|
+
if (!await workspaceCommitsExistAsync(workspacePath, receipt.mounts.map(({ head }) => head))) {
|
|
543
|
+
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
544
|
+
}
|
|
545
|
+
await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
546
|
+
await writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt);
|
|
547
|
+
}
|
|
548
|
+
function prepareHydratedWorkspaceReceiptWrite(workspacePath) {
|
|
376
549
|
const gitDirectory = path.join(workspacePath, ".git");
|
|
377
550
|
const gitDirectoryStatus = fs.lstatSync(gitDirectory);
|
|
378
551
|
if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
|
|
@@ -389,13 +562,16 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
389
562
|
if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
|
|
390
563
|
throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
|
|
391
564
|
}
|
|
392
|
-
|
|
393
|
-
|
|
565
|
+
return { receiptPath, receiptDirectory };
|
|
566
|
+
}
|
|
567
|
+
function stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix) {
|
|
394
568
|
desiredWorkspaceBasisRefs(receipt);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
569
|
+
return receipt.mounts.map(
|
|
570
|
+
(mount) => `update ${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)} ${mount.head}`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
function publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths) {
|
|
574
|
+
const temporaryPath = path.join(paths.receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
399
575
|
let descriptor;
|
|
400
576
|
try {
|
|
401
577
|
descriptor = fs.openSync(temporaryPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
|
|
@@ -404,15 +580,36 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
404
580
|
fs.fsyncSync(descriptor);
|
|
405
581
|
fs.closeSync(descriptor);
|
|
406
582
|
descriptor = void 0;
|
|
407
|
-
fs.renameSync(temporaryPath, receiptPath);
|
|
408
|
-
fsyncDirectory(receiptDirectory);
|
|
409
|
-
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
583
|
+
fs.renameSync(temporaryPath, paths.receiptPath);
|
|
584
|
+
fsyncDirectory(paths.receiptDirectory);
|
|
410
585
|
} catch (error) {
|
|
411
586
|
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
412
587
|
fs.rmSync(temporaryPath, { force: true });
|
|
413
588
|
throw error;
|
|
414
589
|
}
|
|
415
590
|
}
|
|
591
|
+
function writeHydratedWorkspaceReceiptCore(workspacePath, receipt) {
|
|
592
|
+
const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
|
|
593
|
+
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
594
|
+
runWorkspaceRefUpdateBatch(
|
|
595
|
+
workspacePath,
|
|
596
|
+
stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
|
|
597
|
+
"stage workspace hydration basis refs"
|
|
598
|
+
);
|
|
599
|
+
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
600
|
+
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
601
|
+
}
|
|
602
|
+
async function writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt) {
|
|
603
|
+
const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
|
|
604
|
+
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
605
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
606
|
+
workspacePath,
|
|
607
|
+
stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
|
|
608
|
+
"stage workspace hydration basis refs"
|
|
609
|
+
);
|
|
610
|
+
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
611
|
+
await updateWorkspaceBasisRefsAsync(workspacePath, receipt);
|
|
612
|
+
}
|
|
416
613
|
function hydrationTransactionPath(workspacePath) {
|
|
417
614
|
return path.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
|
|
418
615
|
}
|
|
@@ -464,7 +661,7 @@ function fsyncTree(rootPath) {
|
|
|
464
661
|
};
|
|
465
662
|
syncEntry(rootPath);
|
|
466
663
|
}
|
|
467
|
-
function
|
|
664
|
+
function parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, content) {
|
|
468
665
|
let parsed;
|
|
469
666
|
try {
|
|
470
667
|
parsed = JSON.parse(content);
|
|
@@ -487,25 +684,34 @@ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, conte
|
|
|
487
684
|
`) {
|
|
488
685
|
throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
|
|
489
686
|
}
|
|
490
|
-
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
491
|
-
throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
|
|
492
|
-
}
|
|
493
687
|
return record;
|
|
494
688
|
}
|
|
495
|
-
function
|
|
689
|
+
function readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath) {
|
|
496
690
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
497
691
|
const status = lstatIfExists(recordPath);
|
|
498
692
|
if (!status) return null;
|
|
499
693
|
if (!status.isFile() || status.isSymbolicLink()) {
|
|
500
694
|
throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
|
|
501
695
|
}
|
|
502
|
-
return
|
|
696
|
+
return { record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, fs.readFileSync(recordPath, "utf8")), recordPath };
|
|
503
697
|
}
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
|
|
698
|
+
function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
|
|
699
|
+
const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
|
|
700
|
+
if (!unverified) return null;
|
|
701
|
+
if (unverified.record.head && !tryGit(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
|
|
702
|
+
throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
|
|
507
703
|
}
|
|
508
|
-
|
|
704
|
+
return unverified.record;
|
|
705
|
+
}
|
|
706
|
+
async function readWorkspaceCheckoutDurabilityRecordAsync(workspacePath) {
|
|
707
|
+
const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
|
|
708
|
+
if (!unverified) return null;
|
|
709
|
+
if (unverified.record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
|
|
710
|
+
throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
|
|
711
|
+
}
|
|
712
|
+
return unverified.record;
|
|
713
|
+
}
|
|
714
|
+
function writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record) {
|
|
509
715
|
const stateDirectory = workspacePrivateStateDirectory(workspacePath);
|
|
510
716
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
511
717
|
const temporaryPath = path.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
@@ -519,6 +725,20 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
|
519
725
|
throw error;
|
|
520
726
|
}
|
|
521
727
|
}
|
|
728
|
+
function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
729
|
+
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
730
|
+
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
731
|
+
}
|
|
732
|
+
readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
733
|
+
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
734
|
+
}
|
|
735
|
+
async function updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, record) {
|
|
736
|
+
if (record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
737
|
+
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
738
|
+
}
|
|
739
|
+
await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
740
|
+
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
741
|
+
}
|
|
522
742
|
function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
523
743
|
const status = fs.lstatSync(workspacePath);
|
|
524
744
|
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
@@ -530,6 +750,40 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
|
530
750
|
}
|
|
531
751
|
fsyncDirectory(workspacePath);
|
|
532
752
|
}
|
|
753
|
+
const WORKSPACE_FSYNC_YIELD_INTERVAL = 64;
|
|
754
|
+
async function fsyncWorkspaceCheckoutTreeYielding(workspacePath) {
|
|
755
|
+
const status = fs.lstatSync(workspacePath);
|
|
756
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
757
|
+
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
758
|
+
}
|
|
759
|
+
let entriesSinceYield = 0;
|
|
760
|
+
const syncEntry = async (entryPath) => {
|
|
761
|
+
const entryStatus = fs.lstatSync(entryPath);
|
|
762
|
+
if (entryStatus.isSymbolicLink()) return;
|
|
763
|
+
if (entryStatus.isDirectory()) {
|
|
764
|
+
for (const name of fs.readdirSync(entryPath)) await syncEntry(path.join(entryPath, name));
|
|
765
|
+
fsyncDirectory(entryPath);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (!entryStatus.isFile()) return;
|
|
769
|
+
const descriptor = fs.openSync(entryPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
770
|
+
try {
|
|
771
|
+
fs.fsyncSync(descriptor);
|
|
772
|
+
} finally {
|
|
773
|
+
fs.closeSync(descriptor);
|
|
774
|
+
}
|
|
775
|
+
entriesSinceYield += 1;
|
|
776
|
+
if (entriesSinceYield >= WORKSPACE_FSYNC_YIELD_INTERVAL) {
|
|
777
|
+
entriesSinceYield = 0;
|
|
778
|
+
await yieldToWorkerEventLoop();
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
for (const name of fs.readdirSync(workspacePath)) {
|
|
782
|
+
if (name === ".git") continue;
|
|
783
|
+
await syncEntry(path.join(workspacePath, name));
|
|
784
|
+
}
|
|
785
|
+
fsyncDirectory(workspacePath);
|
|
786
|
+
}
|
|
533
787
|
function workspaceRebaseInProgress(workspacePath) {
|
|
534
788
|
const gitDirectory = path.join(workspacePath, ".git");
|
|
535
789
|
return fs.existsSync(path.join(gitDirectory, "rebase-merge")) || fs.existsSync(path.join(gitDirectory, "rebase-apply"));
|
|
@@ -537,8 +791,8 @@ function workspaceRebaseInProgress(workspacePath) {
|
|
|
537
791
|
function workspaceResolutionInProgress(workspacePath) {
|
|
538
792
|
return workspaceRebaseInProgress(workspacePath) || fs.existsSync(path.join(workspacePath, ".git", "MERGE_HEAD"));
|
|
539
793
|
}
|
|
540
|
-
function workspaceHasUnmergedEntries(workspacePath) {
|
|
541
|
-
const result =
|
|
794
|
+
async function workspaceHasUnmergedEntries(workspacePath) {
|
|
795
|
+
const result = await gitResultAsync(workspacePath, ["ls-files", "-u", "-z"]);
|
|
542
796
|
if (result.exitCode !== 0) {
|
|
543
797
|
throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
544
798
|
}
|
|
@@ -594,17 +848,21 @@ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
|
|
|
594
848
|
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
595
849
|
return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
|
|
596
850
|
}
|
|
851
|
+
async function workspaceCheckoutDurabilityIsCurrentAsync(workspacePath) {
|
|
852
|
+
const record = await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
853
|
+
return Boolean(record?.state === "durable" && record.head === await revParseAsync(workspacePath, "HEAD"));
|
|
854
|
+
}
|
|
597
855
|
function beginWorkspaceCheckoutTransition(workspacePath) {
|
|
598
856
|
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
599
857
|
const head = revParse(workspacePath, "HEAD");
|
|
600
858
|
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
|
|
601
859
|
}
|
|
602
|
-
function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
603
|
-
|
|
604
|
-
|
|
860
|
+
async function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
861
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
862
|
+
await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
|
|
605
863
|
version: 1,
|
|
606
864
|
state: "durable",
|
|
607
|
-
head:
|
|
865
|
+
head: await revParseAsync(workspacePath, "HEAD")
|
|
608
866
|
});
|
|
609
867
|
}
|
|
610
868
|
function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
@@ -618,11 +876,11 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
|
618
876
|
}
|
|
619
877
|
throw error;
|
|
620
878
|
}
|
|
621
|
-
function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
879
|
+
async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
622
880
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
623
881
|
try {
|
|
624
|
-
mutate();
|
|
625
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
882
|
+
await mutate();
|
|
883
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
626
884
|
} catch (error) {
|
|
627
885
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
628
886
|
}
|
|
@@ -920,24 +1178,24 @@ function validateMounts(workspacePath, mounts) {
|
|
|
920
1178
|
if (overlap) throw new Error(`Workspace mounts overlap: ${current} and ${overlap}`);
|
|
921
1179
|
}
|
|
922
1180
|
}
|
|
923
|
-
function configureWorkspaceRepository(input) {
|
|
924
|
-
if (
|
|
925
|
-
|
|
1181
|
+
async function configureWorkspaceRepository(input) {
|
|
1182
|
+
if (await tryGitAsync(input.workspacePath, ["remote", "get-url", "origin"])) {
|
|
1183
|
+
await gitAsync(input.workspacePath, ["remote", "set-url", "origin", input.remoteUrl], "configure workspace origin");
|
|
926
1184
|
} else {
|
|
927
|
-
|
|
1185
|
+
await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
|
|
928
1186
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1187
|
+
await tryGitAsync(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
|
|
1188
|
+
await gitAsync(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
|
|
1189
|
+
await gitAsync(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
|
|
932
1190
|
if (input.credentialUsername) {
|
|
933
|
-
|
|
1191
|
+
await gitAsync(
|
|
934
1192
|
input.workspacePath,
|
|
935
1193
|
["config", "--local", "--replace-all", gitCredentialUsernameConfigKey(input.remoteUrl), input.credentialUsername],
|
|
936
1194
|
"configure workspace credential username"
|
|
937
1195
|
);
|
|
938
1196
|
}
|
|
939
1197
|
if (input.credentialHelper) {
|
|
940
|
-
|
|
1198
|
+
await gitAsync(
|
|
941
1199
|
input.workspacePath,
|
|
942
1200
|
["config", "--local", "--add", "credential.helper", input.credentialHelper],
|
|
943
1201
|
"configure workspace credential helper"
|
|
@@ -946,26 +1204,26 @@ function configureWorkspaceRepository(input) {
|
|
|
946
1204
|
const name = input.gitIdentity.name.trim();
|
|
947
1205
|
const email = input.gitIdentity.email.trim();
|
|
948
1206
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1207
|
+
await gitAsync(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
|
|
1208
|
+
await gitAsync(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
|
|
1209
|
+
await gitAsync(
|
|
952
1210
|
input.workspacePath,
|
|
953
1211
|
["config", "--local", "core.fsync", "committed,reference"],
|
|
954
1212
|
"configure durable workspace commits and references"
|
|
955
1213
|
);
|
|
956
|
-
|
|
1214
|
+
await gitAsync(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
957
1215
|
}
|
|
958
|
-
function configureExistingWorkspaceGitForRemediation(input) {
|
|
1216
|
+
async function configureExistingWorkspaceGitForRemediation(input) {
|
|
959
1217
|
const workspacePath = path.resolve(input.workspacePath);
|
|
960
1218
|
const workspaceStatus = lstatIfExists(workspacePath);
|
|
961
1219
|
const gitStatus = lstatIfExists(path.join(workspacePath, ".git"));
|
|
962
1220
|
if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
963
1221
|
throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
|
|
964
1222
|
}
|
|
965
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1223
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
966
1224
|
}
|
|
967
|
-
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
968
|
-
const result =
|
|
1225
|
+
async function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
1226
|
+
const result = await gitResultAsync(workspacePath, [
|
|
969
1227
|
...gitTransportSecurityArgs(remoteUrl, credentialHelper, credentialUsername),
|
|
970
1228
|
"fetch",
|
|
971
1229
|
"--no-recurse-submodules",
|
|
@@ -984,7 +1242,7 @@ ${result.stdout}`;
|
|
|
984
1242
|
}
|
|
985
1243
|
return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
986
1244
|
}
|
|
987
|
-
function ensureWorkspaceGitClone(input) {
|
|
1245
|
+
async function ensureWorkspaceGitClone(input) {
|
|
988
1246
|
const workspacePath = path.resolve(input.workspacePath);
|
|
989
1247
|
const workspaceParent = path.dirname(workspacePath);
|
|
990
1248
|
const parentStatus = fs.lstatSync(workspaceParent);
|
|
@@ -1024,18 +1282,18 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1024
1282
|
git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
|
|
1025
1283
|
}
|
|
1026
1284
|
}
|
|
1027
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1285
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1028
1286
|
recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
|
|
1029
|
-
const previousRemoteHead =
|
|
1030
|
-
const localHeadBeforeFetch =
|
|
1031
|
-
if (!
|
|
1032
|
-
updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
|
|
1287
|
+
const previousRemoteHead = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1288
|
+
const localHeadBeforeFetch = await revParseAsync(workspacePath, "HEAD");
|
|
1289
|
+
if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
1290
|
+
await updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
|
|
1033
1291
|
}
|
|
1034
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1035
|
-
let localHead =
|
|
1292
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1293
|
+
let localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1036
1294
|
if (!localHead && remoteHead) {
|
|
1037
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1038
|
-
|
|
1295
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1296
|
+
await gitAsync(
|
|
1039
1297
|
workspacePath,
|
|
1040
1298
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1041
1299
|
"check out workspace main"
|
|
@@ -1044,12 +1302,12 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1044
1302
|
localHead = remoteHead;
|
|
1045
1303
|
}
|
|
1046
1304
|
if (localHead && remoteHead && localHead === remoteHead) {
|
|
1047
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1305
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1048
1306
|
} else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
|
|
1049
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1050
|
-
|
|
1307
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1308
|
+
await gitAsync(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
|
|
1051
1309
|
});
|
|
1052
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1310
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1053
1311
|
localHead = remoteHead;
|
|
1054
1312
|
}
|
|
1055
1313
|
return { localHead, remoteHead };
|
|
@@ -1173,7 +1431,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1173
1431
|
}
|
|
1174
1432
|
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
|
|
1175
1433
|
}
|
|
1176
|
-
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1434
|
+
async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1177
1435
|
const workspacePath = path.resolve(input.workspacePath);
|
|
1178
1436
|
const durabilityMounts = input.durabilityMounts ?? input.mounts;
|
|
1179
1437
|
const receiptMounts = input.receiptMounts ?? durabilityMounts;
|
|
@@ -1181,19 +1439,34 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1181
1439
|
const candidateMounts = [
|
|
1182
1440
|
...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
|
|
1183
1441
|
];
|
|
1184
|
-
|
|
1185
|
-
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1186
|
-
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1187
|
-
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1188
|
-
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1189
|
-
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1190
|
-
);
|
|
1191
|
-
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1192
|
-
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1442
|
+
if (input.recordCurrentHead && !await workspaceCheckoutDurabilityIsCurrentAsync(workspacePath)) {
|
|
1193
1443
|
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1194
1444
|
}
|
|
1195
|
-
const targetHead = input.recordCurrentHead ?
|
|
1196
|
-
const
|
|
1445
|
+
const targetHead = input.recordCurrentHead ? await revParseAsync(workspacePath, "HEAD") : null;
|
|
1446
|
+
const receiptBeforeTransaction = targetHead ? await readHydratedWorkspaceReceiptAsync(workspacePath) : null;
|
|
1447
|
+
const computeSelection = () => {
|
|
1448
|
+
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1449
|
+
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1450
|
+
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1451
|
+
const hydrationMounts2 = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1452
|
+
const advancedDurabilityMounts2 = durabilityMounts.filter(
|
|
1453
|
+
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1454
|
+
);
|
|
1455
|
+
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1456
|
+
return {
|
|
1457
|
+
busyMountIds,
|
|
1458
|
+
hydrationMounts: hydrationMounts2,
|
|
1459
|
+
advancedDurabilityMounts: advancedDurabilityMounts2,
|
|
1460
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
|
|
1461
|
+
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, advancedDurabilityMounts2, receiptMounts) : null
|
|
1462
|
+
};
|
|
1463
|
+
};
|
|
1464
|
+
const noopCandidate = computeSelection();
|
|
1465
|
+
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
1466
|
+
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1467
|
+
}
|
|
1468
|
+
const selection = computeSelection();
|
|
1469
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1197
1470
|
const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1198
1471
|
try {
|
|
1199
1472
|
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
@@ -1207,7 +1480,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1207
1480
|
removeHydrationTransaction(workspacePath);
|
|
1208
1481
|
return {
|
|
1209
1482
|
hydratedMountIds: hydration.hydratedMountIds,
|
|
1210
|
-
skippedMountIds:
|
|
1483
|
+
skippedMountIds: selection.skippedMountIds
|
|
1211
1484
|
};
|
|
1212
1485
|
} catch (error) {
|
|
1213
1486
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
@@ -1226,7 +1499,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1226
1499
|
throw error;
|
|
1227
1500
|
}
|
|
1228
1501
|
}
|
|
1229
|
-
function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1502
|
+
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1230
1503
|
const gitDirectory = path.join(path.resolve(workspacePath), ".git");
|
|
1231
1504
|
if (!fs.existsSync(gitDirectory)) {
|
|
1232
1505
|
return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
|
|
@@ -1238,16 +1511,18 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1238
1511
|
ignoreBusy: options.ignoreBusy
|
|
1239
1512
|
});
|
|
1240
1513
|
}
|
|
1241
|
-
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1514
|
+
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1242
1515
|
workspacePath = path.resolve(workspacePath);
|
|
1243
1516
|
validateMounts(workspacePath, mounts);
|
|
1244
1517
|
recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
|
|
1245
1518
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1246
|
-
const currentHead =
|
|
1247
|
-
const currentReceipt =
|
|
1248
|
-
|
|
1519
|
+
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1520
|
+
const currentReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1521
|
+
if (!await workspaceBasisRefsAreCurrentAsync(workspacePath, currentReceipt ?? { version: 3, mounts: [] })) {
|
|
1522
|
+
repairWorkspaceBasisRefs(workspacePath, currentReceipt);
|
|
1523
|
+
}
|
|
1249
1524
|
if (!currentHead) return;
|
|
1250
|
-
const outerHeadContainsMount = (mount) =>
|
|
1525
|
+
const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1251
1526
|
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1252
1527
|
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1253
1528
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
@@ -1258,10 +1533,15 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1258
1533
|
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
|
|
1259
1534
|
);
|
|
1260
1535
|
const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
|
|
1261
|
-
const hydrationTargets =
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1536
|
+
const hydrationTargets = [];
|
|
1537
|
+
for (const mount of idleBasislessMounts) {
|
|
1538
|
+
if (mount.deleteWhenSourceMissing) continue;
|
|
1539
|
+
if (mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) {
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (await outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true) hydrationTargets.push(mount);
|
|
1543
|
+
}
|
|
1544
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1265
1545
|
workspacePath,
|
|
1266
1546
|
mounts: hydrationTargets,
|
|
1267
1547
|
durabilityMounts: idleBasislessMounts,
|
|
@@ -1272,27 +1552,31 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1272
1552
|
});
|
|
1273
1553
|
void hydration;
|
|
1274
1554
|
}
|
|
1275
|
-
function resetWorkspaceGit(input) {
|
|
1555
|
+
async function resetWorkspaceGit(input) {
|
|
1276
1556
|
const workspacePath = path.resolve(input.workspacePath);
|
|
1277
1557
|
validateMounts(workspacePath, input.mounts);
|
|
1278
1558
|
const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1279
1559
|
if (busyResetMountIds.length > 0) {
|
|
1280
1560
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
1281
1561
|
}
|
|
1282
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1562
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1283
1563
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1284
1564
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
1285
1565
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
1286
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1566
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1287
1567
|
if (remoteHead) {
|
|
1288
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1289
|
-
|
|
1290
|
-
|
|
1568
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1569
|
+
await gitAsync(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
|
|
1570
|
+
await gitAsync(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
|
|
1291
1571
|
});
|
|
1292
1572
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
1293
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1294
|
-
|
|
1295
|
-
|
|
1573
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1574
|
+
await gitAsync(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
|
|
1575
|
+
await gitAsync(
|
|
1576
|
+
workspacePath,
|
|
1577
|
+
["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })],
|
|
1578
|
+
"record empty workspace reset"
|
|
1579
|
+
);
|
|
1296
1580
|
});
|
|
1297
1581
|
}
|
|
1298
1582
|
const selected = activeMounts(input.mounts);
|
|
@@ -1309,7 +1593,7 @@ function resetWorkspaceGit(input) {
|
|
|
1309
1593
|
const requiredLiveMounts = [
|
|
1310
1594
|
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
1311
1595
|
];
|
|
1312
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1596
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1313
1597
|
workspacePath,
|
|
1314
1598
|
mounts: hydrationMounts,
|
|
1315
1599
|
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
@@ -1331,8 +1615,8 @@ function resetWorkspaceGit(input) {
|
|
|
1331
1615
|
skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1332
1616
|
};
|
|
1333
1617
|
}
|
|
1334
|
-
function stagedPaths(workspacePath) {
|
|
1335
|
-
return
|
|
1618
|
+
async function stagedPaths(workspacePath) {
|
|
1619
|
+
return (await gitAsync(workspacePath, ["diff", "--cached", "--name-only", "--no-renames", "-z"], "inspect staged workspace paths")).split("\0").filter(Boolean).sort();
|
|
1336
1620
|
}
|
|
1337
1621
|
function emptyTreeHash(workspacePath) {
|
|
1338
1622
|
const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "hash-object", "-t", "tree", "--stdin"], {
|
|
@@ -1346,9 +1630,9 @@ function emptyTreeHash(workspacePath) {
|
|
|
1346
1630
|
throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
|
|
1347
1631
|
return result.stdout.toString().trim();
|
|
1348
1632
|
}
|
|
1349
|
-
function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1633
|
+
async function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1350
1634
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
1351
|
-
return
|
|
1635
|
+
return (await gitAsync(workspacePath, ["diff", "--name-only", "--no-renames", "-z", base, headRevision], "inspect workspace commit paths")).split("\0").filter(Boolean).sort();
|
|
1352
1636
|
}
|
|
1353
1637
|
async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
|
|
1354
1638
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
@@ -1391,15 +1675,15 @@ function snapshotConflict(input) {
|
|
|
1391
1675
|
git(input.workspacePath, ["update-ref", refs.remote, input.remoteHead], "snapshot remote workspace conflict head");
|
|
1392
1676
|
return refs;
|
|
1393
1677
|
}
|
|
1394
|
-
function rebaseWorkspace(input) {
|
|
1678
|
+
async function rebaseWorkspace(input) {
|
|
1395
1679
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
1396
1680
|
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
1397
|
-
const rebase =
|
|
1681
|
+
const rebase = await gitResultAsync(
|
|
1398
1682
|
input.workspacePath,
|
|
1399
1683
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
1400
1684
|
);
|
|
1401
1685
|
if (rebase.exitCode === 0) {
|
|
1402
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1686
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1403
1687
|
return { ok: true };
|
|
1404
1688
|
}
|
|
1405
1689
|
const conflicts = conflictPaths(input.workspacePath);
|
|
@@ -1408,7 +1692,7 @@ function rebaseWorkspace(input) {
|
|
|
1408
1692
|
if (abort.exitCode !== 0) {
|
|
1409
1693
|
throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
1410
1694
|
}
|
|
1411
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1695
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1412
1696
|
return {
|
|
1413
1697
|
ok: false,
|
|
1414
1698
|
conflictPaths: conflicts,
|
|
@@ -1416,13 +1700,13 @@ function rebaseWorkspace(input) {
|
|
|
1416
1700
|
error: rebase.stderr || rebase.stdout || `Workspace rebase onto ${input.remoteHead} conflicted`
|
|
1417
1701
|
};
|
|
1418
1702
|
}
|
|
1419
|
-
function synchronizeWithFetchedHead(input) {
|
|
1420
|
-
const localHead =
|
|
1703
|
+
async function synchronizeWithFetchedHead(input) {
|
|
1704
|
+
const localHead = await revParseAsync(input.workspacePath, "HEAD");
|
|
1421
1705
|
const remoteHead = input.remoteHead;
|
|
1422
1706
|
if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
|
|
1423
1707
|
if (!localHead) {
|
|
1424
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1425
|
-
|
|
1708
|
+
await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
|
|
1709
|
+
await gitAsync(
|
|
1426
1710
|
input.workspacePath,
|
|
1427
1711
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1428
1712
|
"check out fetched workspace main"
|
|
@@ -1431,35 +1715,35 @@ function synchronizeWithFetchedHead(input) {
|
|
|
1431
1715
|
return { kind: "ready", updated: true, rebased: false };
|
|
1432
1716
|
}
|
|
1433
1717
|
if (localHead === remoteHead) {
|
|
1434
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1718
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1435
1719
|
return { kind: "ready", updated: false, rebased: false };
|
|
1436
1720
|
}
|
|
1437
|
-
if (
|
|
1438
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1439
|
-
|
|
1721
|
+
if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
|
|
1722
|
+
await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
|
|
1723
|
+
await gitAsync(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
|
|
1440
1724
|
});
|
|
1441
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1725
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1442
1726
|
return { kind: "ready", updated: true, rebased: false };
|
|
1443
1727
|
}
|
|
1444
|
-
if (
|
|
1445
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1728
|
+
if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
|
|
1729
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1446
1730
|
return { kind: "ready", updated: false, rebased: false };
|
|
1447
1731
|
}
|
|
1448
|
-
const integratedHead =
|
|
1732
|
+
const integratedHead = await revParseAsync(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
|
|
1449
1733
|
const truncatedHistoryBase = workspaceIsShallow(input.workspacePath) && integratedHead && tryGit(input.workspacePath, ["merge-base", "--is-ancestor", integratedHead, localHead]) ? integratedHead : void 0;
|
|
1450
1734
|
if (workspaceIsShallow(input.workspacePath) && !truncatedHistoryBase) {
|
|
1451
1735
|
throw new Error(
|
|
1452
1736
|
"Workspace history was truncated before this clone's integrated base could be identified; reset the workspace clone before retrying"
|
|
1453
1737
|
);
|
|
1454
1738
|
}
|
|
1455
|
-
const rebased = rebaseWorkspace({
|
|
1739
|
+
const rebased = await rebaseWorkspace({
|
|
1456
1740
|
workspacePath: input.workspacePath,
|
|
1457
1741
|
localHead,
|
|
1458
1742
|
remoteHead,
|
|
1459
1743
|
attemptId: input.attemptId,
|
|
1460
1744
|
truncatedHistoryBase
|
|
1461
1745
|
});
|
|
1462
|
-
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1746
|
+
if (rebased.ok) await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1463
1747
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
1464
1748
|
}
|
|
1465
1749
|
async function synchronizeWorkspaceGit(input) {
|
|
@@ -1470,7 +1754,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1470
1754
|
validateMounts(workspacePath, input.mounts);
|
|
1471
1755
|
const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
|
|
1472
1756
|
const preserveResolutionInProgress = input.skipMountMirror === true;
|
|
1473
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1757
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1474
1758
|
assertWorkspaceRemediationAncestry({
|
|
1475
1759
|
workspacePath,
|
|
1476
1760
|
currentHead: initial.localHead,
|
|
@@ -1479,7 +1763,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1479
1763
|
});
|
|
1480
1764
|
const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
|
|
1481
1765
|
const startingHead = initial.localHead;
|
|
1482
|
-
const receiptBeforeInitialHydration =
|
|
1766
|
+
const receiptBeforeInitialHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1483
1767
|
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1484
1768
|
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
|
|
1485
1769
|
for (const mount of input.mounts) {
|
|
@@ -1488,7 +1772,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1488
1772
|
}
|
|
1489
1773
|
}
|
|
1490
1774
|
}
|
|
1491
|
-
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1775
|
+
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1492
1776
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1493
1777
|
preserveResolutionInProgress,
|
|
1494
1778
|
preserveStaleBases: true
|
|
@@ -1499,8 +1783,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1499
1783
|
);
|
|
1500
1784
|
const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
|
|
1501
1785
|
const receiptRequiredLiveMounts = selected.active;
|
|
1502
|
-
const projectionBasisHead =
|
|
1503
|
-
const projectionBasisReceipt =
|
|
1786
|
+
const projectionBasisHead = await revParseAsync(workspacePath, "HEAD");
|
|
1787
|
+
const projectionBasisReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1504
1788
|
const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1505
1789
|
const fastProjectionMounts = [];
|
|
1506
1790
|
const mergedProjectionTrees = /* @__PURE__ */ new Map();
|
|
@@ -1541,6 +1825,10 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1541
1825
|
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
1542
1826
|
continue;
|
|
1543
1827
|
}
|
|
1828
|
+
if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
|
|
1829
|
+
cycleSkippedMountIds.add(mount.id);
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1544
1832
|
const merged = mergeWorkspaceProjectionMount({
|
|
1545
1833
|
workspacePath,
|
|
1546
1834
|
mount: {
|
|
@@ -1575,14 +1863,15 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1575
1863
|
}
|
|
1576
1864
|
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
1577
1865
|
mergedProjectionMounts.push(mount);
|
|
1866
|
+
await yieldToWorkerEventLoop();
|
|
1578
1867
|
}
|
|
1579
1868
|
}
|
|
1580
1869
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
1581
1870
|
`);
|
|
1582
|
-
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1583
|
-
const currentHeadBeforeHydration =
|
|
1871
|
+
const completedMountSelection = async (requireCurrentHeadReceipt = false) => {
|
|
1872
|
+
const currentHeadBeforeHydration = await revParseAsync(workspacePath, "HEAD");
|
|
1584
1873
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1585
|
-
const currentReceiptBeforeHydration =
|
|
1874
|
+
const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1586
1875
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1587
1876
|
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1588
1877
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
@@ -1599,7 +1888,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1599
1888
|
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
|
|
1600
1889
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1601
1890
|
);
|
|
1602
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1891
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1603
1892
|
workspacePath,
|
|
1604
1893
|
mounts: uncoveredCompletionMounts,
|
|
1605
1894
|
durabilityMounts: uncoveredCompletionBasisMounts,
|
|
@@ -1610,8 +1899,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1610
1899
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
1611
1900
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1612
1901
|
if (requireCurrentHeadReceipt) {
|
|
1613
|
-
const currentHead =
|
|
1614
|
-
const receipt =
|
|
1902
|
+
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1903
|
+
const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1615
1904
|
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
1616
1905
|
const idleMounts = completionReceiptMounts.filter(
|
|
1617
1906
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
|
|
@@ -1623,17 +1912,17 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1623
1912
|
return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
|
|
1624
1913
|
};
|
|
1625
1914
|
let newlyStagedPaths = [];
|
|
1626
|
-
const stageAndCommitWorkspace = () => {
|
|
1627
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1915
|
+
const stageAndCommitWorkspace = async () => {
|
|
1916
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1628
1917
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1629
1918
|
}
|
|
1630
|
-
|
|
1631
|
-
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1919
|
+
await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1920
|
+
newlyStagedPaths = await stagedPaths(workspacePath);
|
|
1632
1921
|
if (newlyStagedPaths.length > 0) {
|
|
1633
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1922
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1634
1923
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1635
1924
|
}
|
|
1636
|
-
|
|
1925
|
+
await gitAsync(
|
|
1637
1926
|
workspacePath,
|
|
1638
1927
|
[
|
|
1639
1928
|
"commit",
|
|
@@ -1644,64 +1933,77 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1644
1933
|
);
|
|
1645
1934
|
}
|
|
1646
1935
|
};
|
|
1936
|
+
const projectedFastMounts = [];
|
|
1647
1937
|
if (!input.skipMountMirror) {
|
|
1648
1938
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1649
1939
|
try {
|
|
1650
1940
|
const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
|
|
1651
1941
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
1652
1942
|
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
1653
|
-
|
|
1943
|
+
for (const mount of fastActiveMounts) {
|
|
1944
|
+
if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
|
|
1945
|
+
cycleSkippedMountIds.add(mount.id);
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
mirrorMountsToWorkspace(workspacePath, [mount]);
|
|
1949
|
+
projectedFastMounts.push(mount);
|
|
1950
|
+
await yieldToWorkerEventLoop();
|
|
1951
|
+
}
|
|
1654
1952
|
removeWorkspaceMounts(workspacePath, fastTombstones);
|
|
1953
|
+
projectedFastMounts.push(...fastTombstones);
|
|
1655
1954
|
for (const mount of mergedProjectionMounts) {
|
|
1656
1955
|
materializeWorkspaceProjectionTree({
|
|
1657
1956
|
workspacePath,
|
|
1658
1957
|
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
1659
1958
|
resultTree: mergedProjectionTrees.get(mount.id)
|
|
1660
1959
|
});
|
|
1960
|
+
await yieldToWorkerEventLoop();
|
|
1661
1961
|
}
|
|
1662
|
-
const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
|
|
1663
1962
|
restoreDeferredWorkspaceMountsFromHead(
|
|
1664
1963
|
workspacePath,
|
|
1665
|
-
input.mounts.filter(({ id }) =>
|
|
1964
|
+
input.mounts.filter(({ id }) => deferredMountIds.has(id))
|
|
1666
1965
|
);
|
|
1667
|
-
stageAndCommitWorkspace();
|
|
1668
|
-
|
|
1966
|
+
await stageAndCommitWorkspace();
|
|
1967
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
1669
1968
|
} catch (error) {
|
|
1670
1969
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
1671
1970
|
}
|
|
1672
1971
|
} else {
|
|
1673
|
-
stageAndCommitWorkspace();
|
|
1674
|
-
if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
|
|
1972
|
+
await stageAndCommitWorkspace();
|
|
1973
|
+
if (newlyStagedPaths.length > 0) await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1675
1974
|
}
|
|
1676
1975
|
if (!input.skipMountMirror) {
|
|
1677
|
-
const projectedHead =
|
|
1976
|
+
const projectedHead = await revParseAsync(workspacePath, "HEAD");
|
|
1678
1977
|
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1679
1978
|
const targetProjectionReceipt = receiptWithMountsAtHead(
|
|
1680
1979
|
projectionBasisReceipt,
|
|
1681
1980
|
projectedHead,
|
|
1682
|
-
|
|
1981
|
+
projectedFastMounts,
|
|
1683
1982
|
allProjectionReceiptMounts
|
|
1684
1983
|
);
|
|
1685
|
-
const currentProjectionReceipt =
|
|
1686
|
-
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !
|
|
1687
|
-
|
|
1688
|
-
|
|
1984
|
+
const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1985
|
+
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
|
|
1986
|
+
for (const mount of projectedFastMounts) {
|
|
1987
|
+
fsyncHydratedWorkspaceMount(mount);
|
|
1988
|
+
await yieldToWorkerEventLoop();
|
|
1989
|
+
}
|
|
1990
|
+
await updateHydratedWorkspaceReceiptAsync(workspacePath, targetProjectionReceipt);
|
|
1689
1991
|
}
|
|
1690
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
1992
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1691
1993
|
}
|
|
1692
|
-
let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1994
|
+
let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1693
1995
|
let rebaseCount = 0;
|
|
1694
1996
|
let updated = false;
|
|
1695
1997
|
for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
|
|
1696
1998
|
assertWorkspaceRemediationAncestry({
|
|
1697
1999
|
workspacePath,
|
|
1698
|
-
currentHead:
|
|
2000
|
+
currentHead: await revParseAsync(workspacePath, "HEAD"),
|
|
1699
2001
|
remoteHead,
|
|
1700
2002
|
requiredAncestorHeads
|
|
1701
2003
|
});
|
|
1702
|
-
const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
2004
|
+
const reconciled = await synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
1703
2005
|
if (reconciled.kind === "conflict") {
|
|
1704
|
-
const localHead2 =
|
|
2006
|
+
const localHead2 = await revParseAsync(workspacePath, "HEAD");
|
|
1705
2007
|
const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
|
|
1706
2008
|
return {
|
|
1707
2009
|
outcome: "conflict_blocked",
|
|
@@ -1722,8 +2024,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1722
2024
|
}
|
|
1723
2025
|
updated ||= reconciled.updated;
|
|
1724
2026
|
if (reconciled.rebased) rebaseCount += 1;
|
|
1725
|
-
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
1726
|
-
const localHead =
|
|
2027
|
+
if (reconciled.updated || reconciled.rebased) await completedMountSelection(true);
|
|
2028
|
+
const localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1727
2029
|
if (!localHead) {
|
|
1728
2030
|
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
|
|
1729
2031
|
return {
|
|
@@ -1740,7 +2042,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1740
2042
|
...projectionWarning ? { error: projectionWarning } : {}
|
|
1741
2043
|
};
|
|
1742
2044
|
}
|
|
1743
|
-
const paths = changedPaths(workspacePath, remoteHead, localHead);
|
|
2045
|
+
const paths = await changedPaths(workspacePath, remoteHead, localHead);
|
|
1744
2046
|
const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
|
|
1745
2047
|
input.assertStillAdmitted?.();
|
|
1746
2048
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
@@ -1761,7 +2063,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1761
2063
|
}
|
|
1762
2064
|
if (localHead === remoteHead) {
|
|
1763
2065
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1764
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2066
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1765
2067
|
await input.afterWorkspacePublished?.({
|
|
1766
2068
|
publishedHead: localHead,
|
|
1767
2069
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1789,11 +2091,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1789
2091
|
];
|
|
1790
2092
|
if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
|
|
1791
2093
|
pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
|
|
1792
|
-
const push =
|
|
2094
|
+
const push = await gitResultAsync(workspacePath, pushArgs);
|
|
1793
2095
|
if (push.exitCode === 0) {
|
|
1794
2096
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1795
|
-
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1796
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2097
|
+
await updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
2098
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1797
2099
|
await input.afterWorkspacePublished?.({
|
|
1798
2100
|
publishedHead: localHead,
|
|
1799
2101
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1817,7 +2119,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1817
2119
|
${push.stdout}`)) {
|
|
1818
2120
|
throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
|
|
1819
2121
|
}
|
|
1820
|
-
remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2122
|
+
remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1821
2123
|
}
|
|
1822
2124
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
1823
2125
|
}
|