@ricsam/r5d-worker 0.0.94 → 0.0.96

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 CHANGED
@@ -81,6 +81,7 @@ var import_repository_transition_policy = require("./repository-transition-polic
81
81
  var import_supervisor = require("./supervisor.cjs");
82
82
  var import_managed_paths = require("./managed-paths.cjs");
83
83
  var import_project_worktrees = require("./project-worktrees.cjs");
84
+ var import_project_mirror_fetch_policy = require("./project-mirror-fetch-policy.cjs");
84
85
  var import_project_snapshot_recovery_runner = require("./project-snapshot-recovery-runner.cjs");
85
86
  var import_project_workspace_state = require("./project-workspace-state.cjs");
86
87
  var import_workspace_git_sync = require("./workspace-git-sync.cjs");
@@ -113,6 +114,7 @@ const DEFAULT_READ_MAX_BYTES = 5e4;
113
114
  const MAX_LINE_LENGTH = 2e3;
114
115
  const WORKSPACE_GIT_QUIET_MS = 5e3;
115
116
  const WORKSPACE_GIT_PERIODIC_MS = 6e4;
117
+ const PROJECT_MIRROR_OBSERVE_CONCURRENCY = 6;
116
118
  const WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS = 6e4;
117
119
  const PTY_INPUT_BUSY_GRACE_MS = 3e3;
118
120
  const PTY_FOREGROUND_POLL_MS = 1e3;
@@ -1003,6 +1005,21 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
1003
1005
  function canonicalProjectRemoteUrl(baseUrl, projectId) {
1004
1006
  return new URL(`/git/${projectId}.git`, baseUrl).toString();
1005
1007
  }
1008
+ async function mapWithConcurrencyLimit(items, limit, task) {
1009
+ const results = new Array(items.length);
1010
+ let nextIndex = 0;
1011
+ await Promise.all(
1012
+ Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
1013
+ while (true) {
1014
+ const index = nextIndex;
1015
+ nextIndex += 1;
1016
+ if (index >= items.length) return;
1017
+ results[index] = await task(items[index], index);
1018
+ }
1019
+ })
1020
+ );
1021
+ return results;
1022
+ }
1006
1023
  function shellQuote(value) {
1007
1024
  return `'${value.replace(/'/g, "'\\''")}'`;
1008
1025
  }
