@teambit/ci 1.0.494 → 1.0.497

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.
Files changed (38) hide show
  1. package/ci.docs.mdx +39 -9
  2. package/dist/ci.docs.mdx +39 -9
  3. package/dist/ci.main.runtime.d.ts +6 -0
  4. package/dist/ci.main.runtime.js +24 -3
  5. package/dist/ci.main.runtime.js.map +1 -1
  6. package/dist/{preview-1786400681343.js → preview-1786718956243.js} +1 -1
  7. package/dist/sync/git-ops.d.ts +41 -0
  8. package/dist/sync/git-ops.js +71 -0
  9. package/dist/sync/git-ops.js.map +1 -1
  10. package/dist/sync/git-ops.spec.js +39 -0
  11. package/dist/sync/git-ops.spec.js.map +1 -1
  12. package/dist/sync/lane-sync-executor.d.ts +81 -15
  13. package/dist/sync/lane-sync-executor.js +315 -50
  14. package/dist/sync/lane-sync-executor.js.map +1 -1
  15. package/dist/sync/lane-sync-executor.spec.js +235 -13
  16. package/dist/sync/lane-sync-executor.spec.js.map +1 -1
  17. package/dist/sync/main-sync-executor.js +30 -6
  18. package/dist/sync/main-sync-executor.js.map +1 -1
  19. package/dist/sync/sync-planner.d.ts +10 -1
  20. package/dist/sync/sync-planner.js +37 -13
  21. package/dist/sync/sync-planner.js.map +1 -1
  22. package/dist/sync/sync-planner.spec.js +44 -2
  23. package/dist/sync/sync-planner.spec.js.map +1 -1
  24. package/dist/sync/sync-state.d.ts +33 -1
  25. package/dist/sync/sync-state.js +60 -3
  26. package/dist/sync/sync-state.js.map +1 -1
  27. package/dist/sync/sync-state.spec.js +28 -0
  28. package/dist/sync/sync-state.spec.js.map +1 -1
  29. package/package.json +20 -20
  30. package/sync/git-ops.spec.ts +60 -0
  31. package/sync/git-ops.ts +72 -0
  32. package/sync/lane-sync-executor.spec.ts +215 -9
  33. package/sync/lane-sync-executor.ts +354 -47
  34. package/sync/main-sync-executor.ts +37 -5
  35. package/sync/sync-planner.spec.ts +36 -1
  36. package/sync/sync-planner.ts +36 -16
  37. package/sync/sync-state.spec.ts +33 -0
  38. package/sync/sync-state.ts +62 -1
