@teambit/ci 1.0.488 → 1.0.490

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.
@@ -1,5 +1,5 @@
1
1
  ;
2
- import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.git_ci@1.0.488/dist/ci.docs.mdx';
2
+ import * as overview_0 from '/home/circleci/Library/Caches/Bit/capsules/8891be5ad/teambit.git_ci@1.0.490/dist/ci.docs.mdx';
3
3
 
4
4
  export const compositions = [];
5
5
  export const overview = [overview_0];
@@ -38,6 +38,12 @@ export declare class MainSyncExecutor {
38
38
  * branch, which the next run re-forks).
39
39
  */
40
40
  private catchUpWithDefaultBranch;
41
+ /**
42
+ * Re-fork the sync branch from the default branch when every commit it holds beyond it passes
43
+ * `isSyncAuthoredMessage` — machine state is recomputable from the scope. The force push is
44
+ * leased on the tip the ownership was read from, so a concurrent run's push wins the race.
45
+ */
46
+ private reforkIfMachineOwned;
41
47
  /**
42
48
  * The "nothing to do" line — plus the open sync PR, if any: converged means the SYNC branch matches
43
49
  * the scope, and the default branch may still differ until that PR merges.
@@ -58,7 +58,8 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
58
58
  * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`
59
59
  * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the
60
60
  * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on
61
- * the default branch with a plain push. Nothing is ever force-pushed in either mode.
61
+ * the default branch with a plain push. One force push exists: a machine-owned sync branch that
62
+ * conflicts with the default branch is re-forked under a tip lease (`reforkIfMachineOwned`).
62
63
  */
63
64
  /** the "nothing to do" line, shared by both modes */
64
65
  const CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';
