@ricsam/r5d-worker 0.0.86 → 0.0.87
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 +98 -64
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +183 -104
- package/dist/cjs/workspace-git-sync.cjs +491 -189
- package/dist/mjs/main.mjs +98 -64
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +183 -104
- 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
|
@@ -107,6 +107,71 @@ function revParse(workspacePath, revision) {
|
|
|
107
107
|
const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
|
|
108
108
|
return result.exitCode === 0 ? result.stdout : null;
|
|
109
109
|
}
|
|
110
|
+
async function gitResultAsync(cwd, args) {
|
|
111
|
+
const subprocess = Bun.spawn(gitCommandArgs(args), {
|
|
112
|
+
cwd,
|
|
113
|
+
stdin: "ignore",
|
|
114
|
+
stdout: "pipe",
|
|
115
|
+
stderr: "pipe",
|
|
116
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
117
|
+
});
|
|
118
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
119
|
+
new Response(subprocess.stdout).text(),
|
|
120
|
+
new Response(subprocess.stderr).text(),
|
|
121
|
+
subprocess.exited
|
|
122
|
+
]);
|
|
123
|
+
return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
124
|
+
}
|
|
125
|
+
async function gitAsync(cwd, args, action) {
|
|
126
|
+
const result = await gitResultAsync(cwd, args);
|
|
127
|
+
if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
128
|
+
return result.stdout;
|
|
129
|
+
}
|
|
130
|
+
async function tryGitAsync(cwd, args) {
|
|
131
|
+
return (await gitResultAsync(cwd, args)).exitCode === 0;
|
|
132
|
+
}
|
|
133
|
+
async function revParseAsync(workspacePath, revision) {
|
|
134
|
+
const result = await gitResultAsync(workspacePath, ["rev-parse", "--verify", revision]);
|
|
135
|
+
return result.exitCode === 0 ? result.stdout : null;
|
|
136
|
+
}
|
|
137
|
+
function yieldToWorkerEventLoop() {
|
|
138
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
139
|
+
}
|
|
140
|
+
function workspaceCommitsExist(workspacePath, heads) {
|
|
141
|
+
const uniqueHeads = [...new Set(heads)];
|
|
142
|
+
if (uniqueHeads.length === 0) return true;
|
|
143
|
+
const batch = Bun.spawnSync(gitCommandArgs(["cat-file", "--batch-check"]), {
|
|
144
|
+
cwd: workspacePath,
|
|
145
|
+
stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
|
|
146
|
+
`).join("")),
|
|
147
|
+
stdout: "pipe",
|
|
148
|
+
stderr: "pipe",
|
|
149
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
150
|
+
});
|
|
151
|
+
if (batch.exitCode !== 0) return false;
|
|
152
|
+
const lines = batch.stdout.toString().split("\n").filter(Boolean);
|
|
153
|
+
return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
|
|
154
|
+
}
|
|
155
|
+
async function workspaceCommitsExistAsync(workspacePath, heads) {
|
|
156
|
+
const uniqueHeads = [...new Set(heads)];
|
|
157
|
+
if (uniqueHeads.length === 0) return true;
|
|
158
|
+
const subprocess = Bun.spawn(gitCommandArgs(["cat-file", "--batch-check"]), {
|
|
159
|
+
cwd: workspacePath,
|
|
160
|
+
stdin: Buffer.from(uniqueHeads.map((head) => `${head}^{commit}
|
|
161
|
+
`).join("")),
|
|
162
|
+
stdout: "pipe",
|
|
163
|
+
stderr: "pipe",
|
|
164
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
165
|
+
});
|
|
166
|
+
const [stdout, , exitCode] = await Promise.all([
|
|
167
|
+
new Response(subprocess.stdout).text(),
|
|
168
|
+
new Response(subprocess.stderr).text(),
|
|
169
|
+
subprocess.exited
|
|
170
|
+
]);
|
|
171
|
+
if (exitCode !== 0) return false;
|
|
172
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
173
|
+
return lines.length === uniqueHeads.length && lines.every((line) => !line.endsWith(" missing"));
|
|
174
|
+
}
|
|
110
175
|
function requiredWorkspaceAncestorHeads(value) {
|
|
111
176
|
if (value === void 0) return [];
|
|
112
177
|
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) {
|
|
@@ -130,8 +195,8 @@ function assertWorkspaceRemediationAncestry(input) {
|
|
|
130
195
|
);
|
|
131
196
|
}
|
|
132
197
|
}
|
|
133
|
-
function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
134
|
-
|
|
198
|
+
async function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
199
|
+
await gitAsync(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
|
|
135
200
|
}
|
|
136
201
|
function fsyncDirectory(directoryPath) {
|
|
137
202
|
const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
|
|
@@ -235,16 +300,13 @@ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMou
|
|
|
235
300
|
return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
|
|
236
301
|
});
|
|
237
302
|
}
|
|
238
|
-
function
|
|
303
|
+
function parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content) {
|
|
239
304
|
const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
|
|
240
305
|
if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
|
|
241
306
|
`) {
|
|
242
|
-
if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
|
|
243
|
-
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
244
|
-
}
|
|
245
307
|
const receipt2 = { version: 3, mounts: [] };
|
|
246
308
|
Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
|
|
247
|
-
return receipt2;
|
|
309
|
+
return { receipt: receipt2, headsToVerify: [legacyHead] };
|
|
248
310
|
}
|
|
249
311
|
let parsed;
|
|
250
312
|
try {
|
|
@@ -312,14 +374,16 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
312
374
|
}
|
|
313
375
|
const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
|
|
314
376
|
if (liftedV2Head) receiptHeads.add(liftedV2Head);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
377
|
+
return { receipt, headsToVerify: [...receiptHeads] };
|
|
378
|
+
}
|
|
379
|
+
function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
|
|
380
|
+
const { receipt, headsToVerify } = parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content);
|
|
381
|
+
if (!workspaceCommitsExist(workspacePath, headsToVerify)) {
|
|
382
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
319
383
|
}
|
|
320
384
|
return receipt;
|
|
321
385
|
}
|
|
322
|
-
function
|
|
386
|
+
function readHydratedWorkspaceReceiptUnverified(workspacePath) {
|
|
323
387
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
324
388
|
const status = lstatIfExists(receiptPath);
|
|
325
389
|
if (!status) return null;
|
|
@@ -327,7 +391,23 @@ function readHydratedWorkspaceReceipt(workspacePath) {
|
|
|
327
391
|
throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
|
|
328
392
|
}
|
|
329
393
|
const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
|
|
330
|
-
return
|
|
394
|
+
return { ...parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content), receiptPath };
|
|
395
|
+
}
|
|
396
|
+
function readHydratedWorkspaceReceipt(workspacePath) {
|
|
397
|
+
const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
|
|
398
|
+
if (!unverified) return null;
|
|
399
|
+
if (!workspaceCommitsExist(workspacePath, unverified.headsToVerify)) {
|
|
400
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
|
|
401
|
+
}
|
|
402
|
+
return unverified.receipt;
|
|
403
|
+
}
|
|
404
|
+
async function readHydratedWorkspaceReceiptAsync(workspacePath) {
|
|
405
|
+
const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
|
|
406
|
+
if (!unverified) return null;
|
|
407
|
+
if (!await workspaceCommitsExistAsync(workspacePath, unverified.headsToVerify)) {
|
|
408
|
+
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
|
|
409
|
+
}
|
|
410
|
+
return unverified.receipt;
|
|
331
411
|
}
|
|
332
412
|
function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
333
413
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
@@ -374,49 +454,142 @@ function desiredWorkspaceBasisRefs(receipt) {
|
|
|
374
454
|
}
|
|
375
455
|
function workspaceBasisRefsMatch(workspacePath, receipt) {
|
|
376
456
|
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
457
|
+
const actual = git(
|
|
458
|
+
workspacePath,
|
|
459
|
+
["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX],
|
|
460
|
+
`list ${WORKSPACE_GIT_BASIS_REF_PREFIX} refs`
|
|
461
|
+
).split("\n").filter(Boolean);
|
|
462
|
+
if (actual.length !== desired.size) return false;
|
|
463
|
+
return actual.every((line) => {
|
|
464
|
+
const separator = line.lastIndexOf(" ");
|
|
465
|
+
if (separator <= 0) return false;
|
|
466
|
+
return desired.get(line.slice(0, separator)) === line.slice(separator + 1);
|
|
467
|
+
});
|
|
380
468
|
}
|
|
381
469
|
function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
|
|
382
470
|
return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
|
|
383
471
|
}
|
|
472
|
+
async function workspaceBasisRefsAreCurrentAsync(workspacePath, receipt) {
|
|
473
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
474
|
+
const listing = await gitAsync(
|
|
475
|
+
workspacePath,
|
|
476
|
+
["for-each-ref", "--format=%(refname) %(objectname)", WORKSPACE_GIT_BASIS_REF_PREFIX, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
|
|
477
|
+
"list workspace hydration basis refs"
|
|
478
|
+
);
|
|
479
|
+
const stable = /* @__PURE__ */ new Map();
|
|
480
|
+
for (const line of listing.split("\n").filter(Boolean)) {
|
|
481
|
+
const separator = line.lastIndexOf(" ");
|
|
482
|
+
if (separator <= 0) return false;
|
|
483
|
+
const ref = line.slice(0, separator);
|
|
484
|
+
if (ref.startsWith(WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX)) return false;
|
|
485
|
+
stable.set(ref, line.slice(separator + 1));
|
|
486
|
+
}
|
|
487
|
+
if (stable.size !== desired.size) return false;
|
|
488
|
+
return [...stable].every(([ref, head]) => desired.get(ref) === head);
|
|
489
|
+
}
|
|
384
490
|
function repairWorkspaceBasisRefs(workspacePath, receipt) {
|
|
385
491
|
const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
|
|
386
492
|
if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
|
|
387
493
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
const transaction = ["start"];
|
|
391
|
-
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
392
|
-
for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
|
|
393
|
-
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
394
|
-
}
|
|
395
|
-
transaction.push("prepare", "commit", "");
|
|
494
|
+
function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
|
|
495
|
+
if (instructions.length === 0) return;
|
|
396
496
|
const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
397
497
|
cwd: workspacePath,
|
|
398
|
-
stdin: Buffer.from(
|
|
498
|
+
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
399
499
|
stdout: "pipe",
|
|
400
500
|
stderr: "pipe",
|
|
401
501
|
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
402
502
|
});
|
|
403
503
|
if (updated.exitCode !== 0) {
|
|
404
|
-
throw new Error(
|
|
504
|
+
throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
|
|
405
505
|
}
|
|
406
|
-
|
|
407
|
-
|
|
506
|
+
}
|
|
507
|
+
async function runWorkspaceRefUpdateBatchAsync(workspacePath, instructions, action) {
|
|
508
|
+
if (instructions.length === 0) return;
|
|
509
|
+
const subprocess = Bun.spawn(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
510
|
+
cwd: workspacePath,
|
|
511
|
+
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
512
|
+
stdout: "pipe",
|
|
513
|
+
stderr: "pipe",
|
|
514
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
515
|
+
});
|
|
516
|
+
const [, stderr, exitCode] = await Promise.all([
|
|
517
|
+
new Response(subprocess.stdout).text(),
|
|
518
|
+
new Response(subprocess.stderr).text(),
|
|
519
|
+
subprocess.exited
|
|
520
|
+
]);
|
|
521
|
+
if (exitCode !== 0) {
|
|
522
|
+
throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
|
|
408
523
|
}
|
|
409
524
|
}
|
|
410
|
-
function
|
|
525
|
+
function workspaceBasisRefTransaction(receipt, stableRefs) {
|
|
526
|
+
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
527
|
+
const transaction = ["start"];
|
|
528
|
+
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
529
|
+
for (const ref of stableRefs) {
|
|
530
|
+
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
531
|
+
}
|
|
532
|
+
transaction.push("prepare", "commit");
|
|
533
|
+
return transaction;
|
|
534
|
+
}
|
|
535
|
+
function updateWorkspaceBasisRefs(workspacePath, receipt) {
|
|
536
|
+
runWorkspaceRefUpdateBatch(
|
|
537
|
+
workspacePath,
|
|
538
|
+
workspaceBasisRefTransaction(receipt, listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)),
|
|
539
|
+
"Synchronize workspace hydration basis refs"
|
|
540
|
+
);
|
|
541
|
+
runWorkspaceRefUpdateBatch(
|
|
542
|
+
workspacePath,
|
|
543
|
+
listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).map((ref) => `delete ${ref}`),
|
|
544
|
+
"remove staged workspace hydration basis refs"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
async function updateWorkspaceBasisRefsAsync(workspacePath, receipt) {
|
|
548
|
+
const stableRefs = (await gitAsync(workspacePath, ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_REF_PREFIX], "list workspace basis refs")).split("\n").filter(Boolean).sort();
|
|
549
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
550
|
+
workspacePath,
|
|
551
|
+
workspaceBasisRefTransaction(receipt, stableRefs),
|
|
552
|
+
"Synchronize workspace hydration basis refs"
|
|
553
|
+
);
|
|
554
|
+
const stagedRefs = (await gitAsync(
|
|
555
|
+
workspacePath,
|
|
556
|
+
["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
|
|
557
|
+
"list staged workspace basis refs"
|
|
558
|
+
)).split("\n").filter(Boolean).sort();
|
|
559
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
560
|
+
workspacePath,
|
|
561
|
+
stagedRefs.map((ref) => `delete ${ref}`),
|
|
562
|
+
"remove staged workspace hydration basis refs"
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
function assertCanonicalHydrationReceiptHeads(receipt) {
|
|
411
566
|
for (const mount of receipt.mounts) {
|
|
412
567
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
|
|
413
568
|
throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
|
|
414
569
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function throwMissingHydrationReceiptCommit(workspacePath, receipt) {
|
|
573
|
+
const missing = receipt.mounts.find(({ head }) => !tryGit(workspacePath, ["cat-file", "-e", `${head}^{commit}`]));
|
|
574
|
+
throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${missing?.head ?? "unknown"}`);
|
|
575
|
+
}
|
|
576
|
+
function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
577
|
+
assertCanonicalHydrationReceiptHeads(receipt);
|
|
578
|
+
if (!workspaceCommitsExist(workspacePath, receipt.mounts.map(({ head }) => head))) {
|
|
579
|
+
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
418
580
|
}
|
|
419
581
|
readHydratedWorkspaceReceipt(workspacePath);
|
|
582
|
+
writeHydratedWorkspaceReceiptCore(workspacePath, receipt);
|
|
583
|
+
}
|
|
584
|
+
async function updateHydratedWorkspaceReceiptAsync(workspacePath, receipt) {
|
|
585
|
+
assertCanonicalHydrationReceiptHeads(receipt);
|
|
586
|
+
if (!await workspaceCommitsExistAsync(workspacePath, receipt.mounts.map(({ head }) => head))) {
|
|
587
|
+
throwMissingHydrationReceiptCommit(workspacePath, receipt);
|
|
588
|
+
}
|
|
589
|
+
await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
590
|
+
await writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt);
|
|
591
|
+
}
|
|
592
|
+
function prepareHydratedWorkspaceReceiptWrite(workspacePath) {
|
|
420
593
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
421
594
|
const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
|
|
422
595
|
if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
|
|
@@ -433,13 +606,16 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
433
606
|
if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
|
|
434
607
|
throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
|
|
435
608
|
}
|
|
436
|
-
|
|
437
|
-
|
|
609
|
+
return { receiptPath, receiptDirectory };
|
|
610
|
+
}
|
|
611
|
+
function stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix) {
|
|
438
612
|
desiredWorkspaceBasisRefs(receipt);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
613
|
+
return receipt.mounts.map(
|
|
614
|
+
(mount) => `update ${stagingPrefix}${workspaceBasisRefName(mount.id).slice(WORKSPACE_GIT_BASIS_REF_PREFIX.length)} ${mount.head}`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
function publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths) {
|
|
618
|
+
const temporaryPath = import_node_path.default.join(paths.receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
443
619
|
let descriptor;
|
|
444
620
|
try {
|
|
445
621
|
descriptor = import_node_fs.default.openSync(temporaryPath, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL, 384);
|
|
@@ -448,15 +624,36 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
448
624
|
import_node_fs.default.fsyncSync(descriptor);
|
|
449
625
|
import_node_fs.default.closeSync(descriptor);
|
|
450
626
|
descriptor = void 0;
|
|
451
|
-
import_node_fs.default.renameSync(temporaryPath, receiptPath);
|
|
452
|
-
fsyncDirectory(receiptDirectory);
|
|
453
|
-
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
627
|
+
import_node_fs.default.renameSync(temporaryPath, paths.receiptPath);
|
|
628
|
+
fsyncDirectory(paths.receiptDirectory);
|
|
454
629
|
} catch (error) {
|
|
455
630
|
if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
|
|
456
631
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
457
632
|
throw error;
|
|
458
633
|
}
|
|
459
634
|
}
|
|
635
|
+
function writeHydratedWorkspaceReceiptCore(workspacePath, receipt) {
|
|
636
|
+
const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
|
|
637
|
+
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
638
|
+
runWorkspaceRefUpdateBatch(
|
|
639
|
+
workspacePath,
|
|
640
|
+
stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
|
|
641
|
+
"stage workspace hydration basis refs"
|
|
642
|
+
);
|
|
643
|
+
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
644
|
+
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
645
|
+
}
|
|
646
|
+
async function writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt) {
|
|
647
|
+
const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
|
|
648
|
+
const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
|
|
649
|
+
await runWorkspaceRefUpdateBatchAsync(
|
|
650
|
+
workspacePath,
|
|
651
|
+
stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
|
|
652
|
+
"stage workspace hydration basis refs"
|
|
653
|
+
);
|
|
654
|
+
publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
|
|
655
|
+
await updateWorkspaceBasisRefsAsync(workspacePath, receipt);
|
|
656
|
+
}
|
|
460
657
|
function hydrationTransactionPath(workspacePath) {
|
|
461
658
|
return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
|
|
462
659
|
}
|
|
@@ -508,7 +705,7 @@ function fsyncTree(rootPath) {
|
|
|
508
705
|
};
|
|
509
706
|
syncEntry(rootPath);
|
|
510
707
|
}
|
|
511
|
-
function
|
|
708
|
+
function parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, content) {
|
|
512
709
|
let parsed;
|
|
513
710
|
try {
|
|
514
711
|
parsed = JSON.parse(content);
|
|
@@ -531,25 +728,34 @@ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, conte
|
|
|
531
728
|
`) {
|
|
532
729
|
throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
|
|
533
730
|
}
|
|
534
|
-
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
535
|
-
throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
|
|
536
|
-
}
|
|
537
731
|
return record;
|
|
538
732
|
}
|
|
539
|
-
function
|
|
733
|
+
function readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath) {
|
|
540
734
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
541
735
|
const status = lstatIfExists(recordPath);
|
|
542
736
|
if (!status) return null;
|
|
543
737
|
if (!status.isFile() || status.isSymbolicLink()) {
|
|
544
738
|
throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
|
|
545
739
|
}
|
|
546
|
-
return
|
|
740
|
+
return { record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, import_node_fs.default.readFileSync(recordPath, "utf8")), recordPath };
|
|
547
741
|
}
|
|
548
|
-
function
|
|
549
|
-
|
|
550
|
-
|
|
742
|
+
function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
|
|
743
|
+
const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
|
|
744
|
+
if (!unverified) return null;
|
|
745
|
+
if (unverified.record.head && !tryGit(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
|
|
746
|
+
throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
|
|
551
747
|
}
|
|
552
|
-
|
|
748
|
+
return unverified.record;
|
|
749
|
+
}
|
|
750
|
+
async function readWorkspaceCheckoutDurabilityRecordAsync(workspacePath) {
|
|
751
|
+
const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
|
|
752
|
+
if (!unverified) return null;
|
|
753
|
+
if (unverified.record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
|
|
754
|
+
throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
|
|
755
|
+
}
|
|
756
|
+
return unverified.record;
|
|
757
|
+
}
|
|
758
|
+
function writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record) {
|
|
553
759
|
const stateDirectory = workspacePrivateStateDirectory(workspacePath);
|
|
554
760
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
555
761
|
const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
@@ -563,6 +769,20 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
|
563
769
|
throw error;
|
|
564
770
|
}
|
|
565
771
|
}
|
|
772
|
+
function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
773
|
+
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
774
|
+
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
775
|
+
}
|
|
776
|
+
readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
777
|
+
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
778
|
+
}
|
|
779
|
+
async function updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, record) {
|
|
780
|
+
if (record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
781
|
+
throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
|
|
782
|
+
}
|
|
783
|
+
await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
784
|
+
writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
|
|
785
|
+
}
|
|
566
786
|
function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
567
787
|
const status = import_node_fs.default.lstatSync(workspacePath);
|
|
568
788
|
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
@@ -574,6 +794,40 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
|
574
794
|
}
|
|
575
795
|
fsyncDirectory(workspacePath);
|
|
576
796
|
}
|
|
797
|
+
const WORKSPACE_FSYNC_YIELD_INTERVAL = 64;
|
|
798
|
+
async function fsyncWorkspaceCheckoutTreeYielding(workspacePath) {
|
|
799
|
+
const status = import_node_fs.default.lstatSync(workspacePath);
|
|
800
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
801
|
+
throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
|
|
802
|
+
}
|
|
803
|
+
let entriesSinceYield = 0;
|
|
804
|
+
const syncEntry = async (entryPath) => {
|
|
805
|
+
const entryStatus = import_node_fs.default.lstatSync(entryPath);
|
|
806
|
+
if (entryStatus.isSymbolicLink()) return;
|
|
807
|
+
if (entryStatus.isDirectory()) {
|
|
808
|
+
for (const name of import_node_fs.default.readdirSync(entryPath)) await syncEntry(import_node_path.default.join(entryPath, name));
|
|
809
|
+
fsyncDirectory(entryPath);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (!entryStatus.isFile()) return;
|
|
813
|
+
const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
|
|
814
|
+
try {
|
|
815
|
+
import_node_fs.default.fsyncSync(descriptor);
|
|
816
|
+
} finally {
|
|
817
|
+
import_node_fs.default.closeSync(descriptor);
|
|
818
|
+
}
|
|
819
|
+
entriesSinceYield += 1;
|
|
820
|
+
if (entriesSinceYield >= WORKSPACE_FSYNC_YIELD_INTERVAL) {
|
|
821
|
+
entriesSinceYield = 0;
|
|
822
|
+
await yieldToWorkerEventLoop();
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
for (const name of import_node_fs.default.readdirSync(workspacePath)) {
|
|
826
|
+
if (name === ".git") continue;
|
|
827
|
+
await syncEntry(import_node_path.default.join(workspacePath, name));
|
|
828
|
+
}
|
|
829
|
+
fsyncDirectory(workspacePath);
|
|
830
|
+
}
|
|
577
831
|
function workspaceRebaseInProgress(workspacePath) {
|
|
578
832
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
579
833
|
return import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-merge")) || import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-apply"));
|
|
@@ -581,8 +835,8 @@ function workspaceRebaseInProgress(workspacePath) {
|
|
|
581
835
|
function workspaceResolutionInProgress(workspacePath) {
|
|
582
836
|
return workspaceRebaseInProgress(workspacePath) || import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git", "MERGE_HEAD"));
|
|
583
837
|
}
|
|
584
|
-
function workspaceHasUnmergedEntries(workspacePath) {
|
|
585
|
-
const result =
|
|
838
|
+
async function workspaceHasUnmergedEntries(workspacePath) {
|
|
839
|
+
const result = await gitResultAsync(workspacePath, ["ls-files", "-u", "-z"]);
|
|
586
840
|
if (result.exitCode !== 0) {
|
|
587
841
|
throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
588
842
|
}
|
|
@@ -638,17 +892,21 @@ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
|
|
|
638
892
|
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
639
893
|
return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
|
|
640
894
|
}
|
|
895
|
+
async function workspaceCheckoutDurabilityIsCurrentAsync(workspacePath) {
|
|
896
|
+
const record = await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
|
|
897
|
+
return Boolean(record?.state === "durable" && record.head === await revParseAsync(workspacePath, "HEAD"));
|
|
898
|
+
}
|
|
641
899
|
function beginWorkspaceCheckoutTransition(workspacePath) {
|
|
642
900
|
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
643
901
|
const head = revParse(workspacePath, "HEAD");
|
|
644
902
|
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
|
|
645
903
|
}
|
|
646
|
-
function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
647
|
-
|
|
648
|
-
|
|
904
|
+
async function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
905
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
906
|
+
await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
|
|
649
907
|
version: 1,
|
|
650
908
|
state: "durable",
|
|
651
|
-
head:
|
|
909
|
+
head: await revParseAsync(workspacePath, "HEAD")
|
|
652
910
|
});
|
|
653
911
|
}
|
|
654
912
|
function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
@@ -662,11 +920,11 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
|
662
920
|
}
|
|
663
921
|
throw error;
|
|
664
922
|
}
|
|
665
|
-
function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
923
|
+
async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
666
924
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
667
925
|
try {
|
|
668
|
-
mutate();
|
|
669
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
926
|
+
await mutate();
|
|
927
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
670
928
|
} catch (error) {
|
|
671
929
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
672
930
|
}
|
|
@@ -964,24 +1222,24 @@ function validateMounts(workspacePath, mounts) {
|
|
|
964
1222
|
if (overlap) throw new Error(`Workspace mounts overlap: ${current} and ${overlap}`);
|
|
965
1223
|
}
|
|
966
1224
|
}
|
|
967
|
-
function configureWorkspaceRepository(input) {
|
|
968
|
-
if (
|
|
969
|
-
|
|
1225
|
+
async function configureWorkspaceRepository(input) {
|
|
1226
|
+
if (await tryGitAsync(input.workspacePath, ["remote", "get-url", "origin"])) {
|
|
1227
|
+
await gitAsync(input.workspacePath, ["remote", "set-url", "origin", input.remoteUrl], "configure workspace origin");
|
|
970
1228
|
} else {
|
|
971
|
-
|
|
1229
|
+
await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
|
|
972
1230
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1231
|
+
await tryGitAsync(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
|
|
1232
|
+
await gitAsync(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
|
|
1233
|
+
await gitAsync(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
|
|
976
1234
|
if (input.credentialUsername) {
|
|
977
|
-
|
|
1235
|
+
await gitAsync(
|
|
978
1236
|
input.workspacePath,
|
|
979
1237
|
["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.remoteUrl), input.credentialUsername],
|
|
980
1238
|
"configure workspace credential username"
|
|
981
1239
|
);
|
|
982
1240
|
}
|
|
983
1241
|
if (input.credentialHelper) {
|
|
984
|
-
|
|
1242
|
+
await gitAsync(
|
|
985
1243
|
input.workspacePath,
|
|
986
1244
|
["config", "--local", "--add", "credential.helper", input.credentialHelper],
|
|
987
1245
|
"configure workspace credential helper"
|
|
@@ -990,26 +1248,26 @@ function configureWorkspaceRepository(input) {
|
|
|
990
1248
|
const name = input.gitIdentity.name.trim();
|
|
991
1249
|
const email = input.gitIdentity.email.trim();
|
|
992
1250
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1251
|
+
await gitAsync(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
|
|
1252
|
+
await gitAsync(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
|
|
1253
|
+
await gitAsync(
|
|
996
1254
|
input.workspacePath,
|
|
997
1255
|
["config", "--local", "core.fsync", "committed,reference"],
|
|
998
1256
|
"configure durable workspace commits and references"
|
|
999
1257
|
);
|
|
1000
|
-
|
|
1258
|
+
await gitAsync(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
1001
1259
|
}
|
|
1002
|
-
function configureExistingWorkspaceGitForRemediation(input) {
|
|
1260
|
+
async function configureExistingWorkspaceGitForRemediation(input) {
|
|
1003
1261
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1004
1262
|
const workspaceStatus = lstatIfExists(workspacePath);
|
|
1005
1263
|
const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
|
|
1006
1264
|
if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
1007
1265
|
throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
|
|
1008
1266
|
}
|
|
1009
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1267
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1010
1268
|
}
|
|
1011
|
-
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
1012
|
-
const result =
|
|
1269
|
+
async function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
1270
|
+
const result = await gitResultAsync(workspacePath, [
|
|
1013
1271
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
|
|
1014
1272
|
"fetch",
|
|
1015
1273
|
"--no-recurse-submodules",
|
|
@@ -1028,7 +1286,7 @@ ${result.stdout}`;
|
|
|
1028
1286
|
}
|
|
1029
1287
|
return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1030
1288
|
}
|
|
1031
|
-
function ensureWorkspaceGitClone(input) {
|
|
1289
|
+
async function ensureWorkspaceGitClone(input) {
|
|
1032
1290
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1033
1291
|
const workspaceParent = import_node_path.default.dirname(workspacePath);
|
|
1034
1292
|
const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
|
|
@@ -1068,18 +1326,18 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1068
1326
|
git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
|
|
1069
1327
|
}
|
|
1070
1328
|
}
|
|
1071
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1329
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1072
1330
|
recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
|
|
1073
|
-
const previousRemoteHead =
|
|
1074
|
-
const localHeadBeforeFetch =
|
|
1075
|
-
if (!
|
|
1076
|
-
updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
|
|
1331
|
+
const previousRemoteHead = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1332
|
+
const localHeadBeforeFetch = await revParseAsync(workspacePath, "HEAD");
|
|
1333
|
+
if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
1334
|
+
await updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
|
|
1077
1335
|
}
|
|
1078
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1079
|
-
let localHead =
|
|
1336
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1337
|
+
let localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1080
1338
|
if (!localHead && remoteHead) {
|
|
1081
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1082
|
-
|
|
1339
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1340
|
+
await gitAsync(
|
|
1083
1341
|
workspacePath,
|
|
1084
1342
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1085
1343
|
"check out workspace main"
|
|
@@ -1088,12 +1346,12 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1088
1346
|
localHead = remoteHead;
|
|
1089
1347
|
}
|
|
1090
1348
|
if (localHead && remoteHead && localHead === remoteHead) {
|
|
1091
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1349
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1092
1350
|
} else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
|
|
1093
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1094
|
-
|
|
1351
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1352
|
+
await gitAsync(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
|
|
1095
1353
|
});
|
|
1096
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1354
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1097
1355
|
localHead = remoteHead;
|
|
1098
1356
|
}
|
|
1099
1357
|
return { localHead, remoteHead };
|
|
@@ -1217,7 +1475,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1217
1475
|
}
|
|
1218
1476
|
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
|
|
1219
1477
|
}
|
|
1220
|
-
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1478
|
+
async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1221
1479
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1222
1480
|
const durabilityMounts = input.durabilityMounts ?? input.mounts;
|
|
1223
1481
|
const receiptMounts = input.receiptMounts ?? durabilityMounts;
|
|
@@ -1225,19 +1483,34 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1225
1483
|
const candidateMounts = [
|
|
1226
1484
|
...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
|
|
1227
1485
|
];
|
|
1228
|
-
|
|
1229
|
-
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1230
|
-
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1231
|
-
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1232
|
-
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1233
|
-
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1234
|
-
);
|
|
1235
|
-
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1236
|
-
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1486
|
+
if (input.recordCurrentHead && !await workspaceCheckoutDurabilityIsCurrentAsync(workspacePath)) {
|
|
1237
1487
|
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1238
1488
|
}
|
|
1239
|
-
const targetHead = input.recordCurrentHead ?
|
|
1240
|
-
const
|
|
1489
|
+
const targetHead = input.recordCurrentHead ? await revParseAsync(workspacePath, "HEAD") : null;
|
|
1490
|
+
const receiptBeforeTransaction = targetHead ? await readHydratedWorkspaceReceiptAsync(workspacePath) : null;
|
|
1491
|
+
const computeSelection = () => {
|
|
1492
|
+
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1493
|
+
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1494
|
+
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1495
|
+
const hydrationMounts2 = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1496
|
+
const advancedDurabilityMounts2 = durabilityMounts.filter(
|
|
1497
|
+
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1498
|
+
);
|
|
1499
|
+
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1500
|
+
return {
|
|
1501
|
+
busyMountIds,
|
|
1502
|
+
hydrationMounts: hydrationMounts2,
|
|
1503
|
+
advancedDurabilityMounts: advancedDurabilityMounts2,
|
|
1504
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
|
|
1505
|
+
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, advancedDurabilityMounts2, receiptMounts) : null
|
|
1506
|
+
};
|
|
1507
|
+
};
|
|
1508
|
+
const noopCandidate = computeSelection();
|
|
1509
|
+
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)) {
|
|
1510
|
+
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1511
|
+
}
|
|
1512
|
+
const selection = computeSelection();
|
|
1513
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1241
1514
|
const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1242
1515
|
try {
|
|
1243
1516
|
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
@@ -1251,7 +1524,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1251
1524
|
removeHydrationTransaction(workspacePath);
|
|
1252
1525
|
return {
|
|
1253
1526
|
hydratedMountIds: hydration.hydratedMountIds,
|
|
1254
|
-
skippedMountIds:
|
|
1527
|
+
skippedMountIds: selection.skippedMountIds
|
|
1255
1528
|
};
|
|
1256
1529
|
} catch (error) {
|
|
1257
1530
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
@@ -1270,7 +1543,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1270
1543
|
throw error;
|
|
1271
1544
|
}
|
|
1272
1545
|
}
|
|
1273
|
-
function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1546
|
+
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1274
1547
|
const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
|
|
1275
1548
|
if (!import_node_fs.default.existsSync(gitDirectory)) {
|
|
1276
1549
|
return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
|
|
@@ -1282,16 +1555,18 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1282
1555
|
ignoreBusy: options.ignoreBusy
|
|
1283
1556
|
});
|
|
1284
1557
|
}
|
|
1285
|
-
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1558
|
+
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1286
1559
|
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
1287
1560
|
validateMounts(workspacePath, mounts);
|
|
1288
1561
|
recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
|
|
1289
1562
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1290
|
-
const currentHead =
|
|
1291
|
-
const currentReceipt =
|
|
1292
|
-
|
|
1563
|
+
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1564
|
+
const currentReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1565
|
+
if (!await workspaceBasisRefsAreCurrentAsync(workspacePath, currentReceipt ?? { version: 3, mounts: [] })) {
|
|
1566
|
+
repairWorkspaceBasisRefs(workspacePath, currentReceipt);
|
|
1567
|
+
}
|
|
1293
1568
|
if (!currentHead) return;
|
|
1294
|
-
const outerHeadContainsMount = (mount) =>
|
|
1569
|
+
const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1295
1570
|
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1296
1571
|
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1297
1572
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
@@ -1302,10 +1577,15 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1302
1577
|
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
|
|
1303
1578
|
);
|
|
1304
1579
|
const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
|
|
1305
|
-
const hydrationTargets =
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1580
|
+
const hydrationTargets = [];
|
|
1581
|
+
for (const mount of idleBasislessMounts) {
|
|
1582
|
+
if (mount.deleteWhenSourceMissing) continue;
|
|
1583
|
+
if (mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.mounts.find(({ id }) => id === mount.id)?.head === currentHead) {
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (await outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true) hydrationTargets.push(mount);
|
|
1587
|
+
}
|
|
1588
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1309
1589
|
workspacePath,
|
|
1310
1590
|
mounts: hydrationTargets,
|
|
1311
1591
|
durabilityMounts: idleBasislessMounts,
|
|
@@ -1316,27 +1596,31 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1316
1596
|
});
|
|
1317
1597
|
void hydration;
|
|
1318
1598
|
}
|
|
1319
|
-
function resetWorkspaceGit(input) {
|
|
1599
|
+
async function resetWorkspaceGit(input) {
|
|
1320
1600
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1321
1601
|
validateMounts(workspacePath, input.mounts);
|
|
1322
1602
|
const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1323
1603
|
if (busyResetMountIds.length > 0) {
|
|
1324
1604
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
1325
1605
|
}
|
|
1326
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1606
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1327
1607
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1328
1608
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
1329
1609
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
1330
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1610
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1331
1611
|
if (remoteHead) {
|
|
1332
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1333
|
-
|
|
1334
|
-
|
|
1612
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1613
|
+
await gitAsync(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
|
|
1614
|
+
await gitAsync(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
|
|
1335
1615
|
});
|
|
1336
1616
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
1337
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1338
|
-
|
|
1339
|
-
|
|
1617
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1618
|
+
await gitAsync(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
|
|
1619
|
+
await gitAsync(
|
|
1620
|
+
workspacePath,
|
|
1621
|
+
["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })],
|
|
1622
|
+
"record empty workspace reset"
|
|
1623
|
+
);
|
|
1340
1624
|
});
|
|
1341
1625
|
}
|
|
1342
1626
|
const selected = activeMounts(input.mounts);
|
|
@@ -1353,7 +1637,7 @@ function resetWorkspaceGit(input) {
|
|
|
1353
1637
|
const requiredLiveMounts = [
|
|
1354
1638
|
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
1355
1639
|
];
|
|
1356
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1640
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1357
1641
|
workspacePath,
|
|
1358
1642
|
mounts: hydrationMounts,
|
|
1359
1643
|
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
@@ -1375,8 +1659,8 @@ function resetWorkspaceGit(input) {
|
|
|
1375
1659
|
skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1376
1660
|
};
|
|
1377
1661
|
}
|
|
1378
|
-
function stagedPaths(workspacePath) {
|
|
1379
|
-
return
|
|
1662
|
+
async function stagedPaths(workspacePath) {
|
|
1663
|
+
return (await gitAsync(workspacePath, ["diff", "--cached", "--name-only", "--no-renames", "-z"], "inspect staged workspace paths")).split("\0").filter(Boolean).sort();
|
|
1380
1664
|
}
|
|
1381
1665
|
function emptyTreeHash(workspacePath) {
|
|
1382
1666
|
const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "hash-object", "-t", "tree", "--stdin"], {
|
|
@@ -1390,9 +1674,9 @@ function emptyTreeHash(workspacePath) {
|
|
|
1390
1674
|
throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
|
|
1391
1675
|
return result.stdout.toString().trim();
|
|
1392
1676
|
}
|
|
1393
|
-
function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1677
|
+
async function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1394
1678
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
1395
|
-
return
|
|
1679
|
+
return (await gitAsync(workspacePath, ["diff", "--name-only", "--no-renames", "-z", base, headRevision], "inspect workspace commit paths")).split("\0").filter(Boolean).sort();
|
|
1396
1680
|
}
|
|
1397
1681
|
async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
|
|
1398
1682
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
@@ -1435,15 +1719,15 @@ function snapshotConflict(input) {
|
|
|
1435
1719
|
git(input.workspacePath, ["update-ref", refs.remote, input.remoteHead], "snapshot remote workspace conflict head");
|
|
1436
1720
|
return refs;
|
|
1437
1721
|
}
|
|
1438
|
-
function rebaseWorkspace(input) {
|
|
1722
|
+
async function rebaseWorkspace(input) {
|
|
1439
1723
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
1440
1724
|
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
1441
|
-
const rebase =
|
|
1725
|
+
const rebase = await gitResultAsync(
|
|
1442
1726
|
input.workspacePath,
|
|
1443
1727
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
1444
1728
|
);
|
|
1445
1729
|
if (rebase.exitCode === 0) {
|
|
1446
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1730
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1447
1731
|
return { ok: true };
|
|
1448
1732
|
}
|
|
1449
1733
|
const conflicts = conflictPaths(input.workspacePath);
|
|
@@ -1452,7 +1736,7 @@ function rebaseWorkspace(input) {
|
|
|
1452
1736
|
if (abort.exitCode !== 0) {
|
|
1453
1737
|
throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
1454
1738
|
}
|
|
1455
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1739
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1456
1740
|
return {
|
|
1457
1741
|
ok: false,
|
|
1458
1742
|
conflictPaths: conflicts,
|
|
@@ -1460,13 +1744,13 @@ function rebaseWorkspace(input) {
|
|
|
1460
1744
|
error: rebase.stderr || rebase.stdout || `Workspace rebase onto ${input.remoteHead} conflicted`
|
|
1461
1745
|
};
|
|
1462
1746
|
}
|
|
1463
|
-
function synchronizeWithFetchedHead(input) {
|
|
1464
|
-
const localHead =
|
|
1747
|
+
async function synchronizeWithFetchedHead(input) {
|
|
1748
|
+
const localHead = await revParseAsync(input.workspacePath, "HEAD");
|
|
1465
1749
|
const remoteHead = input.remoteHead;
|
|
1466
1750
|
if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
|
|
1467
1751
|
if (!localHead) {
|
|
1468
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1469
|
-
|
|
1752
|
+
await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
|
|
1753
|
+
await gitAsync(
|
|
1470
1754
|
input.workspacePath,
|
|
1471
1755
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1472
1756
|
"check out fetched workspace main"
|
|
@@ -1475,35 +1759,35 @@ function synchronizeWithFetchedHead(input) {
|
|
|
1475
1759
|
return { kind: "ready", updated: true, rebased: false };
|
|
1476
1760
|
}
|
|
1477
1761
|
if (localHead === remoteHead) {
|
|
1478
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1762
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1479
1763
|
return { kind: "ready", updated: false, rebased: false };
|
|
1480
1764
|
}
|
|
1481
|
-
if (
|
|
1482
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1483
|
-
|
|
1765
|
+
if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
|
|
1766
|
+
await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
|
|
1767
|
+
await gitAsync(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
|
|
1484
1768
|
});
|
|
1485
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1769
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1486
1770
|
return { kind: "ready", updated: true, rebased: false };
|
|
1487
1771
|
}
|
|
1488
|
-
if (
|
|
1489
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1772
|
+
if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
|
|
1773
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1490
1774
|
return { kind: "ready", updated: false, rebased: false };
|
|
1491
1775
|
}
|
|
1492
|
-
const integratedHead =
|
|
1776
|
+
const integratedHead = await revParseAsync(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
|
|
1493
1777
|
const truncatedHistoryBase = workspaceIsShallow(input.workspacePath) && integratedHead && tryGit(input.workspacePath, ["merge-base", "--is-ancestor", integratedHead, localHead]) ? integratedHead : void 0;
|
|
1494
1778
|
if (workspaceIsShallow(input.workspacePath) && !truncatedHistoryBase) {
|
|
1495
1779
|
throw new Error(
|
|
1496
1780
|
"Workspace history was truncated before this clone's integrated base could be identified; reset the workspace clone before retrying"
|
|
1497
1781
|
);
|
|
1498
1782
|
}
|
|
1499
|
-
const rebased = rebaseWorkspace({
|
|
1783
|
+
const rebased = await rebaseWorkspace({
|
|
1500
1784
|
workspacePath: input.workspacePath,
|
|
1501
1785
|
localHead,
|
|
1502
1786
|
remoteHead,
|
|
1503
1787
|
attemptId: input.attemptId,
|
|
1504
1788
|
truncatedHistoryBase
|
|
1505
1789
|
});
|
|
1506
|
-
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1790
|
+
if (rebased.ok) await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1507
1791
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
1508
1792
|
}
|
|
1509
1793
|
async function synchronizeWorkspaceGit(input) {
|
|
@@ -1514,7 +1798,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1514
1798
|
validateMounts(workspacePath, input.mounts);
|
|
1515
1799
|
const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
|
|
1516
1800
|
const preserveResolutionInProgress = input.skipMountMirror === true;
|
|
1517
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1801
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1518
1802
|
assertWorkspaceRemediationAncestry({
|
|
1519
1803
|
workspacePath,
|
|
1520
1804
|
currentHead: initial.localHead,
|
|
@@ -1523,7 +1807,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1523
1807
|
});
|
|
1524
1808
|
const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
|
|
1525
1809
|
const startingHead = initial.localHead;
|
|
1526
|
-
const receiptBeforeInitialHydration =
|
|
1810
|
+
const receiptBeforeInitialHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1527
1811
|
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1528
1812
|
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
|
|
1529
1813
|
for (const mount of input.mounts) {
|
|
@@ -1532,7 +1816,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1532
1816
|
}
|
|
1533
1817
|
}
|
|
1534
1818
|
}
|
|
1535
|
-
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1819
|
+
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1536
1820
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1537
1821
|
preserveResolutionInProgress,
|
|
1538
1822
|
preserveStaleBases: true
|
|
@@ -1543,8 +1827,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1543
1827
|
);
|
|
1544
1828
|
const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
|
|
1545
1829
|
const receiptRequiredLiveMounts = selected.active;
|
|
1546
|
-
const projectionBasisHead =
|
|
1547
|
-
const projectionBasisReceipt =
|
|
1830
|
+
const projectionBasisHead = await revParseAsync(workspacePath, "HEAD");
|
|
1831
|
+
const projectionBasisReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1548
1832
|
const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1549
1833
|
const fastProjectionMounts = [];
|
|
1550
1834
|
const mergedProjectionTrees = /* @__PURE__ */ new Map();
|
|
@@ -1585,6 +1869,10 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1585
1869
|
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
1586
1870
|
continue;
|
|
1587
1871
|
}
|
|
1872
|
+
if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
|
|
1873
|
+
cycleSkippedMountIds.add(mount.id);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1588
1876
|
const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
|
|
1589
1877
|
workspacePath,
|
|
1590
1878
|
mount: {
|
|
@@ -1619,14 +1907,15 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1619
1907
|
}
|
|
1620
1908
|
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
1621
1909
|
mergedProjectionMounts.push(mount);
|
|
1910
|
+
await yieldToWorkerEventLoop();
|
|
1622
1911
|
}
|
|
1623
1912
|
}
|
|
1624
1913
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
1625
1914
|
`);
|
|
1626
|
-
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1627
|
-
const currentHeadBeforeHydration =
|
|
1915
|
+
const completedMountSelection = async (requireCurrentHeadReceipt = false) => {
|
|
1916
|
+
const currentHeadBeforeHydration = await revParseAsync(workspacePath, "HEAD");
|
|
1628
1917
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1629
|
-
const currentReceiptBeforeHydration =
|
|
1918
|
+
const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1630
1919
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1631
1920
|
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1632
1921
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
@@ -1643,7 +1932,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1643
1932
|
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
|
|
1644
1933
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1645
1934
|
);
|
|
1646
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1935
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1647
1936
|
workspacePath,
|
|
1648
1937
|
mounts: uncoveredCompletionMounts,
|
|
1649
1938
|
durabilityMounts: uncoveredCompletionBasisMounts,
|
|
@@ -1654,8 +1943,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1654
1943
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
1655
1944
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1656
1945
|
if (requireCurrentHeadReceipt) {
|
|
1657
|
-
const currentHead =
|
|
1658
|
-
const receipt =
|
|
1946
|
+
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1947
|
+
const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1659
1948
|
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
1660
1949
|
const idleMounts = completionReceiptMounts.filter(
|
|
1661
1950
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
|
|
@@ -1667,17 +1956,17 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1667
1956
|
return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
|
|
1668
1957
|
};
|
|
1669
1958
|
let newlyStagedPaths = [];
|
|
1670
|
-
const stageAndCommitWorkspace = () => {
|
|
1671
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1959
|
+
const stageAndCommitWorkspace = async () => {
|
|
1960
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1672
1961
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1673
1962
|
}
|
|
1674
|
-
|
|
1675
|
-
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1963
|
+
await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1964
|
+
newlyStagedPaths = await stagedPaths(workspacePath);
|
|
1676
1965
|
if (newlyStagedPaths.length > 0) {
|
|
1677
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1966
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1678
1967
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1679
1968
|
}
|
|
1680
|
-
|
|
1969
|
+
await gitAsync(
|
|
1681
1970
|
workspacePath,
|
|
1682
1971
|
[
|
|
1683
1972
|
"commit",
|
|
@@ -1688,64 +1977,77 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1688
1977
|
);
|
|
1689
1978
|
}
|
|
1690
1979
|
};
|
|
1980
|
+
const projectedFastMounts = [];
|
|
1691
1981
|
if (!input.skipMountMirror) {
|
|
1692
1982
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1693
1983
|
try {
|
|
1694
1984
|
const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
|
|
1695
1985
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
1696
1986
|
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
1697
|
-
|
|
1987
|
+
for (const mount of fastActiveMounts) {
|
|
1988
|
+
if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
|
|
1989
|
+
cycleSkippedMountIds.add(mount.id);
|
|
1990
|
+
continue;
|
|
1991
|
+
}
|
|
1992
|
+
mirrorMountsToWorkspace(workspacePath, [mount]);
|
|
1993
|
+
projectedFastMounts.push(mount);
|
|
1994
|
+
await yieldToWorkerEventLoop();
|
|
1995
|
+
}
|
|
1698
1996
|
removeWorkspaceMounts(workspacePath, fastTombstones);
|
|
1997
|
+
projectedFastMounts.push(...fastTombstones);
|
|
1699
1998
|
for (const mount of mergedProjectionMounts) {
|
|
1700
1999
|
(0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
|
|
1701
2000
|
workspacePath,
|
|
1702
2001
|
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
1703
2002
|
resultTree: mergedProjectionTrees.get(mount.id)
|
|
1704
2003
|
});
|
|
2004
|
+
await yieldToWorkerEventLoop();
|
|
1705
2005
|
}
|
|
1706
|
-
const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
|
|
1707
2006
|
restoreDeferredWorkspaceMountsFromHead(
|
|
1708
2007
|
workspacePath,
|
|
1709
|
-
input.mounts.filter(({ id }) =>
|
|
2008
|
+
input.mounts.filter(({ id }) => deferredMountIds.has(id))
|
|
1710
2009
|
);
|
|
1711
|
-
stageAndCommitWorkspace();
|
|
1712
|
-
|
|
2010
|
+
await stageAndCommitWorkspace();
|
|
2011
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
1713
2012
|
} catch (error) {
|
|
1714
2013
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
1715
2014
|
}
|
|
1716
2015
|
} else {
|
|
1717
|
-
stageAndCommitWorkspace();
|
|
1718
|
-
if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
|
|
2016
|
+
await stageAndCommitWorkspace();
|
|
2017
|
+
if (newlyStagedPaths.length > 0) await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1719
2018
|
}
|
|
1720
2019
|
if (!input.skipMountMirror) {
|
|
1721
|
-
const projectedHead =
|
|
2020
|
+
const projectedHead = await revParseAsync(workspacePath, "HEAD");
|
|
1722
2021
|
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1723
2022
|
const targetProjectionReceipt = receiptWithMountsAtHead(
|
|
1724
2023
|
projectionBasisReceipt,
|
|
1725
2024
|
projectedHead,
|
|
1726
|
-
|
|
2025
|
+
projectedFastMounts,
|
|
1727
2026
|
allProjectionReceiptMounts
|
|
1728
2027
|
);
|
|
1729
|
-
const currentProjectionReceipt =
|
|
1730
|
-
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !
|
|
1731
|
-
|
|
1732
|
-
|
|
2028
|
+
const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
2029
|
+
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
|
|
2030
|
+
for (const mount of projectedFastMounts) {
|
|
2031
|
+
fsyncHydratedWorkspaceMount(mount);
|
|
2032
|
+
await yieldToWorkerEventLoop();
|
|
2033
|
+
}
|
|
2034
|
+
await updateHydratedWorkspaceReceiptAsync(workspacePath, targetProjectionReceipt);
|
|
1733
2035
|
}
|
|
1734
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
2036
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1735
2037
|
}
|
|
1736
|
-
let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2038
|
+
let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1737
2039
|
let rebaseCount = 0;
|
|
1738
2040
|
let updated = false;
|
|
1739
2041
|
for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
|
|
1740
2042
|
assertWorkspaceRemediationAncestry({
|
|
1741
2043
|
workspacePath,
|
|
1742
|
-
currentHead:
|
|
2044
|
+
currentHead: await revParseAsync(workspacePath, "HEAD"),
|
|
1743
2045
|
remoteHead,
|
|
1744
2046
|
requiredAncestorHeads
|
|
1745
2047
|
});
|
|
1746
|
-
const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
2048
|
+
const reconciled = await synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
1747
2049
|
if (reconciled.kind === "conflict") {
|
|
1748
|
-
const localHead2 =
|
|
2050
|
+
const localHead2 = await revParseAsync(workspacePath, "HEAD");
|
|
1749
2051
|
const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
|
|
1750
2052
|
return {
|
|
1751
2053
|
outcome: "conflict_blocked",
|
|
@@ -1766,8 +2068,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1766
2068
|
}
|
|
1767
2069
|
updated ||= reconciled.updated;
|
|
1768
2070
|
if (reconciled.rebased) rebaseCount += 1;
|
|
1769
|
-
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
1770
|
-
const localHead =
|
|
2071
|
+
if (reconciled.updated || reconciled.rebased) await completedMountSelection(true);
|
|
2072
|
+
const localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1771
2073
|
if (!localHead) {
|
|
1772
2074
|
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
|
|
1773
2075
|
return {
|
|
@@ -1784,7 +2086,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1784
2086
|
...projectionWarning ? { error: projectionWarning } : {}
|
|
1785
2087
|
};
|
|
1786
2088
|
}
|
|
1787
|
-
const paths = changedPaths(workspacePath, remoteHead, localHead);
|
|
2089
|
+
const paths = await changedPaths(workspacePath, remoteHead, localHead);
|
|
1788
2090
|
const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
|
|
1789
2091
|
input.assertStillAdmitted?.();
|
|
1790
2092
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
@@ -1805,7 +2107,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1805
2107
|
}
|
|
1806
2108
|
if (localHead === remoteHead) {
|
|
1807
2109
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1808
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2110
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1809
2111
|
await input.afterWorkspacePublished?.({
|
|
1810
2112
|
publishedHead: localHead,
|
|
1811
2113
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1833,11 +2135,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1833
2135
|
];
|
|
1834
2136
|
if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
|
|
1835
2137
|
pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
|
|
1836
|
-
const push =
|
|
2138
|
+
const push = await gitResultAsync(workspacePath, pushArgs);
|
|
1837
2139
|
if (push.exitCode === 0) {
|
|
1838
2140
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1839
|
-
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1840
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2141
|
+
await updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
2142
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1841
2143
|
await input.afterWorkspacePublished?.({
|
|
1842
2144
|
publishedHead: localHead,
|
|
1843
2145
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1861,7 +2163,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1861
2163
|
${push.stdout}`)) {
|
|
1862
2164
|
throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
|
|
1863
2165
|
}
|
|
1864
|
-
remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2166
|
+
remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1865
2167
|
}
|
|
1866
2168
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
1867
2169
|
}
|