@@ -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","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":";;;;;;AAAA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,KAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,IAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAIA,SAAAI,KAAA;EAAA,MAAAJ,IAAA,GAAAE,OAAA;EAAAE,IAAA,YAAAA,CAAA;IAAA,OAAAJ,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAGA,SAAAK,WAAA;EAAA,MAAAL,IAAA,GAAAE,OAAA;EAAAG,UAAA,YAAAA,CAAA;IAAA,OAAAL,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAM,eAAA;EAAA,MAAAN,IAAA,GAAAE,OAAA;EAAAI,cAAA,YAAAA,CAAA;IAAA,OAAAN,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA,SAAAO,kBAAA;EAAA,MAAAP,IAAA,GAAAE,OAAA;EAAAK,iBAAA,YAAAA,CAAA;IAAA,OAAAP,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAQ,QAAA;EAAA,MAAAR,IAAA,GAAAE,OAAA;EAAAM,OAAA,YAAAA,CAAA;IAAA,OAAAR,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAUmB,SAAAC,uBAAAQ,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAenB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMG,iBAAiB,GAAG,uDAAuD;AAE1E,MAAMC,gBAAgB,CAAC;EAC5BC,WAAWA,CAASC,IAAkB,EAAE;IAAA,KAApBA,IAAkB,GAAlBA,IAAkB;EAAG;;EAEzC;AACF;AACA;AACA;EACE,MAAMC,QAAQA,CAACC,IAA0B,GAAG,CAAC,CAAC,EAAmB;IAC/D,MAAM;MAAEC,GAAG;MAAEC,aAAa;MAAEC,YAAY;MAAEC;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC9D,MAAMO,UAAU,GAAGJ,GAAG,CAACK,QAAQ,KAAK,aAAa;IACjD;IACA,MAAMC,MAAM,GAAGF,UAAU,GAAGH,aAAa,GAAGD,GAAG,CAACO,cAAc;IAE9D,IAAIP,GAAG,CAACQ,mBAAmB,IAAI,CAACJ,UAAU,EAAE;MAC1C;MACAD,MAAM,CAACM,cAAc,CACnB,sGAAsG,GACpG,6EACJ,CAAC;IACH;IAEA,IAAI;MACF,MAAM,IAAAC,0BAAgB,EAAC,CAAC;MACxB;MACA;MACA,MAAMC,gBAAgB,GAAGP,UAAU,GAAG,KAAK,GAAG,MAAM,IAAAQ,8BAAoB,EAACN,MAAM,CAAC;MAChF,MAAMO,UAAU,GAAGF,gBAAgB,GAAG,UAAUL,MAAM,EAAE,GAAG,UAAUL,aAAa,EAAE;MACpFE,MAAM,CAACW,OAAO,CACZC,gBAAK,CAACC,IAAI,CACRZ,UAAU,GACN,2CAA2CH,aAAa,gBAAgB,GACxE,2CAA2CA,aAAa,iBAAiBK,MAAM,GACrF,CACF,CAAC;MAED,MAAM,IAAI,CAACW,iBAAiB,CAACX,MAAM,EAAEO,UAAU,CAAC;MAEhD,IAAIF,gBAAgB,EAAE;QACpB;QACA;QACA,MAAMO,UAAU,GAAG,MAAM,IAAI,CAACC,wBAAwB,CAACb,MAAM,CAAC;QAC9D,IAAIY,UAAU,EAAE;UACd,MAAME,MAAM,GAAG,MAAM,IAAI,CAACC,oBAAoB,CAACf,MAAM,CAAC;UACtD,IAAIc,MAAM,KAAK,aAAa,EAAE,OAAO,GAAGE,uCAAmB,YAAYJ,UAAU,EAAE;UACnF,IAAIE,MAAM,KAAK,OAAO,EAAE,OAAO,WAAWd,MAAM,oDAAoD;QACtG;MACF;;MAEA;MACA;MACA;MACA;MACA,MAAMiB,WAAW,GAAG,IAAAC,iCAAgB,EAAC,IAAI,CAAC3B,IAAI,CAAC4B,KAAK,CAAC;MACrD,IAAIF,WAAW,EAAE;QACf,OACE,GAAGD,uCAAmB,2BAA2BT,UAAU,kBAAkB,GAC7E,IAAIU,WAAW,gEAAgE;MAEnF;;MAEA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA,MAAMG,OAAO,GAAG,MAAM,IAAI,CAAC7B,IAAI,CAAC8B,QAAQ,CAACC,mBAAmB,CAAC,EAAE,EAAE;QAC/DC,IAAI,EAAE,IAAI;QACVC,cAAc,EAAE,IAAI;QACpBC,mBAAmB,EAAE,IAAI;QACzBC,aAAa,EAAE;MACjB,CAAC,CAAC;MACF,MAAMC,YAAY,GAAGP,OAAO,CAACO,YAAY,IAAI,EAAE;MAC/C,IAAIA,YAAY,CAACC,MAAM,EAAE;QACvB/B,MAAM,CAACW,OAAO,CACZC,gBAAK,CAACC,IAAI,CACR,SAASiB,YAAY,CAACC,MAAM,+BAA+BhC,YAAY,0BAA0B,GAC/F,cAAc+B,YAAY,CAACE,IAAI,CAAC,IAAI,CAAC,EACzC,CACF,CAAC;MACH;MAEA,MAAMC,KAAK,GAAG,MAAM,IAAI,CAACC,UAAU,CAAC,CAAC;MACrC;MACA;MACA,IAAI,CAACD,KAAK,CAACF,MAAM,EAAE,OAAO9B,UAAU,GAAGV,iBAAiB,GAAG,MAAM,IAAI,CAAC4C,gBAAgB,CAAChC,MAAM,CAAC;MAE9FH,MAAM,CAACW,OAAO,CACZ,IAAAyB,2BAAoB,EAAC,oBAAoBH,KAAK,CAACF,MAAM,aAAaE,KAAK,CAACI,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAACL,IAAI,CAAC,IAAI,CAAC,EAAE,CACnG,CAAC;MAED,IAAIpC,IAAI,CAAC0C,MAAM,EAAE;QACf;QACA;QACA;QACAtC,MAAM,CAACW,OAAO,CACZ,IAAAyB,2BAAoB,EAClBnC,UAAU,GACN,uDAAuDE,MAAM,EAAE,GAC/D,+BAA+BA,MAAM,qBAC3C,CACF,CAAC;QACD,OAAOF,UAAU,GACb,6BAA6BgC,KAAK,CAACF,MAAM,yBAAyB5B,MAAM,WAAW,GACnF,6BAA6B8B,KAAK,CAACF,MAAM,+BAA+B;MAC9E;MAEA,MAAM,IAAAQ,qCAA2B,EAAC,CAAC;MACnC,MAAM,IAAAC,4BAAkB,EAACC,qBAAqB,CAACR,KAAK,CAACF,MAAM,CAAC,CAAC;MAC7D;MACA;MACA;MACA,MAAMW,UAAG,CAACC,IAAI,CAAC,CAAC,QAAQ,EAAE,mBAAmBxC,MAAM,EAAE,CAAC,CAAC;MACvDH,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACgC,KAAK,CAAC,UAAUzC,MAAM,EAAE,CAAC,CAAC;MAE/C,IAAIF,UAAU,EAAE;QACd,MAAM4C,QAAQ,GAAG,CAAC,MAAMH,UAAG,CAACI,QAAQ,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,EAAEC,IAAI,CAAC,CAAC;QACjE,OAAO,+BAA+B5C,MAAM,MAAM0C,QAAQ,GAAG;MAC/D;MACA,MAAMG,KAAK,GAAG,MAAM,IAAI,CAACC,YAAY,CAAC;QAAE9C,MAAM;QAAE+C,UAAU,EAAEjB,KAAK,CAACF,MAAM;QAAED;MAAa,CAAC,CAAC;MACzF,OAAO,iCAAiC3B,MAAM,GAAG6C,KAAK,GAAG,QAAQA,KAAK,GAAG,GAAG,EAAE,EAAE;IAClF,CAAC,CAAC,OAAO5D,CAAM,EAAE;MACf;MACA,OAAO,GAAG+B,uCAAmB,YAAY/B,CAAC,EAAE+D,OAAO,IAAI/D,CAAC,EAAE;IAC5D,CAAC,SAAS;MACR,MAAM,IAAI,CAACgE,gBAAgB,CAAC,CAAC;IAC/B;EACF;;EAEA;AACF;AACA;AACA;AACA;EACE,MAActC,iBAAiBA,CAACX,MAAc,EAAEO,UAAkB,EAAE;IAClE,MAAM,IAAA2C,0BAAgB,EAAClD,MAAM,EAAEO,UAAU,EAAE,MAAM,IAAI,CAAChB,IAAI,CAAC4D,EAAE,CAACC,uBAAuB,CAAC,CAAC,CAAC;EAC1F;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAcvC,wBAAwBA,CAACb,MAAc,EAA+B;IAClF,MAAM;MAAEL,aAAa;MAAEE;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC3C,IAAI;MACF;MACA,MAAM8D,GAAG,GAAG,MAAM,IAAAC,yBAAe,EAAC,CAChC,OAAO,EACP,IAAI,EACJ,gBAAgB3D,aAAa,SAASK,MAAM,OAAOuD,+BAAkB,EAAE,EACvE,UAAU5D,aAAa,EAAE,CAC1B,CAAC;MACF;MACA,MAAM6D,UAAU,GAAG,CAAC,MAAMjB,UAAG,CAACkB,MAAM,CAAC,CAAC,EAAED,UAAU;MAClD,IAAIA,UAAU,CAAC5B,MAAM,EAAE,MAAM,IAAI8B,KAAK,CAAC,uBAAuBF,UAAU,CAAC3B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;MACtFhC,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACC,IAAI,CAAC,WAAWV,MAAM,2BAA2BL,aAAa,KAAK0D,GAAG,CAACT,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC,CAAC,OAAO3D,CAAM,EAAE;MACf,MAAMsD,UAAG,CAACoB,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAACC,KAAK,CAAC,MAAMC,SAAS,CAAC;MAC1D,OACE,mCAAmC7D,MAAM,2BAA2BL,aAAa,KAAKV,CAAC,EAAE+D,OAAO,IAAI/D,CAAC,IAAI,GACzG,qBAAqBe,MAAM,6CAA6CL,aAAa,mBAAmB;IAE5G;IACA;IACA,MAAM,IAAI,CAACJ,IAAI,CAAC4D,EAAE,CAACC,uBAAuB,CAAC,CAAC;IAC5C,OAAOS,SAAS;EAClB;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAc9C,oBAAoBA,CAACf,MAAc,EAAkD;IACjG,MAAM;MAAEL,aAAa;MAAEE;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC3C,MAAMuE,QAAQ,GAAG,CAAC,MAAMvB,UAAG,CAACI,QAAQ,CAAC,CAAC,UAAU3C,MAAM,EAAE,CAAC,CAAC,EAAE4C,IAAI,CAAC,CAAC;IAClE,MAAMmB,GAAG,GAAG,MAAMxB,UAAG,CAACoB,GAAG,CAAC,CAAC,KAAK,EAAE,UAAUhE,aAAa,KAAKmE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAC7F,MAAME,QAAQ,GAAGD,GAAG,CACjBE,KAAK,CAAC,MAAM,CAAC,CACbC,GAAG,CAAElB,OAAO,IAAKA,OAAO,CAACJ,IAAI,CAAC,CAAC,CAAC,CAChCuB,MAAM,CAACC,OAAO,CAAC;IAClB,MAAMC,YAAY,GAAGL,QAAQ,CAACpC,MAAM,GAAG,CAAC,IAAIoC,QAAQ,CAACM,KAAK,CAAEtB,OAAO,IAAK,IAAAuB,kCAAqB,EAACvB,OAAO,CAAC,CAAC;IACvG,IAAI,CAACqB,YAAY,EAAE,OAAO,aAAa;IACvCxE,MAAM,CAACW,OAAO,CACZ,IAAAyB,2BAAoB,EAClB,WAAWjC,MAAM,mBAAmBL,aAAa,qBAAqB4D,+BAAkB,aAAa,GACnG,6BAA6B5D,aAAa,EAC9C,CACF,CAAC;IACD,MAAM,IAAI,CAACgB,iBAAiB,CAACX,MAAM,EAAE,UAAUL,aAAa,EAAE,CAAC;IAC/D,IAAI;MACF,MAAM4C,UAAG,CAACC,IAAI,CAAC,CAAC,iCAAiCxC,MAAM,IAAI8D,QAAQ,EAAE,EAAE,QAAQ,EAAE,mBAAmB9D,MAAM,EAAE,CAAC,CAAC;IAChH,CAAC,CAAC,OAAOf,CAAM,EAAE;MACf,IAAI,CAAC,IAAAuF,+BAAqB,EAACvF,CAAC,EAAE+D,OAAO,IAAIyB,MAAM,CAACxF,CAAC,CAAC,CAAC,EAAE,MAAMA,CAAC;MAC5DY,MAAM,CAACW,OAAO,CAAC,IAAAyB,2BAAoB,EAAC,WAAWjC,MAAM,mDAAmD,CAAC,CAAC;MAC1G,OAAO,OAAO;IAChB;IACA,OAAO,WAAW;EACpB;;EAEA;AACF;AACA;AACA;EACE,MAAcgC,gBAAgBA,CAAChC,MAAc,EAAmB;IAC9D,MAAM;MAAE0E;IAAQ,CAAC,GAAG,IAAI,CAACnF,IAAI;IAC7B,IAAI,CAACmF,OAAO,EAAE,OAAOtF,iBAAiB;IACtC,MAAMuF,EAAE,GAAG,MAAMD,OAAO,CAACE,cAAc,CAAC5E,MAAM,CAAC,CAAC4D,KAAK,CAAC,MAAMC,SAAS,CAAC;IACtE,OAAOc,EAAE,GAAG,GAAGvF,iBAAiB,oBAAoBuF,EAAE,CAACE,MAAM,4BAA4B,GAAGzF,iBAAiB;EAC/G;;EAEA;AACF;AACA;AACA;EACE,MAAc2C,UAAUA,CAAA,EAAsB;IAC5C,MAAM0B,MAAM,GAAG,MAAMlB,UAAG,CAACkB,MAAM,CAAC,CAAC;IACjC,MAAMqB,KAAK,GAAGrB,MAAM,CAACsB,KAAK,CAACb,GAAG,CAAEc,IAAI,IAAKA,IAAI,CAACC,IAAI,CAAC,CAACd,MAAM,CAAEc,IAAI,IAAK,CAAC,IAAAC,0BAAgB,EAACD,IAAI,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,IAAIE,GAAG,CAACL,KAAK,CAAC,CAAC;EAC5B;;EAEA;AACF;AACA;AACA;EACE,MAAchC,YAAYA,CAAC;IACzB9C,MAAM;IACN+C,UAAU;IACVpB;EAKF,CAAC,EAA+B;IAC9B,MAAM;MAAE+C,OAAO;MAAE7E,MAAM;MAAEF;IAAc,CAAC,GAAG,IAAI,CAACJ,IAAI;IACpD,IAAI,CAACmF,OAAO,EAAE;MACZ7E,MAAM,CAACM,cAAc,CACnB,sGAAsG,GACpG,4CACJ,CAAC;MACD,OAAO0D,SAAS;IAClB;IACA,IAAI;MACF,MAAMuB,QAAQ,GAAG,MAAMV,OAAO,CAACE,cAAc,CAAC5E,MAAM,CAAC;MACrD,IAAIoF,QAAQ,EAAE;QACZvF,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACC,IAAI,CAAC,WAAW0E,QAAQ,CAACC,OAAO,kDAAkD,CAAC,CAAC;QACzG,OAAOD,QAAQ,CAACC,OAAO;MACzB;MACA,MAAMC,OAAO,GAAG,MAAMZ,OAAO,CAACa,QAAQ,CAAC;QACrChE,IAAI,EAAEvB,MAAM;QACZwF,IAAI,EAAE7F,aAAa;QACnB8F,KAAK,EAAE,gDAAgD;QACvDC,IAAI,EAAEC,cAAc,CAAC;UAAE5C,UAAU;UAAEpB;QAAa,CAAC;MACnD,CAAC,CAAC;MACF9B,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACgC,KAAK,CAAC,kBAAkB6C,OAAO,CAACD,OAAO,EAAE,CAAC,CAAC;MAChE,OAAOC,OAAO,CAACD,OAAO;IACxB,CAAC,CAAC,OAAOpG,CAAM,EAAE;MACfY,MAAM,CAACM,cAAc,CAAC,0CAA0CH,MAAM,KAAKf,CAAC,EAAE+D,OAAO,IAAI/D,CAAC,EAAE,CAAC;MAC7F,OAAO4E,SAAS;IAClB;EACF;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAcZ,gBAAgBA,CAAA,EAAG;IAC/B,MAAM;MAAEpD,MAAM;MAAEF;IAAc,CAAC,GAAG,IAAI,CAACJ,IAAI;IAC3C,IAAI;MACF,IAAI,IAAA2B,iCAAgB,EAAC,IAAI,CAAC3B,IAAI,CAAC4B,KAAK,CAAC,EAAE;QACrC,MAAMyE,SAAS,GAAG,MAAM,IAAI,CAACrG,IAAI,CAAC4D,EAAE,CAAC0C,mBAAmB,CAAC,MAAM,CAAC;QAChE,IAAID,SAAS,EAAE/F,MAAM,CAACM,cAAc,CAAC,gDAAgDyF,SAAS,CAAC5C,OAAO,EAAE,CAAC;MAC3G;MACA,MAAM,IAAA8C,iCAAuB,EAACnG,aAAa,EAAE,MAAM,IAAI,CAACJ,IAAI,CAAC4D,EAAE,CAACC,uBAAuB,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC,OAAOnE,CAAM,EAAE;MACfY,MAAM,CAACM,cAAc,CAAC,wDAAwDlB,CAAC,EAAE+D,OAAO,IAAI/D,CAAC,EAAE,CAAC;IAClG;EACF;AACF;;AAEA;AAAA8G,OAAA,CAAA1G,gBAAA,GAAAA,gBAAA;AACA,SAASiD,qBAAqBA,CAACS,UAAkB,EAAU;EACzD,OAAO,CACL,4DAA4DA,UAAU,WAAW,EACjF,EAAE,EACFQ,+BAAkB,CACnB,CAAC1B,IAAI,CAAC,IAAI,CAAC;AACd;AAEA,SAAS8D,cAAcA,CAAC;EAAE5C,UAAU;EAAEpB;AAA6D,CAAC,EAAU;EAC5G,MAAMqE,KAAK,GAAG,CACZ,2GAA2G,GACzG,6EAA6E,EAC/E,EAAE,EACF,oBAAoBjD,UAAU,EAAE,EAChC,8GAA8G,GAC5G,uGAAuG,GACvG,kDAAkD,CACrD;EACD,IAAIpB,YAAY,CAACC,MAAM,EAAE;IACvBoE,KAAK,CAACxD,IAAI,CACR,sCAAsCb,YAAY,CAACC,MAAM,qDAAqD;IAC9G;IACA,GAAG,IAAAqE,8BAAU,EACXtE,YAAY,CAACuC,GAAG,CAAEgC,EAAE,IAAK,SAASA,EAAE,IAAI,CAAC,EACzC,MACF,CACF,CAAC;EACH;EACAF,KAAK,CAACxD,IAAI,CACR,EAAE,EACF,gHACF,CAAC;EACD,OAAOwD,KAAK,CAACnE,IAAI,CAAC,IAAI,CAAC;AACzB","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","racedMainSummary","currentLane","currentLaneIdStr","lanes","results","checkout","checkoutByCLIValues","head","skipNpmInstall","includeNewFromScope","mergeStrategy","newFromScope","length","join","drift","driftFiles","convergedSummary","formatWarningSummary","slice","dryRun","addAllExceptScopeAndModules","commitWithIdentity","mainSyncCommitMessage","baseSha","git","revparse","then","sha","trim","undefined","catch","push","message","String","confirmPushRace","dropErr","dropLocalBranchToRemoteTip","redactUrlCredentials","green","shortSha","prUrl","ensureSyncPr","driftCount","restoreWorkspace","checkoutPristine","ci","reloadWorkspaceFromDisk","out","gitWithIdentity","SYNC_COMMIT_MARKER","conflicted","status","Error","raw","staleTip","log","messages","split","map","filter","Boolean","machineOwned","every","isSyncAuthoredMessage","isStaleLeaseRejection","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 confirmPushRace,\n dropLocalBranchToRemoteTip,\n fetchRemoteHeads,\n gitWithIdentity,\n isNonContentPath,\n isStaleLeaseRejection,\n redactUrlCredentials,\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 racedMainSummary(branch);\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 // The local knowledge of `origin/<branch>` — the tip this run built on; after a rejection it is\n // the baseline `confirmPushRace` checks the re-fetched remote against.\n const baseSha = await git\n .revparse([`origin/${branch}`])\n .then((sha) => sha.trim() || undefined)\n .catch(() => undefined);\n // Never force. Unambiguous refspec — see `lane-sync-executor.commitAllAndPush`. A rejection means\n // a concurrent run pushed in between; what happens next depends on the mode. Direct-push: the\n // rejection IS the default branch's safety story — stay loud, never swallow. 'pr' mode: the push\n // targets the machine-owned sync branch, where a concurrent-run overlap is the same benign race\n // the lane executor tolerates (`reforkIfMachineOwned` already defers the same way) — confirm it\n // and defer.\n try {\n await git.push(['origin', `HEAD:refs/heads/${branch}`]);\n } catch (e: any) {\n const message = String(e?.message || e);\n if (directPush || (await confirmPushRace(branch, baseSha, message)) !== 'confirmed-race') throw e;\n // Drop the losing commit: left on the local sync branch, its orphan sibling would trip the\n // next run's `checkoutPristine` guard. The original rejection text is kept for diagnosability.\n const dropErr = await dropLocalBranchToRemoteTip(branch);\n if (dropErr) {\n logger.consoleWarning(`Could not drop the unpushed sync commit on ${branch}: ${dropErr}`);\n }\n logger.console(\n formatWarningSummary(`main -> push to ${branch} lost to a concurrent run: ${redactUrlCredentials(message)}`)\n );\n return racedMainSummary(branch);\n }\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 deferral line both raced shapes share: the catch-up refork race and the sync-branch push race. */\nfunction racedMainSummary(branch: string): string {\n return `main -> ${branch} was updated by a concurrent run — deferring to it`;\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":";;;;;;AAAA,SAAAA,OAAA;EAAA,MAAAC,IAAA,GAAAC,sBAAA,CAAAC,OAAA;EAAAH,MAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAG,KAAA;EAAA,MAAAH,IAAA,GAAAE,OAAA;EAAAC,IAAA,YAAAA,CAAA;IAAA,OAAAH,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAIA,SAAAI,KAAA;EAAA,MAAAJ,IAAA,GAAAE,OAAA;EAAAE,IAAA,YAAAA,CAAA;IAAA,OAAAJ,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAGA,SAAAK,WAAA;EAAA,MAAAL,IAAA,GAAAE,OAAA;EAAAG,UAAA,YAAAA,CAAA;IAAA,OAAAL,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAM,eAAA;EAAA,MAAAN,IAAA,GAAAE,OAAA;EAAAI,cAAA,YAAAA,CAAA;IAAA,OAAAN,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA,SAAAO,kBAAA;EAAA,MAAAP,IAAA,GAAAE,OAAA;EAAAK,iBAAA,YAAAA,CAAA;IAAA,OAAAP,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAQ,QAAA;EAAA,MAAAR,IAAA,GAAAE,OAAA;EAAAM,OAAA,YAAAA,CAAA;IAAA,OAAAR,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAamB,SAAAC,uBAAAQ,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAenB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMG,iBAAiB,GAAG,uDAAuD;AAE1E,MAAMC,gBAAgB,CAAC;EAC5BC,WAAWA,CAASC,IAAkB,EAAE;IAAA,KAApBA,IAAkB,GAAlBA,IAAkB;EAAG;;EAEzC;AACF;AACA;AACA;EACE,MAAMC,QAAQA,CAACC,IAA0B,GAAG,CAAC,CAAC,EAAmB;IAC/D,MAAM;MAAEC,GAAG;MAAEC,aAAa;MAAEC,YAAY;MAAEC;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC9D,MAAMO,UAAU,GAAGJ,GAAG,CAACK,QAAQ,KAAK,aAAa;IACjD;IACA,MAAMC,MAAM,GAAGF,UAAU,GAAGH,aAAa,GAAGD,GAAG,CAACO,cAAc;IAE9D,IAAIP,GAAG,CAACQ,mBAAmB,IAAI,CAACJ,UAAU,EAAE;MAC1C;MACAD,MAAM,CAACM,cAAc,CACnB,sGAAsG,GACpG,6EACJ,CAAC;IACH;IAEA,IAAI;MACF,MAAM,IAAAC,0BAAgB,EAAC,CAAC;MACxB;MACA;MACA,MAAMC,gBAAgB,GAAGP,UAAU,GAAG,KAAK,GAAG,MAAM,IAAAQ,8BAAoB,EAACN,MAAM,CAAC;MAChF,MAAMO,UAAU,GAAGF,gBAAgB,GAAG,UAAUL,MAAM,EAAE,GAAG,UAAUL,aAAa,EAAE;MACpFE,MAAM,CAACW,OAAO,CACZC,gBAAK,CAACC,IAAI,CACRZ,UAAU,GACN,2CAA2CH,aAAa,gBAAgB,GACxE,2CAA2CA,aAAa,iBAAiBK,MAAM,GACrF,CACF,CAAC;MAED,MAAM,IAAI,CAACW,iBAAiB,CAACX,MAAM,EAAEO,UAAU,CAAC;MAEhD,IAAIF,gBAAgB,EAAE;QACpB;QACA;QACA,MAAMO,UAAU,GAAG,MAAM,IAAI,CAACC,wBAAwB,CAACb,MAAM,CAAC;QAC9D,IAAIY,UAAU,EAAE;UACd,MAAME,MAAM,GAAG,MAAM,IAAI,CAACC,oBAAoB,CAACf,MAAM,CAAC;UACtD,IAAIc,MAAM,KAAK,aAAa,EAAE,OAAO,GAAGE,uCAAmB,YAAYJ,UAAU,EAAE;UACnF,IAAIE,MAAM,KAAK,OAAO,EAAE,OAAOG,gBAAgB,CAACjB,MAAM,CAAC;QACzD;MACF;;MAEA;MACA;MACA;MACA;MACA,MAAMkB,WAAW,GAAG,IAAAC,iCAAgB,EAAC,IAAI,CAAC5B,IAAI,CAAC6B,KAAK,CAAC;MACrD,IAAIF,WAAW,EAAE;QACf,OACE,GAAGF,uCAAmB,2BAA2BT,UAAU,kBAAkB,GAC7E,IAAIW,WAAW,gEAAgE;MAEnF;;MAEA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA,MAAMG,OAAO,GAAG,MAAM,IAAI,CAAC9B,IAAI,CAAC+B,QAAQ,CAACC,mBAAmB,CAAC,EAAE,EAAE;QAC/DC,IAAI,EAAE,IAAI;QACVC,cAAc,EAAE,IAAI;QACpBC,mBAAmB,EAAE,IAAI;QACzBC,aAAa,EAAE;MACjB,CAAC,CAAC;MACF,MAAMC,YAAY,GAAGP,OAAO,CAACO,YAAY,IAAI,EAAE;MAC/C,IAAIA,YAAY,CAACC,MAAM,EAAE;QACvBhC,MAAM,CAACW,OAAO,CACZC,gBAAK,CAACC,IAAI,CACR,SAASkB,YAAY,CAACC,MAAM,+BAA+BjC,YAAY,0BAA0B,GAC/F,cAAcgC,YAAY,CAACE,IAAI,CAAC,IAAI,CAAC,EACzC,CACF,CAAC;MACH;MAEA,MAAMC,KAAK,GAAG,MAAM,IAAI,CAACC,UAAU,CAAC,CAAC;MACrC;MACA;MACA,IAAI,CAACD,KAAK,CAACF,MAAM,EAAE,OAAO/B,UAAU,GAAGV,iBAAiB,GAAG,MAAM,IAAI,CAAC6C,gBAAgB,CAACjC,MAAM,CAAC;MAE9FH,MAAM,CAACW,OAAO,CACZ,IAAA0B,2BAAoB,EAAC,oBAAoBH,KAAK,CAACF,MAAM,aAAaE,KAAK,CAACI,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAACL,IAAI,CAAC,IAAI,CAAC,EAAE,CACnG,CAAC;MAED,IAAIrC,IAAI,CAAC2C,MAAM,EAAE;QACf;QACA;QACA;QACAvC,MAAM,CAACW,OAAO,CACZ,IAAA0B,2BAAoB,EAClBpC,UAAU,GACN,uDAAuDE,MAAM,EAAE,GAC/D,+BAA+BA,MAAM,qBAC3C,CACF,CAAC;QACD,OAAOF,UAAU,GACb,6BAA6BiC,KAAK,CAACF,MAAM,yBAAyB7B,MAAM,WAAW,GACnF,6BAA6B+B,KAAK,CAACF,MAAM,+BAA+B;MAC9E;MAEA,MAAM,IAAAQ,qCAA2B,EAAC,CAAC;MACnC,MAAM,IAAAC,4BAAkB,EAACC,qBAAqB,CAACR,KAAK,CAACF,MAAM,CAAC,CAAC;MAC7D;MACA;MACA,MAAMW,OAAO,GAAG,MAAMC,UAAG,CACtBC,QAAQ,CAAC,CAAC,UAAU1C,MAAM,EAAE,CAAC,CAAC,CAC9B2C,IAAI,CAAEC,GAAG,IAAKA,GAAG,CAACC,IAAI,CAAC,CAAC,IAAIC,SAAS,CAAC,CACtCC,KAAK,CAAC,MAAMD,SAAS,CAAC;MACzB;MACA;MACA;MACA;MACA;MACA;MACA,IAAI;QACF,MAAML,UAAG,CAACO,IAAI,CAAC,CAAC,QAAQ,EAAE,mBAAmBhD,MAAM,EAAE,CAAC,CAAC;MACzD,CAAC,CAAC,OAAOf,CAAM,EAAE;QACf,MAAMgE,OAAO,GAAGC,MAAM,CAACjE,CAAC,EAAEgE,OAAO,IAAIhE,CAAC,CAAC;QACvC,IAAIa,UAAU,IAAI,CAAC,MAAM,IAAAqD,yBAAe,EAACnD,MAAM,EAAEwC,OAAO,EAAES,OAAO,CAAC,MAAM,gBAAgB,EAAE,MAAMhE,CAAC;QACjG;QACA;QACA,MAAMmE,OAAO,GAAG,MAAM,IAAAC,oCAA0B,EAACrD,MAAM,CAAC;QACxD,IAAIoD,OAAO,EAAE;UACXvD,MAAM,CAACM,cAAc,CAAC,8CAA8CH,MAAM,KAAKoD,OAAO,EAAE,CAAC;QAC3F;QACAvD,MAAM,CAACW,OAAO,CACZ,IAAA0B,2BAAoB,EAAC,mBAAmBlC,MAAM,8BAA8B,IAAAsD,8BAAoB,EAACL,OAAO,CAAC,EAAE,CAC7G,CAAC;QACD,OAAOhC,gBAAgB,CAACjB,MAAM,CAAC;MACjC;MACAH,MAAM,CAACW,OAAO,CAACC,gBAAK,CAAC8C,KAAK,CAAC,UAAUvD,MAAM,EAAE,CAAC,CAAC;MAE/C,IAAIF,UAAU,EAAE;QACd,MAAM0D,QAAQ,GAAG,CAAC,MAAMf,UAAG,CAACC,QAAQ,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,EAAEG,IAAI,CAAC,CAAC;QACjE,OAAO,+BAA+B7C,MAAM,MAAMwD,QAAQ,GAAG;MAC/D;MACA,MAAMC,KAAK,GAAG,MAAM,IAAI,CAACC,YAAY,CAAC;QAAE1D,MAAM;QAAE2D,UAAU,EAAE5B,KAAK,CAACF,MAAM;QAAED;MAAa,CAAC,CAAC;MACzF,OAAO,iCAAiC5B,MAAM,GAAGyD,KAAK,GAAG,QAAQA,KAAK,GAAG,GAAG,EAAE,EAAE;IAClF,CAAC,CAAC,OAAOxE,CAAM,EAAE;MACf;MACA,OAAO,GAAG+B,uCAAmB,YAAY/B,CAAC,EAAEgE,OAAO,IAAIhE,CAAC,EAAE;IAC5D,CAAC,SAAS;MACR,MAAM,IAAI,CAAC2E,gBAAgB,CAAC,CAAC;IAC/B;EACF;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAcjD,iBAAiBA,CAACX,MAAc,EAAEO,UAAkB,EAAE;IAClE,MAAM,IAAAsD,0BAAgB,EAAC7D,MAAM,EAAEO,UAAU,EAAE,MAAM,IAAI,CAAChB,IAAI,CAACuE,EAAE,CAACC,uBAAuB,CAAC,CAAC,CAAC;EAC1F;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAclD,wBAAwBA,CAACb,MAAc,EAA+B;IAClF,MAAM;MAAEL,aAAa;MAAEE;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC3C,IAAI;MACF;MACA,MAAMyE,GAAG,GAAG,MAAM,IAAAC,yBAAe,EAAC,CAChC,OAAO,EACP,IAAI,EACJ,gBAAgBtE,aAAa,SAASK,MAAM,OAAOkE,+BAAkB,EAAE,EACvE,UAAUvE,aAAa,EAAE,CAC1B,CAAC;MACF;MACA,MAAMwE,UAAU,GAAG,CAAC,MAAM1B,UAAG,CAAC2B,MAAM,CAAC,CAAC,EAAED,UAAU;MAClD,IAAIA,UAAU,CAACtC,MAAM,EAAE,MAAM,IAAIwC,KAAK,CAAC,uBAAuBF,UAAU,CAACrC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;MACtFjC,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACC,IAAI,CAAC,WAAWV,MAAM,2BAA2BL,aAAa,KAAKqE,GAAG,CAACnB,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC,CAAC,OAAO5D,CAAM,EAAE;MACf,MAAMwD,UAAG,CAAC6B,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAACvB,KAAK,CAAC,MAAMD,SAAS,CAAC;MAC1D,OACE,mCAAmC9C,MAAM,2BAA2BL,aAAa,KAAKV,CAAC,EAAEgE,OAAO,IAAIhE,CAAC,IAAI,GACzG,qBAAqBe,MAAM,6CAA6CL,aAAa,mBAAmB;IAE5G;IACA;IACA,MAAM,IAAI,CAACJ,IAAI,CAACuE,EAAE,CAACC,uBAAuB,CAAC,CAAC;IAC5C,OAAOjB,SAAS;EAClB;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAc/B,oBAAoBA,CAACf,MAAc,EAAkD;IACjG,MAAM;MAAEL,aAAa;MAAEE;IAAO,CAAC,GAAG,IAAI,CAACN,IAAI;IAC3C,MAAMgF,QAAQ,GAAG,CAAC,MAAM9B,UAAG,CAACC,QAAQ,CAAC,CAAC,UAAU1C,MAAM,EAAE,CAAC,CAAC,EAAE6C,IAAI,CAAC,CAAC;IAClE,MAAM2B,GAAG,GAAG,MAAM/B,UAAG,CAAC6B,GAAG,CAAC,CAAC,KAAK,EAAE,UAAU3E,aAAa,KAAK4E,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAC7F,MAAME,QAAQ,GAAGD,GAAG,CACjBE,KAAK,CAAC,MAAM,CAAC,CACbC,GAAG,CAAE1B,OAAO,IAAKA,OAAO,CAACJ,IAAI,CAAC,CAAC,CAAC,CAChC+B,MAAM,CAACC,OAAO,CAAC;IAClB,MAAMC,YAAY,GAAGL,QAAQ,CAAC5C,MAAM,GAAG,CAAC,IAAI4C,QAAQ,CAACM,KAAK,CAAE9B,OAAO,IAAK,IAAA+B,kCAAqB,EAAC/B,OAAO,CAAC,CAAC;IACvG,IAAI,CAAC6B,YAAY,EAAE,OAAO,aAAa;IACvCjF,MAAM,CAACW,OAAO,CACZ,IAAA0B,2BAAoB,EAClB,WAAWlC,MAAM,mBAAmBL,aAAa,qBAAqBuE,+BAAkB,aAAa,GACnG,6BAA6BvE,aAAa,EAC9C,CACF,CAAC;IACD,MAAM,IAAI,CAACgB,iBAAiB,CAACX,MAAM,EAAE,UAAUL,aAAa,EAAE,CAAC;IAC/D,IAAI;MACF,MAAM8C,UAAG,CAACO,IAAI,CAAC,CAAC,iCAAiChD,MAAM,IAAIuE,QAAQ,EAAE,EAAE,QAAQ,EAAE,mBAAmBvE,MAAM,EAAE,CAAC,CAAC;IAChH,CAAC,CAAC,OAAOf,CAAM,EAAE;MACf,IAAI,CAAC,IAAAgG,+BAAqB,EAAChG,CAAC,EAAEgE,OAAO,IAAIC,MAAM,CAACjE,CAAC,CAAC,CAAC,EAAE,MAAMA,CAAC;MAC5DY,MAAM,CAACW,OAAO,CAAC,IAAA0B,2BAAoB,EAAC,WAAWlC,MAAM,mDAAmD,CAAC,CAAC;MAC1G,OAAO,OAAO;IAChB;IACA,OAAO,WAAW;EACpB;;EAEA;AACF;AACA;AACA;EACE,MAAciC,gBAAgBA,CAACjC,MAAc,EAAmB;IAC9D,MAAM;MAAEkF;IAAQ,CAAC,GAAG,IAAI,CAAC3F,IAAI;IAC7B,IAAI,CAAC2F,OAAO,EAAE,OAAO9F,iBAAiB;IACtC,MAAM+F,EAAE,GAAG,MAAMD,OAAO,CAACE,cAAc,CAACpF,MAAM,CAAC,CAAC+C,KAAK,CAAC,MAAMD,SAAS,CAAC;IACtE,OAAOqC,EAAE,GAAG,GAAG/F,iBAAiB,oBAAoB+F,EAAE,CAACE,MAAM,4BAA4B,GAAGjG,iBAAiB;EAC/G;;EAEA;AACF;AACA;AACA;EACE,MAAc4C,UAAUA,CAAA,EAAsB;IAC5C,MAAMoC,MAAM,GAAG,MAAM3B,UAAG,CAAC2B,MAAM,CAAC,CAAC;IACjC,MAAMkB,KAAK,GAAGlB,MAAM,CAACmB,KAAK,CAACZ,GAAG,CAAEa,IAAI,IAAKA,IAAI,CAACC,IAAI,CAAC,CAACb,MAAM,CAAEa,IAAI,IAAK,CAAC,IAAAC,0BAAgB,EAACD,IAAI,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,IAAIE,GAAG,CAACL,KAAK,CAAC,CAAC;EAC5B;;EAEA;AACF;AACA;AACA;EACE,MAAc5B,YAAYA,CAAC;IACzB1D,MAAM;IACN2D,UAAU;IACV/B;EAKF,CAAC,EAA+B;IAC9B,MAAM;MAAEsD,OAAO;MAAErF,MAAM;MAAEF;IAAc,CAAC,GAAG,IAAI,CAACJ,IAAI;IACpD,IAAI,CAAC2F,OAAO,EAAE;MACZrF,MAAM,CAACM,cAAc,CACnB,sGAAsG,GACpG,4CACJ,CAAC;MACD,OAAO2C,SAAS;IAClB;IACA,IAAI;MACF,MAAM8C,QAAQ,GAAG,MAAMV,OAAO,CAACE,cAAc,CAACpF,MAAM,CAAC;MACrD,IAAI4F,QAAQ,EAAE;QACZ/F,MAAM,CAACW,OAAO,CAACC,gBAAK,CAACC,IAAI,CAAC,WAAWkF,QAAQ,CAACC,OAAO,kDAAkD,CAAC,CAAC;QACzG,OAAOD,QAAQ,CAACC,OAAO;MACzB;MACA,MAAMC,OAAO,GAAG,MAAMZ,OAAO,CAACa,QAAQ,CAAC;QACrCvE,IAAI,EAAExB,MAAM;QACZgG,IAAI,EAAErG,aAAa;QACnBsG,KAAK,EAAE,gDAAgD;QACvDC,IAAI,EAAEC,cAAc,CAAC;UAAExC,UAAU;UAAE/B;QAAa,CAAC;MACnD,CAAC,CAAC;MACF/B,MAAM,CAACW,OAAO,CAACC,gBAAK,CAAC8C,KAAK,CAAC,kBAAkBuC,OAAO,CAACD,OAAO,EAAE,CAAC,CAAC;MAChE,OAAOC,OAAO,CAACD,OAAO;IACxB,CAAC,CAAC,OAAO5G,CAAM,EAAE;MACfY,MAAM,CAACM,cAAc,CAAC,0CAA0CH,MAAM,KAAKf,CAAC,EAAEgE,OAAO,IAAIhE,CAAC,EAAE,CAAC;MAC7F,OAAO6D,SAAS;IAClB;EACF;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAcc,gBAAgBA,CAAA,EAAG;IAC/B,MAAM;MAAE/D,MAAM;MAAEF;IAAc,CAAC,GAAG,IAAI,CAACJ,IAAI;IAC3C,IAAI;MACF,IAAI,IAAA4B,iCAAgB,EAAC,IAAI,CAAC5B,IAAI,CAAC6B,KAAK,CAAC,EAAE;QACrC,MAAMgF,SAAS,GAAG,MAAM,IAAI,CAAC7G,IAAI,CAACuE,EAAE,CAACuC,mBAAmB,CAAC,MAAM,CAAC;QAChE,IAAID,SAAS,EAAEvG,MAAM,CAACM,cAAc,CAAC,gDAAgDiG,SAAS,CAACnD,OAAO,EAAE,CAAC;MAC3G;MACA,MAAM,IAAAqD,iCAAuB,EAAC3G,aAAa,EAAE,MAAM,IAAI,CAACJ,IAAI,CAACuE,EAAE,CAACC,uBAAuB,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC,OAAO9E,CAAM,EAAE;MACfY,MAAM,CAACM,cAAc,CAAC,wDAAwDlB,CAAC,EAAEgE,OAAO,IAAIhE,CAAC,EAAE,CAAC;IAClG;EACF;AACF;;AAEA;AAAAsH,OAAA,CAAAlH,gBAAA,GAAAA,gBAAA;AACA,SAAS4B,gBAAgBA,CAACjB,MAAc,EAAU;EAChD,OAAO,WAAWA,MAAM,oDAAoD;AAC9E;;AAEA;AACA,SAASuC,qBAAqBA,CAACoB,UAAkB,EAAU;EACzD,OAAO,CACL,4DAA4DA,UAAU,WAAW,EACjF,EAAE,EACFO,+BAAkB,CACnB,CAACpC,IAAI,CAAC,IAAI,CAAC;AACd;AAEA,SAASqE,cAAcA,CAAC;EAAExC,UAAU;EAAE/B;AAA6D,CAAC,EAAU;EAC5G,MAAM4E,KAAK,GAAG,CACZ,2GAA2G,GACzG,6EAA6E,EAC/E,EAAE,EACF,oBAAoB7C,UAAU,EAAE,EAChC,8GAA8G,GAC5G,uGAAuG,GACvG,kDAAkD,CACrD;EACD,IAAI/B,YAAY,CAACC,MAAM,EAAE;IACvB2E,KAAK,CAACxD,IAAI,CACR,sCAAsCpB,YAAY,CAACC,MAAM,qDAAqD;IAC9G;IACA,GAAG,IAAA4E,8BAAU,EACX7E,YAAY,CAAC+C,GAAG,CAAE+B,EAAE,IAAK,SAASA,EAAE,IAAI,CAAC,EACzC,MACF,CACF,CAAC;EACH;EACAF,KAAK,CAACxD,IAAI,CACR,EAAE,EACF,gHACF,CAAC;EACD,OAAOwD,KAAK,CAAC1E,IAAI,CAAC,IAAI,CAAC;AACzB","ignoreList":[]}
@@ -28,7 +28,14 @@ export type LaneSyncInput = {
28
28
  * with `isSyncAuthoredMessage`, not the loop guard's substring match.
29
29
  */
30
30
  tipIsSyncCommit: boolean;
31
+ /**
32
+ * The branch had real (non-bit) commits before this reconciler touched it — it was adopted, not
33
+ * manufactured. Can only withhold a delete, never authorize one.
34
+ */
35
+ hasIndependentHistory?: boolean;
31
36
  conflictLabelPresent: boolean;
37
+ /** The branch's committed `.bitmap` names a DIFFERENT lane — another lane's claim, never adopted. */
38
+ branchNamesDifferentLane?: boolean;
32
39
  /** @see LaneOwnershipEvidence — consulted only on the "lane is gone" path. */
33
40
  ownership: LaneOwnershipEvidence;
34
41
  };