@@ -95,10 +96,13 @@ class MainSyncExecutor {
95
96
  await this.resetToStartPoint(branch, startPoint);
96
97
  if (syncBranchExists) {
97
98
  // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale
98
- // tree would re-commit pre-merge content over the default branch's changes. A merge — never a
99
- // rebase, never a force-push — is the only non-destructive way to move a branch with an open PR.
99
+ // tree would re-commit pre-merge content over the default branch's changes.
100
100
  const catchUpErr = await this.catchUpWithDefaultBranch(branch);
101
- if (catchUpErr) return `${_laneSyncExecutor().HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;
101
+ if (catchUpErr) {
102
+ const refork = await this.reforkIfMachineOwned(branch);
103
+ if (refork === 'human-owned') return `${_laneSyncExecutor().HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;
104
+ if (refork === 'raced') return `main -> ${branch} was updated by a concurrent run — deferring to it`;
105
+ }
102
106
  }
103
107
 
104
108
  // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch
@@ -185,8 +189,11 @@ class MainSyncExecutor {
185
189
  logger
186
190
  } = this.deps;
187
191
  try {
188
- // under the identity: a non-fast-forward merge writes a merge COMMIT
189
- const out = await (0, _gitOps().gitWithIdentity)(['merge', '--no-edit', `origin/${defaultBranch}`]);
192
+ // the marker line keeps the branch machine-owned across catch-up merges
193
+ const out = await (0, _gitOps().gitWithIdentity)(['merge', '-m', `merge origin/${defaultBranch} into ${branch}\n\n${_syncState().SYNC_COMMIT_MARKER}`, `origin/${defaultBranch}`]);
194
+ // simple-git resolves on some non-zero exits — judge the merge by state, not by rejection
195
+ const conflicted = (await _git().git.status()).conflicted;
196
+ if (conflicted.length) throw new Error(`merge conflicts in: ${conflicted.join(', ')}`);
190
197
  logger.console(_chalk().default.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));
191
198
  } catch (e) {
192
199
  await _git().git.raw(['merge', '--abort']).catch(() => undefined);
@@ -197,6 +204,33 @@ class MainSyncExecutor {
197
204
  return undefined;
198
205
  }
199
206
 
207
+ /**
208
+ * Re-fork the sync branch from the default branch when every commit it holds beyond it passes
209
+ * `isSyncAuthoredMessage` — machine state is recomputable from the scope. The force push is
210
+ * leased on the tip the ownership was read from, so a concurrent run's push wins the race.
211
+ */
212
+ async reforkIfMachineOwned(branch) {
213
+ const {
214
+ defaultBranch,
215
+ logger
216
+ } = this.deps;
217
+ const staleTip = (await _git().git.revparse([`origin/${branch}`])).trim();
218
+ const log = await _git().git.raw(['log', `origin/${defaultBranch}..${staleTip}`, '--format=%B%x1e']);
219
+ const messages = log.split('\x1e').map(message => message.trim()).filter(Boolean);
220
+ const machineOwned = messages.length > 0 && messages.every(message => (0, _syncState().isSyncAuthoredMessage)(message));
221
+ if (!machineOwned) return 'human-owned';
222
+ logger.console((0, _cli().formatWarningSummary)(`main -> ${branch} conflicts with ${defaultBranch} and carries only ${_syncState().SYNC_COMMIT_MARKER} commits — ` + `re-forking it from origin/${defaultBranch}`));
223
+ await this.resetToStartPoint(branch, `origin/${defaultBranch}`);
224
+ try {
225
+ await _git().git.push([`--force-with-lease=refs/heads/${branch}:${staleTip}`, 'origin', `HEAD:refs/heads/${branch}`]);
226
+ } catch (e) {
227
+ if (!(0, _gitOps().isStaleLeaseRejection)(e?.message || String(e))) throw e;
228
+ logger.console((0, _cli().formatWarningSummary)(`main -> ${branch} moved while re-forking — another run owns it now`));
229
+ return 'raced';
230
+ }
231
+ return 're-forked';
232
+ }
233
+
200
234
  /**
201
235
  * The "nothing to do" line — plus the open sync PR, if any: converged means the SYNC branch matches
202
236
  * the scope, and the default branch may still differ until that PR merges.
@@ -1 +1 @@
1
- {"version":3,"names":["_chalk","data","_interopRequireDefault","require","_cli","_git","_syncState","_workspaceLane","_laneSyncExecutor","_gitOps","e","__esModule","default","CONVERGED_SUMMARY","MainSyncExecutor","constructor","deps","syncMain","opts","cfg","defaultBranch","defaultScope","logger","directPush","mainSync","branch","mainSyncBranch","autoMergeMainSyncPr","consoleWarning","fetchRemoteHeads","syncBranchExists","branchExistsOnRemote","startPoint","console","chalk","blue","resetToStartPoint","catchUpErr","catchUpWithDefaultBranch","HALT_SUMMARY_PREFIX","currentLane","currentLaneIdStr","lanes","results","checkout","checkoutByCLIValues","head","skipNpmInstall","includeNewFromScope","mergeStrategy","newFromScope","length","join","drift","driftFiles","convergedSummary","formatWarningSummary","slice","dryRun","addAllExceptScopeAndModules","commitWithIdentity","mainSyncCommitMessage","git","push","green","shortSha","revparse","trim","prUrl","ensureSyncPr","driftCount","message","restoreWorkspace","checkoutPristine","ci","reloadWorkspaceFromDisk","out","gitWithIdentity","raw","catch","undefined","gitHost","pr","findPrByBranch","number","status","paths","files","map","file","path","filter","isNonContentPath","Set","existing","htmlUrl","created","createPr","base","title","body","mainSyncPrBody","switchErr","switchToLaneForSync","checkoutPristineRestore","exports","SYNC_COMMIT_MARKER","lines","capEntries","id"],"sources":["main-sync-executor.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { formatWarningSummary } from '@teambit/cli';\nimport type { Logger } from '@teambit/logger';\nimport type { LanesMain } from '@teambit/lanes';\nimport type { CheckoutMain } from '@teambit/checkout';\nimport { git } from '../git';\nimport type { CiMain } from '../ci.main.runtime';\nimport type { CiSyncConfig } from './sync-config';\nimport { SYNC_COMMIT_MARKER } from './sync-state';\nimport { currentLaneIdStr } from './workspace-lane';\nimport type { GitHostProvider } from './git-host-provider';\nimport { HALT_SUMMARY_PREFIX, capEntries } from './lane-sync-executor';\nimport {\n addAllExceptScopeAndModules,\n branchExistsOnRemote,\n checkoutPristine,\n checkoutPristineRestore,\n commitWithIdentity,\n fetchRemoteHeads,\n gitWithIdentity,\n isNonContentPath,\n} from './git-ops';\n\nexport type MainSyncDeps = {\n checkout: CheckoutMain;\n lanes: LanesMain;\n /** for reloadWorkspaceFromDisk + switchToLaneForSync */\n ci: CiMain;\n logger: Logger;\n /** undefined => PR operations are logged and skipped; the git half of the sync still runs. */\n gitHost?: GitHostProvider;\n cfg: Required<CiSyncConfig>;\n defaultBranch: string;\n defaultScope: string;\n};\n\n/**\n * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`\n * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the\n * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on\n * the default branch with a plain push. Nothing is ever force-pushed in either mode.\n */\n/** the \"nothing to do\" line, shared by both modes */\nconst CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';\n\nexport class MainSyncExecutor {\n constructor(private deps: MainSyncDeps) {}\n\n /**\n * Returns a summary line (HALT-prefixed on failure) and, like `syncLane`, does not throw — a\n * main-sync failure must not erase the lane results collected before it.\n */\n async syncMain(opts: { dryRun?: boolean } = {}): Promise<string> {\n const { cfg, defaultBranch, defaultScope, logger } = this.deps;\n const directPush = cfg.mainSync === 'direct-push';\n // In direct-push mode the sync branch (and its PR) is not consulted, moved or deleted.\n const branch = directPush ? defaultBranch : cfg.mainSyncBranch;\n\n if (cfg.autoMergeMainSyncPr && !directPush) {\n // Enabling auto-merge is host-specific and not part of the GitHostProvider contract.\n logger.consoleWarning(\n `sync.autoMergeMainSyncPr is enabled in the config, but enabling auto-merge is not implemented yet — ` +\n `the sync PR is opened without it. Use a repository auto-merge rule instead.`\n );\n }\n\n try {\n await fetchRemoteHeads();\n // Direct-push always starts from `origin/<defaultBranch>` — the very tip the plain push must\n // fast-forward. In 'pr' mode an existing sync branch is reused so its PR history survives.\n const syncBranchExists = directPush ? false : await branchExistsOnRemote(branch);\n const startPoint = syncBranchExists ? `origin/${branch}` : `origin/${defaultBranch}`;\n logger.console(\n chalk.blue(\n directPush\n ? `main -> checking the main scope against ${defaultBranch} (direct-push)`\n : `main -> checking the main scope against ${defaultBranch} (sync branch ${branch})`\n )\n );\n\n await this.resetToStartPoint(branch, startPoint);\n\n if (syncBranchExists) {\n // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale\n // tree would re-commit pre-merge content over the default branch's changes. A merge — never a\n // rebase, never a force-push — is the only non-destructive way to move a branch with an open PR.\n const catchUpErr = await this.catchUpWithDefaultBranch(branch);\n if (catchUpErr) return `${HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;\n }\n\n // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch\n // would make the \"drift\" the lane's content. Refuse rather than open a wildly wrong PR.\n // `.bitmap`-derived read: the scope-object read answers \"main\" on a fresh runner and the guard\n // would silently stop refusing exactly where it ships (see `workspace-lane.ts`).\n const currentLane = currentLaneIdStr(this.deps.lanes);\n if (currentLane) {\n return (\n `${HALT_SUMMARY_PREFIX} main -> the .bitmap on ${startPoint} points at lane ` +\n `\"${currentLane}\" rather than main, so the main-scope drift cannot be computed`\n );\n }\n\n // `checkoutByCLIValues` rather than `checkout`: it imports current objects first (so `head`\n // resolves to the remote scope's versions) and persists `.bitmap` on destroy.\n // `includeNewFromScope`: a component exported to the scope's main but never in this repo's\n // `.bitmap` would otherwise be invisible to every sync run forever.\n // `mergeStrategy: 'theirs'` is required: unexported source drift makes components \"modified\",\n // and with no strategy the checkout throws. 'theirs' materializes the exported truth — any\n // reversion of unexported drift is visible in the PR diff. 'ours' is forbidden: it would advance\n // `.bitmap` while keeping the old files, a commit asserting a state the tree does not have.\n const results = await this.deps.checkout.checkoutByCLIValues('', {\n head: true,\n skipNpmInstall: true,\n includeNewFromScope: true,\n mergeStrategy: 'theirs',\n });\n const newFromScope = results.newFromScope ?? [];\n if (newFromScope.length) {\n logger.console(\n chalk.blue(\n `Added ${newFromScope.length} component(s) that exist on ${defaultScope}'s main but not in this ` +\n `workspace: ${newFromScope.join(', ')}`\n )\n );\n }\n\n const drift = await this.driftFiles();\n // Direct-push stays bare: asking the host about `mainSyncBranch`'s PR would be the one\n // interaction with it this mode promises not to make.\n if (!drift.length) return directPush ? CONVERGED_SUMMARY : await this.convergedSummary(branch);\n\n logger.console(\n formatWarningSummary(`main -> drift in ${drift.length} file(s): ${drift.slice(0, 20).join(', ')}`)\n );\n\n if (opts.dryRun) {\n // The working tree WAS written (a diff-based check has no other way); `finally` restores it. Safe\n // only because a dry run over a dirty tree is refused before anything runs — the restore rewinds\n // to HEAD, so an uncommitted change here would be gone (`assertCleanForDryRun`).\n logger.console(\n formatWarningSummary(\n directPush\n ? `Dry-run: main -> would push the drift directly onto ${branch}`\n : `Dry-run: main -> would push ${branch} and open a sync PR`\n )\n );\n return directPush\n ? `main -> drift detected in ${drift.length} file(s) — would push ${branch} directly`\n : `main -> drift detected in ${drift.length} file(s) — would open sync PR`;\n }\n\n await addAllExceptScopeAndModules();\n await commitWithIdentity(mainSyncCommitMessage(drift.length));\n // Never force: a rejected push means a concurrent run pushed in between, and in direct-push mode\n // that rejection is the whole safety story for the default branch. Unambiguous refspec — see\n // `lane-sync-executor.commitAllAndPush`.\n await git.push(['origin', `HEAD:refs/heads/${branch}`]);\n logger.console(chalk.green(`Pushed ${branch}`));\n\n if (directPush) {\n const shortSha = (await git.revparse(['--short', 'HEAD'])).trim();\n return `main -> direct-push (pushed ${branch} @ ${shortSha})`;\n }\n const prUrl = await this.ensureSyncPr({ branch, driftCount: drift.length, newFromScope });\n return `main -> pushed sync commit to ${branch}${prUrl ? ` (PR ${prUrl})` : ''}`;\n } catch (e: any) {\n // Same contract as the lane executor: a HALTED line, never a throw mid-run.\n return `${HALT_SUMMARY_PREFIX} main -> ${e?.message || e}`;\n } finally {\n await this.restoreWorkspace();\n }\n }\n\n /**\n * Put the working tree on the sync branch at a pristine copy of `startPoint`. Pristine is\n * load-bearing for a diff-based reconciler: any stray modification would be indistinguishable from\n * main-scope drift. The reload makes `checkout head` read this branch's `.bitmap`.\n */\n private async resetToStartPoint(branch: string, startPoint: string) {\n await checkoutPristine(branch, startPoint, () => this.deps.ci.reloadWorkspaceFromDisk());\n }\n\n /**\n * Merge the default branch into the sync branch so drift is computed against the repository's\n * current state. Returns a reason string on failure; a conflict needs a human (or deleting the sync\n * branch, which the next run re-forks).\n */\n private async catchUpWithDefaultBranch(branch: string): Promise<string | undefined> {\n const { defaultBranch, logger } = this.deps;\n try {\n // under the identity: a non-fast-forward merge writes a merge COMMIT\n const out = await gitWithIdentity(['merge', '--no-edit', `origin/${defaultBranch}`]);\n logger.console(chalk.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));\n } catch (e: any) {\n await git.raw(['merge', '--abort']).catch(() => undefined);\n return (\n `could not bring the sync branch ${branch} up to date with origin/${defaultBranch}: ${e?.message || e}. ` +\n `Resolve or delete ${branch} (a deleted sync branch is re-forked from ${defaultBranch} on the next run)`\n );\n }\n // The merge may have brought a new `.bitmap` in from the default branch.\n await this.deps.ci.reloadWorkspaceFromDisk();\n return undefined;\n }\n\n /**\n * The \"nothing to do\" line — plus the open sync PR, if any: converged means the SYNC branch matches\n * the scope, and the default branch may still differ until that PR merges.\n */\n private async convergedSummary(branch: string): Promise<string> {\n const { gitHost } = this.deps;\n if (!gitHost) return CONVERGED_SUMMARY;\n const pr = await gitHost.findPrByBranch(branch).catch(() => undefined);\n return pr ? `${CONVERGED_SUMMARY} — open sync PR #${pr.number} still awaits review/merge` : CONVERGED_SUMMARY;\n }\n\n /**\n * The files `bit checkout head` changed, i.e. the drift. Excludes `.bit`/`node_modules` — the same\n * set the commit paths refuse to touch, keeping \"counts as drift\" and \"gets committed\" identical.\n */\n private async driftFiles(): Promise<string[]> {\n const status = await git.status();\n const paths = status.files.map((file) => file.path).filter((path) => !isNonContentPath(path));\n return [...new Set(paths)];\n }\n\n /**\n * Make sure the pushed branch has an open PR. A git-host failure only warns: the branch is pushed,\n * which is the load-bearing half, and the next run retries the PR.\n */\n private async ensureSyncPr({\n branch,\n driftCount,\n newFromScope,\n }: {\n branch: string;\n driftCount: number;\n newFromScope: string[];\n }): Promise<string | undefined> {\n const { gitHost, logger, defaultBranch } = this.deps;\n if (!gitHost) {\n logger.consoleWarning(\n 'No configured git host provider (for GitHub: BIT_GITHUB_TOKEN or GITHUB_TOKEN, plus a repository) — ' +\n 'pushed sync branch, skipping PR operations'\n );\n return undefined;\n }\n try {\n const existing = await gitHost.findPrByBranch(branch);\n if (existing) {\n logger.console(chalk.blue(`Sync PR ${existing.htmlUrl} is already open — pushed the new commit onto it`));\n return existing.htmlUrl;\n }\n const created = await gitHost.createPr({\n head: branch,\n base: defaultBranch,\n title: 'Bit sync: update to latest main scope versions',\n body: mainSyncPrBody({ driftCount, newFromScope }),\n });\n logger.console(chalk.green(`Opened sync PR ${created.htmlUrl}`));\n return created.htmlUrl;\n } catch (e: any) {\n logger.consoleWarning(`Could not open or find the sync PR for ${branch}: ${e?.message || e}`);\n return undefined;\n }\n }\n\n /**\n * Leave git on the default branch and bit on main. Mirrors `LaneSyncExecutor.restoreWorkspace`:\n * best-effort and warn-only so a restore hiccup can't throw out of a `finally` and mask the real\n * error; the clean discards untracked files `checkout head` wrote on the converged/dry-run paths.\n */\n private async restoreWorkspace() {\n const { logger, defaultBranch } = this.deps;\n try {\n if (currentLaneIdStr(this.deps.lanes)) {\n const switchErr = await this.deps.ci.switchToLaneForSync('main');\n if (switchErr) logger.consoleWarning(`Could not switch the workspace back to main: ${switchErr.message}`);\n }\n await checkoutPristineRestore(defaultBranch, () => this.deps.ci.reloadWorkspaceFromDisk());\n } catch (e: any) {\n logger.consoleWarning(`Could not restore the workspace after the main sync: ${e?.message || e}`);\n }\n }\n}\n\n/** The sync commit message: no `Bit-Lane-Head` trailer — main sync keeps no state. */\nfunction mainSyncCommitMessage(driftCount: number): string {\n return [\n `chore(bit-sync): sync git to latest main scope versions (${driftCount} file(s))`,\n '',\n SYNC_COMMIT_MARKER,\n ].join('\\n');\n}\n\nfunction mainSyncPrBody({ driftCount, newFromScope }: { driftCount: number; newFromScope: string[] }): string {\n const lines = [\n 'Automated sync PR: the Bit scope moved ahead of this repository. This PR checks the workspace out to the ' +\n 'latest exported versions (`bit checkout head --auto-merge-resolve theirs`).',\n '',\n `- files changed: ${driftCount}`,\n '- conflicts between this repository and the scope were resolved **in favour of the scope** — if a file here ' +\n 'held changes that were never exported to Bit, this PR reverts them. Review the diff and close the PR ' +\n 'instead of merging if that is not what you want.',\n ];\n if (newFromScope.length) {\n lines.push(\n `- components added from the scope (${newFromScope.length}, not previously in this repository's \\`.bitmap\\`):`,\n // Bounded like the lane PR body: an over-long body is rejected by the host outright.\n ...capEntries(\n newFromScope.map((id) => ` - \\`${id}\\``),\n ' - '\n )\n );\n }\n lines.push(\n '',\n 'This PR is maintained by `bit ci sync` — re-running the command pushes any further drift onto the same branch.'\n );\n return lines.join('\\n');\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_chalk","data","_interopRequireDefault","require","_cli","_git","_syncState","_workspaceLane","_laneSyncExecutor","_gitOps","e","__esModule","default","CONVERGED_SUMMARY","MainSyncExecutor","constructor","deps","syncMain","opts","cfg","defaultBranch","defaultScope","logger","directPush","mainSync","branch","mainSyncBranch","autoMergeMainSyncPr","consoleWarning","fetchRemoteHeads","syncBranchExists","branchExistsOnRemote","startPoint","console","chalk","blue","resetToStartPoint","catchUpErr","catchUpWithDefaultBranch","refork","reforkIfMachineOwned","HALT_SUMMARY_PREFIX","currentLane","currentLaneIdStr","lanes","results","checkout","checkoutByCLIValues","head","skipNpmInstall","includeNewFromScope","mergeStrategy","newFromScope","length","join","drift","driftFiles","convergedSummary","formatWarningSummary","slice","dryRun","addAllExceptScopeAndModules","commitWithIdentity","mainSyncCommitMessage","git","push","green","shortSha","revparse","trim","prUrl","ensureSyncPr","driftCount","message","restoreWorkspace","checkoutPristine","ci","reloadWorkspaceFromDisk","out","gitWithIdentity","SYNC_COMMIT_MARKER","conflicted","status","Error","raw","catch","undefined","staleTip","log","messages","split","map","filter","Boolean","machineOwned","every","isSyncAuthoredMessage","isStaleLeaseRejection","String","gitHost","pr","findPrByBranch","number","paths","files","file","path","isNonContentPath","Set","existing","htmlUrl","created","createPr","base","title","body","mainSyncPrBody","switchErr","switchToLaneForSync","checkoutPristineRestore","exports","lines","capEntries","id"],"sources":["main-sync-executor.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { formatWarningSummary } from '@teambit/cli';\nimport type { Logger } from '@teambit/logger';\nimport type { LanesMain } from '@teambit/lanes';\nimport type { CheckoutMain } from '@teambit/checkout';\nimport { git } from '../git';\nimport type { CiMain } from '../ci.main.runtime';\nimport type { CiSyncConfig } from './sync-config';\nimport { SYNC_COMMIT_MARKER, isSyncAuthoredMessage } from './sync-state';\nimport { currentLaneIdStr } from './workspace-lane';\nimport type { GitHostProvider } from './git-host-provider';\nimport { HALT_SUMMARY_PREFIX, capEntries } from './lane-sync-executor';\nimport {\n addAllExceptScopeAndModules,\n branchExistsOnRemote,\n checkoutPristine,\n checkoutPristineRestore,\n commitWithIdentity,\n fetchRemoteHeads,\n gitWithIdentity,\n isNonContentPath,\n isStaleLeaseRejection,\n} from './git-ops';\n\nexport type MainSyncDeps = {\n checkout: CheckoutMain;\n lanes: LanesMain;\n /** for reloadWorkspaceFromDisk + switchToLaneForSync */\n ci: CiMain;\n logger: Logger;\n /** undefined => PR operations are logged and skipped; the git half of the sync still runs. */\n gitHost?: GitHostProvider;\n cfg: Required<CiSyncConfig>;\n defaultBranch: string;\n defaultScope: string;\n};\n\n/**\n * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`\n * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the\n * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on\n * the default branch with a plain push. One force push exists: a machine-owned sync branch that\n * conflicts with the default branch is re-forked under a tip lease (`reforkIfMachineOwned`).\n */\n/** the \"nothing to do\" line, shared by both modes */\nconst CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';\n\nexport class MainSyncExecutor {\n constructor(private deps: MainSyncDeps) {}\n\n /**\n * Returns a summary line (HALT-prefixed on failure) and, like `syncLane`, does not throw — a\n * main-sync failure must not erase the lane results collected before it.\n */\n async syncMain(opts: { dryRun?: boolean } = {}): Promise<string> {\n const { cfg, defaultBranch, defaultScope, logger } = this.deps;\n const directPush = cfg.mainSync === 'direct-push';\n // In direct-push mode the sync branch (and its PR) is not consulted, moved or deleted.\n const branch = directPush ? defaultBranch : cfg.mainSyncBranch;\n\n if (cfg.autoMergeMainSyncPr && !directPush) {\n // Enabling auto-merge is host-specific and not part of the GitHostProvider contract.\n logger.consoleWarning(\n `sync.autoMergeMainSyncPr is enabled in the config, but enabling auto-merge is not implemented yet — ` +\n `the sync PR is opened without it. Use a repository auto-merge rule instead.`\n );\n }\n\n try {\n await fetchRemoteHeads();\n // Direct-push always starts from `origin/<defaultBranch>` — the very tip the plain push must\n // fast-forward. In 'pr' mode an existing sync branch is reused so its PR history survives.\n const syncBranchExists = directPush ? false : await branchExistsOnRemote(branch);\n const startPoint = syncBranchExists ? `origin/${branch}` : `origin/${defaultBranch}`;\n logger.console(\n chalk.blue(\n directPush\n ? `main -> checking the main scope against ${defaultBranch} (direct-push)`\n : `main -> checking the main scope against ${defaultBranch} (sync branch ${branch})`\n )\n );\n\n await this.resetToStartPoint(branch, startPoint);\n\n if (syncBranchExists) {\n // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale\n // tree would re-commit pre-merge content over the default branch's changes.\n const catchUpErr = await this.catchUpWithDefaultBranch(branch);\n if (catchUpErr) {\n const refork = await this.reforkIfMachineOwned(branch);\n if (refork === 'human-owned') return `${HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;\n if (refork === 'raced') return `main -> ${branch} was updated by a concurrent run — deferring to it`;\n }\n }\n\n // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch\n // would make the \"drift\" the lane's content. Refuse rather than open a wildly wrong PR.\n // `.bitmap`-derived read: the scope-object read answers \"main\" on a fresh runner and the guard\n // would silently stop refusing exactly where it ships (see `workspace-lane.ts`).\n const currentLane = currentLaneIdStr(this.deps.lanes);\n if (currentLane) {\n return (\n `${HALT_SUMMARY_PREFIX} main -> the .bitmap on ${startPoint} points at lane ` +\n `\"${currentLane}\" rather than main, so the main-scope drift cannot be computed`\n );\n }\n\n // `checkoutByCLIValues` rather than `checkout`: it imports current objects first (so `head`\n // resolves to the remote scope's versions) and persists `.bitmap` on destroy.\n // `includeNewFromScope`: a component exported to the scope's main but never in this repo's\n // `.bitmap` would otherwise be invisible to every sync run forever.\n // `mergeStrategy: 'theirs'` is required: unexported source drift makes components \"modified\",\n // and with no strategy the checkout throws. 'theirs' materializes the exported truth — any\n // reversion of unexported drift is visible in the PR diff. 'ours' is forbidden: it would advance\n // `.bitmap` while keeping the old files, a commit asserting a state the tree does not have.\n const results = await this.deps.checkout.checkoutByCLIValues('', {\n head: true,\n skipNpmInstall: true,\n includeNewFromScope: true,\n mergeStrategy: 'theirs',\n });\n const newFromScope = results.newFromScope ?? [];\n if (newFromScope.length) {\n logger.console(\n chalk.blue(\n `Added ${newFromScope.length} component(s) that exist on ${defaultScope}'s main but not in this ` +\n `workspace: ${newFromScope.join(', ')}`\n )\n );\n }\n\n const drift = await this.driftFiles();\n // Direct-push stays bare: asking the host about `mainSyncBranch`'s PR would be the one\n // interaction with it this mode promises not to make.\n if (!drift.length) return directPush ? CONVERGED_SUMMARY : await this.convergedSummary(branch);\n\n logger.console(\n formatWarningSummary(`main -> drift in ${drift.length} file(s): ${drift.slice(0, 20).join(', ')}`)\n );\n\n if (opts.dryRun) {\n // The working tree WAS written (a diff-based check has no other way); `finally` restores it. Safe\n // only because a dry run over a dirty tree is refused before anything runs — the restore rewinds\n // to HEAD, so an uncommitted change here would be gone (`assertCleanForDryRun`).\n logger.console(\n formatWarningSummary(\n directPush\n ? `Dry-run: main -> would push the drift directly onto ${branch}`\n : `Dry-run: main -> would push ${branch} and open a sync PR`\n )\n );\n return directPush\n ? `main -> drift detected in ${drift.length} file(s) — would push ${branch} directly`\n : `main -> drift detected in ${drift.length} file(s) — would open sync PR`;\n }\n\n await addAllExceptScopeAndModules();\n await commitWithIdentity(mainSyncCommitMessage(drift.length));\n // Never force: a rejected push means a concurrent run pushed in between, and in direct-push mode\n // that rejection is the whole safety story for the default branch. Unambiguous refspec — see\n // `lane-sync-executor.commitAllAndPush`.\n await git.push(['origin', `HEAD:refs/heads/${branch}`]);\n logger.console(chalk.green(`Pushed ${branch}`));\n\n if (directPush) {\n const shortSha = (await git.revparse(['--short', 'HEAD'])).trim();\n return `main -> direct-push (pushed ${branch} @ ${shortSha})`;\n }\n const prUrl = await this.ensureSyncPr({ branch, driftCount: drift.length, newFromScope });\n return `main -> pushed sync commit to ${branch}${prUrl ? ` (PR ${prUrl})` : ''}`;\n } catch (e: any) {\n // Same contract as the lane executor: a HALTED line, never a throw mid-run.\n return `${HALT_SUMMARY_PREFIX} main -> ${e?.message || e}`;\n } finally {\n await this.restoreWorkspace();\n }\n }\n\n /**\n * Put the working tree on the sync branch at a pristine copy of `startPoint`. Pristine is\n * load-bearing for a diff-based reconciler: any stray modification would be indistinguishable from\n * main-scope drift. The reload makes `checkout head` read this branch's `.bitmap`.\n */\n private async resetToStartPoint(branch: string, startPoint: string) {\n await checkoutPristine(branch, startPoint, () => this.deps.ci.reloadWorkspaceFromDisk());\n }\n\n /**\n * Merge the default branch into the sync branch so drift is computed against the repository's\n * current state. Returns a reason string on failure; a conflict needs a human (or deleting the sync\n * branch, which the next run re-forks).\n */\n private async catchUpWithDefaultBranch(branch: string): Promise<string | undefined> {\n const { defaultBranch, logger } = this.deps;\n try {\n // the marker line keeps the branch machine-owned across catch-up merges\n const out = await gitWithIdentity([\n 'merge',\n '-m',\n `merge origin/${defaultBranch} into ${branch}\\n\\n${SYNC_COMMIT_MARKER}`,\n `origin/${defaultBranch}`,\n ]);\n // simple-git resolves on some non-zero exits — judge the merge by state, not by rejection\n const conflicted = (await git.status()).conflicted;\n if (conflicted.length) throw new Error(`merge conflicts in: ${conflicted.join(', ')}`);\n logger.console(chalk.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));\n } catch (e: any) {\n await git.raw(['merge', '--abort']).catch(() => undefined);\n return (\n `could not bring the sync branch ${branch} up to date with origin/${defaultBranch}: ${e?.message || e}. ` +\n `Resolve or delete ${branch} (a deleted sync branch is re-forked from ${defaultBranch} on the next run)`\n );\n }\n // The merge may have brought a new `.bitmap` in from the default branch.\n await this.deps.ci.reloadWorkspaceFromDisk();\n return undefined;\n }\n\n /**\n * Re-fork the sync branch from the default branch when every commit it holds beyond it passes\n * `isSyncAuthoredMessage` — machine state is recomputable from the scope. The force push is\n * leased on the tip the ownership was read from, so a concurrent run's push wins the race.\n */\n private async reforkIfMachineOwned(branch: string): Promise<'re-forked' | 'raced' | 'human-owned'> {\n const { defaultBranch, logger } = this.deps;\n const staleTip = (await git.revparse([`origin/${branch}`])).trim();\n const log = await git.raw(['log', `origin/${defaultBranch}..${staleTip}`, '--format=%B%x1e']);\n const messages = log\n .split('\\x1e')\n .map((message) => message.trim())\n .filter(Boolean);\n const machineOwned = messages.length > 0 && messages.every((message) => isSyncAuthoredMessage(message));\n if (!machineOwned) return 'human-owned';\n logger.console(\n formatWarningSummary(\n `main -> ${branch} conflicts with ${defaultBranch} and carries only ${SYNC_COMMIT_MARKER} commits — ` +\n `re-forking it from origin/${defaultBranch}`\n )\n );\n await this.resetToStartPoint(branch, `origin/${defaultBranch}`);\n try {\n await git.push([`--force-with-lease=refs/heads/${branch}:${staleTip}`, 'origin', `HEAD:refs/heads/${branch}`]);\n } catch (e: any) {\n if (!isStaleLeaseRejection(e?.message || String(e))) throw e;\n logger.console(formatWarningSummary(`main -> ${branch} moved while re-forking — another run owns it now`));\n return 'raced';\n }\n return 're-forked';\n }\n\n /**\n * The \"nothing to do\" line — plus the open sync PR, if any: converged means the SYNC branch matches\n * the scope, and the default branch may still differ until that PR merges.\n */\n private async convergedSummary(branch: string): Promise<string> {\n const { gitHost } = this.deps;\n if (!gitHost) return CONVERGED_SUMMARY;\n const pr = await gitHost.findPrByBranch(branch).catch(() => undefined);\n return pr ? `${CONVERGED_SUMMARY} — open sync PR #${pr.number} still awaits review/merge` : CONVERGED_SUMMARY;\n }\n\n /**\n * The files `bit checkout head` changed, i.e. the drift. Excludes `.bit`/`node_modules` — the same\n * set the commit paths refuse to touch, keeping \"counts as drift\" and \"gets committed\" identical.\n */\n private async driftFiles(): Promise<string[]> {\n const status = await git.status();\n const paths = status.files.map((file) => file.path).filter((path) => !isNonContentPath(path));\n return [...new Set(paths)];\n }\n\n /**\n * Make sure the pushed branch has an open PR. A git-host failure only warns: the branch is pushed,\n * which is the load-bearing half, and the next run retries the PR.\n */\n private async ensureSyncPr({\n branch,\n driftCount,\n newFromScope,\n }: {\n branch: string;\n driftCount: number;\n newFromScope: string[];\n }): Promise<string | undefined> {\n const { gitHost, logger, defaultBranch } = this.deps;\n if (!gitHost) {\n logger.consoleWarning(\n 'No configured git host provider (for GitHub: BIT_GITHUB_TOKEN or GITHUB_TOKEN, plus a repository) — ' +\n 'pushed sync branch, skipping PR operations'\n );\n return undefined;\n }\n try {\n const existing = await gitHost.findPrByBranch(branch);\n if (existing) {\n logger.console(chalk.blue(`Sync PR ${existing.htmlUrl} is already open — pushed the new commit onto it`));\n return existing.htmlUrl;\n }\n const created = await gitHost.createPr({\n head: branch,\n base: defaultBranch,\n title: 'Bit sync: update to latest main scope versions',\n body: mainSyncPrBody({ driftCount, newFromScope }),\n });\n logger.console(chalk.green(`Opened sync PR ${created.htmlUrl}`));\n return created.htmlUrl;\n } catch (e: any) {\n logger.consoleWarning(`Could not open or find the sync PR for ${branch}: ${e?.message || e}`);\n return undefined;\n }\n }\n\n /**\n * Leave git on the default branch and bit on main. Mirrors `LaneSyncExecutor.restoreWorkspace`:\n * best-effort and warn-only so a restore hiccup can't throw out of a `finally` and mask the real\n * error; the clean discards untracked files `checkout head` wrote on the converged/dry-run paths.\n */\n private async restoreWorkspace() {\n const { logger, defaultBranch } = this.deps;\n try {\n if (currentLaneIdStr(this.deps.lanes)) {\n const switchErr = await this.deps.ci.switchToLaneForSync('main');\n if (switchErr) logger.consoleWarning(`Could not switch the workspace back to main: ${switchErr.message}`);\n }\n await checkoutPristineRestore(defaultBranch, () => this.deps.ci.reloadWorkspaceFromDisk());\n } catch (e: any) {\n logger.consoleWarning(`Could not restore the workspace after the main sync: ${e?.message || e}`);\n }\n }\n}\n\n/** The sync commit message: no `Bit-Lane-Head` trailer — main sync keeps no state. */\nfunction mainSyncCommitMessage(driftCount: number): string {\n return [\n `chore(bit-sync): sync git to latest main scope versions (${driftCount} file(s))`,\n '',\n SYNC_COMMIT_MARKER,\n ].join('\\n');\n}\n\nfunction mainSyncPrBody({ driftCount, newFromScope }: { driftCount: number; newFromScope: string[] }): string {\n const lines = [\n 'Automated sync PR: the Bit scope moved ahead of this repository. This PR checks the workspace out to the ' +\n 'latest exported versions (`bit checkout head --auto-merge-resolve theirs`).',\n '',\n `- files changed: ${driftCount}`,\n '- conflicts between this repository and the scope were resolved **in favour of the scope** — if a file here ' +\n 'held changes that were never exported to Bit, this PR reverts them. Review the diff and close the PR ' +\n 'instead of merging if that is not what you want.',\n ];\n if (newFromScope.length) {\n lines.push(\n `- components added from the scope (${newFromScope.length}, not previously in this repository's \\`.bitmap\\`):`,\n // Bounded like the lane PR body: an over-long body is rejected by the host outright.\n ...capEntries(\n newFromScope.map((id) => ` - \\`${id}\\``),\n ' - '\n )\n );\n }\n lines.push(\n '',\n 'This PR is maintained by `bit ci sync` — re-running the command pushes any further drift onto the same branch.'\n );\n return lines.join('\\n');\n}\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/ci",
3
- "version": "1.0.488",
3
+ "version": "1.0.490",
4
4
  "homepage": "https://bit.cloud/teambit/git/ci",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.git",