@@ -3462,6 +3479,10 @@ async function startWorker(options) {
3462
3479
  const readyProjectIds = /* @__PURE__ */ new Set();
3463
3480
  const reconciledProjectConfigFingerprints = /* @__PURE__ */ new Map();
3464
3481
  const lastObservedProjectHeads = /* @__PURE__ */ new Map();
3482
+ const advertisedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
3483
+ const fetchedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
3484
+ const lastProjectMirrorFetchAtMs = /* @__PURE__ */ new Map();
3485
+ const projectMirrorFetchSkippingEnabled = process.env.R5D_PROJECT_MIRROR_FETCH_SKIP !== "0";
3465
3486
  const pendingMirrorDeletes = /* @__PURE__ */ new Map();
3466
3487
  let workspaceRemoteUrl = null;
3467
3488
  let workspaceCredentialHelper = null;
@@ -3479,6 +3500,7 @@ async function startWorker(options) {
3479
3500
  let automaticSyncInFlight = false;
3480
3501
  let terminalReplayTimer;
3481
3502
  let workspaceSyncRequestsInFlight = 0;
3503
+ let lastWorkspaceSyncMirrorObservationMs = 0;
3482
3504
  let sessionArtifactSyncRequestsInFlight = 0;
3483
3505
  let cliUpdateInProgress = false;
3484
3506
  let reloadAfterClose = false;
@@ -3802,6 +3824,7 @@ async function startWorker(options) {
3802
3824
  const branchKey = projectBranchKey(project.projectId, branch.branchName);
3803
3825
  const creatorLocalIncarnation = creatorLocalIncarnations.get(branchKey) === branch.branchId;
3804
3826
  const preservesCheckoutPathMove = checkoutPathMoveBranches.has(branchKey);
3827
+ const initialPublicationSourceBranch = creatorLocalIncarnation ? branch.sourceBranchName : null;
3805
3828
  mounts.push({
3806
3829
  id: projectMountId(project.projectId, branch.branchName),
3807
3830
  hydrationIncarnationKey: branch.branchId,
@@ -3811,6 +3834,7 @@ async function startWorker(options) {
3811
3834
  sourceMode: "git",
3812
3835
  hydrateDeletionMode: "git",
3813
3836
  preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
3837
+ ...initialPublicationSourceBranch ? { initialPublicationSourceRelativePath: workspaceProjectRelativePath(project.projectId, initialPublicationSourceBranch) } : {},
3814
3838
  preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
3815
3839
  busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
3816
3840
  mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
@@ -3825,6 +3849,7 @@ async function startWorker(options) {
3825
3849
  sourceMode: "all",
3826
3850
  hydrateDeletionMode: "all",
3827
3851
  preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
3852
+ ...initialPublicationSourceBranch ? { initialPublicationSourceRelativePath: workspacePlanRelativePath(project.projectId, initialPublicationSourceBranch) } : {},
3828
3853
  preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
3829
3854
  busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
3830
3855
  mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
@@ -4104,6 +4129,17 @@ async function startWorker(options) {
4104
4129
  for (const result of results) {
4105
4130
  lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
4106
4131
  }
4132
+ try {
4133
+ fetchedProjectMirrorRefsTokens.set(
4134
+ project.projectId,
4135
+ await (0, import_project_worktrees.computeLocalProjectMirrorRefsToken)({
4136
+ projectRoot: configuredProjectRoot(projectsRoot, project),
4137
+ primaryBranchName: primaryProjectBranch(project)
4138
+ })
4139
+ );
4140
+ } catch {
4141
+ fetchedProjectMirrorRefsTokens.delete(project.projectId);
4142
+ }
4107
4143
  }
4108
4144
  const publishedTombstoneIds = [
4109
4145
  ...new Set(
@@ -4129,6 +4165,7 @@ async function startWorker(options) {
4129
4165
  credentialHelper: deletion.credentialHelper,
4130
4166
  credentialUsername: deletion.credentialUsername
4131
4167
  });
4168
+ fetchedProjectMirrorRefsTokens.delete(deletion.projectId);
4132
4169
  }
4133
4170
  if (deletion.tombstoneId && !deletion.projectDeleted) {
4134
4171
  projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
@@ -4139,42 +4176,101 @@ async function startWorker(options) {
4139
4176
  pendingMirrorDeletes.delete(key);
4140
4177
  if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
4141
4178
  removeProjectCredentialStore(deletion.projectId);
4179
+ advertisedProjectMirrorRefsTokens.delete(deletion.projectId);
4180
+ fetchedProjectMirrorRefsTokens.delete(deletion.projectId);
4181
+ lastProjectMirrorFetchAtMs.delete(deletion.projectId);
4142
4182
  }
4143
4183
  lastObservedProjectHeads.delete(key);
4144
4184
  }
4145
4185
  };
4186
+ const observeSingleProjectMirrorBundle = async (project, options2) => {
4187
+ const connection = projectConnection(project);
4188
+ const projectRoot = configuredProjectRoot(projectsRoot, project);
4189
+ const primaryBranchName = primaryProjectBranch(project);
4190
+ if (options2.refreshRemote) {
4191
+ const advertisedBeforeFetch = advertisedProjectMirrorRefsTokens.get(project.projectId);
4192
+ const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
4193
+ projectRoot,
4194
+ primaryBranchName,
4195
+ branchNames: project.branches.map(({ branchName }) => branchName),
4196
+ mirrorUrl: connection.mirrorUrl,
4197
+ credentialHelper: connection.credentialHelper,
4198
+ credentialUsername: connection.mirrorCredentialUsername,
4199
+ allowNonFastForward: options2.allowNonFastForward,
4200
+ branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId]))
4201
+ });
4202
+ if (advertisedBeforeFetch === void 0) fetchedProjectMirrorRefsTokens.delete(project.projectId);
4203
+ else fetchedProjectMirrorRefsTokens.set(project.projectId, advertisedBeforeFetch);
4204
+ lastProjectMirrorFetchAtMs.set(project.projectId, Date.now());
4205
+ return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
4206
+ }
4207
+ return {
4208
+ projectId: project.projectId,
4209
+ projectRoot,
4210
+ primaryBranchName,
4211
+ observations: await (0, import_project_worktrees.observeProjectMirrorHeads)({
4212
+ projectRoot,
4213
+ primaryBranchName,
4214
+ branchNames: project.branches.map(({ branchName }) => branchName),
4215
+ mirrorUrl: connection.mirrorUrl,
4216
+ credentialHelper: connection.credentialHelper,
4217
+ credentialUsername: connection.mirrorCredentialUsername,
4218
+ allowNonFastForward: options2.allowNonFastForward,
4219
+ branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
4220
+ refreshRemote: false
4221
+ })
4222
+ };
4223
+ };
4146
4224
  const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
4147
- const bundles = [];
4148
- for (const project of projectConfigById.values()) {
4149
- if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
4150
- try {
4151
- const connection = projectConnection(project);
4152
- const projectRoot = configuredProjectRoot(projectsRoot, project);
4153
- const primaryBranchName = primaryProjectBranch(project);
4154
- bundles.push({
4155
- projectId: project.projectId,
4156
- projectRoot,
4157
- primaryBranchName,
4158
- observations: await (0, import_project_worktrees.observeProjectMirrorHeads)({
4159
- projectRoot,
4160
- primaryBranchName,
4161
- branchNames: project.branches.map(({ branchName }) => branchName),
4162
- mirrorUrl: connection.mirrorUrl,
4163
- credentialHelper: connection.credentialHelper,
4164
- credentialUsername: connection.mirrorCredentialUsername,
4165
- allowNonFastForward: input.allowNonFastForward,
4166
- branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId]))
4167
- })
4168
- });
4169
- } catch (error) {
4170
- if (input.failClosed) throw error;
4225
+ const observationStartedAt = Date.now();
4226
+ const eligibleProjects = [...projectConfigById.values()].filter(
4227
+ (project) => !project.executionDisabled && readyProjectIds.has(project.projectId) && project.branches.length > 0
4228
+ );
4229
+ try {
4230
+ if (input.failClosed) {
4231
+ const bundles2 = [];
4232
+ for (const project of eligibleProjects) {
4233
+ bundles2.push(await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, refreshRemote: true }));
4234
+ }
4235
+ return bundles2;
4236
+ }
4237
+ let fetchedCount = 0;
4238
+ const bundles = await mapWithConcurrencyLimit(
4239
+ eligibleProjects,
4240
+ PROJECT_MIRROR_OBSERVE_CONCURRENCY,
4241
+ async (project) => {
4242
+ try {
4243
+ const decision = (0, import_project_mirror_fetch_policy.projectMirrorFetchDecision)({
4244
+ advertisedToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
4245
+ fetchedToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
4246
+ lastFetchAtMs: lastProjectMirrorFetchAtMs.get(project.projectId),
4247
+ nowMs: Date.now(),
4248
+ skippingEnabled: projectMirrorFetchSkippingEnabled
4249
+ });
4250
+ if (decision.fetch) fetchedCount += 1;
4251
+ return await observeSingleProjectMirrorBundle(project, {
4252
+ allowNonFastForward: input.allowNonFastForward,
4253
+ refreshRemote: decision.fetch
4254
+ });
4255
+ } catch (error) {
4256
+ process.stderr.write(
4257
+ `[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
4258
+ `
4259
+ );
4260
+ return null;
4261
+ }
4262
+ }
4263
+ );
4264
+ if (fetchedCount > 0) {
4171
4265
  process.stderr.write(
4172
- `[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
4266
+ `[r5d-worker] project mirror observation fetched ${fetchedCount} of ${eligibleProjects.length} mirror(s)
4173
4267
  `
4174
4268
  );
4175
4269
  }
4270
+ return bundles.filter((bundle) => bundle !== null);
4271
+ } finally {
4272
+ lastWorkspaceSyncMirrorObservationMs = Date.now() - observationStartedAt;
4176
4273
  }
4177
- return bundles;
4178
4274
  };
4179
4275
  const applyObservedProjectHeadsAfterInboundWorkspace = async (bundles, activeMountIds, requireObservedHeads, integratedWorkspaceHead) => {
4180
4276
  const active = new Set(activeMountIds);
@@ -4353,6 +4449,12 @@ async function startWorker(options) {
4353
4449
  }
4354
4450
  });
4355
4451
  input.assertStillAdmitted?.();
4452
+ if (result.initialPublicationMountIds && result.initialPublicationMountIds.length > 0) {
4453
+ process.stderr.write(
4454
+ `[r5d-worker] published first checkout copies above the automatic diff-size limit, measured against their source subtrees at ${result.diffSizeBytes} bytes: ${result.initialPublicationMountIds.join(", ")}
4455
+ `
4456
+ );
4457
+ }
4356
4458
  if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
4357
4459
  await applyObservedProjectHeadsAfterInboundWorkspace(
4358
4460
  observedProjectHeads,
@@ -4443,6 +4545,7 @@ async function startWorker(options) {
4443
4545
  result = await workspaceSyncSingleFlight.runExclusive(async () => {
4444
4546
  queueEnteredAt = Date.now();
4445
4547
  syncStartedAt = Date.now();
4548
+ lastWorkspaceSyncMirrorObservationMs = 0;
4446
4549
  if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4447
4550
  return await failedWorkspaceSyncResult(
4448
4551
  attemptId,
@@ -4527,7 +4630,9 @@ async function startWorker(options) {
4527
4630
  telemetry: {
4528
4631
  totalMs: finishedAt - requestedAt,
4529
4632
  queueMs: queueEnteredAt - requestedAt,
4530
- prepareMs: 0,
4633
+ // Duration of the pre-outer mirror-head observation phase; near zero
4634
+ // on a cycle whose token-gated observation skipped every fetch.
4635
+ prepareMs: lastWorkspaceSyncMirrorObservationMs,
4531
4636
  synchronizeMs: finishedAt - syncStartedAt
4532
4637
  }
4533
4638
  };
@@ -5245,7 +5350,8 @@ async function startWorker(options) {
5245
5350
  workspaceIncidentConfigDeferralV1: true,
5246
5351
  workspaceConfigResetToCanonicalV1: true,
5247
5352
  projectBranchDeletionFastAckV1: true,
5248
- projectMirrorLeaseV1: true
5353
+ projectMirrorLeaseV1: true,
5354
+ projectMirrorRefsTokensV1: true
5249
5355
  },
5250
5356
  projectRoot: projectsRoot,
5251
5357
  artifactRoot,
@@ -5283,6 +5389,17 @@ async function startWorker(options) {
5283
5389
  if (message.type === "connected") {
5284
5390
  return;
5285
5391
  }
5392
+ if (message.type === "project_mirror_refs_tokens") {
5393
+ advertisedProjectMirrorRefsTokens.clear();
5394
+ if (message.tokens && typeof message.tokens === "object") {
5395
+ for (const [projectId, token2] of Object.entries(message.tokens)) {
5396
+ if (typeof token2 === "string" && /^[0-9a-f]{64}$/.test(token2)) {
5397
+ advertisedProjectMirrorRefsTokens.set(projectId, token2);
5398
+ }
5399
+ }
5400
+ }
5401
+ return;
5402
+ }
5286
5403
  if (message.type === "workspace_config") {
5287
5404
  const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
5288
5405
  const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.94",
3
+ "version": "0.0.96",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,41 @@
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 project_mirror_fetch_policy_exports = {};
20
+ __export(project_mirror_fetch_policy_exports, {
21
+ PROJECT_MIRROR_FETCH_BACKSTOP_MS: () => PROJECT_MIRROR_FETCH_BACKSTOP_MS,
22
+ projectMirrorFetchDecision: () => projectMirrorFetchDecision
23
+ });
24
+ module.exports = __toCommonJS(project_mirror_fetch_policy_exports);
25
+ const PROJECT_MIRROR_FETCH_BACKSTOP_MS = 10 * 6e4;
26
+ function projectMirrorFetchDecision(input) {
27
+ if (!input.skippingEnabled) return { fetch: true, reason: "skipping_disabled" };
28
+ if (input.advertisedToken === void 0) return { fetch: true, reason: "no_advertised_token" };
29
+ if (input.fetchedToken === void 0) return { fetch: true, reason: "never_fetched" };
30
+ if (input.advertisedToken !== input.fetchedToken) return { fetch: true, reason: "token_changed" };
31
+ const backstopMs = input.backstopMs ?? PROJECT_MIRROR_FETCH_BACKSTOP_MS;
32
+ if (input.lastFetchAtMs === void 0 || input.nowMs - input.lastFetchAtMs >= backstopMs) {
33
+ return { fetch: true, reason: "backstop_elapsed" };
34
+ }
35
+ return { fetch: false, reason: "token_match" };
36
+ }
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ PROJECT_MIRROR_FETCH_BACKSTOP_MS,
40
+ projectMirrorFetchDecision
41
+ });
@@ -0,0 +1,51 @@
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 project_mirror_refs_token_exports = {};
20
+ __export(project_mirror_refs_token_exports, {
21
+ PROJECT_MIRROR_REF_PREFIX: () => PROJECT_MIRROR_REF_PREFIX,
22
+ PROJECT_SERVER_SYNCED_REF_PREFIX: () => PROJECT_SERVER_SYNCED_REF_PREFIX,
23
+ canonicalizeLocalProjectMirrorRefs: () => canonicalizeLocalProjectMirrorRefs,
24
+ hashProjectMirrorRefs: () => hashProjectMirrorRefs
25
+ });
26
+ module.exports = __toCommonJS(project_mirror_refs_token_exports);
27
+ var import_node_crypto = require("node:crypto");
28
+ const PROJECT_MIRROR_REF_PREFIX = "refs/r5d/mirror/";
29
+ const PROJECT_SERVER_SYNCED_REF_PREFIX = "refs/r5d/server-synced/";
30
+ function hashProjectMirrorRefs(refs) {
31
+ const lines = refs.map(({ objectName, refname }) => `${objectName} ${refname}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
32
+ return (0, import_node_crypto.createHash)("sha256").update(lines.join("\n")).digest("hex");
33
+ }
34
+ function canonicalizeLocalProjectMirrorRefs(refs) {
35
+ const canonical = [];
36
+ for (const { refname, objectName } of refs) {
37
+ if (refname.startsWith(PROJECT_MIRROR_REF_PREFIX)) {
38
+ canonical.push({ refname: `refs/heads/${refname.slice(PROJECT_MIRROR_REF_PREFIX.length)}`, objectName });
39
+ } else if (refname.startsWith(PROJECT_SERVER_SYNCED_REF_PREFIX)) {
40
+ canonical.push({ refname, objectName });
41
+ }
42
+ }
43
+ return canonical;
44
+ }
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ PROJECT_MIRROR_REF_PREFIX,
48
+ PROJECT_SERVER_SYNCED_REF_PREFIX,
49
+ canonicalizeLocalProjectMirrorRefs,
50
+ hashProjectMirrorRefs
51
+ });
@@ -32,6 +32,7 @@ __export(project_worktrees_exports, {
32
32
  ProjectBranchCreationRollbackIncompleteError: () => ProjectBranchCreationRollbackIncompleteError,
33
33
  applyObservedProjectMirrorHeads: () => applyObservedProjectMirrorHeads,
34
34
  assertProjectMirrorHeadsPushed: () => assertProjectMirrorHeadsPushed,
35
+ computeLocalProjectMirrorRefsToken: () => computeLocalProjectMirrorRefsToken,
35
36
  createLinkedProjectBranch: () => createLinkedProjectBranch,
36
37
  createOrRetryLinkedProjectBranch: () => createOrRetryLinkedProjectBranch,
37
38
  deleteLinkedProjectBranch: () => deleteLinkedProjectBranch,
@@ -57,6 +58,7 @@ var import_node_os = __toESM(require("node:os"), 1);
57
58
  var import_node_path = __toESM(require("node:path"), 1);
58
59
  var import_git_process_environment = require("./git-process-environment.cjs");
59
60
  var import_managed_paths = require("./managed-paths.cjs");
61
+ var import_project_mirror_refs_token = require("./project-mirror-refs-token.cjs");
60
62
  var import_working_tree_mirror = require("./working-tree-mirror.cjs");
61
63
  const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
62
64
  const PROJECT_WORKTREE_SNAPSHOT_MANIFEST = "transaction.json";
@@ -1294,6 +1296,7 @@ ${deleted.stdout}`)) {
1294
1296
  `Delete hidden project mirror for ${input.branchName}: ${deleted.stderr || deleted.stdout || `git exited ${deleted.exitCode}`}`
1295
1297
  );
1296
1298
  }
1299
+ await gitResultAsync(input.gitDirectory, ["update-ref", "-d", `refs/r5d/mirror/${input.branchName}`]);
1297
1300
  }
1298
1301
  async function pushProjectMirrorHeads(input) {
1299
1302
  const results = [];
@@ -1351,6 +1354,7 @@ async function pushProjectMirrorHeads(input) {
1351
1354
  if (pushed.exitCode !== 0) {
1352
1355
  throw new Error(`Push hidden project mirror for ${branchName}: ${pushed.stderr || pushed.stdout || `git exited ${pushed.exitCode}`}`);
1353
1356
  }
1357
+ await gitAsync(checkoutPath, ["update-ref", `refs/r5d/mirror/${branchName}`, head], `record pushed mirror tip ${branchName}`);
1354
1358
  results.push({ branchName, head, pushed: true });
1355
1359
  }
1356
1360
  return results;
@@ -1363,21 +1367,23 @@ function assertProjectMirrorHeadsPushed(results) {
1363
1367
  }
1364
1368
  async function observeProjectMirrorHeads(input) {
1365
1369
  const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
1366
- const fetch = await gitResultAsync(
1367
- primaryPath,
1368
- [
1369
- ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
1370
- "fetch",
1371
- "--no-recurse-submodules",
1372
- "--prune",
1373
- input.mirrorUrl,
1374
- "+refs/heads/*:refs/r5d/mirror/*",
1375
- "+refs/r5d/server-synced/*:refs/r5d/server-synced/*"
1376
- ],
1377
- PROJECT_GIT_TRANSPORT_TIMEOUT_MS
1378
- );
1379
- if (fetch.exitCode !== 0)
1380
- throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
1370
+ if (input.refreshRemote !== false) {
1371
+ const fetch = await gitResultAsync(
1372
+ primaryPath,
1373
+ [
1374
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.mirrorUrl, input.credentialHelper, input.credentialUsername),
1375
+ "fetch",
1376
+ "--no-recurse-submodules",
1377
+ "--prune",
1378
+ input.mirrorUrl,
1379
+ "+refs/heads/*:refs/r5d/mirror/*",
1380
+ "+refs/r5d/server-synced/*:refs/r5d/server-synced/*"
1381
+ ],
1382
+ PROJECT_GIT_TRANSPORT_TIMEOUT_MS
1383
+ );
1384
+ if (fetch.exitCode !== 0)
1385
+ throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
1386
+ }
1381
1387
  const observations = [];
1382
1388
  for (const branchName of [...input.branchNames].sort()) {
1383
1389
  const checkoutPath = branchPath(input.projectRoot, branchName);
@@ -1423,6 +1429,24 @@ async function observeProjectMirrorHeads(input) {
1423
1429
  }
1424
1430
  return observations;
1425
1431
  }
1432
+ async function computeLocalProjectMirrorRefsToken(input) {
1433
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
1434
+ const rows = await gitAsync(
1435
+ primaryPath,
1436
+ ["for-each-ref", "--format=%(objectname) %(refname)", import_project_mirror_refs_token.PROJECT_MIRROR_REF_PREFIX, import_project_mirror_refs_token.PROJECT_SERVER_SYNCED_REF_PREFIX],
1437
+ "list cached project mirror refs"
1438
+ );
1439
+ const refs = rows.split(/\r?\n/).filter(Boolean).map((row) => {
1440
+ const separator = row.indexOf(" ");
1441
+ const objectName = separator < 0 ? "" : row.slice(0, separator);
1442
+ const refname = separator < 0 ? "" : row.slice(separator + 1);
1443
+ if (!/^[0-9a-f]{40,64}$/.test(objectName) || refname.length === 0) {
1444
+ throw new Error(`Invalid cached mirror ref listing row: ${row}`);
1445
+ }
1446
+ return { refname, objectName };
1447
+ });
1448
+ return (0, import_project_mirror_refs_token.hashProjectMirrorRefs)((0, import_project_mirror_refs_token.canonicalizeLocalProjectMirrorRefs)(refs));
1449
+ }
1426
1450
  async function applyObservedProjectMirrorHeads(input) {
1427
1451
  const results = [];
1428
1452
  for (const observation of [...input.observations].sort((left, right) => left.branchName.localeCompare(right.branchName))) {
@@ -1505,6 +1529,7 @@ const projectWorktreesTestHarness = {
1505
1529
  ProjectBranchCreationRollbackIncompleteError,
1506
1530
  applyObservedProjectMirrorHeads,
1507
1531
  assertProjectMirrorHeadsPushed,
1532
+ computeLocalProjectMirrorRefsToken,
1508
1533
  createLinkedProjectBranch,
1509
1534
  createOrRetryLinkedProjectBranch,
1510
1535
  deleteLinkedProjectBranch,