@@ -36,7 +43,7 @@ export type LaneSyncInput = {
36
43
  * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one
37
44
  * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.
38
45
  */
39
- export type BranchKeepReason = 'unmerged-commits' | 'tip-not-a-sync-commit' | 'tip-advanced-during-run';
46
+ export type BranchKeepReason = 'unmerged-commits' | 'tip-not-a-sync-commit' | 'tip-advanced-during-run' | 'adopted-branch';
40
47
  export type LaneSyncAction = {
41
48
  type: 'noop';
42
49
  reason: string;
@@ -46,6 +53,8 @@ export type LaneSyncAction = {
46
53
  type: 'export-branch';
47
54
  } | {
48
55
  type: 'merge-diverged';
56
+ } | {
57
+ type: 'adopt-branch';
49
58
  } | {
50
59
  type: 'close-pr';
51
60
  deleteBranch: true;
@@ -32,7 +32,9 @@ function planLaneSync(input) {
32
32
  ownership
33
33
  } = input;
34
34
  const {
35
- tipIsSyncCommit
35
+ tipIsSyncCommit,
36
+ hasIndependentHistory,
37
+ branchNamesDifferentLane
36
38
  } = input;
37
39
  if (conflictLabelPresent) {
38
40
  return {
@@ -47,18 +49,34 @@ function planLaneSync(input) {
47
49
  };
48
50
  switch (ownership) {
49
51
  case 'own-live':
50
- // DELETION NEEDS BOTH HALVES: `.bitmap` attribution alone is not enough — a developer who commits
51
- // their own `.bitmap` write (bit create / unexported snap / deps set) looks exactly like
52
- // "own-live, nothing above the state commit". The marker can only withhold a deletion, never
53
- // authorize one; a false keep is harmless, a false delete destroys the only copy of the work.
54
- if (!hasDevCommits && tipIsSyncCommit) return {
55
- type: 'close-pr',
56
- deleteBranch: true
57
- };
52
+ // Each check is a separate reason to withhold a delete: dev commits are work that exists
53
+ // nowhere else; a non-sync tip may be a developer's own `.bitmap` write; an adopted branch's
54
+ // pre-existing history was never proven disposable. A false keep is harmless, a false delete
55
+ // destroys the only copy.
56
+ if (hasDevCommits) {
57
+ return {
58
+ type: 'close-pr',
59
+ deleteBranch: false,
60
+ keepReason: 'unmerged-commits'
61
+ };
62
+ }
63
+ if (!tipIsSyncCommit) {
64
+ return {
65
+ type: 'close-pr',
66
+ deleteBranch: false,
67
+ keepReason: 'tip-not-a-sync-commit'
68
+ };
69
+ }
70
+ if (hasIndependentHistory) {
71
+ return {
72
+ type: 'close-pr',
73
+ deleteBranch: false,
74
+ keepReason: 'adopted-branch'
75
+ };
76
+ }
58
77
  return {
59
78
  type: 'close-pr',
60
- deleteBranch: false,
61
- keepReason: hasDevCommits ? 'unmerged-commits' : 'tip-not-a-sync-commit'
79
+ deleteBranch: true
62
80
  };
63
81
  case 'own-merged':
64
82
  // No marker conjunction needed: the tip being an ancestor of the default branch is unforgeable
@@ -86,9 +104,15 @@ function planLaneSync(input) {
86
104
  };
87
105
  if (!lastSyncedHead) {
88
106
  if (hasDevCommits) {
107
+ if (branchNamesDifferentLane) {
108
+ return {
109
+ type: 'halt',
110
+ reason: 'branch has commits but its .bitmap names a different lane; cannot tell which side is newer'
111
+ };
112
+ }
113
+ // The lane exists but the pair has no sync history: first contact, not a conflict.
89
114
  return {
90
- type: 'halt',
91
- reason: 'branch has commits but its .bitmap records no state for this lane; cannot tell which side is newer'
115
+ type: 'adopt-branch'
92
116
  };
93
117
  }
94
118
  return {
@@ -1 +1 @@
1
- {"version":3,"names":["planLaneSync","input","laneHead","branchExists","lastSyncedHead","hasDevCommits","conflictLabelPresent","ownership","tipIsSyncCommit","type","reason","deleteBranch","keepReason","laneMoved"],"sources":["sync-planner.ts"],"sourcesContent":["/**\n * How much of a claim this lane/branch pair has on `origin/<branch>` — the only thing that licenses\n * retiring it, because `close-pr` deletes the branch. Under the default config every branch name maps\n * to a same-named lane, and branches cut after a merged sync PR inherit its `.bitmap`, so \"looks\n * bit-managed\" is not enough. Evidence = attribution (the branch's `.bitmap` lane pointer) plus\n * reachability of the state commit / tip from the default branch:\n *\n * - `own-live` — names this lane; state commit not in the default branch (real, unmerged lane branch).\n * - `own-merged` — names this lane; branch tip already in the default branch (deleting loses nothing).\n * - `own-superseded` — state commit merged but tip not: close the PR, KEEP the branch.\n * - `inherited-or-none` — no pointer, another lane's, or unreadable: not ours, touch nothing.\n */\nexport type LaneOwnershipEvidence = 'own-live' | 'own-merged' | 'own-superseded' | 'inherited-or-none';\n\nexport type LaneSyncInput = {\n /** fingerprint of the lane's component heads on bit.cloud; undefined when the lane is gone */\n laneHead?: string;\n branchExists: boolean;\n /**\n * The lane-component fingerprint as the branch's committed `.bitmap` records it; undefined when the\n * branch holds no state for this lane. Equality with `laneHead` is the definition of converged.\n */\n lastSyncedHead?: string;\n /** whether the branch carries commits on top of the commit that last wrote its `.bitmap` */\n hasDevCommits: boolean;\n /**\n * Whether the reconciler itself wrote the tip commit. The one exception to \"markers are annotations\":\n * consulted only on the branch-deletion path, and only ever to WITHHOLD a deletion. Must be computed\n * with `isSyncAuthoredMessage`, not the loop guard's substring match.\n */\n tipIsSyncCommit: boolean;\n conflictLabelPresent: boolean;\n /** @see LaneOwnershipEvidence — consulted only on the \"lane is gone\" path. */\n ownership: LaneOwnershipEvidence;\n};\n\n/**\n * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one\n * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.\n */\nexport type BranchKeepReason = 'unmerged-commits' | 'tip-not-a-sync-commit' | 'tip-advanced-during-run';\n\nexport type LaneSyncAction =\n | { type: 'noop'; reason: string }\n | { type: 'import-lane' }\n | { type: 'export-branch' }\n | { type: 'merge-diverged' }\n // Two variants rather than an optional `keepReason` so the type enforces that a keep always says why.\n | { type: 'close-pr'; deleteBranch: true }\n | { type: 'close-pr'; deleteBranch: false; keepReason: BranchKeepReason }\n | { type: 'halt'; reason: string };\n\nexport function planLaneSync(input: LaneSyncInput): LaneSyncAction {\n const { laneHead, branchExists, lastSyncedHead, hasDevCommits, conflictLabelPresent, ownership } = input;\n const { tipIsSyncCommit } = input;\n if (conflictLabelPresent) {\n return { type: 'noop', reason: 'PR is labeled bit-sync-conflict; resolve and remove the label to resume' };\n }\n if (!laneHead) {\n if (!branchExists) return { type: 'noop', reason: 'lane and branch both absent' };\n switch (ownership) {\n case 'own-live':\n // DELETION NEEDS BOTH HALVES: `.bitmap` attribution alone is not enough — a developer who commits\n // their own `.bitmap` write (bit create / unexported snap / deps set) looks exactly like\n // \"own-live, nothing above the state commit\". The marker can only withhold a deletion, never\n // authorize one; a false keep is harmless, a false delete destroys the only copy of the work.\n if (!hasDevCommits && tipIsSyncCommit) return { type: 'close-pr', deleteBranch: true };\n return {\n type: 'close-pr',\n deleteBranch: false,\n keepReason: hasDevCommits ? 'unmerged-commits' : 'tip-not-a-sync-commit',\n };\n case 'own-merged':\n // No marker conjunction needed: the tip being an ancestor of the default branch is unforgeable\n // proof that deleting loses nothing.\n return { type: 'close-pr', deleteBranch: true };\n case 'own-superseded':\n // Those commits are in no other ref.\n return { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' };\n default:\n return { type: 'noop', reason: 'branch maps to no lane and has no sync history of its own; ignoring' };\n }\n }\n if (!branchExists) return { type: 'import-lane' };\n if (!lastSyncedHead) {\n if (hasDevCommits) {\n return {\n type: 'halt',\n reason: 'branch has commits but its .bitmap records no state for this lane; cannot tell which side is newer',\n };\n }\n return { type: 'import-lane' };\n }\n const laneMoved = laneHead !== lastSyncedHead;\n if (laneMoved && hasDevCommits) return { type: 'merge-diverged' };\n if (laneMoved) return { type: 'import-lane' };\n if (hasDevCommits) return { type: 'export-branch' };\n return { type: 'noop', reason: 'converged' };\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAyBA;AACA;AACA;AACA;;AAaO,SAASA,YAAYA,CAACC,KAAoB,EAAkB;EACjE,MAAM;IAAEC,QAAQ;IAAEC,YAAY;IAAEC,cAAc;IAAEC,aAAa;IAAEC,oBAAoB;IAAEC;EAAU,CAAC,GAAGN,KAAK;EACxG,MAAM;IAAEO;EAAgB,CAAC,GAAGP,KAAK;EACjC,IAAIK,oBAAoB,EAAE;IACxB,OAAO;MAAEG,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA0E,CAAC;EAC5G;EACA,IAAI,CAACR,QAAQ,EAAE;IACb,IAAI,CAACC,YAAY,EAAE,OAAO;MAAEM,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA8B,CAAC;IACjF,QAAQH,SAAS;MACf,KAAK,UAAU;QACb;QACA;QACA;QACA;QACA,IAAI,CAACF,aAAa,IAAIG,eAAe,EAAE,OAAO;UAAEC,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;QACtF,OAAO;UACLF,IAAI,EAAE,UAAU;UAChBE,YAAY,EAAE,KAAK;UACnBC,UAAU,EAAEP,aAAa,GAAG,kBAAkB,GAAG;QACnD,CAAC;MACH,KAAK,YAAY;QACf;QACA;QACA,OAAO;UAAEI,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;MACjD,KAAK,gBAAgB;QACnB;QACA,OAAO;UAAEF,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE,KAAK;UAAEC,UAAU,EAAE;QAAmB,CAAC;MAClF;QACE,OAAO;UAAEH,IAAI,EAAE,MAAM;UAAEC,MAAM,EAAE;QAAsE,CAAC;IAC1G;EACF;EACA,IAAI,CAACP,YAAY,EAAE,OAAO;IAAEM,IAAI,EAAE;EAAc,CAAC;EACjD,IAAI,CAACL,cAAc,EAAE;IACnB,IAAIC,aAAa,EAAE;MACjB,OAAO;QACLI,IAAI,EAAE,MAAM;QACZC,MAAM,EAAE;MACV,CAAC;IACH;IACA,OAAO;MAAED,IAAI,EAAE;IAAc,CAAC;EAChC;EACA,MAAMI,SAAS,GAAGX,QAAQ,KAAKE,cAAc;EAC7C,IAAIS,SAAS,IAAIR,aAAa,EAAE,OAAO;IAAEI,IAAI,EAAE;EAAiB,CAAC;EACjE,IAAII,SAAS,EAAE,OAAO;IAAEJ,IAAI,EAAE;EAAc,CAAC;EAC7C,IAAIJ,aAAa,EAAE,OAAO;IAAEI,IAAI,EAAE;EAAgB,CAAC;EACnD,OAAO;IAAEA,IAAI,EAAE,MAAM;IAAEC,MAAM,EAAE;EAAY,CAAC;AAC9C","ignoreList":[]}
1
+ {"version":3,"names":["planLaneSync","input","laneHead","branchExists","lastSyncedHead","hasDevCommits","conflictLabelPresent","ownership","tipIsSyncCommit","hasIndependentHistory","branchNamesDifferentLane","type","reason","deleteBranch","keepReason","laneMoved"],"sources":["sync-planner.ts"],"sourcesContent":["/**\n * How much of a claim this lane/branch pair has on `origin/<branch>` — the only thing that licenses\n * retiring it, because `close-pr` deletes the branch. Under the default config every branch name maps\n * to a same-named lane, and branches cut after a merged sync PR inherit its `.bitmap`, so \"looks\n * bit-managed\" is not enough. Evidence = attribution (the branch's `.bitmap` lane pointer) plus\n * reachability of the state commit / tip from the default branch:\n *\n * - `own-live` — names this lane; state commit not in the default branch (real, unmerged lane branch).\n * - `own-merged` — names this lane; branch tip already in the default branch (deleting loses nothing).\n * - `own-superseded` — state commit merged but tip not: close the PR, KEEP the branch.\n * - `inherited-or-none` — no pointer, another lane's, or unreadable: not ours, touch nothing.\n */\nexport type LaneOwnershipEvidence = 'own-live' | 'own-merged' | 'own-superseded' | 'inherited-or-none';\n\nexport type LaneSyncInput = {\n /** fingerprint of the lane's component heads on bit.cloud; undefined when the lane is gone */\n laneHead?: string;\n branchExists: boolean;\n /**\n * The lane-component fingerprint as the branch's committed `.bitmap` records it; undefined when the\n * branch holds no state for this lane. Equality with `laneHead` is the definition of converged.\n */\n lastSyncedHead?: string;\n /** whether the branch carries commits on top of the commit that last wrote its `.bitmap` */\n hasDevCommits: boolean;\n /**\n * Whether the reconciler itself wrote the tip commit. The one exception to \"markers are annotations\":\n * consulted only on the branch-deletion path, and only ever to WITHHOLD a deletion. Must be computed\n * with `isSyncAuthoredMessage`, not the loop guard's substring match.\n */\n tipIsSyncCommit: boolean;\n /**\n * The branch had real (non-bit) commits before this reconciler touched it — it was adopted, not\n * manufactured. Can only withhold a delete, never authorize one.\n */\n hasIndependentHistory?: boolean;\n conflictLabelPresent: boolean;\n /** The branch's committed `.bitmap` names a DIFFERENT lane — another lane's claim, never adopted. */\n branchNamesDifferentLane?: boolean;\n /** @see LaneOwnershipEvidence — consulted only on the \"lane is gone\" path. */\n ownership: LaneOwnershipEvidence;\n};\n\n/**\n * Why a `close-pr` is keeping the branch rather than deleting it. `tip-advanced-during-run` is the one\n * the planner never emits: it is discovered when the executor re-reads the branch just before deleting.\n */\nexport type BranchKeepReason =\n | 'unmerged-commits'\n | 'tip-not-a-sync-commit'\n | 'tip-advanced-during-run'\n | 'adopted-branch';\n\nexport type LaneSyncAction =\n | { type: 'noop'; reason: string }\n | { type: 'import-lane' }\n | { type: 'export-branch' }\n | { type: 'merge-diverged' }\n | { type: 'adopt-branch' }\n // Two variants rather than an optional `keepReason` so the type enforces that a keep always says why.\n | { type: 'close-pr'; deleteBranch: true }\n | { type: 'close-pr'; deleteBranch: false; keepReason: BranchKeepReason }\n | { type: 'halt'; reason: string };\n\nexport function planLaneSync(input: LaneSyncInput): LaneSyncAction {\n const { laneHead, branchExists, lastSyncedHead, hasDevCommits, conflictLabelPresent, ownership } = input;\n const { tipIsSyncCommit, hasIndependentHistory, branchNamesDifferentLane } = input;\n if (conflictLabelPresent) {\n return { type: 'noop', reason: 'PR is labeled bit-sync-conflict; resolve and remove the label to resume' };\n }\n if (!laneHead) {\n if (!branchExists) return { type: 'noop', reason: 'lane and branch both absent' };\n switch (ownership) {\n case 'own-live':\n // Each check is a separate reason to withhold a delete: dev commits are work that exists\n // nowhere else; a non-sync tip may be a developer's own `.bitmap` write; an adopted branch's\n // pre-existing history was never proven disposable. A false keep is harmless, a false delete\n // destroys the only copy.\n if (hasDevCommits) {\n return { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' };\n }\n if (!tipIsSyncCommit) {\n return { type: 'close-pr', deleteBranch: false, keepReason: 'tip-not-a-sync-commit' };\n }\n if (hasIndependentHistory) {\n return { type: 'close-pr', deleteBranch: false, keepReason: 'adopted-branch' };\n }\n return { type: 'close-pr', deleteBranch: true };\n case 'own-merged':\n // No marker conjunction needed: the tip being an ancestor of the default branch is unforgeable\n // proof that deleting loses nothing.\n return { type: 'close-pr', deleteBranch: true };\n case 'own-superseded':\n // Those commits are in no other ref.\n return { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' };\n default:\n return { type: 'noop', reason: 'branch maps to no lane and has no sync history of its own; ignoring' };\n }\n }\n if (!branchExists) return { type: 'import-lane' };\n if (!lastSyncedHead) {\n if (hasDevCommits) {\n if (branchNamesDifferentLane) {\n return {\n type: 'halt',\n reason: 'branch has commits but its .bitmap names a different lane; cannot tell which side is newer',\n };\n }\n // The lane exists but the pair has no sync history: first contact, not a conflict.\n return { type: 'adopt-branch' };\n }\n return { type: 'import-lane' };\n }\n const laneMoved = laneHead !== lastSyncedHead;\n if (laneMoved && hasDevCommits) return { type: 'merge-diverged' };\n if (laneMoved) return { type: 'import-lane' };\n if (hasDevCommits) return { type: 'export-branch' };\n return { type: 'noop', reason: 'converged' };\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAgCA;AACA;AACA;AACA;;AAkBO,SAASA,YAAYA,CAACC,KAAoB,EAAkB;EACjE,MAAM;IAAEC,QAAQ;IAAEC,YAAY;IAAEC,cAAc;IAAEC,aAAa;IAAEC,oBAAoB;IAAEC;EAAU,CAAC,GAAGN,KAAK;EACxG,MAAM;IAAEO,eAAe;IAAEC,qBAAqB;IAAEC;EAAyB,CAAC,GAAGT,KAAK;EAClF,IAAIK,oBAAoB,EAAE;IACxB,OAAO;MAAEK,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA0E,CAAC;EAC5G;EACA,IAAI,CAACV,QAAQ,EAAE;IACb,IAAI,CAACC,YAAY,EAAE,OAAO;MAAEQ,IAAI,EAAE,MAAM;MAAEC,MAAM,EAAE;IAA8B,CAAC;IACjF,QAAQL,SAAS;MACf,KAAK,UAAU;QACb;QACA;QACA;QACA;QACA,IAAIF,aAAa,EAAE;UACjB,OAAO;YAAEM,IAAI,EAAE,UAAU;YAAEE,YAAY,EAAE,KAAK;YAAEC,UAAU,EAAE;UAAmB,CAAC;QAClF;QACA,IAAI,CAACN,eAAe,EAAE;UACpB,OAAO;YAAEG,IAAI,EAAE,UAAU;YAAEE,YAAY,EAAE,KAAK;YAAEC,UAAU,EAAE;UAAwB,CAAC;QACvF;QACA,IAAIL,qBAAqB,EAAE;UACzB,OAAO;YAAEE,IAAI,EAAE,UAAU;YAAEE,YAAY,EAAE,KAAK;YAAEC,UAAU,EAAE;UAAiB,CAAC;QAChF;QACA,OAAO;UAAEH,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;MACjD,KAAK,YAAY;QACf;QACA;QACA,OAAO;UAAEF,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE;QAAK,CAAC;MACjD,KAAK,gBAAgB;QACnB;QACA,OAAO;UAAEF,IAAI,EAAE,UAAU;UAAEE,YAAY,EAAE,KAAK;UAAEC,UAAU,EAAE;QAAmB,CAAC;MAClF;QACE,OAAO;UAAEH,IAAI,EAAE,MAAM;UAAEC,MAAM,EAAE;QAAsE,CAAC;IAC1G;EACF;EACA,IAAI,CAACT,YAAY,EAAE,OAAO;IAAEQ,IAAI,EAAE;EAAc,CAAC;EACjD,IAAI,CAACP,cAAc,EAAE;IACnB,IAAIC,aAAa,EAAE;MACjB,IAAIK,wBAAwB,EAAE;QAC5B,OAAO;UACLC,IAAI,EAAE,MAAM;UACZC,MAAM,EAAE;QACV,CAAC;MACH;MACA;MACA,OAAO;QAAED,IAAI,EAAE;MAAe,CAAC;IACjC;IACA,OAAO;MAAEA,IAAI,EAAE;IAAc,CAAC;EAChC;EACA,MAAMI,SAAS,GAAGb,QAAQ,KAAKE,cAAc;EAC7C,IAAIW,SAAS,IAAIV,aAAa,EAAE,OAAO;IAAEM,IAAI,EAAE;EAAiB,CAAC;EACjE,IAAII,SAAS,EAAE,OAAO;IAAEJ,IAAI,EAAE;EAAc,CAAC;EAC7C,IAAIN,aAAa,EAAE,OAAO;IAAEM,IAAI,EAAE;EAAgB,CAAC;EACnD,OAAO;IAAEA,IAAI,EAAE,MAAM;IAAEC,MAAM,EAAE;EAAY,CAAC;AAC9C","ignoreList":[]}
@@ -104,6 +104,18 @@ const table = [{
104
104
  deleteBranch: false,
105
105
  keepReason: 'unmerged-commits'
106
106
  }
107
+ }, {
108
+ // Adoption proved content matched, never that the branch's pre-existing history is disposable.
109
+ name: 'own-live, no dev commits, OUR tip, but it is the adoption ledger commit -> KEEP, never delete',
110
+ input: _objectSpread(_objectSpread({}, laneGone('own-live')), {}, {
111
+ hasIndependentHistory: true
112
+ }),
113
+ type: 'close-pr',
114
+ action: {
115
+ type: 'close-pr',
116
+ deleteBranch: false,
117
+ keepReason: 'adopted-branch'
118
+ }
107
119
  }, {
108
120
  name: 'own-merged (branch tip already in the default branch) -> close-pr AND delete',
109
121
  input: laneGone('own-merged'),
@@ -124,6 +136,16 @@ const table = [{
124
136
  type: 'close-pr',
125
137
  deleteBranch: true
126
138
  }
139
+ }, {
140
+ name: 'own-merged deletes even when the tip is the adoption ledger commit — reachability is enough',
141
+ input: _objectSpread(_objectSpread({}, laneGone('own-merged')), {}, {
142
+ hasIndependentHistory: true
143
+ }),
144
+ type: 'close-pr',
145
+ action: {
146
+ type: 'close-pr',
147
+ deleteBranch: true
148
+ }
127
149
  }, {
128
150
  name: 'own-superseded (PR merged, then more commits pushed) -> close-pr but KEEP the branch',
129
151
  input: laneGone('own-superseded'),
@@ -182,13 +204,33 @@ const table = [{
182
204
  },
183
205
  type: 'import-lane'
184
206
  }, {
185
- name: 'existing branch, never synced, has dev commits, lane EXISTS -> halt (ambiguous)',
207
+ name: 'existing branch, never synced, has dev commits, lane EXISTS -> adopt-branch (first contact)',
186
208
  input: {
187
209
  lastSyncedHead: undefined,
188
210
  ownership: 'inherited-or-none',
189
211
  hasDevCommits: true
190
212
  },
191
- type: 'halt'
213
+ type: 'adopt-branch'
214
+ }, {
215
+ name: 'existing branch, never synced, has dev commits, but .bitmap names a DIFFERENT lane -> halt',
216
+ input: {
217
+ lastSyncedHead: undefined,
218
+ ownership: 'inherited-or-none',
219
+ hasDevCommits: true,
220
+ branchNamesDifferentLane: true
221
+ },
222
+ type: 'halt',
223
+ reason: 'names a different lane'
224
+ }, {
225
+ // The executor resolves an inherited (merged) different-lane pointer to `false` — history, not a claim.
226
+ name: 'existing branch, has dev commits, .bitmap names a MERGED-AND-INHERITED different lane -> adopt-branch',
227
+ input: {
228
+ lastSyncedHead: undefined,
229
+ ownership: 'inherited-or-none',
230
+ hasDevCommits: true,
231
+ branchNamesDifferentLane: false
232
+ },
233
+ type: 'adopt-branch'
192
234
  }];
193
235
  describe('planLaneSync', () => {
194
236
  table.forEach(({
@@ -1 +1 @@
1
- {"version":3,"names":["_chai","data","require","_syncPlanner","ownKeys","e","r","t","Object","keys","getOwnPropertySymbols","o","filter","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","defineProperty","_toPropertyKey","value","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","base","laneHead","branchExists","lastSyncedHead","hasDevCommits","tipIsSyncCommit","conflictLabelPresent","ownership","laneGone","undefined","table","name","input","type","reason","action","deleteBranch","keepReason","describe","it","result","planLaneSync","expect","to","equal","deep","contain"],"sources":["sync-planner.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport type { LaneOwnershipEvidence, LaneSyncAction, LaneSyncInput } from './sync-planner';\nimport { planLaneSync } from './sync-planner';\n\nconst base: LaneSyncInput = {\n laneHead: 'L1',\n branchExists: true,\n lastSyncedHead: 'L1',\n hasDevCommits: false,\n tipIsSyncCommit: true,\n conflictLabelPresent: false,\n ownership: 'own-live',\n};\n\n/** the \"lane deleted remotely, branch still there\" shape — the only path that reads `ownership` */\nconst laneGone = (ownership: LaneOwnershipEvidence) => ({ laneHead: undefined, ownership });\n\n/**\n * The decision table. `action` (exact) is required on every row that decides a branch deletion: under\n * the defaults an ordinary developer branch reaches the planner in the same shape as a deleted lane's\n * branch, so `deleteBranch` and the keep reason are part of the contract, not incidental output.\n */\nconst table: Array<{\n name: string;\n input: Partial<LaneSyncInput>;\n type: LaneSyncAction['type'];\n action?: LaneSyncAction;\n reason?: string;\n}> = [\n { name: 'converged -> noop', input: {}, type: 'noop', reason: 'converged' },\n {\n name: 'conflict label present -> noop until resolved',\n input: { laneHead: 'L2', conflictLabelPresent: true },\n type: 'noop',\n },\n { name: 'lane gone + no branch -> noop', input: { laneHead: undefined, branchExists: false }, type: 'noop' },\n\n {\n name: \"own-live, no dev commits, OUR tip (the branch is exactly the lane's mirror) -> close-pr AND delete\",\n input: laneGone('own-live'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // A developer who commits their own `.bitmap` write looks own-live with nothing above the state\n // commit; the marker is what separates our mirror from their branch.\n name: 'own-live, no dev commits, a tip WE DID NOT WRITE -> close-pr but KEEP the branch',\n input: { ...laneGone('own-live'), tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'tip-not-a-sync-commit' },\n },\n {\n name: 'own-live WITH dev commits (never-exported work above the state commit) -> KEEP the branch',\n input: { ...laneGone('own-live'), hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n // \"tip-not-a-sync-commit\" would send the reader after the wrong thing when there is real work.\n name: 'own-live with dev commits AND a foreign tip -> keeps, naming the commits as the reason',\n input: { ...laneGone('own-live'), hasDevCommits: true, tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n name: 'own-merged (branch tip already in the default branch) -> close-pr AND delete',\n input: laneGone('own-merged'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // Reachability alone proves deleting loses nothing, so no marker conjunction applies here.\n name: 'own-merged deletes even with a foreign tip and dev commits',\n input: { ...laneGone('own-merged'), tipIsSyncCommit: false, hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n name: 'own-superseded (PR merged, then more commits pushed) -> close-pr but KEEP the branch',\n input: laneGone('own-superseded'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n name: 'inherited-or-none -> noop, and never close-pr',\n input: laneGone('inherited-or-none'),\n type: 'noop',\n reason: 'maps to no lane and has no sync history',\n },\n {\n name: 'inherited-or-none with the branch carrying its own dev commits -> still noop',\n input: { ...laneGone('inherited-or-none'), lastSyncedHead: undefined, hasDevCommits: true },\n type: 'noop',\n },\n\n // The adopt paths need no claim: nothing on them can delete a branch.\n {\n name: 'no branch yet -> import-lane',\n input: { branchExists: false, lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n { name: 'lane moved, branch untouched -> import-lane', input: { laneHead: 'L2' }, type: 'import-lane' },\n { name: 'dev commits, lane unchanged -> export-branch', input: { hasDevCommits: true }, type: 'export-branch' },\n { name: 'both moved -> merge-diverged', input: { laneHead: 'L2', hasDevCommits: true }, type: 'merge-diverged' },\n {\n name: 'existing branch, never synced, no dev commits, lane EXISTS -> import-lane (adopt branch)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n {\n name: 'existing branch, never synced, has dev commits, lane EXISTS -> halt (ambiguous)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none', hasDevCommits: true },\n type: 'halt',\n },\n];\n\ndescribe('planLaneSync', () => {\n table.forEach(({ name, input, type, action, reason }) => {\n it(name, () => {\n const result = planLaneSync({ ...base, ...input });\n expect(result.type, name).to.equal(type);\n if (action) expect(result).to.deep.equal(action);\n if (reason) expect((result as { reason?: string }).reason).to.contain(reason);\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_chai","data","require","_syncPlanner","ownKeys","e","r","t","Object","keys","getOwnPropertySymbols","o","filter","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","arguments","length","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","defineProperty","_toPropertyKey","value","configurable","writable","i","_toPrimitive","Symbol","toPrimitive","call","TypeError","String","Number","base","laneHead","branchExists","lastSyncedHead","hasDevCommits","tipIsSyncCommit","conflictLabelPresent","ownership","laneGone","undefined","table","name","input","type","reason","action","deleteBranch","keepReason","hasIndependentHistory","branchNamesDifferentLane","describe","it","result","planLaneSync","expect","to","equal","deep","contain"],"sources":["sync-planner.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport type { LaneOwnershipEvidence, LaneSyncAction, LaneSyncInput } from './sync-planner';\nimport { planLaneSync } from './sync-planner';\n\nconst base: LaneSyncInput = {\n laneHead: 'L1',\n branchExists: true,\n lastSyncedHead: 'L1',\n hasDevCommits: false,\n tipIsSyncCommit: true,\n conflictLabelPresent: false,\n ownership: 'own-live',\n};\n\n/** the \"lane deleted remotely, branch still there\" shape — the only path that reads `ownership` */\nconst laneGone = (ownership: LaneOwnershipEvidence) => ({ laneHead: undefined, ownership });\n\n/**\n * The decision table. `action` (exact) is required on every row that decides a branch deletion: under\n * the defaults an ordinary developer branch reaches the planner in the same shape as a deleted lane's\n * branch, so `deleteBranch` and the keep reason are part of the contract, not incidental output.\n */\nconst table: Array<{\n name: string;\n input: Partial<LaneSyncInput>;\n type: LaneSyncAction['type'];\n action?: LaneSyncAction;\n reason?: string;\n}> = [\n { name: 'converged -> noop', input: {}, type: 'noop', reason: 'converged' },\n {\n name: 'conflict label present -> noop until resolved',\n input: { laneHead: 'L2', conflictLabelPresent: true },\n type: 'noop',\n },\n { name: 'lane gone + no branch -> noop', input: { laneHead: undefined, branchExists: false }, type: 'noop' },\n\n {\n name: \"own-live, no dev commits, OUR tip (the branch is exactly the lane's mirror) -> close-pr AND delete\",\n input: laneGone('own-live'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // A developer who commits their own `.bitmap` write looks own-live with nothing above the state\n // commit; the marker is what separates our mirror from their branch.\n name: 'own-live, no dev commits, a tip WE DID NOT WRITE -> close-pr but KEEP the branch',\n input: { ...laneGone('own-live'), tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'tip-not-a-sync-commit' },\n },\n {\n name: 'own-live WITH dev commits (never-exported work above the state commit) -> KEEP the branch',\n input: { ...laneGone('own-live'), hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n // \"tip-not-a-sync-commit\" would send the reader after the wrong thing when there is real work.\n name: 'own-live with dev commits AND a foreign tip -> keeps, naming the commits as the reason',\n input: { ...laneGone('own-live'), hasDevCommits: true, tipIsSyncCommit: false },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n // Adoption proved content matched, never that the branch's pre-existing history is disposable.\n name: 'own-live, no dev commits, OUR tip, but it is the adoption ledger commit -> KEEP, never delete',\n input: { ...laneGone('own-live'), hasIndependentHistory: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'adopted-branch' },\n },\n {\n name: 'own-merged (branch tip already in the default branch) -> close-pr AND delete',\n input: laneGone('own-merged'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n // Reachability alone proves deleting loses nothing, so no marker conjunction applies here.\n name: 'own-merged deletes even with a foreign tip and dev commits',\n input: { ...laneGone('own-merged'), tipIsSyncCommit: false, hasDevCommits: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n name: 'own-merged deletes even when the tip is the adoption ledger commit — reachability is enough',\n input: { ...laneGone('own-merged'), hasIndependentHistory: true },\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: true },\n },\n {\n name: 'own-superseded (PR merged, then more commits pushed) -> close-pr but KEEP the branch',\n input: laneGone('own-superseded'),\n type: 'close-pr',\n action: { type: 'close-pr', deleteBranch: false, keepReason: 'unmerged-commits' },\n },\n {\n name: 'inherited-or-none -> noop, and never close-pr',\n input: laneGone('inherited-or-none'),\n type: 'noop',\n reason: 'maps to no lane and has no sync history',\n },\n {\n name: 'inherited-or-none with the branch carrying its own dev commits -> still noop',\n input: { ...laneGone('inherited-or-none'), lastSyncedHead: undefined, hasDevCommits: true },\n type: 'noop',\n },\n\n // The adopt paths need no claim: nothing on them can delete a branch.\n {\n name: 'no branch yet -> import-lane',\n input: { branchExists: false, lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n { name: 'lane moved, branch untouched -> import-lane', input: { laneHead: 'L2' }, type: 'import-lane' },\n { name: 'dev commits, lane unchanged -> export-branch', input: { hasDevCommits: true }, type: 'export-branch' },\n { name: 'both moved -> merge-diverged', input: { laneHead: 'L2', hasDevCommits: true }, type: 'merge-diverged' },\n {\n name: 'existing branch, never synced, no dev commits, lane EXISTS -> import-lane (adopt branch)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none' },\n type: 'import-lane',\n },\n {\n name: 'existing branch, never synced, has dev commits, lane EXISTS -> adopt-branch (first contact)',\n input: { lastSyncedHead: undefined, ownership: 'inherited-or-none', hasDevCommits: true },\n type: 'adopt-branch',\n },\n {\n name: 'existing branch, never synced, has dev commits, but .bitmap names a DIFFERENT lane -> halt',\n input: {\n lastSyncedHead: undefined,\n ownership: 'inherited-or-none',\n hasDevCommits: true,\n branchNamesDifferentLane: true,\n },\n type: 'halt',\n reason: 'names a different lane',\n },\n {\n // The executor resolves an inherited (merged) different-lane pointer to `false` — history, not a claim.\n name: 'existing branch, has dev commits, .bitmap names a MERGED-AND-INHERITED different lane -> adopt-branch',\n input: {\n lastSyncedHead: undefined,\n ownership: 'inherited-or-none',\n hasDevCommits: true,\n branchNamesDifferentLane: false,\n },\n type: 'adopt-branch',\n },\n];\n\ndescribe('planLaneSync', () => {\n table.forEach(({ name, input, type, action, reason }) => {\n it(name, () => {\n const result = planLaneSync({ ...base, ...input });\n expect(result.type, name).to.equal(type);\n if (action) expect(result).to.deep.equal(action);\n if (reason) expect((result as { reason?: string }).reason).to.contain(reason);\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
@@ -36,6 +36,11 @@ export declare const ABSENT_ON_BRANCH = "<absent>";
36
36
  * an untouched pair read as diverged after any unrelated release.
37
37
  */
38
38
  export declare function branchStateFingerprint(state: BranchBitmapState, laneComponentIds: string[]): string;
39
+ /**
40
+ * `snapPrCommit`'s return value when there was nothing to snap — its only signal either way. Lives in
41
+ * this leaf module so `ci.main.runtime.ts` and `lane-sync-executor.ts` share it without a cycle.
42
+ */
43
+ export declare const NO_CHANGES_TO_SNAP = "No changes detected, nothing to snap";
39
44
  export declare const LANE_HEAD_TRAILER = "Bit-Lane-Head";
40
45
  /**
41
46
  * Marks a commit as machine-generated. Duplicated in the `bit-git-sync` action repo's event router
@@ -55,11 +60,31 @@ export declare function hasSyncMarker(message: string): boolean;
55
60
  * `\r?` tolerates CRLF; a recognition failure errs toward keeping the branch / re-attempting the export.
56
61
  */
57
62
  export declare function isSyncAuthoredMessage(message: string): boolean;
63
+ /**
64
+ * Whether the OLDEST record of a `git log --reverse --format=%B%x1e` run is NOT bit-authored — i.e. a
65
+ * human created the branch before this reconciler touched it. Oldest only, not "any": ordinary lane
66
+ * branches carry real dev commits between ledger commits too, and "any" would misclassify them all.
67
+ */
68
+ export declare function oldestCommitIsNonSync(rawLog: string): boolean;
69
+ /**
70
+ * Strict probe for "this is one of our LEDGER commits": the sync marker on its own line AND the
71
+ * `Bit-Lane-Head` trailer — `buildSyncCommitMessage` always writes both. The deletion guard reads
72
+ * this instead of `isSyncAuthoredMessage` so a human commit merely quoting `[bit-sync]` still counts
73
+ * as independent history (a false "human" only ever keeps a branch).
74
+ */
75
+ export declare function isLedgerCommitMessage(message: string): boolean;
76
+ /**
77
+ * Marks `adopt-branch`'s ledger commit. Audit-only — the deletion guard reads the branch's ancestry
78
+ * instead, since a trailer marks only the one commit it is on and later ledger commits carry none.
79
+ */
80
+ export declare const ADOPTION_TRAILER = "Bit-Adopted";
58
81
  /**
59
82
  * The sync commit's message. Every part is an annotation (audit trail + loop-guard marker); nothing reads
60
83
  * it as state — messages are forgeable and rewritten on squash-merge. State comes from `.bitmap`.
61
84
  */
62
- export declare function buildSyncCommitMessage(laneIdStr: string, laneHead: string): string;
85
+ export declare function buildSyncCommitMessage(laneIdStr: string, laneHead: string, opts?: {
86
+ adopted?: boolean;
87
+ }): string;
63
88
  export type BranchSyncState = {
64
89
  /**
65
90
  * The newest first-parent commit that changed `.bitmap` — the reachability anchor for branch ownership
@@ -93,3 +118,10 @@ export declare function parseDevCommitCount(raw: string): boolean;
93
118
  * change that arrived through a merge would otherwise be picked as this branch's own state commit.
94
119
  */
95
120
  export declare function readBranchSyncState(branch: string, defaultBranch: string, defaultScope: string): Promise<BranchSyncState>;
121
+ /**
122
+ * Whether `branch`'s committed `.bitmap` is byte-identical (by blob sha) to the one at its merge-base
123
+ * with the default branch — i.e. the branch never asserted a `.bitmap` change of its own, so whatever
124
+ * pointer it carries is INHERITED. Compared at the fork point, not the default branch's current tip,
125
+ * which moves on and would false-block. False on any git error: unreadable must not license adoption.
126
+ */
127
+ export declare function branchBitmapUnchangedSinceFork(branch: string, defaultBranch: string): Promise<boolean>;
@@ -3,12 +3,15 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.SYNC_COMMIT_MARKER = exports.LANE_HEAD_TRAILER = exports.CONFLICT_LABEL = exports.ABSENT_ON_BRANCH = void 0;
6
+ exports.SYNC_COMMIT_MARKER = exports.NO_CHANGES_TO_SNAP = exports.LANE_HEAD_TRAILER = exports.CONFLICT_LABEL = exports.ADOPTION_TRAILER = exports.ABSENT_ON_BRANCH = void 0;
7
+ exports.branchBitmapUnchangedSinceFork = branchBitmapUnchangedSinceFork;
7
8
  exports.branchStateFingerprint = branchStateFingerprint;
8
9
  exports.buildSyncCommitMessage = buildSyncCommitMessage;
9
10
  exports.fingerprintIdVersions = fingerprintIdVersions;
10
11
  exports.hasSyncMarker = hasSyncMarker;
12
+ exports.isLedgerCommitMessage = isLedgerCommitMessage;
11
13
  exports.isSyncAuthoredMessage = isSyncAuthoredMessage;
14
+ exports.oldestCommitIsNonSync = oldestCommitIsNonSync;
12
15
  exports.parseBranchBitmap = parseBranchBitmap;
13
16
  exports.parseDevCommitCount = parseDevCommitCount;
14
17
  exports.readBranchSyncState = readBranchSyncState;
@@ -98,6 +101,12 @@ const ABSENT_ON_BRANCH = exports.ABSENT_ON_BRANCH = '<absent>';
98
101
  function branchStateFingerprint(state, laneComponentIds) {
99
102
  return fingerprintIdVersions(laneComponentIds.map(id => `${id}@${state.versions[id] ?? ABSENT_ON_BRANCH}`));
100
103
  }
104
+
105
+ /**
106
+ * `snapPrCommit`'s return value when there was nothing to snap — its only signal either way. Lives in
107
+ * this leaf module so `ci.main.runtime.ts` and `lane-sync-executor.ts` share it without a cycle.
108
+ */
109
+ const NO_CHANGES_TO_SNAP = exports.NO_CHANGES_TO_SNAP = 'No changes detected, nothing to snap';
101
110
  const LANE_HEAD_TRAILER = exports.LANE_HEAD_TRAILER = 'Bit-Lane-Head';
102
111
  /**
103
112
  * Marks a commit as machine-generated. Duplicated in the `bit-git-sync` action repo's event router
@@ -124,12 +133,42 @@ function isSyncAuthoredMessage(message) {
124
133
  return new RegExp(`^${SYNC_COMMIT_MARKER.replace(/[[\]]/g, '\\$&')}\\r?$`, 'm').test(message);
125
134
  }
126
135
 
136
+ /** The record separator `hasIndependentHistoryBelowStateCommit`'s `git log --format=%B%x1e` uses. */
137
+ const COMMIT_MESSAGE_RECORD_SEPARATOR = '\x1e';
138
+
139
+ /**
140
+ * Whether the OLDEST record of a `git log --reverse --format=%B%x1e` run is NOT bit-authored — i.e. a
141
+ * human created the branch before this reconciler touched it. Oldest only, not "any": ordinary lane
142
+ * branches carry real dev commits between ledger commits too, and "any" would misclassify them all.
143
+ */
144
+ function oldestCommitIsNonSync(rawLog) {
145
+ const messages = rawLog.split(COMMIT_MESSAGE_RECORD_SEPARATOR).map(entry => entry.trim()).filter(Boolean);
146
+ const oldest = messages[0];
147
+ return oldest !== undefined && !isLedgerCommitMessage(oldest);
148
+ }
149
+
150
+ /**
151
+ * Strict probe for "this is one of our LEDGER commits": the sync marker on its own line AND the
152
+ * `Bit-Lane-Head` trailer — `buildSyncCommitMessage` always writes both. The deletion guard reads
153
+ * this instead of `isSyncAuthoredMessage` so a human commit merely quoting `[bit-sync]` still counts
154
+ * as independent history (a false "human" only ever keeps a branch).
155
+ */
156
+ function isLedgerCommitMessage(message) {
157
+ return isSyncAuthoredMessage(message) && new RegExp(`^${LANE_HEAD_TRAILER}: `, 'm').test(message);
158
+ }
159
+
160
+ /**
161
+ * Marks `adopt-branch`'s ledger commit. Audit-only — the deletion guard reads the branch's ancestry
162
+ * instead, since a trailer marks only the one commit it is on and later ledger commits carry none.
163
+ */
164
+ const ADOPTION_TRAILER = exports.ADOPTION_TRAILER = 'Bit-Adopted';
165
+
127
166
  /**
128
167
  * The sync commit's message. Every part is an annotation (audit trail + loop-guard marker); nothing reads
129
168
  * it as state — messages are forgeable and rewritten on squash-merge. State comes from `.bitmap`.
130
169
  */
131
- function buildSyncCommitMessage(laneIdStr, laneHead) {
132
- return [`chore(bit-sync): sync lane ${laneIdStr} @ ${laneHead.slice(0, 9)}`, '', `${LANE_HEAD_TRAILER}: ${laneHead}`, SYNC_COMMIT_MARKER].join('\n');
170
+ function buildSyncCommitMessage(laneIdStr, laneHead, opts = {}) {
171
+ return [`chore(bit-sync): sync lane ${laneIdStr} @ ${laneHead.slice(0, 9)}`, '', `${LANE_HEAD_TRAILER}: ${laneHead}`, ...(opts.adopted ? [`${ADOPTION_TRAILER}: true`] : []), SYNC_COMMIT_MARKER].join('\n');
133
172
  }
134
173
  /**
135
174
  * Whether a `rev-list --count` output reports commits. An unreadable count answers `true`: `false` is an
@@ -163,6 +202,24 @@ async function readBranchSyncState(branch, defaultBranch, defaultScope) {
163
202
  };
164
203
  }
165
204
 
205
+ /**
206
+ * Whether `branch`'s committed `.bitmap` is byte-identical (by blob sha) to the one at its merge-base
207
+ * with the default branch — i.e. the branch never asserted a `.bitmap` change of its own, so whatever
208
+ * pointer it carries is INHERITED. Compared at the fork point, not the default branch's current tip,
209
+ * which moves on and would false-block. False on any git error: unreadable must not license adoption.
210
+ */
211
+ async function branchBitmapUnchangedSinceFork(branch, defaultBranch) {
212
+ try {
213
+ const base = (await _git().git.raw(['merge-base', `origin/${defaultBranch}`, `origin/${branch}`])).trim();
214
+ if (!base) return false;
215
+ const [branchBlob, baseBlob] = await Promise.all([_git().git.raw(['rev-parse', `origin/${branch}:./${_legacy2().BIT_MAP}`]), _git().git.raw(['rev-parse', `${base}:./${_legacy2().BIT_MAP}`])]);
216
+ const branchSha = branchBlob.trim();
217
+ return Boolean(branchSha) && branchSha === baseBlob.trim();
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+
166
223
  /**
167
224
  * A file's content at a git ref, or undefined when it isn't there. simple-git's `raw` can resolve with
168
225
  * empty output instead of rejecting on a missing path; `:./` keeps the path cwd-relative in case the