8
8
  "name": "ci",
9
- "version": "1.0.488"
9
+ "version": "1.0.490"
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "4.1.2",
@@ -15,31 +15,31 @@
15
15
  "semver": "7.7.1",
16
16
  "simple-git": "^3.28.0",
17
17
  "@teambit/harmony": "0.4.12",
18
- "@teambit/cli": "0.0.1362",
19
- "@teambit/logger": "0.0.1455",
20
- "@teambit/workspace": "1.0.1095",
21
- "@teambit/builder": "1.0.1095",
22
- "@teambit/checkout": "1.0.1096",
18
+ "@teambit/cli": "0.0.1363",
19
+ "@teambit/logger": "0.0.1456",
20
+ "@teambit/workspace": "1.0.1096",
21
+ "@teambit/builder": "1.0.1096",
22
+ "@teambit/checkout": "1.0.1097",
23
23
  "@teambit/component-id": "1.2.4",
24
- "@teambit/component.modules.merge-helper": "0.0.86",
25
- "@teambit/component.snap-distance": "0.0.144",
26
- "@teambit/config-merger": "0.0.962",
27
- "@teambit/dependency-resolver": "1.0.1095",
28
- "@teambit/export": "1.0.1095",
29
- "@teambit/harmony.modules.concurrency": "0.0.40",
30
- "@teambit/importer": "1.0.1095",
24
+ "@teambit/component.modules.merge-helper": "0.0.87",
25
+ "@teambit/component.snap-distance": "0.0.145",
26
+ "@teambit/config-merger": "0.0.963",
27
+ "@teambit/dependency-resolver": "1.0.1096",
28
+ "@teambit/export": "1.0.1096",
29
+ "@teambit/harmony.modules.concurrency": "0.0.41",
30
+ "@teambit/importer": "1.0.1096",
31
31
  "@teambit/lane-id": "0.0.312",
