@ricsam/r5d-worker 0.0.83 → 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 +180 -106
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +183 -104
- package/dist/cjs/workspace-automatic-sync-policy.cjs +12 -2
- package/dist/cjs/workspace-command-sync-policy.cjs +21 -0
- package/dist/cjs/workspace-git-sync.cjs +501 -191
- package/dist/mjs/main.mjs +185 -107
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +183 -104
- package/dist/mjs/workspace-automatic-sync-policy.mjs +9 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +20 -0
- package/dist/mjs/workspace-git-sync.mjs +500 -191
- package/dist/types/main.d.ts +19 -2
- package/dist/types/project-worktrees.d.ts +12 -12
- package/dist/types/workspace-automatic-sync-policy.d.ts +24 -4
- package/dist/types/workspace-command-sync-policy.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +17 -9
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ __export(workspace_git_sync_exports, {
|
|
|
42
42
|
resetWorkspaceGit: () => resetWorkspaceGit,
|
|
43
43
|
synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
|
|
44
44
|
workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
|
|
45
|
+
workspaceGitMountsForRemediation: () => workspaceGitMountsForRemediation,
|
|
45
46
|
workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
|
|
46
47
|
});
|
|
47
48
|
module.exports = __toCommonJS(workspace_git_sync_exports);
|
|
@@ -58,6 +59,9 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
|
58
59
|
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
59
60
|
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
60
61
|
const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
62
|
+
function workspaceGitMountsForRemediation(mounts) {
|
|
63
|
+
return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
|
|
64
|
+
}
|
|
61
65
|
class WorkspaceRemediationAncestryError extends Error {
|
|
62
66
|
constructor(message) {
|
|
63
67
|
super(message);
|
|
@@ -103,6 +107,71 @@ function revParse(workspacePath, revision) {
|
|
|
103
107
|
const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
|
|
104
108
|
return result.exitCode === 0 ? result.stdout : null;
|
|
105
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
|
+
}
|
|
106
175
|
function requiredWorkspaceAncestorHeads(value) {
|
|
107
176
|
if (value === void 0) return [];
|
|
108
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) {
|
|
@@ -126,8 +195,8 @@ function assertWorkspaceRemediationAncestry(input) {
|
|
|
126
195
|
);
|
|
127
196
|
}
|
|
128
197
|
}
|
|
129
|
-
function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
130
|
-
|
|
198
|
+
async function updateIntegratedWorkspaceHead(workspacePath, head) {
|
|
199
|
+
await gitAsync(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
|
|
131
200
|
}
|
|
132
201
|
function fsyncDirectory(directoryPath) {
|
|
133
202
|
const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
|
|
@@ -231,16 +300,13 @@ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMou
|
|
|
231
300
|
return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
|
|
232
301
|
});
|
|
233
302
|
}
|
|
234
|
-
function
|
|
303
|
+
function parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content) {
|
|
235
304
|
const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
|
|
236
305
|
if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
|
|
237
306
|
`) {
|
|
238
|
-
if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
|
|
239
|
-
throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
|
|
240
|
-
}
|
|
241
307
|
const receipt2 = { version: 3, mounts: [] };
|
|
242
308
|
Object.defineProperty(receipt2, "legacyHead", { value: legacyHead, enumerable: false });
|
|
243
|
-
return receipt2;
|
|
309
|
+
return { receipt: receipt2, headsToVerify: [legacyHead] };
|
|
244
310
|
}
|
|
245
311
|
let parsed;
|
|
246
312
|
try {
|
|
@@ -308,14 +374,16 @@ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, conte
|
|
|
308
374
|
}
|
|
309
375
|
const receiptHeads = new Set(receipt.mounts.map((mount) => mount.head));
|
|
310
376
|
if (liftedV2Head) receiptHeads.add(liftedV2Head);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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}`);
|
|
315
383
|
}
|
|
316
384
|
return receipt;
|
|
317
385
|
}
|
|
318
|
-
function
|
|
386
|
+
function readHydratedWorkspaceReceiptUnverified(workspacePath) {
|
|
319
387
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
320
388
|
const status = lstatIfExists(receiptPath);
|
|
321
389
|
if (!status) return null;
|
|
@@ -323,7 +391,23 @@ function readHydratedWorkspaceReceipt(workspacePath) {
|
|
|
323
391
|
throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
|
|
324
392
|
}
|
|
325
393
|
const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
|
|
326
|
-
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;
|
|
327
411
|
}
|
|
328
412
|
function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
|
|
329
413
|
const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
|
|
@@ -370,49 +454,142 @@ function desiredWorkspaceBasisRefs(receipt) {
|
|
|
370
454
|
}
|
|
371
455
|
function workspaceBasisRefsMatch(workspacePath, receipt) {
|
|
372
456
|
const desired = desiredWorkspaceBasisRefs(receipt);
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
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
|
+
});
|
|
376
468
|
}
|
|
377
469
|
function workspaceBasisRefsAreCurrent(workspacePath, receipt) {
|
|
378
470
|
return workspaceBasisRefsMatch(workspacePath, receipt) && listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX).length === 0;
|
|
379
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
|
+
}
|
|
380
490
|
function repairWorkspaceBasisRefs(workspacePath, receipt) {
|
|
381
491
|
const effectiveReceipt = receipt ?? { version: 3, mounts: [] };
|
|
382
492
|
if (!workspaceBasisRefsAreCurrent(workspacePath, effectiveReceipt)) updateWorkspaceBasisRefs(workspacePath, effectiveReceipt);
|
|
383
493
|
}
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
const transaction = ["start"];
|
|
387
|
-
for (const [ref, head] of [...desired].sort(([left], [right]) => left.localeCompare(right))) transaction.push(`update ${ref} ${head}`);
|
|
388
|
-
for (const ref of listWorkspaceRefs(workspacePath, WORKSPACE_GIT_BASIS_REF_PREFIX)) {
|
|
389
|
-
if (!desired.has(ref)) transaction.push(`delete ${ref}`);
|
|
390
|
-
}
|
|
391
|
-
transaction.push("prepare", "commit", "");
|
|
494
|
+
function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
|
|
495
|
+
if (instructions.length === 0) return;
|
|
392
496
|
const updated = Bun.spawnSync(gitCommandArgs(["update-ref", "--stdin"]), {
|
|
393
497
|
cwd: workspacePath,
|
|
394
|
-
stdin: Buffer.from(
|
|
498
|
+
stdin: Buffer.from([...instructions, ""].join("\n")),
|
|
395
499
|
stdout: "pipe",
|
|
396
500
|
stderr: "pipe",
|
|
397
501
|
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
398
502
|
});
|
|
399
503
|
if (updated.exitCode !== 0) {
|
|
400
|
-
throw new Error(
|
|
504
|
+
throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
|
|
401
505
|
}
|
|
402
|
-
|
|
403
|
-
|
|
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}`}`);
|
|
404
523
|
}
|
|
405
524
|
}
|
|
406
|
-
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) {
|
|
407
566
|
for (const mount of receipt.mounts) {
|
|
408
567
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
|
|
409
568
|
throw new Error(`Cannot record invalid workspace hydration object id: ${mount.head}`);
|
|
410
569
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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);
|
|
414
580
|
}
|
|
415
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) {
|
|
416
593
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
417
594
|
const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
|
|
418
595
|
if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
|
|
@@ -429,13 +606,16 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
429
606
|
if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
|
|
430
607
|
throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
|
|
431
608
|
}
|
|
432
|
-
|
|
433
|
-
|
|
609
|
+
return { receiptPath, receiptDirectory };
|
|
610
|
+
}
|
|
611
|
+
function stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix) {
|
|
434
612
|
desiredWorkspaceBasisRefs(receipt);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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`);
|
|
439
619
|
let descriptor;
|
|
440
620
|
try {
|
|
441
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);
|
|
@@ -444,15 +624,36 @@ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
|
|
|
444
624
|
import_node_fs.default.fsyncSync(descriptor);
|
|
445
625
|
import_node_fs.default.closeSync(descriptor);
|
|
446
626
|
descriptor = void 0;
|
|
447
|
-
import_node_fs.default.renameSync(temporaryPath, receiptPath);
|
|
448
|
-
fsyncDirectory(receiptDirectory);
|
|
449
|
-
updateWorkspaceBasisRefs(workspacePath, receipt);
|
|
627
|
+
import_node_fs.default.renameSync(temporaryPath, paths.receiptPath);
|
|
628
|
+
fsyncDirectory(paths.receiptDirectory);
|
|
450
629
|
} catch (error) {
|
|
451
630
|
if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
|
|
452
631
|
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
453
632
|
throw error;
|
|
454
633
|
}
|
|
455
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
|
+
}
|
|
456
657
|
function hydrationTransactionPath(workspacePath) {
|
|
457
658
|
return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
|
|
458
659
|
}
|
|
@@ -504,7 +705,7 @@ function fsyncTree(rootPath) {
|
|
|
504
705
|
};
|
|
505
706
|
syncEntry(rootPath);
|
|
506
707
|
}
|
|
507
|
-
function
|
|
708
|
+
function parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, content) {
|
|
508
709
|
let parsed;
|
|
509
710
|
try {
|
|
510
711
|
parsed = JSON.parse(content);
|
|
@@ -527,25 +728,34 @@ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, conte
|
|
|
527
728
|
`) {
|
|
528
729
|
throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
|
|
529
730
|
}
|
|
530
|
-
if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
|
|
531
|
-
throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
|
|
532
|
-
}
|
|
533
731
|
return record;
|
|
534
732
|
}
|
|
535
|
-
function
|
|
733
|
+
function readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath) {
|
|
536
734
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
537
735
|
const status = lstatIfExists(recordPath);
|
|
538
736
|
if (!status) return null;
|
|
539
737
|
if (!status.isFile() || status.isSymbolicLink()) {
|
|
540
738
|
throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
|
|
541
739
|
}
|
|
542
|
-
return
|
|
740
|
+
return { record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, import_node_fs.default.readFileSync(recordPath, "utf8")), recordPath };
|
|
543
741
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
546
|
-
|
|
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}`);
|
|
547
747
|
}
|
|
548
|
-
|
|
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) {
|
|
549
759
|
const stateDirectory = workspacePrivateStateDirectory(workspacePath);
|
|
550
760
|
const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
|
|
551
761
|
const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
@@ -559,6 +769,20 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
|
|
|
559
769
|
throw error;
|
|
560
770
|
}
|
|
561
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
|
+
}
|
|
562
786
|
function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
563
787
|
const status = import_node_fs.default.lstatSync(workspacePath);
|
|
564
788
|
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
@@ -570,6 +794,40 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
|
|
|
570
794
|
}
|
|
571
795
|
fsyncDirectory(workspacePath);
|
|
572
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
|
+
}
|
|
573
831
|
function workspaceRebaseInProgress(workspacePath) {
|
|
574
832
|
const gitDirectory = import_node_path.default.join(workspacePath, ".git");
|
|
575
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"));
|
|
@@ -577,8 +835,8 @@ function workspaceRebaseInProgress(workspacePath) {
|
|
|
577
835
|
function workspaceResolutionInProgress(workspacePath) {
|
|
578
836
|
return workspaceRebaseInProgress(workspacePath) || import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git", "MERGE_HEAD"));
|
|
579
837
|
}
|
|
580
|
-
function workspaceHasUnmergedEntries(workspacePath) {
|
|
581
|
-
const result =
|
|
838
|
+
async function workspaceHasUnmergedEntries(workspacePath) {
|
|
839
|
+
const result = await gitResultAsync(workspacePath, ["ls-files", "-u", "-z"]);
|
|
582
840
|
if (result.exitCode !== 0) {
|
|
583
841
|
throw new Error(`Inspect unfinished workspace merge: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
584
842
|
}
|
|
@@ -634,17 +892,21 @@ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
|
|
|
634
892
|
const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
|
|
635
893
|
return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
|
|
636
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
|
+
}
|
|
637
899
|
function beginWorkspaceCheckoutTransition(workspacePath) {
|
|
638
900
|
recoverWorkspaceCheckoutDurability(workspacePath);
|
|
639
901
|
const head = revParse(workspacePath, "HEAD");
|
|
640
902
|
updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
|
|
641
903
|
}
|
|
642
|
-
function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
643
|
-
|
|
644
|
-
|
|
904
|
+
async function completeWorkspaceCheckoutTransition(workspacePath) {
|
|
905
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
906
|
+
await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
|
|
645
907
|
version: 1,
|
|
646
908
|
state: "durable",
|
|
647
|
-
head:
|
|
909
|
+
head: await revParseAsync(workspacePath, "HEAD")
|
|
648
910
|
});
|
|
649
911
|
}
|
|
650
912
|
function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
@@ -658,11 +920,11 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
|
|
|
658
920
|
}
|
|
659
921
|
throw error;
|
|
660
922
|
}
|
|
661
|
-
function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
923
|
+
async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
|
|
662
924
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
663
925
|
try {
|
|
664
|
-
mutate();
|
|
665
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
926
|
+
await mutate();
|
|
927
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
666
928
|
} catch (error) {
|
|
667
929
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
668
930
|
}
|
|
@@ -960,24 +1222,24 @@ function validateMounts(workspacePath, mounts) {
|
|
|
960
1222
|
if (overlap) throw new Error(`Workspace mounts overlap: ${current} and ${overlap}`);
|
|
961
1223
|
}
|
|
962
1224
|
}
|
|
963
|
-
function configureWorkspaceRepository(input) {
|
|
964
|
-
if (
|
|
965
|
-
|
|
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");
|
|
966
1228
|
} else {
|
|
967
|
-
|
|
1229
|
+
await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
|
|
968
1230
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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");
|
|
972
1234
|
if (input.credentialUsername) {
|
|
973
|
-
|
|
1235
|
+
await gitAsync(
|
|
974
1236
|
input.workspacePath,
|
|
975
1237
|
["config", "--local", "--replace-all", (0, import_git_process_environment.gitCredentialUsernameConfigKey)(input.remoteUrl), input.credentialUsername],
|
|
976
1238
|
"configure workspace credential username"
|
|
977
1239
|
);
|
|
978
1240
|
}
|
|
979
1241
|
if (input.credentialHelper) {
|
|
980
|
-
|
|
1242
|
+
await gitAsync(
|
|
981
1243
|
input.workspacePath,
|
|
982
1244
|
["config", "--local", "--add", "credential.helper", input.credentialHelper],
|
|
983
1245
|
"configure workspace credential helper"
|
|
@@ -986,26 +1248,26 @@ function configureWorkspaceRepository(input) {
|
|
|
986
1248
|
const name = input.gitIdentity.name.trim();
|
|
987
1249
|
const email = input.gitIdentity.email.trim();
|
|
988
1250
|
if (!name || !email) throw new Error("Workspace Git identity must include name and email");
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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(
|
|
992
1254
|
input.workspacePath,
|
|
993
1255
|
["config", "--local", "core.fsync", "committed,reference"],
|
|
994
1256
|
"configure durable workspace commits and references"
|
|
995
1257
|
);
|
|
996
|
-
|
|
1258
|
+
await gitAsync(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
|
|
997
1259
|
}
|
|
998
|
-
function configureExistingWorkspaceGitForRemediation(input) {
|
|
1260
|
+
async function configureExistingWorkspaceGitForRemediation(input) {
|
|
999
1261
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1000
1262
|
const workspaceStatus = lstatIfExists(workspacePath);
|
|
1001
1263
|
const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
|
|
1002
1264
|
if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
|
|
1003
1265
|
throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
|
|
1004
1266
|
}
|
|
1005
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1267
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1006
1268
|
}
|
|
1007
|
-
function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
1008
|
-
const result =
|
|
1269
|
+
async function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
|
|
1270
|
+
const result = await gitResultAsync(workspacePath, [
|
|
1009
1271
|
...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
|
|
1010
1272
|
"fetch",
|
|
1011
1273
|
"--no-recurse-submodules",
|
|
@@ -1024,7 +1286,7 @@ ${result.stdout}`;
|
|
|
1024
1286
|
}
|
|
1025
1287
|
return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1026
1288
|
}
|
|
1027
|
-
function ensureWorkspaceGitClone(input) {
|
|
1289
|
+
async function ensureWorkspaceGitClone(input) {
|
|
1028
1290
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1029
1291
|
const workspaceParent = import_node_path.default.dirname(workspacePath);
|
|
1030
1292
|
const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
|
|
@@ -1064,18 +1326,18 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1064
1326
|
git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
|
|
1065
1327
|
}
|
|
1066
1328
|
}
|
|
1067
|
-
configureWorkspaceRepository({ ...input, workspacePath });
|
|
1329
|
+
await configureWorkspaceRepository({ ...input, workspacePath });
|
|
1068
1330
|
recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
|
|
1069
|
-
const previousRemoteHead =
|
|
1070
|
-
const localHeadBeforeFetch =
|
|
1071
|
-
if (!
|
|
1072
|
-
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);
|
|
1073
1335
|
}
|
|
1074
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1075
|
-
let localHead =
|
|
1336
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1337
|
+
let localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1076
1338
|
if (!localHead && remoteHead) {
|
|
1077
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1078
|
-
|
|
1339
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1340
|
+
await gitAsync(
|
|
1079
1341
|
workspacePath,
|
|
1080
1342
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1081
1343
|
"check out workspace main"
|
|
@@ -1084,12 +1346,12 @@ function ensureWorkspaceGitClone(input) {
|
|
|
1084
1346
|
localHead = remoteHead;
|
|
1085
1347
|
}
|
|
1086
1348
|
if (localHead && remoteHead && localHead === remoteHead) {
|
|
1087
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1349
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1088
1350
|
} else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
|
|
1089
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1090
|
-
|
|
1351
|
+
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
1352
|
+
await gitAsync(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
|
|
1091
1353
|
});
|
|
1092
|
-
updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1354
|
+
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1093
1355
|
localHead = remoteHead;
|
|
1094
1356
|
}
|
|
1095
1357
|
return { localHead, remoteHead };
|
|
@@ -1213,7 +1475,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1213
1475
|
}
|
|
1214
1476
|
return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
|
|
1215
1477
|
}
|
|
1216
|
-
function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1478
|
+
async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
1217
1479
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1218
1480
|
const durabilityMounts = input.durabilityMounts ?? input.mounts;
|
|
1219
1481
|
const receiptMounts = input.receiptMounts ?? durabilityMounts;
|
|
@@ -1221,19 +1483,34 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1221
1483
|
const candidateMounts = [
|
|
1222
1484
|
...new Map([...input.mounts, ...durabilityMounts, ...requiredMounts].map((mount) => [mount.id, mount])).values()
|
|
1223
1485
|
];
|
|
1224
|
-
|
|
1225
|
-
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1226
|
-
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1227
|
-
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1228
|
-
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1229
|
-
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1230
|
-
);
|
|
1231
|
-
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1232
|
-
if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
|
|
1486
|
+
if (input.recordCurrentHead && !await workspaceCheckoutDurabilityIsCurrentAsync(workspacePath)) {
|
|
1233
1487
|
throw new Error("Workspace checkout is not durably materialized at its current HEAD");
|
|
1234
1488
|
}
|
|
1235
|
-
const targetHead = input.recordCurrentHead ?
|
|
1236
|
-
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;
|
|
1237
1514
|
const manifest = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1238
1515
|
try {
|
|
1239
1516
|
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
@@ -1247,7 +1524,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1247
1524
|
removeHydrationTransaction(workspacePath);
|
|
1248
1525
|
return {
|
|
1249
1526
|
hydratedMountIds: hydration.hydratedMountIds,
|
|
1250
|
-
skippedMountIds:
|
|
1527
|
+
skippedMountIds: selection.skippedMountIds
|
|
1251
1528
|
};
|
|
1252
1529
|
} catch (error) {
|
|
1253
1530
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
@@ -1266,7 +1543,7 @@ function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1266
1543
|
throw error;
|
|
1267
1544
|
}
|
|
1268
1545
|
}
|
|
1269
|
-
function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1546
|
+
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
1270
1547
|
const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
|
|
1271
1548
|
if (!import_node_fs.default.existsSync(gitDirectory)) {
|
|
1272
1549
|
return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
|
|
@@ -1278,16 +1555,18 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1278
1555
|
ignoreBusy: options.ignoreBusy
|
|
1279
1556
|
});
|
|
1280
1557
|
}
|
|
1281
|
-
function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1558
|
+
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
1282
1559
|
workspacePath = import_node_path.default.resolve(workspacePath);
|
|
1283
1560
|
validateMounts(workspacePath, mounts);
|
|
1284
1561
|
recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
|
|
1285
1562
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1286
|
-
const currentHead =
|
|
1287
|
-
const currentReceipt =
|
|
1288
|
-
|
|
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
|
+
}
|
|
1289
1568
|
if (!currentHead) return;
|
|
1290
|
-
const outerHeadContainsMount = (mount) =>
|
|
1569
|
+
const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
|
|
1291
1570
|
const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
|
|
1292
1571
|
const uncoveredBasisMounts = configuredBasisMounts.filter(
|
|
1293
1572
|
(mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
|
|
@@ -1298,10 +1577,15 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1298
1577
|
(mount) => workspaceHydrationReceiptMountBasisHead(currentReceipt, mount) === null || !options.preserveStaleBases && !busyMountIds.has(mount.id)
|
|
1299
1578
|
);
|
|
1300
1579
|
const idleBasislessMounts = recoveryMounts.filter((mount) => !busyMountIds.has(mount.id));
|
|
1301
|
-
const hydrationTargets =
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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({
|
|
1305
1589
|
workspacePath,
|
|
1306
1590
|
mounts: hydrationTargets,
|
|
1307
1591
|
durabilityMounts: idleBasislessMounts,
|
|
@@ -1312,27 +1596,31 @@ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
|
1312
1596
|
});
|
|
1313
1597
|
void hydration;
|
|
1314
1598
|
}
|
|
1315
|
-
function resetWorkspaceGit(input) {
|
|
1599
|
+
async function resetWorkspaceGit(input) {
|
|
1316
1600
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1317
1601
|
validateMounts(workspacePath, input.mounts);
|
|
1318
1602
|
const busyResetMountIds = input.mounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
|
|
1319
1603
|
if (busyResetMountIds.length > 0) {
|
|
1320
1604
|
throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
|
|
1321
1605
|
}
|
|
1322
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1606
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
|
|
1323
1607
|
recoverPendingHydrationTransaction(workspacePath);
|
|
1324
1608
|
const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
|
|
1325
1609
|
const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
|
|
1326
|
-
const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1610
|
+
const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1327
1611
|
if (remoteHead) {
|
|
1328
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1329
|
-
|
|
1330
|
-
|
|
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");
|
|
1331
1615
|
});
|
|
1332
1616
|
} else if (revParse(workspacePath, "HEAD")) {
|
|
1333
|
-
runWorkspaceCheckoutTransition(workspacePath, () => {
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
+
);
|
|
1336
1624
|
});
|
|
1337
1625
|
}
|
|
1338
1626
|
const selected = activeMounts(input.mounts);
|
|
@@ -1342,14 +1630,17 @@ function resetWorkspaceGit(input) {
|
|
|
1342
1630
|
const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1343
1631
|
return import_node_fs.default.existsSync(outerPath);
|
|
1344
1632
|
});
|
|
1633
|
+
const bootstrapMountIds = new Set(bootstrapMounts.map(({ id }) => id));
|
|
1634
|
+
const durablyAbsentMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode === "all");
|
|
1635
|
+
const uncoveredSkippedMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode !== "all");
|
|
1345
1636
|
const hydrationMounts = [...selected.active, ...bootstrapMounts];
|
|
1346
1637
|
const requiredLiveMounts = [
|
|
1347
1638
|
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
1348
1639
|
];
|
|
1349
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1640
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1350
1641
|
workspacePath,
|
|
1351
1642
|
mounts: hydrationMounts,
|
|
1352
|
-
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
|
|
1643
|
+
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1353
1644
|
receiptMounts: input.mounts,
|
|
1354
1645
|
requiredMounts: requiredLiveMounts,
|
|
1355
1646
|
recordCurrentHead: true
|
|
@@ -1365,11 +1656,11 @@ function resetWorkspaceGit(input) {
|
|
|
1365
1656
|
remoteHead,
|
|
1366
1657
|
discardedPaths,
|
|
1367
1658
|
activeMountIds: hydration.hydratedMountIds,
|
|
1368
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...
|
|
1659
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1369
1660
|
};
|
|
1370
1661
|
}
|
|
1371
|
-
function stagedPaths(workspacePath) {
|
|
1372
|
-
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();
|
|
1373
1664
|
}
|
|
1374
1665
|
function emptyTreeHash(workspacePath) {
|
|
1375
1666
|
const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, "hash-object", "-t", "tree", "--stdin"], {
|
|
@@ -1383,9 +1674,9 @@ function emptyTreeHash(workspacePath) {
|
|
|
1383
1674
|
throw new Error(`Create empty workspace tree: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
|
|
1384
1675
|
return result.stdout.toString().trim();
|
|
1385
1676
|
}
|
|
1386
|
-
function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1677
|
+
async function changedPaths(workspacePath, baseRevision, headRevision) {
|
|
1387
1678
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
1388
|
-
return
|
|
1679
|
+
return (await gitAsync(workspacePath, ["diff", "--name-only", "--no-renames", "-z", base, headRevision], "inspect workspace commit paths")).split("\0").filter(Boolean).sort();
|
|
1389
1680
|
}
|
|
1390
1681
|
async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit) {
|
|
1391
1682
|
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
@@ -1428,15 +1719,15 @@ function snapshotConflict(input) {
|
|
|
1428
1719
|
git(input.workspacePath, ["update-ref", refs.remote, input.remoteHead], "snapshot remote workspace conflict head");
|
|
1429
1720
|
return refs;
|
|
1430
1721
|
}
|
|
1431
|
-
function rebaseWorkspace(input) {
|
|
1722
|
+
async function rebaseWorkspace(input) {
|
|
1432
1723
|
const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
|
|
1433
1724
|
beginWorkspaceCheckoutTransition(input.workspacePath);
|
|
1434
|
-
const rebase =
|
|
1725
|
+
const rebase = await gitResultAsync(
|
|
1435
1726
|
input.workspacePath,
|
|
1436
1727
|
input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
|
|
1437
1728
|
);
|
|
1438
1729
|
if (rebase.exitCode === 0) {
|
|
1439
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1730
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1440
1731
|
return { ok: true };
|
|
1441
1732
|
}
|
|
1442
1733
|
const conflicts = conflictPaths(input.workspacePath);
|
|
@@ -1445,7 +1736,7 @@ function rebaseWorkspace(input) {
|
|
|
1445
1736
|
if (abort.exitCode !== 0) {
|
|
1446
1737
|
throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
|
|
1447
1738
|
}
|
|
1448
|
-
completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1739
|
+
await completeWorkspaceCheckoutTransition(input.workspacePath);
|
|
1449
1740
|
return {
|
|
1450
1741
|
ok: false,
|
|
1451
1742
|
conflictPaths: conflicts,
|
|
@@ -1453,13 +1744,13 @@ function rebaseWorkspace(input) {
|
|
|
1453
1744
|
error: rebase.stderr || rebase.stdout || `Workspace rebase onto ${input.remoteHead} conflicted`
|
|
1454
1745
|
};
|
|
1455
1746
|
}
|
|
1456
|
-
function synchronizeWithFetchedHead(input) {
|
|
1457
|
-
const localHead =
|
|
1747
|
+
async function synchronizeWithFetchedHead(input) {
|
|
1748
|
+
const localHead = await revParseAsync(input.workspacePath, "HEAD");
|
|
1458
1749
|
const remoteHead = input.remoteHead;
|
|
1459
1750
|
if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
|
|
1460
1751
|
if (!localHead) {
|
|
1461
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1462
|
-
|
|
1752
|
+
await runWorkspaceCheckoutTransition(input.workspacePath, async () => {
|
|
1753
|
+
await gitAsync(
|
|
1463
1754
|
input.workspacePath,
|
|
1464
1755
|
["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
|
|
1465
1756
|
"check out fetched workspace main"
|
|
@@ -1468,35 +1759,35 @@ function synchronizeWithFetchedHead(input) {
|
|
|
1468
1759
|
return { kind: "ready", updated: true, rebased: false };
|
|
1469
1760
|
}
|
|
1470
1761
|
if (localHead === remoteHead) {
|
|
1471
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1762
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1472
1763
|
return { kind: "ready", updated: false, rebased: false };
|
|
1473
1764
|
}
|
|
1474
|
-
if (
|
|
1475
|
-
runWorkspaceCheckoutTransition(input.workspacePath, () => {
|
|
1476
|
-
|
|
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");
|
|
1477
1768
|
});
|
|
1478
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1769
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1479
1770
|
return { kind: "ready", updated: true, rebased: false };
|
|
1480
1771
|
}
|
|
1481
|
-
if (
|
|
1482
|
-
updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1772
|
+
if (await tryGitAsync(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
|
|
1773
|
+
await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1483
1774
|
return { kind: "ready", updated: false, rebased: false };
|
|
1484
1775
|
}
|
|
1485
|
-
const integratedHead =
|
|
1776
|
+
const integratedHead = await revParseAsync(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
|
|
1486
1777
|
const truncatedHistoryBase = workspaceIsShallow(input.workspacePath) && integratedHead && tryGit(input.workspacePath, ["merge-base", "--is-ancestor", integratedHead, localHead]) ? integratedHead : void 0;
|
|
1487
1778
|
if (workspaceIsShallow(input.workspacePath) && !truncatedHistoryBase) {
|
|
1488
1779
|
throw new Error(
|
|
1489
1780
|
"Workspace history was truncated before this clone's integrated base could be identified; reset the workspace clone before retrying"
|
|
1490
1781
|
);
|
|
1491
1782
|
}
|
|
1492
|
-
const rebased = rebaseWorkspace({
|
|
1783
|
+
const rebased = await rebaseWorkspace({
|
|
1493
1784
|
workspacePath: input.workspacePath,
|
|
1494
1785
|
localHead,
|
|
1495
1786
|
remoteHead,
|
|
1496
1787
|
attemptId: input.attemptId,
|
|
1497
1788
|
truncatedHistoryBase
|
|
1498
1789
|
});
|
|
1499
|
-
if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1790
|
+
if (rebased.ok) await updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
|
|
1500
1791
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
1501
1792
|
}
|
|
1502
1793
|
async function synchronizeWorkspaceGit(input) {
|
|
@@ -1507,7 +1798,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1507
1798
|
validateMounts(workspacePath, input.mounts);
|
|
1508
1799
|
const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
|
|
1509
1800
|
const preserveResolutionInProgress = input.skipMountMirror === true;
|
|
1510
|
-
const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1801
|
+
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
1511
1802
|
assertWorkspaceRemediationAncestry({
|
|
1512
1803
|
workspacePath,
|
|
1513
1804
|
currentHead: initial.localHead,
|
|
@@ -1516,7 +1807,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1516
1807
|
});
|
|
1517
1808
|
const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
|
|
1518
1809
|
const startingHead = initial.localHead;
|
|
1519
|
-
const receiptBeforeInitialHydration =
|
|
1810
|
+
const receiptBeforeInitialHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1520
1811
|
const deferredMountIds = /* @__PURE__ */ new Set();
|
|
1521
1812
|
if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead) {
|
|
1522
1813
|
for (const mount of input.mounts) {
|
|
@@ -1525,7 +1816,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1525
1816
|
}
|
|
1526
1817
|
}
|
|
1527
1818
|
}
|
|
1528
|
-
recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1819
|
+
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1529
1820
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1530
1821
|
preserveResolutionInProgress,
|
|
1531
1822
|
preserveStaleBases: true
|
|
@@ -1536,8 +1827,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1536
1827
|
);
|
|
1537
1828
|
const cycleSkippedMountIds = new Set(selected.skipped.map(({ id }) => id));
|
|
1538
1829
|
const receiptRequiredLiveMounts = selected.active;
|
|
1539
|
-
const projectionBasisHead =
|
|
1540
|
-
const projectionBasisReceipt =
|
|
1830
|
+
const projectionBasisHead = await revParseAsync(workspacePath, "HEAD");
|
|
1831
|
+
const projectionBasisReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1541
1832
|
const allProjectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1542
1833
|
const fastProjectionMounts = [];
|
|
1543
1834
|
const mergedProjectionTrees = /* @__PURE__ */ new Map();
|
|
@@ -1578,6 +1869,10 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1578
1869
|
projectionWarning = mergeSupport.error ?? "Git 2.40 or newer is required for stale workspace mount projection";
|
|
1579
1870
|
continue;
|
|
1580
1871
|
}
|
|
1872
|
+
if (cycleSkippedMountIds.has(mount.id) || mount.busy?.()) {
|
|
1873
|
+
cycleSkippedMountIds.add(mount.id);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1581
1876
|
const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
|
|
1582
1877
|
workspacePath,
|
|
1583
1878
|
mount: {
|
|
@@ -1612,14 +1907,15 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1612
1907
|
}
|
|
1613
1908
|
mergedProjectionTrees.set(mount.id, merged.resultTree);
|
|
1614
1909
|
mergedProjectionMounts.push(mount);
|
|
1910
|
+
await yieldToWorkerEventLoop();
|
|
1615
1911
|
}
|
|
1616
1912
|
}
|
|
1617
1913
|
if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
|
|
1618
1914
|
`);
|
|
1619
|
-
const completedMountSelection = (requireCurrentHeadReceipt = false) => {
|
|
1620
|
-
const currentHeadBeforeHydration =
|
|
1915
|
+
const completedMountSelection = async (requireCurrentHeadReceipt = false) => {
|
|
1916
|
+
const currentHeadBeforeHydration = await revParseAsync(workspacePath, "HEAD");
|
|
1621
1917
|
const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
|
|
1622
|
-
const currentReceiptBeforeHydration =
|
|
1918
|
+
const currentReceiptBeforeHydration = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1623
1919
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1624
1920
|
const mountIsAlreadyCovered = (mount) => Boolean(
|
|
1625
1921
|
currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
|
|
@@ -1636,7 +1932,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1636
1932
|
const uncoveredCompletionBasisMounts = completionReceiptMounts.filter(
|
|
1637
1933
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !projectionSkippedIds.has(mount.id) && !mountIsAlreadyCovered(mount)
|
|
1638
1934
|
);
|
|
1639
|
-
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1935
|
+
const hydration = await hydrateWorkspaceGitMountsTransactionally({
|
|
1640
1936
|
workspacePath,
|
|
1641
1937
|
mounts: uncoveredCompletionMounts,
|
|
1642
1938
|
durabilityMounts: uncoveredCompletionBasisMounts,
|
|
@@ -1647,8 +1943,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1647
1943
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
1648
1944
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
|
1649
1945
|
if (requireCurrentHeadReceipt) {
|
|
1650
|
-
const currentHead =
|
|
1651
|
-
const receipt =
|
|
1946
|
+
const currentHead = await revParseAsync(workspacePath, "HEAD");
|
|
1947
|
+
const receipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
1652
1948
|
const transactionSkippedMountIds = new Set(hydration.skippedMountIds);
|
|
1653
1949
|
const idleMounts = completionReceiptMounts.filter(
|
|
1654
1950
|
(mount) => !cycleSkippedMountIds.has(mount.id) && !transactionSkippedMountIds.has(mount.id) && !mount.busy?.() && !projectionSkippedIds.has(mount.id)
|
|
@@ -1660,17 +1956,17 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1660
1956
|
return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
|
|
1661
1957
|
};
|
|
1662
1958
|
let newlyStagedPaths = [];
|
|
1663
|
-
const stageAndCommitWorkspace = () => {
|
|
1664
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1959
|
+
const stageAndCommitWorkspace = async () => {
|
|
1960
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1665
1961
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1666
1962
|
}
|
|
1667
|
-
|
|
1668
|
-
newlyStagedPaths = stagedPaths(workspacePath);
|
|
1963
|
+
await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
1964
|
+
newlyStagedPaths = await stagedPaths(workspacePath);
|
|
1669
1965
|
if (newlyStagedPaths.length > 0) {
|
|
1670
|
-
if (workspaceResolutionInProgress(workspacePath) || workspaceHasUnmergedEntries(workspacePath)) {
|
|
1966
|
+
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
1671
1967
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
1672
1968
|
}
|
|
1673
|
-
|
|
1969
|
+
await gitAsync(
|
|
1674
1970
|
workspacePath,
|
|
1675
1971
|
[
|
|
1676
1972
|
"commit",
|
|
@@ -1681,64 +1977,77 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1681
1977
|
);
|
|
1682
1978
|
}
|
|
1683
1979
|
};
|
|
1980
|
+
const projectedFastMounts = [];
|
|
1684
1981
|
if (!input.skipMountMirror) {
|
|
1685
1982
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
1686
1983
|
try {
|
|
1687
1984
|
const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
|
|
1688
1985
|
const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
|
|
1689
1986
|
const fastTombstones = fastProjectionMounts.filter(({ id }) => !selectedActiveIds.has(id));
|
|
1690
|
-
|
|
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
|
+
}
|
|
1691
1996
|
removeWorkspaceMounts(workspacePath, fastTombstones);
|
|
1997
|
+
projectedFastMounts.push(...fastTombstones);
|
|
1692
1998
|
for (const mount of mergedProjectionMounts) {
|
|
1693
1999
|
(0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
|
|
1694
2000
|
workspacePath,
|
|
1695
2001
|
workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
|
|
1696
2002
|
resultTree: mergedProjectionTrees.get(mount.id)
|
|
1697
2003
|
});
|
|
2004
|
+
await yieldToWorkerEventLoop();
|
|
1698
2005
|
}
|
|
1699
|
-
const restoreFromHeadIds = /* @__PURE__ */ new Set([...deferredMountIds, ...selected.skipped.map(({ id }) => id)]);
|
|
1700
2006
|
restoreDeferredWorkspaceMountsFromHead(
|
|
1701
2007
|
workspacePath,
|
|
1702
|
-
input.mounts.filter(({ id }) =>
|
|
2008
|
+
input.mounts.filter(({ id }) => deferredMountIds.has(id))
|
|
1703
2009
|
);
|
|
1704
|
-
stageAndCommitWorkspace();
|
|
1705
|
-
|
|
2010
|
+
await stageAndCommitWorkspace();
|
|
2011
|
+
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
1706
2012
|
} catch (error) {
|
|
1707
2013
|
throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
|
|
1708
2014
|
}
|
|
1709
2015
|
} else {
|
|
1710
|
-
stageAndCommitWorkspace();
|
|
1711
|
-
if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
|
|
2016
|
+
await stageAndCommitWorkspace();
|
|
2017
|
+
if (newlyStagedPaths.length > 0) await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1712
2018
|
}
|
|
1713
2019
|
if (!input.skipMountMirror) {
|
|
1714
|
-
const projectedHead =
|
|
2020
|
+
const projectedHead = await revParseAsync(workspacePath, "HEAD");
|
|
1715
2021
|
if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
|
|
1716
2022
|
const targetProjectionReceipt = receiptWithMountsAtHead(
|
|
1717
2023
|
projectionBasisReceipt,
|
|
1718
2024
|
projectedHead,
|
|
1719
|
-
|
|
2025
|
+
projectedFastMounts,
|
|
1720
2026
|
allProjectionReceiptMounts
|
|
1721
2027
|
);
|
|
1722
|
-
const currentProjectionReceipt =
|
|
1723
|
-
if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !
|
|
1724
|
-
|
|
1725
|
-
|
|
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);
|
|
1726
2035
|
}
|
|
1727
|
-
completeWorkspaceCheckoutTransition(workspacePath);
|
|
2036
|
+
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
1728
2037
|
}
|
|
1729
|
-
let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2038
|
+
let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1730
2039
|
let rebaseCount = 0;
|
|
1731
2040
|
let updated = false;
|
|
1732
2041
|
for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
|
|
1733
2042
|
assertWorkspaceRemediationAncestry({
|
|
1734
2043
|
workspacePath,
|
|
1735
|
-
currentHead:
|
|
2044
|
+
currentHead: await revParseAsync(workspacePath, "HEAD"),
|
|
1736
2045
|
remoteHead,
|
|
1737
2046
|
requiredAncestorHeads
|
|
1738
2047
|
});
|
|
1739
|
-
const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
2048
|
+
const reconciled = await synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
|
|
1740
2049
|
if (reconciled.kind === "conflict") {
|
|
1741
|
-
const localHead2 =
|
|
2050
|
+
const localHead2 = await revParseAsync(workspacePath, "HEAD");
|
|
1742
2051
|
const classifiedMounts = classifyMounts([...fastProjectionMounts, ...mergedProjectionMounts], deferredMountIds);
|
|
1743
2052
|
return {
|
|
1744
2053
|
outcome: "conflict_blocked",
|
|
@@ -1759,8 +2068,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1759
2068
|
}
|
|
1760
2069
|
updated ||= reconciled.updated;
|
|
1761
2070
|
if (reconciled.rebased) rebaseCount += 1;
|
|
1762
|
-
if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
|
|
1763
|
-
const localHead =
|
|
2071
|
+
if (reconciled.updated || reconciled.rebased) await completedMountSelection(true);
|
|
2072
|
+
const localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1764
2073
|
if (!localHead) {
|
|
1765
2074
|
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones], deferredMountIds);
|
|
1766
2075
|
return {
|
|
@@ -1777,7 +2086,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1777
2086
|
...projectionWarning ? { error: projectionWarning } : {}
|
|
1778
2087
|
};
|
|
1779
2088
|
}
|
|
1780
|
-
const paths = changedPaths(workspacePath, remoteHead, localHead);
|
|
2089
|
+
const paths = await changedPaths(workspacePath, remoteHead, localHead);
|
|
1781
2090
|
const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
|
|
1782
2091
|
input.assertStillAdmitted?.();
|
|
1783
2092
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
@@ -1798,7 +2107,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1798
2107
|
}
|
|
1799
2108
|
if (localHead === remoteHead) {
|
|
1800
2109
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1801
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2110
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1802
2111
|
await input.afterWorkspacePublished?.({
|
|
1803
2112
|
publishedHead: localHead,
|
|
1804
2113
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1826,11 +2135,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1826
2135
|
];
|
|
1827
2136
|
if (input.allowLargeDiff) pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
|
|
1828
2137
|
pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
|
|
1829
|
-
const push =
|
|
2138
|
+
const push = await gitResultAsync(workspacePath, pushArgs);
|
|
1830
2139
|
if (push.exitCode === 0) {
|
|
1831
2140
|
assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
|
|
1832
|
-
updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
1833
|
-
const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
|
|
2141
|
+
await updateIntegratedWorkspaceHead(workspacePath, localHead);
|
|
2142
|
+
const completedMounts = await completedMountSelection(Boolean(input.skipMountMirror));
|
|
1834
2143
|
await input.afterWorkspacePublished?.({
|
|
1835
2144
|
publishedHead: localHead,
|
|
1836
2145
|
activeMountIds: completedMounts.activeMountIds
|
|
@@ -1854,7 +2163,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1854
2163
|
${push.stdout}`)) {
|
|
1855
2164
|
throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
|
|
1856
2165
|
}
|
|
1857
|
-
remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2166
|
+
remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
1858
2167
|
}
|
|
1859
2168
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
1860
2169
|
}
|
|
@@ -1881,5 +2190,6 @@ const workspaceGitSyncTestHarness = {
|
|
|
1881
2190
|
resetWorkspaceGit,
|
|
1882
2191
|
synchronizeWorkspaceGit,
|
|
1883
2192
|
workspaceGitHydrationIsCurrent,
|
|
2193
|
+
workspaceGitMountsForRemediation,
|
|
1884
2194
|
workspaceGitSyncTestHarness
|
|
1885
2195
|
});
|