@ricsam/r5d-worker 0.0.101 → 0.0.104

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.
@@ -1389,6 +1389,19 @@ ${result.stdout}`;
1389
1389
  }
1390
1390
  return revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1391
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
+ }
1392
1405
  async function ensureWorkspaceGitClone(input) {
1393
1406
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
1394
1407
  const workspaceParent = import_node_path.default.dirname(workspacePath);
@@ -1436,7 +1449,8 @@ async function ensureWorkspaceGitClone(input) {
1436
1449
  if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
1437
1450
  await updateIntegratedWorkspaceHead(workspacePath, previousRemoteHead);
1438
1451
  }
1439
- const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1452
+ const resolvedRemoteHead = await resolveWorkspaceRemoteHead(workspacePath, input);
1453
+ const remoteHead = resolvedRemoteHead.head;
1440
1454
  let localHead = await revParseAsync(workspacePath, "HEAD");
1441
1455
  if (!localHead && remoteHead) {
1442
1456
  await runWorkspaceCheckoutTransition(workspacePath, async () => {
@@ -1457,7 +1471,7 @@ async function ensureWorkspaceGitClone(input) {
1457
1471
  await updateIntegratedWorkspaceHead(workspacePath, remoteHead);
1458
1472
  localHead = remoteHead;
1459
1473
  }
1460
- return { localHead, remoteHead };
1474
+ return { localHead, remoteHead, remoteHeadFetched: resolvedRemoteHead.fetched };
1461
1475
  }
1462
1476
  function activeMounts(mounts) {
1463
1477
  const active = [];
@@ -2106,6 +2120,11 @@ async function synchronizeWithFetchedHead(input) {
2106
2120
  return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
2107
2121
  }
2108
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) {
2109
2128
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
2110
2129
  const attemptId = input.attemptId ?? crypto.randomUUID();
2111
2130
  const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
@@ -2114,6 +2133,7 @@ async function synchronizeWorkspaceGit(input) {
2114
2133
  const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
2115
2134
  const preserveResolutionInProgress = input.skipMountMirror === true;
2116
2135
  const initial = await ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
2136
+ if (initial.remoteHeadFetched) remoteHeadFetches.count += 1;
2117
2137
  assertWorkspaceRemediationAncestry({
2118
2138
  workspacePath,
2119
2139
  currentHead: initial.localHead,
@@ -2161,11 +2181,22 @@ async function synchronizeWorkspaceGit(input) {
2161
2181
  const classifyMounts = (activeCandidates, additionalSkippedIds = []) => {
2162
2182
  refreshCycleSkippedMounts();
2163
2183
  const skippedIds = /* @__PURE__ */ new Set([...cycleSkippedMountIds, ...projectionSkippedIds, ...additionalSkippedIds]);
2184
+ const activeMountIds = [...new Set(activeCandidates.filter(({ id }) => !skippedIds.has(id)).map(({ id }) => id))].sort();
2164
2185
  return {
2165
- activeMountIds: [...new Set(activeCandidates.filter(({ id }) => !skippedIds.has(id)).map(({ id }) => id))].sort(),
2166
- 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
+ )
2167
2197
  };
2168
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;
2169
2200
  if (!input.skipMountMirror) {
2170
2201
  const mergeProjectionEnabled = process.env.R5D_WORKSPACE_MERGE_PROJECTION !== "0";
2171
2202
  const mergeSupport = mergeProjectionEnabled ? (0, import_workspace_merge_projection.workspaceMergeProjectionSupport)() : { supported: false, error: "Workspace merge projection is disabled by R5D_WORKSPACE_MERGE_PROJECTION=0" };
@@ -2304,6 +2335,7 @@ async function synchronizeWorkspaceGit(input) {
2304
2335
  }
2305
2336
  };
2306
2337
  const projectedFastMounts = [];
2338
+ const currentProjectionMounts = [];
2307
2339
  if (!input.skipMountMirror) {
2308
2340
  beginWorkspaceCheckoutTransition(workspacePath);
2309
2341
  try {
@@ -2315,6 +2347,10 @@ async function synchronizeWorkspaceGit(input) {
2315
2347
  cycleSkippedMountIds.add(mount.id);
2316
2348
  continue;
2317
2349
  }
2350
+ if (mountProjectionIsCurrent(mount)) {
2351
+ currentProjectionMounts.push(mount);
2352
+ continue;
2353
+ }
2318
2354
  projectedGitlinks.push(...mirrorMountsToWorkspace(workspacePath, [mount]));
2319
2355
  projectedFastMounts.push(mount);
2320
2356
  await yieldToWorkerEventLoop();
@@ -2348,7 +2384,7 @@ async function synchronizeWorkspaceGit(input) {
2348
2384
  const targetProjectionReceipt = receiptWithMountsAtHead(
2349
2385
  projectionBasisReceipt,
2350
2386
  projectedHead,
2351
- projectedFastMounts,
2387
+ [...projectedFastMounts, ...currentProjectionMounts],
2352
2388
  allProjectionReceiptMounts
2353
2389
  );
2354
2390
  const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
@@ -2361,7 +2397,9 @@ async function synchronizeWorkspaceGit(input) {
2361
2397
  }
2362
2398
  await completeWorkspaceCheckoutTransition(workspacePath);
2363
2399
  }
2364
- let remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2400
+ const prePublishRemoteHead = await resolveWorkspaceRemoteHead(workspacePath, input);
2401
+ if (prePublishRemoteHead.fetched) remoteHeadFetches.count += 1;
2402
+ let remoteHead = prePublishRemoteHead.head;
2365
2403
  let rebaseCount = 0;
2366
2404
  let updated = false;
2367
2405
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
@@ -2518,6 +2556,7 @@ ${push.stdout}`)) {
2518
2556
  throw new Error(`Push workspace main: ${push.stderr || push.stdout || `git exited ${push.exitCode}`}`);
2519
2557
  }
2520
2558
  remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
2559
+ remoteHeadFetches.count += 1;
2521
2560
  }
2522
2561
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
2523
2562
  }
@@ -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
+ });