@ricsam/r5d-worker 0.0.100 → 0.0.103
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/README.md +16 -3
- package/dist/cjs/main.cjs +390 -322
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-mirror-fetch-policy.cjs +48 -7
- package/dist/cjs/project-mirror-refs-token.cjs +36 -2
- package/dist/cjs/project-worktrees.cjs +49 -32
- package/dist/cjs/working-tree-mirror.cjs +347 -99
- package/dist/cjs/workspace-git-sync.cjs +189 -28
- package/dist/cjs/workspace-merge-projection.cjs +5 -19
- package/dist/cjs/workspace-mutation-gate.cjs +35 -0
- package/dist/cjs/workspace-projection-ledger.cjs +81 -0
- package/dist/cjs/workspace-sync-coalescer.cjs +58 -0
- package/dist/mjs/main.mjs +395 -324
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-mirror-fetch-policy.mjs +46 -6
- package/dist/mjs/project-mirror-refs-token.mjs +30 -1
- package/dist/mjs/project-worktrees.mjs +52 -33
- package/dist/mjs/working-tree-mirror.mjs +347 -99
- package/dist/mjs/workspace-git-sync.mjs +189 -28
- package/dist/mjs/workspace-merge-projection.mjs +5 -19
- package/dist/mjs/workspace-mutation-gate.mjs +35 -0
- package/dist/mjs/workspace-projection-ledger.mjs +57 -0
- package/dist/mjs/workspace-sync-coalescer.mjs +34 -0
- package/dist/types/main.d.ts +29 -17
- package/dist/types/project-mirror-fetch-policy.d.ts +70 -15
- package/dist/types/project-mirror-refs-token.d.ts +34 -0
- package/dist/types/project-worktrees.d.ts +26 -8
- package/dist/types/working-tree-mirror.d.ts +56 -11
- package/dist/types/workspace-git-sync.d.ts +41 -4
- package/dist/types/workspace-mutation-gate.d.ts +24 -3
- package/dist/types/workspace-projection-ledger.d.ts +59 -0
- package/dist/types/workspace-sync-coalescer.d.ts +28 -0
- package/package.json +1 -1
|
@@ -128,6 +128,22 @@ async function gitAsync(cwd, args, action) {
|
|
|
128
128
|
if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
|
|
129
129
|
return result.stdout;
|
|
130
130
|
}
|
|
131
|
+
async function gitOutputAsync(cwd, args, action, input) {
|
|
132
|
+
const subprocess = Bun.spawn(gitCommandArgs(args), {
|
|
133
|
+
cwd,
|
|
134
|
+
stdin: input ?? "ignore",
|
|
135
|
+
stdout: "pipe",
|
|
136
|
+
stderr: "pipe",
|
|
137
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
138
|
+
});
|
|
139
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
140
|
+
new Response(subprocess.stdout).arrayBuffer(),
|
|
141
|
+
new Response(subprocess.stderr).text(),
|
|
142
|
+
subprocess.exited
|
|
143
|
+
]);
|
|
144
|
+
if (exitCode !== 0) throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
|
|
145
|
+
return Buffer.from(stdout);
|
|
146
|
+
}
|
|
131
147
|
async function tryGitAsync(cwd, args) {
|
|
132
148
|
return (await gitResultAsync(cwd, args)).exitCode === 0;
|
|
133
149
|
}
|
|
@@ -1139,7 +1155,13 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
|
1139
1155
|
candidate: outerPath,
|
|
1140
1156
|
label: `Workspace mount ${mount.id} outer source`
|
|
1141
1157
|
});
|
|
1142
|
-
const plan = (0, import_working_tree_mirror.planWorkingTreeMirror)({
|
|
1158
|
+
const plan = (0, import_working_tree_mirror.planWorkingTreeMirror)({
|
|
1159
|
+
sourceRoot: outerPath,
|
|
1160
|
+
targetRoot: sourcePath,
|
|
1161
|
+
sourceMode: "all",
|
|
1162
|
+
deletionMode: "git",
|
|
1163
|
+
sourceGitlinks: outerSubtreeGitlinks(workspacePath, mount)
|
|
1164
|
+
});
|
|
1143
1165
|
scope = (0, import_working_tree_mirror.captureWorkingTreeMirrorPlan)({
|
|
1144
1166
|
plan,
|
|
1145
1167
|
targetRoot: sourcePath,
|
|
@@ -1367,6 +1389,19 @@ ${result.stdout}`;
|
|
|
1367
1389
|
}
|
|
1368
1390
|
return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1369
1391
|
}
|
|
1392
|
+
async function resolveWorkspaceRemoteHead(workspacePath, input) {
|
|
1393
|
+
const advertised = input.advertisedRemoteHead;
|
|
1394
|
+
if (advertised && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(advertised)) {
|
|
1395
|
+
const tracked = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
|
|
1396
|
+
if (tracked === advertised && await tryGitAsync(workspacePath, ["cat-file", "-e", `${tracked}^{commit}`])) {
|
|
1397
|
+
return { head: tracked, fetched: false };
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
head: await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername),
|
|
1402
|
+
fetched: true
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1370
1405
|
async function ensureWorkspaceGitClone(input) {
|
|
1371
1406
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1372
1407
|
const workspaceParent = import_node_path.default.dirname(workspacePath);
|
|
@@ -1414,7 +1449,8 @@ async function ensureWorkspaceGitClone(input) {
|
|
|
1414
1449
|
if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
|
|
1415
1450
|
await updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
|
|
1416
1451
|
}
|
|
1417
|
-
const
|
|
1452
|
+
const resolvedRemoteHead = await resolveWorkspaceRemoteHead(workspacePath, input);
|
|
1453
|
+
const remoteHead = resolvedRemoteHead.head;
|
|
1418
1454
|
let localHead = await revParseAsync(workspacePath, "HEAD");
|
|
1419
1455
|
if (!localHead && remoteHead) {
|
|
1420
1456
|
await runWorkspaceCheckoutTransition(workspacePath, async () => {
|
|
@@ -1435,7 +1471,7 @@ async function ensureWorkspaceGitClone(input) {
|
|
|
1435
1471
|
await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
|
|
1436
1472
|
localHead = remoteHead;
|
|
1437
1473
|
}
|
|
1438
|
-
return { localHead, remoteHead };
|
|
1474
|
+
return { localHead, remoteHead, remoteHeadFetched: resolvedRemoteHead.fetched };
|
|
1439
1475
|
}
|
|
1440
1476
|
function activeMounts(mounts) {
|
|
1441
1477
|
const active = [];
|
|
@@ -1450,6 +1486,7 @@ function activeMounts(mounts) {
|
|
|
1450
1486
|
return { active, tombstones, skipped };
|
|
1451
1487
|
}
|
|
1452
1488
|
function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
1489
|
+
const projected = [];
|
|
1453
1490
|
for (const mount of mounts) {
|
|
1454
1491
|
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1455
1492
|
trustedRoot: mount.durabilityRootPath,
|
|
@@ -1461,13 +1498,61 @@ function mirrorMountsToWorkspace(workspacePath, mounts) {
|
|
|
1461
1498
|
candidate: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
1462
1499
|
label: `Workspace mount ${mount.id} outer target`
|
|
1463
1500
|
});
|
|
1464
|
-
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
1501
|
+
const mirrored = (0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
1465
1502
|
sourceRoot: mount.sourcePath,
|
|
1466
1503
|
targetRoot: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
|
|
1467
1504
|
sourceMode: mount.sourceMode,
|
|
1468
1505
|
deletionMode: "all"
|
|
1469
1506
|
});
|
|
1507
|
+
projected.push({ mount, gitlinks: mirrored.gitlinks });
|
|
1470
1508
|
}
|
|
1509
|
+
return projected;
|
|
1510
|
+
}
|
|
1511
|
+
async function stageWorkspaceGitlinks(workspacePath, projected) {
|
|
1512
|
+
if (projected.length === 0) return;
|
|
1513
|
+
const mountPaths = projected.map(({ mount }) => normalizedWorkspaceMountPath(mount.workspaceRelativePath));
|
|
1514
|
+
const desired = /* @__PURE__ */ new Map();
|
|
1515
|
+
projected.forEach(({ gitlinks }, index) => {
|
|
1516
|
+
for (const gitlink of gitlinks) desired.set(`${mountPaths[index]}/${gitlink.relativePath}`, gitlink.objectId);
|
|
1517
|
+
});
|
|
1518
|
+
const desiredRoots = new Set(desired.keys());
|
|
1519
|
+
const listed = await gitOutputAsync(
|
|
1520
|
+
workspacePath,
|
|
1521
|
+
["ls-files", "-z", "--stage", "--", ...mountPaths.map((mountPath) => `:(literal)${mountPath}`)],
|
|
1522
|
+
"inspect staged workspace mount entries"
|
|
1523
|
+
);
|
|
1524
|
+
const feed = [];
|
|
1525
|
+
const current = /* @__PURE__ */ new Map();
|
|
1526
|
+
const drop = (objectId, relativePath) => {
|
|
1527
|
+
feed.push(Buffer.concat([Buffer.from(`0 ${"0".repeat(objectId.length)} `), Buffer.from(relativePath), Buffer.from([0])]));
|
|
1528
|
+
};
|
|
1529
|
+
for (const record of listed.toString().split("\0")) {
|
|
1530
|
+
const match = /^([0-7]{6}) ([0-9a-f]{40,64}) [0-3]\t(.+)$/su.exec(record);
|
|
1531
|
+
if (!match) continue;
|
|
1532
|
+
const mode = match[1];
|
|
1533
|
+
const objectId = match[2];
|
|
1534
|
+
const relativePath = match[3];
|
|
1535
|
+
if (mode === "160000") {
|
|
1536
|
+
current.set(relativePath, objectId);
|
|
1537
|
+
if (!desired.has(relativePath)) drop(objectId, relativePath);
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (desired.has(relativePath) || hasAncestorIn(relativePath, desiredRoots)) drop(objectId, relativePath);
|
|
1541
|
+
}
|
|
1542
|
+
for (const [relativePath, objectId] of desired) {
|
|
1543
|
+
if (current.get(relativePath) === objectId) continue;
|
|
1544
|
+
feed.push(Buffer.concat([Buffer.from(`160000 ${objectId} `), Buffer.from(relativePath), Buffer.from([0])]));
|
|
1545
|
+
}
|
|
1546
|
+
if (feed.length === 0) return;
|
|
1547
|
+
await gitOutputAsync(workspacePath, ["update-index", "-z", "--index-info"], "stage workspace gitlinks", Buffer.concat(feed));
|
|
1548
|
+
}
|
|
1549
|
+
function hasAncestorIn(relativePath, ancestors) {
|
|
1550
|
+
let parent = import_node_path.default.posix.dirname(relativePath);
|
|
1551
|
+
while (parent !== ".") {
|
|
1552
|
+
if (ancestors.has(parent)) return true;
|
|
1553
|
+
parent = import_node_path.default.posix.dirname(parent);
|
|
1554
|
+
}
|
|
1555
|
+
return false;
|
|
1471
1556
|
}
|
|
1472
1557
|
function removeWorkspaceMounts(workspacePath, mounts) {
|
|
1473
1558
|
for (const mount of mounts) {
|
|
@@ -1507,6 +1592,24 @@ function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
|
|
|
1507
1592
|
function workspaceMountOuterPath(workspacePath, mount) {
|
|
1508
1593
|
return import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1509
1594
|
}
|
|
1595
|
+
function outerSubtreeGitlinks(workspacePath, mount) {
|
|
1596
|
+
const gitlinks = /* @__PURE__ */ new Map();
|
|
1597
|
+
const head = revParse(workspacePath, "HEAD");
|
|
1598
|
+
if (!head) return gitlinks;
|
|
1599
|
+
const subtree = `${head}:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`;
|
|
1600
|
+
const listed = Bun.spawnSync(gitCommandArgs(["ls-tree", "-r", "-z", subtree]), {
|
|
1601
|
+
cwd: workspacePath,
|
|
1602
|
+
stdout: "pipe",
|
|
1603
|
+
stderr: "pipe",
|
|
1604
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
1605
|
+
});
|
|
1606
|
+
if (listed.exitCode !== 0) return gitlinks;
|
|
1607
|
+
for (const record of Buffer.from(listed.stdout).toString().split("\0")) {
|
|
1608
|
+
const match = /^160000 commit ([0-9a-f]{40,64})\t(.+)$/su.exec(record);
|
|
1609
|
+
if (match) gitlinks.set(match[2], match[1]);
|
|
1610
|
+
}
|
|
1611
|
+
return gitlinks;
|
|
1612
|
+
}
|
|
1510
1613
|
function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
1511
1614
|
const hydratedMountIds = [];
|
|
1512
1615
|
const skippedMountIds = [];
|
|
@@ -1550,6 +1653,9 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1550
1653
|
sourceRoot,
|
|
1551
1654
|
targetRoot: mount.sourcePath,
|
|
1552
1655
|
sourceMode: "all",
|
|
1656
|
+
// The outer worktree shows a submodule as an empty directory; the tree
|
|
1657
|
+
// carries its pointer, which a Git checkout applies to its index (D4).
|
|
1658
|
+
sourceGitlinks: outerSubtreeGitlinks(workspacePath, mount),
|
|
1553
1659
|
// A missing checkout has no Git index to define git-only deletions. Its
|
|
1554
1660
|
// newly created directory is empty, so all-mode is equivalent and lets
|
|
1555
1661
|
// bootstrap hydration preserve the fetched tree before Git initializes.
|
|
@@ -1565,7 +1671,7 @@ async function selectMountsWithUnchangedOuterSubtree(workspacePath, receipt, hea
|
|
|
1565
1671
|
return basisHead && basisHead !== head ? [{ mount, basisHead }] : [];
|
|
1566
1672
|
});
|
|
1567
1673
|
if (candidates.length === 0) return /* @__PURE__ */ new Set();
|
|
1568
|
-
const objects = await
|
|
1674
|
+
const objects = await resolveGitObjectsAsync(
|
|
1569
1675
|
workspacePath,
|
|
1570
1676
|
candidates.flatMap(({ mount, basisHead }) => [
|
|
1571
1677
|
workspaceTreeObjectName(basisHead, mount.workspaceRelativePath),
|
|
@@ -1574,8 +1680,8 @@ async function selectMountsWithUnchangedOuterSubtree(workspacePath, receipt, hea
|
|
|
1574
1680
|
);
|
|
1575
1681
|
const unchanged = /* @__PURE__ */ new Set();
|
|
1576
1682
|
for (const { mount, basisHead } of candidates) {
|
|
1577
|
-
const before = objects.get(workspaceTreeObjectName(basisHead, mount.workspaceRelativePath)) ?? null;
|
|
1578
|
-
const after = objects.get(workspaceTreeObjectName(head, mount.workspaceRelativePath)) ?? null;
|
|
1683
|
+
const before = objects.get(workspaceTreeObjectName(basisHead, mount.workspaceRelativePath))?.objectId ?? null;
|
|
1684
|
+
const after = objects.get(workspaceTreeObjectName(head, mount.workspaceRelativePath))?.objectId ?? null;
|
|
1579
1685
|
if (before === after) unchanged.add(mount.id);
|
|
1580
1686
|
}
|
|
1581
1687
|
return unchanged;
|
|
@@ -1841,7 +1947,7 @@ async function diffSizeBytes(workspacePath, baseRevision, headRevision, limit, p
|
|
|
1841
1947
|
if (exitCode !== 0) throw new Error(`Measure workspace diff: ${stderr || `git exited ${exitCode}`}`);
|
|
1842
1948
|
return total;
|
|
1843
1949
|
}
|
|
1844
|
-
async function
|
|
1950
|
+
async function resolveGitObjectsAsync(workspacePath, names) {
|
|
1845
1951
|
const resolved = /* @__PURE__ */ new Map();
|
|
1846
1952
|
const unique = [...new Set(names)];
|
|
1847
1953
|
if (unique.length === 0) return resolved;
|
|
@@ -1864,21 +1970,43 @@ async function resolveGitObjectIdsAsync(workspacePath, names) {
|
|
|
1864
1970
|
throw new Error(`Resolve workspace objects: expected ${unique.length} results, received ${lines.length}`);
|
|
1865
1971
|
}
|
|
1866
1972
|
unique.forEach((name, index) => {
|
|
1867
|
-
const match = /^([0-9a-f]{40,64}) \S+ \d+$/.exec(lines[index]);
|
|
1868
|
-
resolved.set(name, match ? match[1] : null);
|
|
1973
|
+
const match = /^([0-9a-f]{40,64}) (\S+) \d+$/.exec(lines[index]);
|
|
1974
|
+
resolved.set(name, match ? { objectId: match[1], type: match[2] } : null);
|
|
1869
1975
|
});
|
|
1870
1976
|
return resolved;
|
|
1871
1977
|
}
|
|
1872
1978
|
function workspaceTreeObjectName(revision, workspaceRelativePath) {
|
|
1873
1979
|
return `${revision}:${normalizedWorkspaceMountPath(workspaceRelativePath)}`;
|
|
1874
1980
|
}
|
|
1981
|
+
async function pathsReplacedByGitlinks(workspacePath, baseRevision, headRevision) {
|
|
1982
|
+
const base = baseRevision ?? emptyTreeHash(workspacePath);
|
|
1983
|
+
const raw = await gitOutputAsync(
|
|
1984
|
+
workspacePath,
|
|
1985
|
+
["diff", "--raw", "--no-renames", "-z", base, headRevision],
|
|
1986
|
+
"inspect workspace gitlink changes"
|
|
1987
|
+
);
|
|
1988
|
+
const records = raw.toString().split("\0");
|
|
1989
|
+
const addedGitlinks = [];
|
|
1990
|
+
for (let index = 0; index + 1 < records.length; index += 2) {
|
|
1991
|
+
if (/^:[0-7]{6} 160000 \S+ \S+ A$/u.test(records[index])) addedGitlinks.push(records[index + 1]);
|
|
1992
|
+
}
|
|
1993
|
+
if (addedGitlinks.length === 0) return [];
|
|
1994
|
+
const objects = await resolveGitObjectsAsync(
|
|
1995
|
+
workspacePath,
|
|
1996
|
+
addedGitlinks.map((relativePath) => `${base}:${relativePath}`)
|
|
1997
|
+
);
|
|
1998
|
+
return addedGitlinks.filter((relativePath) => objects.get(`${base}:${relativePath}`)?.type === "tree").sort();
|
|
1999
|
+
}
|
|
2000
|
+
function gitlinkMigrationPathspecs(paths) {
|
|
2001
|
+
return paths.map((relativePath) => `:(exclude,literal)${relativePath}`);
|
|
2002
|
+
}
|
|
1875
2003
|
async function measureInitialPublicationDiffSize(input) {
|
|
1876
2004
|
const candidates = input.mounts.filter(
|
|
1877
2005
|
(mount) => typeof mount.initialPublicationSourceRelativePath === "string"
|
|
1878
2006
|
);
|
|
1879
2007
|
if (candidates.length === 0) return null;
|
|
1880
2008
|
const revisions = [input.localHead, ...input.remoteHead ? [input.remoteHead] : []];
|
|
1881
|
-
const objects = await
|
|
2009
|
+
const objects = await resolveGitObjectsAsync(
|
|
1882
2010
|
input.workspacePath,
|
|
1883
2011
|
candidates.flatMap(
|
|
1884
2012
|
(mount) => revisions.flatMap((revision) => [
|
|
@@ -1887,7 +2015,7 @@ async function measureInitialPublicationDiffSize(input) {
|
|
|
1887
2015
|
])
|
|
1888
2016
|
)
|
|
1889
2017
|
);
|
|
1890
|
-
const lookup = (revision, workspaceRelativePath) => revision ? objects.get(workspaceTreeObjectName(revision, workspaceRelativePath)) ?? null : null;
|
|
2018
|
+
const lookup = (revision, workspaceRelativePath) => revision ? objects.get(workspaceTreeObjectName(revision, workspaceRelativePath))?.objectId ?? null : null;
|
|
1891
2019
|
const initialPublications = candidates.flatMap((mount) => {
|
|
1892
2020
|
const published = lookup(input.localHead, mount.workspaceRelativePath);
|
|
1893
2021
|
if (!published || lookup(input.remoteHead, mount.workspaceRelativePath)) return [];
|
|
@@ -1895,16 +2023,14 @@ async function measureInitialPublicationDiffSize(input) {
|
|
|
1895
2023
|
return [{ mount, published, source }];
|
|
1896
2024
|
});
|
|
1897
2025
|
if (initialPublications.length === 0) return null;
|
|
1898
|
-
let size = await input.measure(
|
|
1899
|
-
|
|
1900
|
-
input.
|
|
1901
|
-
|
|
1902
|
-
input.limit,
|
|
1903
|
-
initialPublications.map(({ mount }) => `:(exclude,literal)${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`)
|
|
1904
|
-
);
|
|
2026
|
+
let size = await input.measure(input.workspacePath, input.remoteHead, input.localHead, input.limit, [
|
|
2027
|
+
...initialPublications.map(({ mount }) => `:(exclude,literal)${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`),
|
|
2028
|
+
...input.excludePathspecs ?? []
|
|
2029
|
+
]);
|
|
1905
2030
|
for (const { published, source } of initialPublications) {
|
|
1906
2031
|
if (size > input.limit) break;
|
|
1907
|
-
|
|
2032
|
+
const migrations = source ? await pathsReplacedByGitlinks(input.workspacePath, source, published) : [];
|
|
2033
|
+
size += await input.measure(input.workspacePath, source, published, input.limit - size, gitlinkMigrationPathspecs(migrations));
|
|
1908
2034
|
}
|
|
1909
2035
|
return { size, mountIds: initialPublications.map(({ mount }) => mount.id).sort() };
|
|
1910
2036
|
}
|
|
@@ -1994,6 +2120,11 @@ async function synchronizeWithFetchedHead(input) {
|
|
|
1994
2120
|
return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
|
|
1995
2121
|
}
|
|
1996
2122
|
async function synchronizeWorkspaceGit(input) {
|
|
2123
|
+
const remoteHeadFetches = { count: 0 };
|
|
2124
|
+
const result = await runWorkspaceGitSynchronization(input, remoteHeadFetches);
|
|
2125
|
+
return { ...result, remoteHeadFetches: remoteHeadFetches.count };
|
|
2126
|
+
}
|
|
2127
|
+
async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
1997
2128
|
const workspacePath = import_node_path.default.resolve(input.workspacePath);
|
|
1998
2129
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
1999
2130
|
const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
|
|
@@ -2002,6 +2133,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2002
2133
|
const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
|
|
2003
2134
|
const preserveResolutionInProgress = input.skipMountMirror === true;
|
|
2004
2135
|
const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
|
|
2136
|
+
if (initial.remoteHeadFetched) remoteHeadFetches.count += 1;
|
|
2005
2137
|
assertWorkspaceRemediationAncestry({
|
|
2006
2138
|
workspacePath,
|
|
2007
2139
|
currentHead: initial.localHead,
|
|
@@ -2049,11 +2181,22 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2049
2181
|
const classifyMounts = (activeCandidates, additionalSkippedIds = []) => {
|
|
2050
2182
|
refreshCycleSkippedMounts();
|
|
2051
2183
|
const skippedIds = /* @__PURE__ */ new Set([...cycleSkippedMountIds, ...projectionSkippedIds, ...additionalSkippedIds]);
|
|
2184
|
+
const activeMountIds = [...new Set(activeCandidates.filter(({ id }) => !skippedIds.has(id)).map(({ id }) => id))].sort();
|
|
2052
2185
|
return {
|
|
2053
|
-
activeMountIds
|
|
2054
|
-
skippedMountIds: [...skippedIds].sort()
|
|
2186
|
+
activeMountIds,
|
|
2187
|
+
skippedMountIds: [...skippedIds].sort(),
|
|
2188
|
+
// An active mount's token is unchanged since capture (a moved token
|
|
2189
|
+
// lands it in cycleSkippedMountIds above), so this is the token its
|
|
2190
|
+
// projected bytes belong to.
|
|
2191
|
+
projectedMutationTokens: Object.fromEntries(
|
|
2192
|
+
activeMountIds.flatMap((id) => {
|
|
2193
|
+
const token = projectionMutationTokens.get(id);
|
|
2194
|
+
return token === void 0 ? [] : [[id, token]];
|
|
2195
|
+
})
|
|
2196
|
+
)
|
|
2055
2197
|
};
|
|
2056
2198
|
};
|
|
2199
|
+
const mountProjectionIsCurrent = (mount) => mount.lastProjectedMutationToken !== void 0 && !mount.deleteWhenSourceMissing && mount.preserveLocalOnHydrationBasisChange !== true && projectionBasisHead !== null && workspaceHydrationReceiptMountBasisHead(projectionBasisReceipt, mount) === projectionBasisHead && projectionMutationTokens.get(mount.id) === mount.lastProjectedMutationToken && mount.mutationToken?.() === mount.lastProjectedMutationToken;
|
|
2057
2200
|
if (!input.skipMountMirror) {
|
|
2058
2201
|
const mergeProjectionEnabled = process.env.R5D_WORKSPACE_MERGE_PROJECTION !== "0";
|
|
2059
2202
|
const mergeSupport = mergeProjectionEnabled ? (0, import_workspace_merge_projection.workspaceMergeProjectionSupport)() : { supported: false, error: "Workspace merge projection is disabled by R5D_WORKSPACE_MERGE_PROJECTION=0" };
|
|
@@ -2168,10 +2311,12 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2168
2311
|
return classifyMounts([...selected.active, ...selected.tombstones], hydration.skippedMountIds);
|
|
2169
2312
|
};
|
|
2170
2313
|
let newlyStagedPaths = [];
|
|
2314
|
+
const projectedGitlinks = [];
|
|
2171
2315
|
const stageAndCommitWorkspace = async () => {
|
|
2172
2316
|
if (workspaceResolutionInProgress(workspacePath) || await workspaceHasUnmergedEntries(workspacePath)) {
|
|
2173
2317
|
throw new Error("Outer workspace clone has an unfinished merge/rebase; finish or abort it before synchronizing");
|
|
2174
2318
|
}
|
|
2319
|
+
await stageWorkspaceGitlinks(workspacePath, projectedGitlinks);
|
|
2175
2320
|
await gitAsync(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
|
|
2176
2321
|
newlyStagedPaths = await stagedPaths(workspacePath);
|
|
2177
2322
|
if (newlyStagedPaths.length > 0) {
|
|
@@ -2190,6 +2335,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2190
2335
|
}
|
|
2191
2336
|
};
|
|
2192
2337
|
const projectedFastMounts = [];
|
|
2338
|
+
const currentProjectionMounts = [];
|
|
2193
2339
|
if (!input.skipMountMirror) {
|
|
2194
2340
|
beginWorkspaceCheckoutTransition(workspacePath);
|
|
2195
2341
|
try {
|
|
@@ -2201,7 +2347,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2201
2347
|
cycleSkippedMountIds.add(mount.id);
|
|
2202
2348
|
continue;
|
|
2203
2349
|
}
|
|
2204
|
-
|
|
2350
|
+
if (mountProjectionIsCurrent(mount)) {
|
|
2351
|
+
currentProjectionMounts.push(mount);
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
projectedGitlinks.push(...mirrorMountsToWorkspace(workspacePath, [mount]));
|
|
2205
2355
|
projectedFastMounts.push(mount);
|
|
2206
2356
|
await yieldToWorkerEventLoop();
|
|
2207
2357
|
}
|
|
@@ -2234,7 +2384,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2234
2384
|
const targetProjectionReceipt = receiptWithMountsAtHead(
|
|
2235
2385
|
projectionBasisReceipt,
|
|
2236
2386
|
projectedHead,
|
|
2237
|
-
projectedFastMounts,
|
|
2387
|
+
[...projectedFastMounts, ...currentProjectionMounts],
|
|
2238
2388
|
allProjectionReceiptMounts
|
|
2239
2389
|
);
|
|
2240
2390
|
const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
|
|
@@ -2247,7 +2397,9 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2247
2397
|
}
|
|
2248
2398
|
await completeWorkspaceCheckoutTransition(workspacePath);
|
|
2249
2399
|
}
|
|
2250
|
-
|
|
2400
|
+
const prePublishRemoteHead = await resolveWorkspaceRemoteHead(workspacePath, input);
|
|
2401
|
+
if (prePublishRemoteHead.fetched) remoteHeadFetches.count += 1;
|
|
2402
|
+
let remoteHead = prePublishRemoteHead.head;
|
|
2251
2403
|
let rebaseCount = 0;
|
|
2252
2404
|
let updated = false;
|
|
2253
2405
|
for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
|
|
@@ -2300,7 +2452,9 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2300
2452
|
}
|
|
2301
2453
|
const paths = await changedPaths(workspacePath, remoteHead, localHead);
|
|
2302
2454
|
const measureDiffSize = input.measureDiffSize ?? diffSizeBytes;
|
|
2303
|
-
|
|
2455
|
+
const gitlinkMigrations = paths.length > 0 ? await pathsReplacedByGitlinks(workspacePath, remoteHead, localHead) : [];
|
|
2456
|
+
const gitlinkMigrationExcludes = gitlinkMigrationPathspecs(gitlinkMigrations);
|
|
2457
|
+
let size = paths.length > 0 ? await measureDiffSize(workspacePath, remoteHead, localHead, maxDiffBytes, gitlinkMigrationExcludes) : 0;
|
|
2304
2458
|
input.assertStillAdmitted?.();
|
|
2305
2459
|
let initialPublicationMountIds;
|
|
2306
2460
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
@@ -2310,7 +2464,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2310
2464
|
localHead,
|
|
2311
2465
|
mounts: input.mounts,
|
|
2312
2466
|
limit: maxDiffBytes,
|
|
2313
|
-
measure: measureDiffSize
|
|
2467
|
+
measure: measureDiffSize,
|
|
2468
|
+
excludePathspecs: gitlinkMigrationExcludes
|
|
2314
2469
|
});
|
|
2315
2470
|
input.assertStillAdmitted?.();
|
|
2316
2471
|
if (initialPublication && initialPublication.size <= maxDiffBytes) {
|
|
@@ -2318,6 +2473,11 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2318
2473
|
initialPublicationMountIds = initialPublication.mountIds;
|
|
2319
2474
|
}
|
|
2320
2475
|
}
|
|
2476
|
+
let gitlinkMigrationConfirmed = false;
|
|
2477
|
+
if (gitlinkMigrations.length > 0 && size <= maxDiffBytes && !input.allowLargeDiff) {
|
|
2478
|
+
gitlinkMigrationConfirmed = await measureDiffSize(workspacePath, remoteHead, localHead, maxDiffBytes) > maxDiffBytes;
|
|
2479
|
+
input.assertStillAdmitted?.();
|
|
2480
|
+
}
|
|
2321
2481
|
if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
|
|
2322
2482
|
const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones]);
|
|
2323
2483
|
return {
|
|
@@ -2362,7 +2522,7 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2362
2522
|
"push",
|
|
2363
2523
|
"--no-recurse-submodules"
|
|
2364
2524
|
];
|
|
2365
|
-
if (input.allowLargeDiff || initialPublicationMountIds) {
|
|
2525
|
+
if (input.allowLargeDiff || initialPublicationMountIds || gitlinkMigrationConfirmed) {
|
|
2366
2526
|
pushArgs.push(`--push-option=${WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION}`);
|
|
2367
2527
|
}
|
|
2368
2528
|
pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
|
|
@@ -2396,6 +2556,7 @@ ${push.stdout}`)) {
|
|
|
2396
2556
|
throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
|
|
2397
2557
|
}
|
|
2398
2558
|
remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
|
|
2559
|
+
remoteHeadFetches.count += 1;
|
|
2399
2560
|
}
|
|
2400
2561
|
throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
|
|
2401
2562
|
}
|
|
@@ -206,14 +206,6 @@ function indexMode(entry) {
|
|
|
206
206
|
function indexInfoRecord(mode, objectId, relativePath) {
|
|
207
207
|
return Buffer.concat([Buffer.from(`${mode} ${objectId} `), Buffer.from(relativePath), Buffer.from([0])]);
|
|
208
208
|
}
|
|
209
|
-
function basisGitlinks(workspacePath, basis) {
|
|
210
|
-
const listed = gitResult(workspacePath, ["ls-tree", "-r", "-z", `${basis.basisHead}:${basis.workspaceRelativePath}`]);
|
|
211
|
-
if (listed.exitCode !== 0) return [];
|
|
212
|
-
return nulRecords(listed.stdout).flatMap((record) => {
|
|
213
|
-
const match = /^160000 commit ([0-9a-f]{40,64})\t(.+)$/su.exec(record.toString());
|
|
214
|
-
return match ? [{ objectId: match[1], relativePath: match[2] }] : [];
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
209
|
function synthesizeMountTree(input) {
|
|
218
210
|
const sourceRoot = import_node_path.default.resolve(input.sourcePath);
|
|
219
211
|
const entries = (0, import_working_tree_mirror.inspectWorkingTree)(sourceRoot, input.sourceMode);
|
|
@@ -224,19 +216,14 @@ function synthesizeMountTree(input) {
|
|
|
224
216
|
const records = [];
|
|
225
217
|
for (const [relativePath, entry] of [...entries.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
226
218
|
if (entry.kind === "directory") continue;
|
|
219
|
+
if (entry.kind === "gitlink") {
|
|
220
|
+
records.push(indexInfoRecord("160000", entry.objectId, relativePath));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
227
223
|
const sourcePath = sourceEntryPath(sourceRoot, relativePath);
|
|
228
224
|
const objectId = entry.kind === "file" ? hashRegularFile(input.workspacePath, sourcePath, entry) : hashSymlink(input.workspacePath, sourcePath, entry);
|
|
229
225
|
records.push(indexInfoRecord(indexMode(entry), objectId, relativePath));
|
|
230
226
|
}
|
|
231
|
-
if (input.gitlinkBasis && input.sourceMode === "git") {
|
|
232
|
-
for (const gitlink of basisGitlinks(input.workspacePath, input.gitlinkBasis)) {
|
|
233
|
-
const populatedPrefix = `${gitlink.relativePath}/`;
|
|
234
|
-
const populated = [...entries.keys()].some((relativePath) => relativePath.startsWith(populatedPrefix));
|
|
235
|
-
if (entries.get(gitlink.relativePath)?.kind === "directory" && !populated) {
|
|
236
|
-
records.push(indexInfoRecord("160000", gitlink.objectId, gitlink.relativePath));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
227
|
if (records.length > 0) {
|
|
241
228
|
git(input.workspacePath, ["update-index", "-z", "--index-info"], "populate workspace projection index", {
|
|
242
229
|
environment,
|
|
@@ -301,8 +288,7 @@ function synthesizeOursCommit(input) {
|
|
|
301
288
|
const mountTree = synthesizeMountTree({
|
|
302
289
|
workspacePath: input.workspacePath,
|
|
303
290
|
sourcePath: input.mount.sourcePath,
|
|
304
|
-
sourceMode: input.mount.sourceMode
|
|
305
|
-
gitlinkBasis: { basisHead: input.basisHead, workspaceRelativePath: input.workspaceRelativePath }
|
|
291
|
+
sourceMode: input.mount.sourceMode
|
|
306
292
|
});
|
|
307
293
|
const rootTree = graftMountTree({
|
|
308
294
|
workspacePath: input.workspacePath,
|
|
@@ -50,8 +50,43 @@ class WorkspaceMutationGate {
|
|
|
50
50
|
release();
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
/** A worktree create/delete lease; see the branch lane in the class comment. */
|
|
54
|
+
acquireBranchMutation() {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
this.waiters.push({ kind: "branch", resolve });
|
|
57
|
+
this.drain();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async runBranchMutation(operation) {
|
|
61
|
+
const release = await this.acquireBranchMutation();
|
|
62
|
+
try {
|
|
63
|
+
return await operation();
|
|
64
|
+
} finally {
|
|
65
|
+
release();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Operations a branch mutation requested now would wait for: the running
|
|
70
|
+
* sync, if any, plus branch mutations queued behind it. Zero means it runs
|
|
71
|
+
* at once. Ordinary waiters are not counted because the branch lane admits
|
|
72
|
+
* ahead of them.
|
|
73
|
+
*/
|
|
74
|
+
branchMutationQueuePosition() {
|
|
75
|
+
if (!this.syncActive) return 0;
|
|
76
|
+
return 1 + this.waiters.filter((waiter) => waiter.kind === "branch").length;
|
|
77
|
+
}
|
|
53
78
|
drain() {
|
|
54
79
|
if (this.syncActive || this.waiters.length === 0) return;
|
|
80
|
+
for (let index = 0; index < this.waiters.length; ) {
|
|
81
|
+
const waiter = this.waiters[index];
|
|
82
|
+
if (waiter?.kind !== "branch") {
|
|
83
|
+
index += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
this.waiters.splice(index, 1);
|
|
87
|
+
this.activeMutations += 1;
|
|
88
|
+
waiter.resolve(this.releaseMutation());
|
|
89
|
+
}
|
|
55
90
|
const first = this.waiters[0];
|
|
56
91
|
if (first?.kind === "sync") {
|
|
57
92
|
if (this.activeMutations > 0) return;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var workspace_projection_ledger_exports = {};
|
|
20
|
+
__export(workspace_projection_ledger_exports, {
|
|
21
|
+
WorkspaceProjectionLedger: () => WorkspaceProjectionLedger
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(workspace_projection_ledger_exports);
|
|
24
|
+
const RETAINED_OUTCOMES = /* @__PURE__ */ new Set(["no_change", "updated", "published", "large_diff_blocked"]);
|
|
25
|
+
class WorkspaceProjectionLedger {
|
|
26
|
+
projectedTokens = /* @__PURE__ */ new Map();
|
|
27
|
+
lastCycle = null;
|
|
28
|
+
static recordKey(mount) {
|
|
29
|
+
return `${mount.hydrationIncarnationKey}\0${mount.id}`;
|
|
30
|
+
}
|
|
31
|
+
get lastCycleResult() {
|
|
32
|
+
return this.lastCycle;
|
|
33
|
+
}
|
|
34
|
+
lastProjectedToken(mount) {
|
|
35
|
+
return this.projectedTokens.get(WorkspaceProjectionLedger.recordKey(mount));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Record a completed cycle. `projectedMutationTokens` names the token each
|
|
39
|
+
* active mount carried when the cycle projected it; a mount absent from it
|
|
40
|
+
* keeps its earlier record. Any outcome that leaves the outer clone without
|
|
41
|
+
* a trustworthy projection clears the ledger.
|
|
42
|
+
*/
|
|
43
|
+
recordCycle(input) {
|
|
44
|
+
if (!RETAINED_OUTCOMES.has(input.result.outcome)) {
|
|
45
|
+
this.reset();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const mountsById = new Map(input.mounts.map((mount) => [mount.id, mount]));
|
|
49
|
+
for (const [mountId, token] of Object.entries(input.projectedMutationTokens ?? {})) {
|
|
50
|
+
const mount = mountsById.get(mountId);
|
|
51
|
+
if (mount) this.projectedTokens.set(WorkspaceProjectionLedger.recordKey(mount), token);
|
|
52
|
+
}
|
|
53
|
+
this.lastCycle = input.result;
|
|
54
|
+
}
|
|
55
|
+
reset() {
|
|
56
|
+
this.projectedTokens.clear();
|
|
57
|
+
this.lastCycle = null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The previous cycle's result when every request targets a configured
|
|
61
|
+
* mount and no mount's token moved since its recorded projection. A request
|
|
62
|
+
* without a target (a workspace-rooted command may touch any checkout), a
|
|
63
|
+
* mount without a token (a pending deletion), or pending deletion work all
|
|
64
|
+
* require a real cycle.
|
|
65
|
+
*/
|
|
66
|
+
answerFromLastCycle(input) {
|
|
67
|
+
if (!this.lastCycle || input.pendingDeletions || input.targetMountIds.length === 0) return null;
|
|
68
|
+
const mountIds = new Set(input.mounts.map(({ id }) => id));
|
|
69
|
+
if (!input.targetMountIds.every((mountId) => mountId !== null && mountIds.has(mountId))) return null;
|
|
70
|
+
for (const mount of input.mounts) {
|
|
71
|
+
if (!mount.mutationToken) return null;
|
|
72
|
+
const recorded = this.projectedTokens.get(WorkspaceProjectionLedger.recordKey(mount));
|
|
73
|
+
if (recorded === void 0 || recorded !== mount.mutationToken()) return null;
|
|
74
|
+
}
|
|
75
|
+
return this.lastCycle;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
79
|
+
0 && (module.exports = {
|
|
80
|
+
WorkspaceProjectionLedger
|
|
81
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var workspace_sync_coalescer_exports = {};
|
|
20
|
+
__export(workspace_sync_coalescer_exports, {
|
|
21
|
+
WorkspaceSyncCoalescer: () => WorkspaceSyncCoalescer
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(workspace_sync_coalescer_exports);
|
|
24
|
+
class WorkspaceSyncCoalescer {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
}
|
|
28
|
+
options;
|
|
29
|
+
pendingBatch = null;
|
|
30
|
+
batchesStarted = 0;
|
|
31
|
+
/** Requests attached to the batch that has not started yet. */
|
|
32
|
+
get queuedRequestCount() {
|
|
33
|
+
return this.pendingBatch?.requests.length ?? 0;
|
|
34
|
+
}
|
|
35
|
+
/** Batches whose body has been entered, whether or not they ran a cycle. */
|
|
36
|
+
get startedBatchCount() {
|
|
37
|
+
return this.batchesStarted;
|
|
38
|
+
}
|
|
39
|
+
request(request) {
|
|
40
|
+
const queued = this.pendingBatch;
|
|
41
|
+
if (queued) {
|
|
42
|
+
queued.requests.push(request);
|
|
43
|
+
return queued.settlement;
|
|
44
|
+
}
|
|
45
|
+
const batch = { requests: [request], settlement: Promise.resolve() };
|
|
46
|
+
this.pendingBatch = batch;
|
|
47
|
+
batch.settlement = this.options.runExclusive(async () => {
|
|
48
|
+
if (this.pendingBatch === batch) this.pendingBatch = null;
|
|
49
|
+
this.batchesStarted += 1;
|
|
50
|
+
return await this.options.runBatch(batch.requests);
|
|
51
|
+
});
|
|
52
|
+
return batch.settlement;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
56
|
+
0 && (module.exports = {
|
|
57
|
+
WorkspaceSyncCoalescer
|
|
58
|
+
});
|