32
- "@teambit/lanes": "1.0.1116",
33
- "@teambit/legacy.consumer-component": "0.0.144",
34
- "@teambit/objects": "0.0.602",
35
- "@teambit/snapping": "1.0.1095",
36
- "@teambit/status": "1.0.1120",
32
+ "@teambit/lanes": "1.0.1117",
33
+ "@teambit/legacy.consumer-component": "0.0.145",
34
+ "@teambit/objects": "0.0.603",
35
+ "@teambit/snapping": "1.0.1096",
36
+ "@teambit/status": "1.0.1121",
37
37
  "@teambit/toolbox.promise.map-pool": "0.0.19",
38
38
  "@teambit/toolbox.string.random": "0.0.18",
39
39
  "@teambit/bit-error": "0.0.404",
40
- "@teambit/legacy.constants": "0.0.39",
41
- "@teambit/legacy.scope": "0.0.143",
42
- "@teambit/legacy.bit-map": "0.0.200",
40
+ "@teambit/legacy.constants": "0.0.40",
41
+ "@teambit/legacy.scope": "0.0.144",
42
+ "@teambit/legacy.bit-map": "0.0.201",
43
43
  "@teambit/toolbox.crypto.sha1": "0.0.20"
44
44
  },
45
45
  "devDependencies": {
@@ -6,7 +6,7 @@ import type { CheckoutMain } from '@teambit/checkout';
6
6
  import { git } from '../git';
7
7
  import type { CiMain } from '../ci.main.runtime';
8
8
  import type { CiSyncConfig } from './sync-config';
9
- import { SYNC_COMMIT_MARKER } from './sync-state';
9
+ import { SYNC_COMMIT_MARKER, isSyncAuthoredMessage } from './sync-state';
10
10
  import { currentLaneIdStr } from './workspace-lane';
11
11
  import type { GitHostProvider } from './git-host-provider';
12
12
  import { HALT_SUMMARY_PREFIX, capEntries } from './lane-sync-executor';
@@ -19,6 +19,7 @@ import {
19
19
  fetchRemoteHeads,
20
20
  gitWithIdentity,
21
21
  isNonContentPath,
22
+ isStaleLeaseRejection,
22
23
  } from './git-ops';
23
24
 
24
25
  export type MainSyncDeps = {
@@ -38,7 +39,8 @@ export type MainSyncDeps = {
38
39
  * Reconcile the main scope with the repository's default branch. Stateless: `bit checkout head`
39
40
  * writes the latest exported versions, so an empty `git status` IS convergence and any diff IS the
40
41
  * drift. Under 'pr' the convergence is proposed via `mainSyncBranch`; under 'direct-push' it lands on
41
- * the default branch with a plain push. Nothing is ever force-pushed in either mode.
42
+ * the default branch with a plain push. One force push exists: a machine-owned sync branch that
43
+ * conflicts with the default branch is re-forked under a tip lease (`reforkIfMachineOwned`).
42
44
  */
43
45
  /** the "nothing to do" line, shared by both modes */
44
46
  const CONVERGED_SUMMARY = 'main -> converged (checkout head produced no changes)';
@@ -82,10 +84,13 @@ export class MainSyncExecutor {
82
84
 
83
85
  if (syncBranchExists) {
84
86
  // Catch up with the default branch: the PR must stay mergeable, and `checkout head` on a stale
85
- // tree would re-commit pre-merge content over the default branch's changes. A merge — never a
86
- // rebase, never a force-push — is the only non-destructive way to move a branch with an open PR.
87
+ // tree would re-commit pre-merge content over the default branch's changes.
87
88
  const catchUpErr = await this.catchUpWithDefaultBranch(branch);
88
- if (catchUpErr) return `${HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;
89
+ if (catchUpErr) {
90
+ const refork = await this.reforkIfMachineOwned(branch);
91
+ if (refork === 'human-owned') return `${HALT_SUMMARY_PREFIX} main -> ${catchUpErr}`;
92
+ if (refork === 'raced') return `main -> ${branch} was updated by a concurrent run — deferring to it`;
93
+ }
89
94
  }
90
95
 
91
96
  // `checkout head` resolves versions against the CURRENT lane; a lane pointer on this branch
@@ -188,8 +193,16 @@ export class MainSyncExecutor {
188
193
  private async catchUpWithDefaultBranch(branch: string): Promise<string | undefined> {
189
194
  const { defaultBranch, logger } = this.deps;
190
195
  try {
191
- // under the identity: a non-fast-forward merge writes a merge COMMIT
192
- const out = await gitWithIdentity(['merge', '--no-edit', `origin/${defaultBranch}`]);
196
+ // the marker line keeps the branch machine-owned across catch-up merges
197
+ const out = await gitWithIdentity([
198
+ 'merge',
199
+ '-m',
200
+ `merge origin/${defaultBranch} into ${branch}\n\n${SYNC_COMMIT_MARKER}`,
201
+ `origin/${defaultBranch}`,
202
+ ]);
203
+ // simple-git resolves on some non-zero exits — judge the merge by state, not by rejection
204
+ const conflicted = (await git.status()).conflicted;
205
+ if (conflicted.length) throw new Error(`merge conflicts in: ${conflicted.join(', ')}`);
193
206
  logger.console(chalk.blue(`Brought ${branch} up to date with origin/${defaultBranch}: ${out.trim()}`));
194
207
  } catch (e: any) {
195
208
  await git.raw(['merge', '--abort']).catch(() => undefined);
@@ -203,6 +216,38 @@ export class MainSyncExecutor {
203
216
  return undefined;
204
217
  }
205
218
 
219
+ /**
220
+ * Re-fork the sync branch from the default branch when every commit it holds beyond it passes
221
+ * `isSyncAuthoredMessage` — machine state is recomputable from the scope. The force push is
222
+ * leased on the tip the ownership was read from, so a concurrent run's push wins the race.
223
+ */
224
+ private async reforkIfMachineOwned(branch: string): Promise<'re-forked' | 'raced' | 'human-owned'> {
225
+ const { defaultBranch, logger } = this.deps;
226
+ const staleTip = (await git.revparse([`origin/${branch}`])).trim();
227
+ const log = await git.raw(['log', `origin/${defaultBranch}..${staleTip}`, '--format=%B%x1e']);
228
+ const messages = log
229
+ .split('\x1e')
230
+ .map((message) => message.trim())
231
+ .filter(Boolean);
232
+ const machineOwned = messages.length > 0 && messages.every((message) => isSyncAuthoredMessage(message));
233
+ if (!machineOwned) return 'human-owned';
234
+ logger.console(
235
+ formatWarningSummary(
236
+ `main -> ${branch} conflicts with ${defaultBranch} and carries only ${SYNC_COMMIT_MARKER} commits — ` +
237
+ `re-forking it from origin/${defaultBranch}`
238
+ )
239
+ );
240
+ await this.resetToStartPoint(branch, `origin/${defaultBranch}`);
241
+ try {
242
+ await git.push([`--force-with-lease=refs/heads/${branch}:${staleTip}`, 'origin', `HEAD:refs/heads/${branch}`]);
243
+ } catch (e: any) {
244
+ if (!isStaleLeaseRejection(e?.message || String(e))) throw e;
245
+ logger.console(formatWarningSummary(`main -> ${branch} moved while re-forking — another run owns it now`));
246
+ return 'raced';
247
+ }
248
+ return 're-forked';
249
+ }
250
+
206
251
  /**
207
252
  * The "nothing to do" line — plus the open sync PR, if any: converged means the SYNC branch matches
208
253
  * the scope, and the default branch may still differ until that PR merges.