@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":["_legacy","data","require","_legacy2","_toolboxCrypto","_git","parseBranchBitmap","content","defaultScope","trim","undefined","bitMap","BitMap","loadFromContentWithoutLoadingFiles","Buffer","from","versions","components","forEach","componentMap","version","id","toStringWithoutVersion","laneId","isLaneExported","laneIdStr","scope","toString","fingerprintIdVersions","idAtVersion","sha1","sort","join","ABSENT_ON_BRANCH","exports","branchStateFingerprint","state","laneComponentIds","map","LANE_HEAD_TRAILER","SYNC_COMMIT_MARKER","CONFLICT_LABEL","hasSyncMarker","message","includes","isSyncAuthoredMessage","RegExp","replace","test","buildSyncCommitMessage","laneHead","slice","parseDevCommitCount","raw","count","Number","parseInt","isNaN","readBranchSyncState","branch","defaultBranch","revision","tipSha","git","tipMessage","trimEnd","stateCommit","BIT_MAP","bitmap","readFileAtRef","range","hasDevCommits","filePath"],"sources":["sync-state.ts"],"sourcesContent":["import { BitMap } from '@teambit/legacy.bit-map';\nimport { BIT_MAP } from '@teambit/legacy.constants';\nimport { sha1 } from '@teambit/toolbox.crypto.sha1';\nimport { git } from '../git';\n\n/**\n * Sync state is derived from the `.bitmap` committed on a branch — the lane pointer (`_bit_lane`) and the\n * per-component versions — never from commit messages, which are forgeable and rewritten by squash/rebase.\n */\n\n/** What a branch's committed `.bitmap` says about the branch's bit state. */\nexport type BranchBitmapState = {\n /**\n * The scope-qualified lane id `.bitmap` points at (`<scope>/<name>`), or undefined when there is no\n * usable pointer — none at all (branch on main), or one bit marked not exported.\n */\n laneIdStr?: string;\n /** `<scope>/<name>` -> version (a snap hash, or a semver tag on main), exactly as `.bitmap` records it. */\n versions: Record<string, string>;\n};\n\n/**\n * Parse a `.bitmap` file's content — no workspace, no filesystem. Fail-safe: an unreadable file returns\n * undefined, and a pointer that parses but cannot attribute (unscoped id, `exported: false`) withholds\n * `laneIdStr`; either way no lane id licenses nothing, so a parse failure can never authorize a deletion.\n * An unexported pointer is ignored because its lane never existed on any remote and treating it as a\n * mirror would retire the branch.\n */\nexport function parseBranchBitmap(content: string | undefined, defaultScope: string): BranchBitmapState | undefined {\n if (!content || !content.trim()) return undefined;\n try {\n const bitMap = BitMap.loadFromContentWithoutLoadingFiles(Buffer.from(content, 'utf8'), '', '', defaultScope);\n const versions: Record<string, string> = {};\n bitMap.components.forEach((componentMap) => {\n const version = componentMap.id.version;\n // No version = never snapped/tagged; contributes nothing to the fingerprint.\n if (version) versions[componentMap.id.toStringWithoutVersion()] = version;\n });\n // An unscoped lane id cannot be attribution: every comparison target is scope-qualified.\n const laneId = bitMap.isLaneExported ? bitMap.laneId : undefined;\n const laneIdStr = laneId?.scope ? laneId.toString() : undefined;\n return { laneIdStr, versions };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Fingerprint of `<component-id>@<version>` pairs: sorted (so listing/key order cannot perturb it), then\n * sha1'd (the value doubles as a single-token `Bit-Lane-Head` trailer).\n */\nexport function fingerprintIdVersions(idAtVersion: string[]): string {\n return sha1([...idAtVersion].sort().join('\\n'));\n}\n\n/**\n * Placeholder for a lane component the branch's `.bitmap` lacks. It must participate in the fingerprint,\n * or \"the lane grew a component\" would fingerprint identically to \"converged\".\n */\nexport const ABSENT_ON_BRANCH = '<absent>';\n\n/**\n * What the branch reflects, comparable to the lane's own fingerprint. Only the lane's components count:\n * the `.bitmap` also carries non-lane components at their main versions, and including those would make\n * an untouched pair read as diverged after any unrelated release.\n */\nexport function branchStateFingerprint(state: BranchBitmapState, laneComponentIds: string[]): string {\n return fingerprintIdVersions(laneComponentIds.map((id) => `${id}@${state.versions[id] ?? ABSENT_ON_BRANCH}`));\n}\n\nexport const LANE_HEAD_TRAILER = 'Bit-Lane-Head';\n/**\n * Marks a commit as machine-generated. Duplicated in the `bit-git-sync` action repo's event router\n * (the loop guard) — changing one copy without the other makes the reconciler re-trigger itself.\n */\nexport const SYNC_COMMIT_MARKER = '[bit-sync]';\nexport const CONFLICT_LABEL = 'bit-sync-conflict';\n\n/**\n * Permissive substring probe for \"looks machine-generated\" — the loop guard. Must NOT decide anything\n * irreversible; see `isSyncAuthoredMessage`.\n */\nexport function hasSyncMarker(message: string): boolean {\n return message.includes(SYNC_COMMIT_MARKER);\n}\n\n/**\n * Strict probe for \"we wrote this commit\": the marker alone on its own line. This is an input to branch\n * deletion and to the export-branch withhold (a branch whose tip is already our own commit settles\n * instead of re-exporting), so a developer merely quoting the marker must never count as authorship.\n * `\\r?` tolerates CRLF; a recognition failure errs toward keeping the branch / re-attempting the export.\n */\nexport function isSyncAuthoredMessage(message: string): boolean {\n return new RegExp(`^${SYNC_COMMIT_MARKER.replace(/[[\\]]/g, '\\\\$&')}\\\\r?$`, 'm').test(message);\n}\n\n/**\n * The sync commit's message. Every part is an annotation (audit trail + loop-guard marker); nothing reads\n * it as state — messages are forgeable and rewritten on squash-merge. State comes from `.bitmap`.\n */\nexport function buildSyncCommitMessage(laneIdStr: string, laneHead: string): string {\n return [\n `chore(bit-sync): sync lane ${laneIdStr} @ ${laneHead.slice(0, 9)}`,\n '',\n `${LANE_HEAD_TRAILER}: ${laneHead}`,\n SYNC_COMMIT_MARKER,\n ].join('\\n');\n}\n\nexport type BranchSyncState = {\n /**\n * The newest first-parent commit that changed `.bitmap` — the reachability anchor for branch ownership\n * and the baseline for `hasDevCommits`. Undefined when `.bitmap` was never written on the branch.\n */\n stateCommit?: string;\n /**\n * The branch tip's `.bitmap`, parsed; undefined when absent/unparseable (treated as \"not ours\").\n * Reading at the tip equals reading at `stateCommit` — nothing after it changed the file.\n */\n bitmap?: BranchBitmapState;\n /** Whether the branch carries commits on top of its bit state — work bit has not seen. */\n hasDevCommits: boolean;\n /** The branch tip's full commit message (subject + body), for the sync-marker loop-guard probe. */\n tipMessage: string;\n /**\n * The tip sha every field above was read from. The retirement path re-reads the branch and refuses to\n * delete anything else — the evidence licensed deleting THIS commit, not a later one.\n */\n tipSha?: string;\n};\n\n/**\n * Whether a `rev-list --count` output reports commits. An unreadable count answers `true`: `false` is an\n * input to branch retirement, so not knowing must withhold a deletion rather than license one. Empty\n * output is reachable — simple-git's `raw` resolves with it on some non-zero exits.\n */\nexport function parseDevCommitCount(raw: string): boolean {\n const count = Number.parseInt(raw.trim(), 10);\n return Number.isNaN(count) ? true : count > 0;\n}\n\n/**\n * Read a remote branch's sync state from its committed `.bitmap`. Assumes `git fetch origin` already ran.\n * `--first-parent` is a correctness requirement: `git log` orders across all parents, so a `.bitmap`\n * change that arrived through a merge would otherwise be picked as this branch's own state commit.\n */\nexport async function readBranchSyncState(\n branch: string,\n defaultBranch: string,\n defaultScope: string\n): Promise<BranchSyncState> {\n const revision = `origin/${branch}`;\n const tipSha = (await git.raw(['rev-parse', revision])).trim() || undefined;\n const tipMessage = (await git.raw(['log', revision, '-n', '1', '--format=%B'])).trimEnd();\n const stateCommit =\n (await git.raw(['log', revision, '--first-parent', '-n', '1', '--format=%H', '--', BIT_MAP])).trim() || undefined;\n\n const bitmap = parseBranchBitmap(await readFileAtRef(revision, BIT_MAP), defaultScope);\n\n const range = stateCommit ? `${stateCommit}..${revision}` : `origin/${defaultBranch}..${revision}`;\n const count = await git.raw(['rev-list', range, '--count']);\n\n return { stateCommit, bitmap, hasDevCommits: parseDevCommitCount(count), tipMessage, tipSha };\n}\n\n/**\n * A file's content at a git ref, or undefined when it isn't there. simple-git's `raw` can resolve with\n * empty output instead of rejecting on a missing path; `:./` keeps the path cwd-relative in case the\n * workspace is a subdirectory of the repo.\n */\nasync function readFileAtRef(revision: string, filePath: string): Promise<string | undefined> {\n try {\n return await git.raw(['show', `${revision}:./${filePath}`]);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_legacy","data","require","_legacy2","_toolboxCrypto","_git","parseBranchBitmap","content","defaultScope","trim","undefined","bitMap","BitMap","loadFromContentWithoutLoadingFiles","Buffer","from","versions","components","forEach","componentMap","version","id","toStringWithoutVersion","laneId","isLaneExported","laneIdStr","scope","toString","fingerprintIdVersions","idAtVersion","sha1","sort","join","ABSENT_ON_BRANCH","exports","branchStateFingerprint","state","laneComponentIds","map","NO_CHANGES_TO_SNAP","LANE_HEAD_TRAILER","SYNC_COMMIT_MARKER","CONFLICT_LABEL","hasSyncMarker","message","includes","isSyncAuthoredMessage","RegExp","replace","test","COMMIT_MESSAGE_RECORD_SEPARATOR","oldestCommitIsNonSync","rawLog","messages","split","entry","filter","Boolean","oldest","isLedgerCommitMessage","ADOPTION_TRAILER","buildSyncCommitMessage","laneHead","opts","slice","adopted","parseDevCommitCount","raw","count","Number","parseInt","isNaN","readBranchSyncState","branch","defaultBranch","revision","tipSha","git","tipMessage","trimEnd","stateCommit","BIT_MAP","bitmap","readFileAtRef","range","hasDevCommits","branchBitmapUnchangedSinceFork","base","branchBlob","baseBlob","Promise","all","branchSha","filePath"],"sources":["sync-state.ts"],"sourcesContent":["import { BitMap } from '@teambit/legacy.bit-map';\nimport { BIT_MAP } from '@teambit/legacy.constants';\nimport { sha1 } from '@teambit/toolbox.crypto.sha1';\nimport { git } from '../git';\n\n/**\n * Sync state is derived from the `.bitmap` committed on a branch — the lane pointer (`_bit_lane`) and the\n * per-component versions — never from commit messages, which are forgeable and rewritten by squash/rebase.\n */\n\n/** What a branch's committed `.bitmap` says about the branch's bit state. */\nexport type BranchBitmapState = {\n /**\n * The scope-qualified lane id `.bitmap` points at (`<scope>/<name>`), or undefined when there is no\n * usable pointer — none at all (branch on main), or one bit marked not exported.\n */\n laneIdStr?: string;\n /** `<scope>/<name>` -> version (a snap hash, or a semver tag on main), exactly as `.bitmap` records it. */\n versions: Record<string, string>;\n};\n\n/**\n * Parse a `.bitmap` file's content — no workspace, no filesystem. Fail-safe: an unreadable file returns\n * undefined, and a pointer that parses but cannot attribute (unscoped id, `exported: false`) withholds\n * `laneIdStr`; either way no lane id licenses nothing, so a parse failure can never authorize a deletion.\n * An unexported pointer is ignored because its lane never existed on any remote and treating it as a\n * mirror would retire the branch.\n */\nexport function parseBranchBitmap(content: string | undefined, defaultScope: string): BranchBitmapState | undefined {\n if (!content || !content.trim()) return undefined;\n try {\n const bitMap = BitMap.loadFromContentWithoutLoadingFiles(Buffer.from(content, 'utf8'), '', '', defaultScope);\n const versions: Record<string, string> = {};\n bitMap.components.forEach((componentMap) => {\n const version = componentMap.id.version;\n // No version = never snapped/tagged; contributes nothing to the fingerprint.\n if (version) versions[componentMap.id.toStringWithoutVersion()] = version;\n });\n // An unscoped lane id cannot be attribution: every comparison target is scope-qualified.\n const laneId = bitMap.isLaneExported ? bitMap.laneId : undefined;\n const laneIdStr = laneId?.scope ? laneId.toString() : undefined;\n return { laneIdStr, versions };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Fingerprint of `<component-id>@<version>` pairs: sorted (so listing/key order cannot perturb it), then\n * sha1'd (the value doubles as a single-token `Bit-Lane-Head` trailer).\n */\nexport function fingerprintIdVersions(idAtVersion: string[]): string {\n return sha1([...idAtVersion].sort().join('\\n'));\n}\n\n/**\n * Placeholder for a lane component the branch's `.bitmap` lacks. It must participate in the fingerprint,\n * or \"the lane grew a component\" would fingerprint identically to \"converged\".\n */\nexport const ABSENT_ON_BRANCH = '<absent>';\n\n/**\n * What the branch reflects, comparable to the lane's own fingerprint. Only the lane's components count:\n * the `.bitmap` also carries non-lane components at their main versions, and including those would make\n * an untouched pair read as diverged after any unrelated release.\n */\nexport function branchStateFingerprint(state: BranchBitmapState, laneComponentIds: string[]): string {\n return fingerprintIdVersions(laneComponentIds.map((id) => `${id}@${state.versions[id] ?? ABSENT_ON_BRANCH}`));\n}\n\n/**\n * `snapPrCommit`'s return value when there was nothing to snap — its only signal either way. Lives in\n * this leaf module so `ci.main.runtime.ts` and `lane-sync-executor.ts` share it without a cycle.\n */\nexport const NO_CHANGES_TO_SNAP = 'No changes detected, nothing to snap';\n\nexport const LANE_HEAD_TRAILER = 'Bit-Lane-Head';\n/**\n * Marks a commit as machine-generated. Duplicated in the `bit-git-sync` action repo's event router\n * (the loop guard) — changing one copy without the other makes the reconciler re-trigger itself.\n */\nexport const SYNC_COMMIT_MARKER = '[bit-sync]';\nexport const CONFLICT_LABEL = 'bit-sync-conflict';\n\n/**\n * Permissive substring probe for \"looks machine-generated\" — the loop guard. Must NOT decide anything\n * irreversible; see `isSyncAuthoredMessage`.\n */\nexport function hasSyncMarker(message: string): boolean {\n return message.includes(SYNC_COMMIT_MARKER);\n}\n\n/**\n * Strict probe for \"we wrote this commit\": the marker alone on its own line. This is an input to branch\n * deletion and to the export-branch withhold (a branch whose tip is already our own commit settles\n * instead of re-exporting), so a developer merely quoting the marker must never count as authorship.\n * `\\r?` tolerates CRLF; a recognition failure errs toward keeping the branch / re-attempting the export.\n */\nexport function isSyncAuthoredMessage(message: string): boolean {\n return new RegExp(`^${SYNC_COMMIT_MARKER.replace(/[[\\]]/g, '\\\\$&')}\\\\r?$`, 'm').test(message);\n}\n\n/** The record separator `hasIndependentHistoryBelowStateCommit`'s `git log --format=%B%x1e` uses. */\nconst COMMIT_MESSAGE_RECORD_SEPARATOR = '\\x1e';\n\n/**\n * Whether the OLDEST record of a `git log --reverse --format=%B%x1e` run is NOT bit-authored — i.e. a\n * human created the branch before this reconciler touched it. Oldest only, not \"any\": ordinary lane\n * branches carry real dev commits between ledger commits too, and \"any\" would misclassify them all.\n */\nexport function oldestCommitIsNonSync(rawLog: string): boolean {\n const messages = rawLog\n .split(COMMIT_MESSAGE_RECORD_SEPARATOR)\n .map((entry) => entry.trim())\n .filter(Boolean);\n const oldest = messages[0];\n return oldest !== undefined && !isLedgerCommitMessage(oldest);\n}\n\n/**\n * Strict probe for \"this is one of our LEDGER commits\": the sync marker on its own line AND the\n * `Bit-Lane-Head` trailer — `buildSyncCommitMessage` always writes both. The deletion guard reads\n * this instead of `isSyncAuthoredMessage` so a human commit merely quoting `[bit-sync]` still counts\n * as independent history (a false \"human\" only ever keeps a branch).\n */\nexport function isLedgerCommitMessage(message: string): boolean {\n return isSyncAuthoredMessage(message) && new RegExp(`^${LANE_HEAD_TRAILER}: `, 'm').test(message);\n}\n\n/**\n * Marks `adopt-branch`'s ledger commit. Audit-only — the deletion guard reads the branch's ancestry\n * instead, since a trailer marks only the one commit it is on and later ledger commits carry none.\n */\nexport const ADOPTION_TRAILER = 'Bit-Adopted';\n\n/**\n * The sync commit's message. Every part is an annotation (audit trail + loop-guard marker); nothing reads\n * it as state — messages are forgeable and rewritten on squash-merge. State comes from `.bitmap`.\n */\nexport function buildSyncCommitMessage(laneIdStr: string, laneHead: string, opts: { adopted?: boolean } = {}): string {\n return [\n `chore(bit-sync): sync lane ${laneIdStr} @ ${laneHead.slice(0, 9)}`,\n '',\n `${LANE_HEAD_TRAILER}: ${laneHead}`,\n ...(opts.adopted ? [`${ADOPTION_TRAILER}: true`] : []),\n SYNC_COMMIT_MARKER,\n ].join('\\n');\n}\n\nexport type BranchSyncState = {\n /**\n * The newest first-parent commit that changed `.bitmap` — the reachability anchor for branch ownership\n * and the baseline for `hasDevCommits`. Undefined when `.bitmap` was never written on the branch.\n */\n stateCommit?: string;\n /**\n * The branch tip's `.bitmap`, parsed; undefined when absent/unparseable (treated as \"not ours\").\n * Reading at the tip equals reading at `stateCommit` — nothing after it changed the file.\n */\n bitmap?: BranchBitmapState;\n /** Whether the branch carries commits on top of its bit state — work bit has not seen. */\n hasDevCommits: boolean;\n /** The branch tip's full commit message (subject + body), for the sync-marker loop-guard probe. */\n tipMessage: string;\n /**\n * The tip sha every field above was read from. The retirement path re-reads the branch and refuses to\n * delete anything else — the evidence licensed deleting THIS commit, not a later one.\n */\n tipSha?: string;\n};\n\n/**\n * Whether a `rev-list --count` output reports commits. An unreadable count answers `true`: `false` is an\n * input to branch retirement, so not knowing must withhold a deletion rather than license one. Empty\n * output is reachable — simple-git's `raw` resolves with it on some non-zero exits.\n */\nexport function parseDevCommitCount(raw: string): boolean {\n const count = Number.parseInt(raw.trim(), 10);\n return Number.isNaN(count) ? true : count > 0;\n}\n\n/**\n * Read a remote branch's sync state from its committed `.bitmap`. Assumes `git fetch origin` already ran.\n * `--first-parent` is a correctness requirement: `git log` orders across all parents, so a `.bitmap`\n * change that arrived through a merge would otherwise be picked as this branch's own state commit.\n */\nexport async function readBranchSyncState(\n branch: string,\n defaultBranch: string,\n defaultScope: string\n): Promise<BranchSyncState> {\n const revision = `origin/${branch}`;\n const tipSha = (await git.raw(['rev-parse', revision])).trim() || undefined;\n const tipMessage = (await git.raw(['log', revision, '-n', '1', '--format=%B'])).trimEnd();\n const stateCommit =\n (await git.raw(['log', revision, '--first-parent', '-n', '1', '--format=%H', '--', BIT_MAP])).trim() || undefined;\n\n const bitmap = parseBranchBitmap(await readFileAtRef(revision, BIT_MAP), defaultScope);\n\n const range = stateCommit ? `${stateCommit}..${revision}` : `origin/${defaultBranch}..${revision}`;\n const count = await git.raw(['rev-list', range, '--count']);\n\n return { stateCommit, bitmap, hasDevCommits: parseDevCommitCount(count), tipMessage, tipSha };\n}\n\n/**\n * Whether `branch`'s committed `.bitmap` is byte-identical (by blob sha) to the one at its merge-base\n * with the default branch — i.e. the branch never asserted a `.bitmap` change of its own, so whatever\n * pointer it carries is INHERITED. Compared at the fork point, not the default branch's current tip,\n * which moves on and would false-block. False on any git error: unreadable must not license adoption.\n */\nexport async function branchBitmapUnchangedSinceFork(branch: string, defaultBranch: string): Promise<boolean> {\n try {\n const base = (await git.raw(['merge-base', `origin/${defaultBranch}`, `origin/${branch}`])).trim();\n if (!base) return false;\n const [branchBlob, baseBlob] = await Promise.all([\n git.raw(['rev-parse', `origin/${branch}:./${BIT_MAP}`]),\n git.raw(['rev-parse', `${base}:./${BIT_MAP}`]),\n ]);\n const branchSha = branchBlob.trim();\n return Boolean(branchSha) && branchSha === baseBlob.trim();\n } catch {\n return false;\n }\n}\n\n/**\n * A file's content at a git ref, or undefined when it isn't there. simple-git's `raw` can resolve with\n * empty output instead of rejecting on a missing path; `:./` keeps the path cwd-relative in case the\n * workspace is a subdirectory of the repo.\n */\nasync function readFileAtRef(revision: string, filePath: string): Promise<string | undefined> {\n try {\n return await git.raw(['show', `${revision}:./${filePath}`]);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -223,6 +223,34 @@ describe('parseDevCommitCount', () => {
223
223
  });
224
224
  });
225
225
  });
226
+ describe('oldestCommitIsNonSync', () => {
227
+ it('is false for an empty range — no commits at all', () => {
228
+ (0, _chai().expect)((0, _syncState().oldestCommitIsNonSync)('')).to.equal(false);
229
+ });
230
+ it('is false when the oldest (first, with --reverse) record is bit-authored', () => {
231
+ // A normal bit-created branch: its own first ledger commit, then a later human dev commit and a
232
+ // second ledger commit from export-branch — non-sync work, but never the OLDEST record.
233
+ const log = [LANE_SYNC, 'feat: a later, normal dev commit\n', MAIN_SYNC].join('\x1e');
234
+ (0, _chai().expect)((0, _syncState().oldestCommitIsNonSync)(log)).to.equal(false);
235
+ });
236
+ it('is true when the oldest (first, with --reverse) record is a genuine, non-sync commit', () => {
237
+ // Adoption's exact shape: the branch's own first commit was a human's, before bit ever touched it.
238
+ const log = ['feat: a human created this branch before adoption\n', LANE_SYNC].join('\x1e');
239
+ (0, _chai().expect)((0, _syncState().oldestCommitIsNonSync)(log)).to.equal(true);
240
+ });
241
+ it('ignores a trailing separator with nothing after it', () => {
242
+ const log = `${LANE_SYNC}\x1e`;
243
+ (0, _chai().expect)((0, _syncState().oldestCommitIsNonSync)(log)).to.equal(false);
244
+ });
245
+
246
+ // A ledger commit needs the marker AND the Bit-Lane-Head trailer; a human message that merely
247
+ // quotes [bit-sync] on its own line must still read as independent history (a keep, never a delete).
248
+ it('is true when the oldest human commit quotes the [bit-sync] marker but has no ledger trailer', () => {
249
+ const spoof = 'feat: mention our tooling\n\n[bit-sync]\n';
250
+ const log = [spoof, LANE_SYNC].join('\x1e');
251
+ (0, _chai().expect)((0, _syncState().oldestCommitIsNonSync)(log)).to.equal(true);
252
+ });
253
+ });
226
254
  describe('fingerprintIdVersions', () => {
227
255
  it('is a single 40-hex token, and stable under reordering', () => {
228
256
  // A single token survives being written into a commit trailer; order-stability keeps neither
@@ -1 +1 @@
1
- {"version":3,"names":["_chai","data","require","_syncState","DEFAULT_SCOPE","SNAP_1","repeat","SNAP_2","LANE_SYNC","buildSyncCommitMessage","MAIN_SYNC","LANE_SNAP","describe","it","expect","split","to","equal","include","LANE_HEAD_TRAILER","SYNC_COMMIT_MARKER","MESSAGES","forEach","name","message","marker","authored","hasSyncMarker","isSyncAuthoredMessage","bitmapContent","lane","components","comp1","comp2","prefix","body","Object","entries","version","scope","mainFile","rootDir","_bit_lane","id","exported","JSON","stringify","bitmapWithRawLaneKey","laneKey","onLane","parseBranchBitmap","laneIdStr","versions","deep","state","POINTERS","undefined","not","UNREADABLE","content","defaultScope","DEV_COMMIT_COUNTS","raw","hasDevCommits","parseDevCommitCount","a","b","fingerprintIdVersions","match","laneIds","laneSide","branchStateFingerprint","laneMoved","grown","ABSENT_ON_BRANCH","withExtra","comp9"],"sources":["sync-state.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport {\n ABSENT_ON_BRANCH,\n LANE_HEAD_TRAILER,\n SYNC_COMMIT_MARKER,\n branchStateFingerprint,\n buildSyncCommitMessage,\n fingerprintIdVersions,\n hasSyncMarker,\n isSyncAuthoredMessage,\n parseBranchBitmap,\n parseDevCommitCount,\n} from './sync-state';\n\nconst DEFAULT_SCOPE = 'acme.shop';\nconst SNAP_1 = 'a'.repeat(40);\nconst SNAP_2 = 'b'.repeat(40);\n\nconst LANE_SYNC = buildSyncCommitMessage('acme.shop/my-lane', 'a'.repeat(40));\nconst MAIN_SYNC = 'chore(bit-sync): sync git to latest main scope versions\\n\\n[bit-sync]';\n/** `executeMergeDiverged`'s snap message: a LANE message, whose marker is inline and never a tip. */\nconst LANE_SNAP = 'merge remote lane acme.shop/my-lane into my-lane [bit-sync]';\n\ndescribe('buildSyncCommitMessage', () => {\n it('names the lane scope-qualified in the subject and carries both annotations', () => {\n expect(LANE_SYNC.split('\\n', 1)[0]).to.equal(`chore(bit-sync): sync lane acme.shop/my-lane @ ${'a'.repeat(9)}`);\n expect(LANE_SYNC).to.include(`${LANE_HEAD_TRAILER}: ${'a'.repeat(40)}`);\n expect(LANE_SYNC).to.include(SYNC_COMMIT_MARKER);\n expect(buildSyncCommitMessage('other.scope/my-lane', 'b'.repeat(40))).to.include('sync lane other.scope/my-lane');\n });\n});\n\n/**\n * `hasSyncMarker` is the permissive loop guard; `isSyncAuthoredMessage` is an input to branch DELETION,\n * so a false positive there costs a developer's branch. Every row states both answers, and the rows\n * where they disagree are the laundering holes the strict probe exists to close.\n */\nconst MESSAGES: Array<[string, string, boolean, boolean]> = [\n // message, hasSyncMarker, isSyncAuthoredMessage\n ['what buildSyncCommitMessage produces', LANE_SYNC, true, true],\n ['the raw `git log %B` shape, trailing newline and all', `${LANE_SYNC}\\n`, true, true],\n ['the main-scope sync commit, whose marker is also its own line', MAIN_SYNC, true, true],\n ['CRLF line endings', `subject\\r\\n\\r\\n${SYNC_COMMIT_MARKER}\\r\\n`, true, true],\n ['a message that merely quotes the marker mid-line', 'revert the [bit-sync] bitmap churn', true, false],\n ['the marker quoted mid-line inside a body', 'fix: undo\\n\\nthis reverts a [bit-sync] commit\\n', true, false],\n ['the marker mentioned in a subject', 'chore: mention [bit-sync] here', true, false],\n ['the inline-marker lane snap message', LANE_SNAP, true, false],\n ['an ordinary commit', 'feat: something', false, false],\n ['the lane-head trailer on its own', `${LANE_HEAD_TRAILER}: ${'c'.repeat(40)}`, false, false],\n];\n\ndescribe('sync-commit recognition', () => {\n MESSAGES.forEach(([name, message, marker, authored]) => {\n it(`${authored ? 'accepts' : 'REJECTS'} ${name}`, () => {\n expect(hasSyncMarker(message), 'loop guard').to.equal(marker);\n expect(isSyncAuthoredMessage(message), 'deletion gate').to.equal(authored);\n });\n });\n});\n\n/** A `.bitmap` in the exact shape bit writes it (schema 17) — what `git show` hands the parser. */\nfunction bitmapContent({\n lane,\n components = { comp1: SNAP_1, comp2: SNAP_2 },\n prefix = '/**\\n * DO NOT EDIT THIS FILE\\n */\\n\\n',\n}: {\n lane?: { scope: string; name: string };\n components?: Record<string, string>;\n prefix?: string;\n} = {}): string {\n const body: Record<string, any> = {};\n Object.entries(components).forEach(([name, version]) => {\n body[name] = { name, scope: DEFAULT_SCOPE, version, mainFile: 'index.js', rootDir: name };\n });\n if (lane) body._bit_lane = { id: lane, exported: true };\n body['$schema-version'] = '17.0.0';\n return `${prefix}${JSON.stringify(body, null, 4)}`;\n}\n\n/** a `.bitmap` carrying whatever `_bit_lane` value the caller wants to see rejected */\nfunction bitmapWithRawLaneKey(laneKey: any): string {\n return JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, version: SNAP_1, mainFile: 'index.js', rootDir: 'comp1' },\n _bit_lane: laneKey,\n '$schema-version': '17.0.0',\n });\n}\n\nconst onLane = (name = 'my-lane', scope = DEFAULT_SCOPE) =>\n parseBranchBitmap(bitmapContent({ lane: { scope, name } }), DEFAULT_SCOPE) as NonNullable<\n ReturnType<typeof parseBranchBitmap>\n >;\n\ndescribe('parseBranchBitmap', () => {\n it('reads the lane pointer scope-qualified — the attribution the ownership rule is built on', () => {\n expect(onLane('my-lane', 'other.scope').laneIdStr).to.equal('other.scope/my-lane');\n });\n\n it('reads every component at the exact version the branch records', () => {\n expect(onLane().versions).to.deep.equal({\n [`${DEFAULT_SCOPE}/comp1`]: SNAP_1,\n [`${DEFAULT_SCOPE}/comp2`]: SNAP_2,\n });\n });\n\n it('survives the comment header bit prefixes every `.bitmap` with', () => {\n // `.bitmap` is JSON-with-comments; a throw here silently degrades every branch to \"not ours\".\n const state = parseBranchBitmap(\n bitmapContent({ lane: { scope: DEFAULT_SCOPE, name: 'my-lane' }, prefix: '/* generated, do not edit */\\n' }),\n DEFAULT_SCOPE\n );\n expect(state?.laneIdStr).to.equal(`${DEFAULT_SCOPE}/my-lane`);\n });\n\n /**\n * Only a pointer bit marked exported, carrying a scoped id, is attribution. Honouring anything else\n * would read \"lane removed\" for a lane that never existed remotely and retire a developer branch —\n * and a bare name could still trigger the branch-aliasing halt over a lane that does not exist.\n */\n const POINTERS: Array<[string, any, string | undefined]> = [\n ['bit marked it not exported', { id: { scope: DEFAULT_SCOPE, name: 'foo' }, exported: false }, undefined],\n ['`exported` is missing entirely', { id: { scope: DEFAULT_SCOPE, name: 'foo' } }, undefined],\n ['it is an empty object', {}, undefined],\n ['`id` is a string rather than a {scope, name}', { id: `${DEFAULT_SCOPE}/foo`, exported: true }, undefined],\n ['the lane id has no scope', { id: { scope: '', name: 'foo' }, exported: true }, undefined],\n // non-vacuity for the rows above: a well-formed pointer through the same helper DOES attribute\n [\n 'bit has marked the lane exported',\n { id: { scope: DEFAULT_SCOPE, name: 'foo' }, exported: true },\n `${DEFAULT_SCOPE}/foo`,\n ],\n ];\n\n POINTERS.forEach(([name, laneKey, laneIdStr]) => {\n it(`withholds attribution unless the pointer is usable: ${name}`, () => {\n const state = parseBranchBitmap(bitmapWithRawLaneKey(laneKey), DEFAULT_SCOPE);\n expect(state?.laneIdStr).to.equal(laneIdStr);\n });\n });\n\n it('reports no lane pointer at all for a `.bitmap` on main — every ordinary developer branch', () => {\n const state = parseBranchBitmap(bitmapContent(), DEFAULT_SCOPE);\n expect(state).to.not.equal(undefined);\n expect(state?.laneIdStr).to.equal(undefined);\n });\n\n /** Every way of not knowing must resolve to the answer that licenses nothing (no branch retired). */\n const UNREADABLE: Array<[string, string | undefined]> = [\n ['a `.bitmap` that is not valid JSON', '{ this is not json'],\n ['a file git could not produce at all', undefined],\n ['an empty file', ''],\n ['a whitespace-only file', ' \\n '],\n [\n 'an entry bit itself refuses — a scoped component with no version',\n JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, mainFile: 'index.js', rootDir: 'comp1' },\n '$schema-version': '17.0.0',\n }),\n ],\n [\n 'two components claiming the same rootDir',\n JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, version: SNAP_1, mainFile: 'index.js', rootDir: 'shared' },\n comp2: { name: 'comp2', scope: DEFAULT_SCOPE, version: SNAP_2, mainFile: 'index.js', rootDir: 'shared' },\n '$schema-version': '17.0.0',\n }),\n ],\n ];\n\n UNREADABLE.forEach(([name, content]) => {\n it(`degrades to undefined rather than guessing, for ${name}`, () => {\n expect(parseBranchBitmap(content, DEFAULT_SCOPE)).to.equal(undefined);\n });\n });\n\n it('falls back to the repository default scope for a component `.bitmap` does not scope', () => {\n // `latest` can never equal a snap hash, so such a pair reads as not-converged — the safe direction.\n const content = JSON.stringify({\n comp1: { name: 'comp1', scope: '', defaultScope: DEFAULT_SCOPE, mainFile: 'index.js', rootDir: 'comp1' },\n '$schema-version': '17.0.0',\n });\n expect(parseBranchBitmap(content, DEFAULT_SCOPE)?.versions).to.deep.equal({\n [`${DEFAULT_SCOPE}/comp1`]: 'latest',\n });\n });\n});\n\n/**\n * Only a count git actually printed may report \"no dev commits\" — that answer is an input to branch\n * retirement, so every unreadable shape must read as \"there is unmerged work\" and keep the branch.\n */\nconst DEV_COMMIT_COUNTS: Array<[string, string, boolean]> = [\n ['a count of commits on top of the state commit', '3', true],\n ['the raw output shape, trailing newline and all', '3\\n', true],\n ['a genuine zero — the only license to retire', '0', false],\n [\"a genuine zero with git's trailing newline\", '0\\n', false],\n ['empty output, which simple-git resolves with on some non-zero exits', '', true],\n ['whitespace-only output', ' \\n ', true],\n ['git writing an error where the count was expected', 'fatal: bad revision', true],\n];\n\ndescribe('parseDevCommitCount', () => {\n DEV_COMMIT_COUNTS.forEach(([name, raw, hasDevCommits]) => {\n it(`${hasDevCommits ? 'keeps the branch' : 'permits retirement'} for ${name}`, () => {\n expect(parseDevCommitCount(raw), JSON.stringify(raw)).to.equal(hasDevCommits);\n });\n });\n});\n\ndescribe('fingerprintIdVersions', () => {\n it('is a single 40-hex token, and stable under reordering', () => {\n // A single token survives being written into a commit trailer; order-stability keeps neither\n // listing order looking like a state change.\n const a = `${DEFAULT_SCOPE}/comp1@${SNAP_1}`;\n const b = `${DEFAULT_SCOPE}/comp2@${SNAP_2}`;\n expect(fingerprintIdVersions([a])).to.match(/^[0-9a-f]{40}$/);\n expect(fingerprintIdVersions([a, b])).to.equal(fingerprintIdVersions([b, a]));\n });\n});\n\ndescribe('branchStateFingerprint', () => {\n const laneIds = [`${DEFAULT_SCOPE}/comp1`, `${DEFAULT_SCOPE}/comp2`];\n\n it('equals the lane fingerprint when the branch records every lane component at the lane head', () => {\n // Computed the way `laneHeadFingerprint` computes it — the two sides must be comparable.\n const laneSide = fingerprintIdVersions([`${DEFAULT_SCOPE}/comp1@${SNAP_1}`, `${DEFAULT_SCOPE}/comp2@${SNAP_2}`]);\n expect(branchStateFingerprint(onLane(), laneIds)).to.equal(laneSide);\n });\n\n it('differs when the lane moved a component the branch has not caught up with', () => {\n const laneMoved = fingerprintIdVersions([\n `${DEFAULT_SCOPE}/comp1@${SNAP_1}`,\n `${DEFAULT_SCOPE}/comp2@${'c'.repeat(40)}`,\n ]);\n expect(branchStateFingerprint(onLane(), laneIds)).to.not.equal(laneMoved);\n });\n\n it('counts a lane component the branch does not have at all', () => {\n // Without the placeholder, \"the lane grew a component\" would fingerprint identically to \"converged\".\n const grown = [...laneIds, `${DEFAULT_SCOPE}/comp3`];\n expect(branchStateFingerprint(onLane(), grown)).to.not.equal(branchStateFingerprint(onLane(), laneIds));\n expect(branchStateFingerprint(onLane(), grown)).to.equal(\n fingerprintIdVersions([\n `${DEFAULT_SCOPE}/comp1@${SNAP_1}`,\n `${DEFAULT_SCOPE}/comp2@${SNAP_2}`,\n `${DEFAULT_SCOPE}/comp3@${ABSENT_ON_BRANCH}`,\n ])\n );\n });\n\n it('ignores components the branch has that are not on the lane', () => {\n // The `.bitmap` also carries non-lane components at their main versions; counting those would make\n // an untouched pair read as diverged after any unrelated release.\n const withExtra = parseBranchBitmap(\n bitmapContent({\n lane: { scope: DEFAULT_SCOPE, name: 'my-lane' },\n components: { comp1: SNAP_1, comp2: SNAP_2, comp9: '0.0.7' },\n }),\n DEFAULT_SCOPE\n ) as NonNullable<ReturnType<typeof parseBranchBitmap>>;\n expect(branchStateFingerprint(withExtra, laneIds)).to.equal(branchStateFingerprint(onLane(), laneIds));\n });\n});\n"],"mappings":";;AAAA,SAAAA,MAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,KAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AACA,SAAAE,WAAA;EAAA,MAAAF,IAAA,GAAAC,OAAA;EAAAC,UAAA,YAAAA,CAAA;IAAA,OAAAF,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAaA,MAAMG,aAAa,GAAG,WAAW;AACjC,MAAMC,MAAM,GAAG,GAAG,CAACC,MAAM,CAAC,EAAE,CAAC;AAC7B,MAAMC,MAAM,GAAG,GAAG,CAACD,MAAM,CAAC,EAAE,CAAC;AAE7B,MAAME,SAAS,GAAG,IAAAC,mCAAsB,EAAC,mBAAmB,EAAE,GAAG,CAACH,MAAM,CAAC,EAAE,CAAC,CAAC;AAC7E,MAAMI,SAAS,GAAG,uEAAuE;AACzF;AACA,MAAMC,SAAS,GAAG,6DAA6D;AAE/EC,QAAQ,CAAC,wBAAwB,EAAE,MAAM;EACvCC,EAAE,CAAC,4EAA4E,EAAE,MAAM;IACrF,IAAAC,cAAM,EAACN,SAAS,CAACO,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAACC,EAAE,CAACC,KAAK,CAAC,kDAAkD,GAAG,CAACX,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/G,IAAAQ,cAAM,EAACN,SAAS,CAAC,CAACQ,EAAE,CAACE,OAAO,CAAC,GAAGC,8BAAiB,KAAK,GAAG,CAACb,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IACvE,IAAAQ,cAAM,EAACN,SAAS,CAAC,CAACQ,EAAE,CAACE,OAAO,CAACE,+BAAkB,CAAC;IAChD,IAAAN,cAAM,EAAC,IAAAL,mCAAsB,EAAC,qBAAqB,EAAE,GAAG,CAACH,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAACU,EAAE,CAACE,OAAO,CAAC,+BAA+B,CAAC;EACnH,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,MAAMG,QAAmD,GAAG;AAC1D;AACA,CAAC,sCAAsC,EAAEb,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,EAC/D,CAAC,sDAAsD,EAAE,GAAGA,SAAS,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EACtF,CAAC,+DAA+D,EAAEE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,EACxF,CAAC,mBAAmB,EAAE,kBAAkBU,+BAAkB,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,EAC7E,CAAC,kDAAkD,EAAE,oCAAoC,EAAE,IAAI,EAAE,KAAK,CAAC,EACvG,CAAC,0CAA0C,EAAE,iDAAiD,EAAE,IAAI,EAAE,KAAK,CAAC,EAC5G,CAAC,mCAAmC,EAAE,gCAAgC,EAAE,IAAI,EAAE,KAAK,CAAC,EACpF,CAAC,qCAAqC,EAAET,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,EAC/D,CAAC,oBAAoB,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,CAAC,EACvD,CAAC,kCAAkC,EAAE,GAAGQ,8BAAiB,KAAK,GAAG,CAACb,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAC9F;AAEDM,QAAQ,CAAC,yBAAyB,EAAE,MAAM;EACxCS,QAAQ,CAACC,OAAO,CAAC,CAAC,CAACC,IAAI,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,CAAC,KAAK;IACtDb,EAAE,CAAC,GAAGa,QAAQ,GAAG,SAAS,GAAG,SAAS,IAAIH,IAAI,EAAE,EAAE,MAAM;MACtD,IAAAT,cAAM,EAAC,IAAAa,0BAAa,EAACH,OAAO,CAAC,EAAE,YAAY,CAAC,CAACR,EAAE,CAACC,KAAK,CAACQ,MAAM,CAAC;MAC7D,IAAAX,cAAM,EAAC,IAAAc,kCAAqB,EAACJ,OAAO,CAAC,EAAE,eAAe,CAAC,CAACR,EAAE,CAACC,KAAK,CAACS,QAAQ,CAAC;IAC5E,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA,SAASG,aAAaA,CAAC;EACrBC,IAAI;EACJC,UAAU,GAAG;IAAEC,KAAK,EAAE3B,MAAM;IAAE4B,KAAK,EAAE1B;EAAO,CAAC;EAC7C2B,MAAM,GAAG;AAKX,CAAC,GAAG,CAAC,CAAC,EAAU;EACd,MAAMC,IAAyB,GAAG,CAAC,CAAC;EACpCC,MAAM,CAACC,OAAO,CAACN,UAAU,CAAC,CAACT,OAAO,CAAC,CAAC,CAACC,IAAI,EAAEe,OAAO,CAAC,KAAK;IACtDH,IAAI,CAACZ,IAAI,CAAC,GAAG;MAAEA,IAAI;MAAEgB,KAAK,EAAEnC,aAAa;MAAEkC,OAAO;MAAEE,QAAQ,EAAE,UAAU;MAAEC,OAAO,EAAElB;IAAK,CAAC;EAC3F,CAAC,CAAC;EACF,IAAIO,IAAI,EAAEK,IAAI,CAACO,SAAS,GAAG;IAAEC,EAAE,EAAEb,IAAI;IAAEc,QAAQ,EAAE;EAAK,CAAC;EACvDT,IAAI,CAAC,iBAAiB,CAAC,GAAG,QAAQ;EAClC,OAAO,GAAGD,MAAM,GAAGW,IAAI,CAACC,SAAS,CAACX,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;AACpD;;AAEA;AACA,SAASY,oBAAoBA,CAACC,OAAY,EAAU;EAClD,OAAOH,IAAI,CAACC,SAAS,CAAC;IACpBd,KAAK,EAAE;MAAET,IAAI,EAAE,OAAO;MAAEgB,KAAK,EAAEnC,aAAa;MAAEkC,OAAO,EAAEjC,MAAM;MAAEmC,QAAQ,EAAE,UAAU;MAAEC,OAAO,EAAE;IAAQ,CAAC;IACvGC,SAAS,EAAEM,OAAO;IAClB,iBAAiB,EAAE;EACrB,CAAC,CAAC;AACJ;AAEA,MAAMC,MAAM,GAAGA,CAAC1B,IAAI,GAAG,SAAS,EAAEgB,KAAK,GAAGnC,aAAa,KACrD,IAAA8C,8BAAiB,EAACrB,aAAa,CAAC;EAAEC,IAAI,EAAE;IAAES,KAAK;IAAEhB;EAAK;AAAE,CAAC,CAAC,EAAEnB,aAAa,CAExE;AAEHQ,QAAQ,CAAC,mBAAmB,EAAE,MAAM;EAClCC,EAAE,CAAC,yFAAyF,EAAE,MAAM;IAClG,IAAAC,cAAM,EAACmC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAACE,SAAS,CAAC,CAACnC,EAAE,CAACC,KAAK,CAAC,qBAAqB,CAAC;EACpF,CAAC,CAAC;EAEFJ,EAAE,CAAC,+DAA+D,EAAE,MAAM;IACxE,IAAAC,cAAM,EAACmC,MAAM,CAAC,CAAC,CAACG,QAAQ,CAAC,CAACpC,EAAE,CAACqC,IAAI,CAACpC,KAAK,CAAC;MACtC,CAAC,GAAGb,aAAa,QAAQ,GAAGC,MAAM;MAClC,CAAC,GAAGD,aAAa,QAAQ,GAAGG;IAC9B,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFM,EAAE,CAAC,+DAA+D,EAAE,MAAM;IACxE;IACA,MAAMyC,KAAK,GAAG,IAAAJ,8BAAiB,EAC7BrB,aAAa,CAAC;MAAEC,IAAI,EAAE;QAAES,KAAK,EAAEnC,aAAa;QAAEmB,IAAI,EAAE;MAAU,CAAC;MAAEW,MAAM,EAAE;IAAiC,CAAC,CAAC,EAC5G9B,aACF,CAAC;IACD,IAAAU,cAAM,EAACwC,KAAK,EAAEH,SAAS,CAAC,CAACnC,EAAE,CAACC,KAAK,CAAC,GAAGb,aAAa,UAAU,CAAC;EAC/D,CAAC,CAAC;;EAEF;AACF;AACA;AACA;AACA;EACE,MAAMmD,QAAkD,GAAG,CACzD,CAAC,4BAA4B,EAAE;IAAEZ,EAAE,EAAE;MAAEJ,KAAK,EAAEnC,aAAa;MAAEmB,IAAI,EAAE;IAAM,CAAC;IAAEqB,QAAQ,EAAE;EAAM,CAAC,EAAEY,SAAS,CAAC,EACzG,CAAC,gCAAgC,EAAE;IAAEb,EAAE,EAAE;MAAEJ,KAAK,EAAEnC,aAAa;MAAEmB,IAAI,EAAE;IAAM;EAAE,CAAC,EAAEiC,SAAS,CAAC,EAC5F,CAAC,uBAAuB,EAAE,CAAC,CAAC,EAAEA,SAAS,CAAC,EACxC,CAAC,8CAA8C,EAAE;IAAEb,EAAE,EAAE,GAAGvC,aAAa,MAAM;IAAEwC,QAAQ,EAAE;EAAK,CAAC,EAAEY,SAAS,CAAC,EAC3G,CAAC,0BAA0B,EAAE;IAAEb,EAAE,EAAE;MAAEJ,KAAK,EAAE,EAAE;MAAEhB,IAAI,EAAE;IAAM,CAAC;IAAEqB,QAAQ,EAAE;EAAK,CAAC,EAAEY,SAAS,CAAC;EAC3F;EACA,CACE,kCAAkC,EAClC;IAAEb,EAAE,EAAE;MAAEJ,KAAK,EAAEnC,aAAa;MAAEmB,IAAI,EAAE;IAAM,CAAC;IAAEqB,QAAQ,EAAE;EAAK,CAAC,EAC7D,GAAGxC,aAAa,MAAM,CACvB,CACF;EAEDmD,QAAQ,CAACjC,OAAO,CAAC,CAAC,CAACC,IAAI,EAAEyB,OAAO,EAAEG,SAAS,CAAC,KAAK;IAC/CtC,EAAE,CAAC,uDAAuDU,IAAI,EAAE,EAAE,MAAM;MACtE,MAAM+B,KAAK,GAAG,IAAAJ,8BAAiB,EAACH,oBAAoB,CAACC,OAAO,CAAC,EAAE5C,aAAa,CAAC;MAC7E,IAAAU,cAAM,EAACwC,KAAK,EAAEH,SAAS,CAAC,CAACnC,EAAE,CAACC,KAAK,CAACkC,SAAS,CAAC;IAC9C,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFtC,EAAE,CAAC,0FAA0F,EAAE,MAAM;IACnG,MAAMyC,KAAK,GAAG,IAAAJ,8BAAiB,EAACrB,aAAa,CAAC,CAAC,EAAEzB,aAAa,CAAC;IAC/D,IAAAU,cAAM,EAACwC,KAAK,CAAC,CAACtC,EAAE,CAACyC,GAAG,CAACxC,KAAK,CAACuC,SAAS,CAAC;IACrC,IAAA1C,cAAM,EAACwC,KAAK,EAAEH,SAAS,CAAC,CAACnC,EAAE,CAACC,KAAK,CAACuC,SAAS,CAAC;EAC9C,CAAC,CAAC;;EAEF;EACA,MAAME,UAA+C,GAAG,CACtD,CAAC,oCAAoC,EAAE,oBAAoB,CAAC,EAC5D,CAAC,qCAAqC,EAAEF,SAAS,CAAC,EAClD,CAAC,eAAe,EAAE,EAAE,CAAC,EACrB,CAAC,wBAAwB,EAAE,QAAQ,CAAC,EACpC,CACE,kEAAkE,EAClEX,IAAI,CAACC,SAAS,CAAC;IACbd,KAAK,EAAE;MAAET,IAAI,EAAE,OAAO;MAAEgB,KAAK,EAAEnC,aAAa;MAAEoC,QAAQ,EAAE,UAAU;MAAEC,OAAO,EAAE;IAAQ,CAAC;IACtF,iBAAiB,EAAE;EACrB,CAAC,CAAC,CACH,EACD,CACE,0CAA0C,EAC1CI,IAAI,CAACC,SAAS,CAAC;IACbd,KAAK,EAAE;MAAET,IAAI,EAAE,OAAO;MAAEgB,KAAK,EAAEnC,aAAa;MAAEkC,OAAO,EAAEjC,MAAM;MAAEmC,QAAQ,EAAE,UAAU;MAAEC,OAAO,EAAE;IAAS,CAAC;IACxGR,KAAK,EAAE;MAAEV,IAAI,EAAE,OAAO;MAAEgB,KAAK,EAAEnC,aAAa;MAAEkC,OAAO,EAAE/B,MAAM;MAAEiC,QAAQ,EAAE,UAAU;MAAEC,OAAO,EAAE;IAAS,CAAC;IACxG,iBAAiB,EAAE;EACrB,CAAC,CAAC,CACH,CACF;EAEDiB,UAAU,CAACpC,OAAO,CAAC,CAAC,CAACC,IAAI,EAAEoC,OAAO,CAAC,KAAK;IACtC9C,EAAE,CAAC,mDAAmDU,IAAI,EAAE,EAAE,MAAM;MAClE,IAAAT,cAAM,EAAC,IAAAoC,8BAAiB,EAACS,OAAO,EAAEvD,aAAa,CAAC,CAAC,CAACY,EAAE,CAACC,KAAK,CAACuC,SAAS,CAAC;IACvE,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF3C,EAAE,CAAC,qFAAqF,EAAE,MAAM;IAC9F;IACA,MAAM8C,OAAO,GAAGd,IAAI,CAACC,SAAS,CAAC;MAC7Bd,KAAK,EAAE;QAAET,IAAI,EAAE,OAAO;QAAEgB,KAAK,EAAE,EAAE;QAAEqB,YAAY,EAAExD,aAAa;QAAEoC,QAAQ,EAAE,UAAU;QAAEC,OAAO,EAAE;MAAQ,CAAC;MACxG,iBAAiB,EAAE;IACrB,CAAC,CAAC;IACF,IAAA3B,cAAM,EAAC,IAAAoC,8BAAiB,EAACS,OAAO,EAAEvD,aAAa,CAAC,EAAEgD,QAAQ,CAAC,CAACpC,EAAE,CAACqC,IAAI,CAACpC,KAAK,CAAC;MACxE,CAAC,GAAGb,aAAa,QAAQ,GAAG;IAC9B,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA,MAAMyD,iBAAmD,GAAG,CAC1D,CAAC,+CAA+C,EAAE,GAAG,EAAE,IAAI,CAAC,EAC5D,CAAC,gDAAgD,EAAE,KAAK,EAAE,IAAI,CAAC,EAC/D,CAAC,6CAA6C,EAAE,GAAG,EAAE,KAAK,CAAC,EAC3D,CAAC,4CAA4C,EAAE,KAAK,EAAE,KAAK,CAAC,EAC5D,CAAC,qEAAqE,EAAE,EAAE,EAAE,IAAI,CAAC,EACjF,CAAC,wBAAwB,EAAE,OAAO,EAAE,IAAI,CAAC,EACzC,CAAC,mDAAmD,EAAE,qBAAqB,EAAE,IAAI,CAAC,CACnF;AAEDjD,QAAQ,CAAC,qBAAqB,EAAE,MAAM;EACpCiD,iBAAiB,CAACvC,OAAO,CAAC,CAAC,CAACC,IAAI,EAAEuC,GAAG,EAAEC,aAAa,CAAC,KAAK;IACxDlD,EAAE,CAAC,GAAGkD,aAAa,GAAG,kBAAkB,GAAG,oBAAoB,QAAQxC,IAAI,EAAE,EAAE,MAAM;MACnF,IAAAT,cAAM,EAAC,IAAAkD,gCAAmB,EAACF,GAAG,CAAC,EAAEjB,IAAI,CAACC,SAAS,CAACgB,GAAG,CAAC,CAAC,CAAC9C,EAAE,CAACC,KAAK,CAAC8C,aAAa,CAAC;IAC/E,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;AAEFnD,QAAQ,CAAC,uBAAuB,EAAE,MAAM;EACtCC,EAAE,CAAC,uDAAuD,EAAE,MAAM;IAChE;IACA;IACA,MAAMoD,CAAC,GAAG,GAAG7D,aAAa,UAAUC,MAAM,EAAE;IAC5C,MAAM6D,CAAC,GAAG,GAAG9D,aAAa,UAAUG,MAAM,EAAE;IAC5C,IAAAO,cAAM,EAAC,IAAAqD,kCAAqB,EAAC,CAACF,CAAC,CAAC,CAAC,CAAC,CAACjD,EAAE,CAACoD,KAAK,CAAC,gBAAgB,CAAC;IAC7D,IAAAtD,cAAM,EAAC,IAAAqD,kCAAqB,EAAC,CAACF,CAAC,EAAEC,CAAC,CAAC,CAAC,CAAC,CAAClD,EAAE,CAACC,KAAK,CAAC,IAAAkD,kCAAqB,EAAC,CAACD,CAAC,EAAED,CAAC,CAAC,CAAC,CAAC;EAC/E,CAAC,CAAC;AACJ,CAAC,CAAC;AAEFrD,QAAQ,CAAC,wBAAwB,EAAE,MAAM;EACvC,MAAMyD,OAAO,GAAG,CAAC,GAAGjE,aAAa,QAAQ,EAAE,GAAGA,aAAa,QAAQ,CAAC;EAEpES,EAAE,CAAC,2FAA2F,EAAE,MAAM;IACpG;IACA,MAAMyD,QAAQ,GAAG,IAAAH,kCAAqB,EAAC,CAAC,GAAG/D,aAAa,UAAUC,MAAM,EAAE,EAAE,GAAGD,aAAa,UAAUG,MAAM,EAAE,CAAC,CAAC;IAChH,IAAAO,cAAM,EAAC,IAAAyD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEoB,OAAO,CAAC,CAAC,CAACrD,EAAE,CAACC,KAAK,CAACqD,QAAQ,CAAC;EACtE,CAAC,CAAC;EAEFzD,EAAE,CAAC,2EAA2E,EAAE,MAAM;IACpF,MAAM2D,SAAS,GAAG,IAAAL,kCAAqB,EAAC,CACtC,GAAG/D,aAAa,UAAUC,MAAM,EAAE,EAClC,GAAGD,aAAa,UAAU,GAAG,CAACE,MAAM,CAAC,EAAE,CAAC,EAAE,CAC3C,CAAC;IACF,IAAAQ,cAAM,EAAC,IAAAyD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEoB,OAAO,CAAC,CAAC,CAACrD,EAAE,CAACyC,GAAG,CAACxC,KAAK,CAACuD,SAAS,CAAC;EAC3E,CAAC,CAAC;EAEF3D,EAAE,CAAC,yDAAyD,EAAE,MAAM;IAClE;IACA,MAAM4D,KAAK,GAAG,CAAC,GAAGJ,OAAO,EAAE,GAAGjE,aAAa,QAAQ,CAAC;IACpD,IAAAU,cAAM,EAAC,IAAAyD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEwB,KAAK,CAAC,CAAC,CAACzD,EAAE,CAACyC,GAAG,CAACxC,KAAK,CAAC,IAAAsD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEoB,OAAO,CAAC,CAAC;IACvG,IAAAvD,cAAM,EAAC,IAAAyD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEwB,KAAK,CAAC,CAAC,CAACzD,EAAE,CAACC,KAAK,CACtD,IAAAkD,kCAAqB,EAAC,CACpB,GAAG/D,aAAa,UAAUC,MAAM,EAAE,EAClC,GAAGD,aAAa,UAAUG,MAAM,EAAE,EAClC,GAAGH,aAAa,UAAUsE,6BAAgB,EAAE,CAC7C,CACH,CAAC;EACH,CAAC,CAAC;EAEF7D,EAAE,CAAC,4DAA4D,EAAE,MAAM;IACrE;IACA;IACA,MAAM8D,SAAS,GAAG,IAAAzB,8BAAiB,EACjCrB,aAAa,CAAC;MACZC,IAAI,EAAE;QAAES,KAAK,EAAEnC,aAAa;QAAEmB,IAAI,EAAE;MAAU,CAAC;MAC/CQ,UAAU,EAAE;QAAEC,KAAK,EAAE3B,MAAM;QAAE4B,KAAK,EAAE1B,MAAM;QAAEqE,KAAK,EAAE;MAAQ;IAC7D,CAAC,CAAC,EACFxE,aACF,CAAsD;IACtD,IAAAU,cAAM,EAAC,IAAAyD,mCAAsB,EAACI,SAAS,EAAEN,OAAO,CAAC,CAAC,CAACrD,EAAE,CAACC,KAAK,CAAC,IAAAsD,mCAAsB,EAACtB,MAAM,CAAC,CAAC,EAAEoB,OAAO,CAAC,CAAC;EACxG,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_chai","data","require","_syncState","DEFAULT_SCOPE","SNAP_1","repeat","SNAP_2","LANE_SYNC","buildSyncCommitMessage","MAIN_SYNC","LANE_SNAP","describe","it","expect","split","to","equal","include","LANE_HEAD_TRAILER","SYNC_COMMIT_MARKER","MESSAGES","forEach","name","message","marker","authored","hasSyncMarker","isSyncAuthoredMessage","bitmapContent","lane","components","comp1","comp2","prefix","body","Object","entries","version","scope","mainFile","rootDir","_bit_lane","id","exported","JSON","stringify","bitmapWithRawLaneKey","laneKey","onLane","parseBranchBitmap","laneIdStr","versions","deep","state","POINTERS","undefined","not","UNREADABLE","content","defaultScope","DEV_COMMIT_COUNTS","raw","hasDevCommits","parseDevCommitCount","oldestCommitIsNonSync","log","join","spoof","a","b","fingerprintIdVersions","match","laneIds","laneSide","branchStateFingerprint","laneMoved","grown","ABSENT_ON_BRANCH","withExtra","comp9"],"sources":["sync-state.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport {\n ABSENT_ON_BRANCH,\n LANE_HEAD_TRAILER,\n SYNC_COMMIT_MARKER,\n branchStateFingerprint,\n buildSyncCommitMessage,\n fingerprintIdVersions,\n oldestCommitIsNonSync,\n hasSyncMarker,\n isSyncAuthoredMessage,\n parseBranchBitmap,\n parseDevCommitCount,\n} from './sync-state';\n\nconst DEFAULT_SCOPE = 'acme.shop';\nconst SNAP_1 = 'a'.repeat(40);\nconst SNAP_2 = 'b'.repeat(40);\n\nconst LANE_SYNC = buildSyncCommitMessage('acme.shop/my-lane', 'a'.repeat(40));\nconst MAIN_SYNC = 'chore(bit-sync): sync git to latest main scope versions\\n\\n[bit-sync]';\n/** `executeMergeDiverged`'s snap message: a LANE message, whose marker is inline and never a tip. */\nconst LANE_SNAP = 'merge remote lane acme.shop/my-lane into my-lane [bit-sync]';\n\ndescribe('buildSyncCommitMessage', () => {\n it('names the lane scope-qualified in the subject and carries both annotations', () => {\n expect(LANE_SYNC.split('\\n', 1)[0]).to.equal(`chore(bit-sync): sync lane acme.shop/my-lane @ ${'a'.repeat(9)}`);\n expect(LANE_SYNC).to.include(`${LANE_HEAD_TRAILER}: ${'a'.repeat(40)}`);\n expect(LANE_SYNC).to.include(SYNC_COMMIT_MARKER);\n expect(buildSyncCommitMessage('other.scope/my-lane', 'b'.repeat(40))).to.include('sync lane other.scope/my-lane');\n });\n});\n\n/**\n * `hasSyncMarker` is the permissive loop guard; `isSyncAuthoredMessage` is an input to branch DELETION,\n * so a false positive there costs a developer's branch. Every row states both answers, and the rows\n * where they disagree are the laundering holes the strict probe exists to close.\n */\nconst MESSAGES: Array<[string, string, boolean, boolean]> = [\n // message, hasSyncMarker, isSyncAuthoredMessage\n ['what buildSyncCommitMessage produces', LANE_SYNC, true, true],\n ['the raw `git log %B` shape, trailing newline and all', `${LANE_SYNC}\\n`, true, true],\n ['the main-scope sync commit, whose marker is also its own line', MAIN_SYNC, true, true],\n ['CRLF line endings', `subject\\r\\n\\r\\n${SYNC_COMMIT_MARKER}\\r\\n`, true, true],\n ['a message that merely quotes the marker mid-line', 'revert the [bit-sync] bitmap churn', true, false],\n ['the marker quoted mid-line inside a body', 'fix: undo\\n\\nthis reverts a [bit-sync] commit\\n', true, false],\n ['the marker mentioned in a subject', 'chore: mention [bit-sync] here', true, false],\n ['the inline-marker lane snap message', LANE_SNAP, true, false],\n ['an ordinary commit', 'feat: something', false, false],\n ['the lane-head trailer on its own', `${LANE_HEAD_TRAILER}: ${'c'.repeat(40)}`, false, false],\n];\n\ndescribe('sync-commit recognition', () => {\n MESSAGES.forEach(([name, message, marker, authored]) => {\n it(`${authored ? 'accepts' : 'REJECTS'} ${name}`, () => {\n expect(hasSyncMarker(message), 'loop guard').to.equal(marker);\n expect(isSyncAuthoredMessage(message), 'deletion gate').to.equal(authored);\n });\n });\n});\n\n/** A `.bitmap` in the exact shape bit writes it (schema 17) — what `git show` hands the parser. */\nfunction bitmapContent({\n lane,\n components = { comp1: SNAP_1, comp2: SNAP_2 },\n prefix = '/**\\n * DO NOT EDIT THIS FILE\\n */\\n\\n',\n}: {\n lane?: { scope: string; name: string };\n components?: Record<string, string>;\n prefix?: string;\n} = {}): string {\n const body: Record<string, any> = {};\n Object.entries(components).forEach(([name, version]) => {\n body[name] = { name, scope: DEFAULT_SCOPE, version, mainFile: 'index.js', rootDir: name };\n });\n if (lane) body._bit_lane = { id: lane, exported: true };\n body['$schema-version'] = '17.0.0';\n return `${prefix}${JSON.stringify(body, null, 4)}`;\n}\n\n/** a `.bitmap` carrying whatever `_bit_lane` value the caller wants to see rejected */\nfunction bitmapWithRawLaneKey(laneKey: any): string {\n return JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, version: SNAP_1, mainFile: 'index.js', rootDir: 'comp1' },\n _bit_lane: laneKey,\n '$schema-version': '17.0.0',\n });\n}\n\nconst onLane = (name = 'my-lane', scope = DEFAULT_SCOPE) =>\n parseBranchBitmap(bitmapContent({ lane: { scope, name } }), DEFAULT_SCOPE) as NonNullable<\n ReturnType<typeof parseBranchBitmap>\n >;\n\ndescribe('parseBranchBitmap', () => {\n it('reads the lane pointer scope-qualified — the attribution the ownership rule is built on', () => {\n expect(onLane('my-lane', 'other.scope').laneIdStr).to.equal('other.scope/my-lane');\n });\n\n it('reads every component at the exact version the branch records', () => {\n expect(onLane().versions).to.deep.equal({\n [`${DEFAULT_SCOPE}/comp1`]: SNAP_1,\n [`${DEFAULT_SCOPE}/comp2`]: SNAP_2,\n });\n });\n\n it('survives the comment header bit prefixes every `.bitmap` with', () => {\n // `.bitmap` is JSON-with-comments; a throw here silently degrades every branch to \"not ours\".\n const state = parseBranchBitmap(\n bitmapContent({ lane: { scope: DEFAULT_SCOPE, name: 'my-lane' }, prefix: '/* generated, do not edit */\\n' }),\n DEFAULT_SCOPE\n );\n expect(state?.laneIdStr).to.equal(`${DEFAULT_SCOPE}/my-lane`);\n });\n\n /**\n * Only a pointer bit marked exported, carrying a scoped id, is attribution. Honouring anything else\n * would read \"lane removed\" for a lane that never existed remotely and retire a developer branch —\n * and a bare name could still trigger the branch-aliasing halt over a lane that does not exist.\n */\n const POINTERS: Array<[string, any, string | undefined]> = [\n ['bit marked it not exported', { id: { scope: DEFAULT_SCOPE, name: 'foo' }, exported: false }, undefined],\n ['`exported` is missing entirely', { id: { scope: DEFAULT_SCOPE, name: 'foo' } }, undefined],\n ['it is an empty object', {}, undefined],\n ['`id` is a string rather than a {scope, name}', { id: `${DEFAULT_SCOPE}/foo`, exported: true }, undefined],\n ['the lane id has no scope', { id: { scope: '', name: 'foo' }, exported: true }, undefined],\n // non-vacuity for the rows above: a well-formed pointer through the same helper DOES attribute\n [\n 'bit has marked the lane exported',\n { id: { scope: DEFAULT_SCOPE, name: 'foo' }, exported: true },\n `${DEFAULT_SCOPE}/foo`,\n ],\n ];\n\n POINTERS.forEach(([name, laneKey, laneIdStr]) => {\n it(`withholds attribution unless the pointer is usable: ${name}`, () => {\n const state = parseBranchBitmap(bitmapWithRawLaneKey(laneKey), DEFAULT_SCOPE);\n expect(state?.laneIdStr).to.equal(laneIdStr);\n });\n });\n\n it('reports no lane pointer at all for a `.bitmap` on main — every ordinary developer branch', () => {\n const state = parseBranchBitmap(bitmapContent(), DEFAULT_SCOPE);\n expect(state).to.not.equal(undefined);\n expect(state?.laneIdStr).to.equal(undefined);\n });\n\n /** Every way of not knowing must resolve to the answer that licenses nothing (no branch retired). */\n const UNREADABLE: Array<[string, string | undefined]> = [\n ['a `.bitmap` that is not valid JSON', '{ this is not json'],\n ['a file git could not produce at all', undefined],\n ['an empty file', ''],\n ['a whitespace-only file', ' \\n '],\n [\n 'an entry bit itself refuses — a scoped component with no version',\n JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, mainFile: 'index.js', rootDir: 'comp1' },\n '$schema-version': '17.0.0',\n }),\n ],\n [\n 'two components claiming the same rootDir',\n JSON.stringify({\n comp1: { name: 'comp1', scope: DEFAULT_SCOPE, version: SNAP_1, mainFile: 'index.js', rootDir: 'shared' },\n comp2: { name: 'comp2', scope: DEFAULT_SCOPE, version: SNAP_2, mainFile: 'index.js', rootDir: 'shared' },\n '$schema-version': '17.0.0',\n }),\n ],\n ];\n\n UNREADABLE.forEach(([name, content]) => {\n it(`degrades to undefined rather than guessing, for ${name}`, () => {\n expect(parseBranchBitmap(content, DEFAULT_SCOPE)).to.equal(undefined);\n });\n });\n\n it('falls back to the repository default scope for a component `.bitmap` does not scope', () => {\n // `latest` can never equal a snap hash, so such a pair reads as not-converged — the safe direction.\n const content = JSON.stringify({\n comp1: { name: 'comp1', scope: '', defaultScope: DEFAULT_SCOPE, mainFile: 'index.js', rootDir: 'comp1' },\n '$schema-version': '17.0.0',\n });\n expect(parseBranchBitmap(content, DEFAULT_SCOPE)?.versions).to.deep.equal({\n [`${DEFAULT_SCOPE}/comp1`]: 'latest',\n });\n });\n});\n\n/**\n * Only a count git actually printed may report \"no dev commits\" — that answer is an input to branch\n * retirement, so every unreadable shape must read as \"there is unmerged work\" and keep the branch.\n */\nconst DEV_COMMIT_COUNTS: Array<[string, string, boolean]> = [\n ['a count of commits on top of the state commit', '3', true],\n ['the raw output shape, trailing newline and all', '3\\n', true],\n ['a genuine zero — the only license to retire', '0', false],\n [\"a genuine zero with git's trailing newline\", '0\\n', false],\n ['empty output, which simple-git resolves with on some non-zero exits', '', true],\n ['whitespace-only output', ' \\n ', true],\n ['git writing an error where the count was expected', 'fatal: bad revision', true],\n];\n\ndescribe('parseDevCommitCount', () => {\n DEV_COMMIT_COUNTS.forEach(([name, raw, hasDevCommits]) => {\n it(`${hasDevCommits ? 'keeps the branch' : 'permits retirement'} for ${name}`, () => {\n expect(parseDevCommitCount(raw), JSON.stringify(raw)).to.equal(hasDevCommits);\n });\n });\n});\n\ndescribe('oldestCommitIsNonSync', () => {\n it('is false for an empty range — no commits at all', () => {\n expect(oldestCommitIsNonSync('')).to.equal(false);\n });\n\n it('is false when the oldest (first, with --reverse) record is bit-authored', () => {\n // A normal bit-created branch: its own first ledger commit, then a later human dev commit and a\n // second ledger commit from export-branch — non-sync work, but never the OLDEST record.\n const log = [LANE_SYNC, 'feat: a later, normal dev commit\\n', MAIN_SYNC].join('\\x1e');\n expect(oldestCommitIsNonSync(log)).to.equal(false);\n });\n\n it('is true when the oldest (first, with --reverse) record is a genuine, non-sync commit', () => {\n // Adoption's exact shape: the branch's own first commit was a human's, before bit ever touched it.\n const log = ['feat: a human created this branch before adoption\\n', LANE_SYNC].join('\\x1e');\n expect(oldestCommitIsNonSync(log)).to.equal(true);\n });\n\n it('ignores a trailing separator with nothing after it', () => {\n const log = `${LANE_SYNC}\\x1e`;\n expect(oldestCommitIsNonSync(log)).to.equal(false);\n });\n\n // A ledger commit needs the marker AND the Bit-Lane-Head trailer; a human message that merely\n // quotes [bit-sync] on its own line must still read as independent history (a keep, never a delete).\n it('is true when the oldest human commit quotes the [bit-sync] marker but has no ledger trailer', () => {\n const spoof = 'feat: mention our tooling\\n\\n[bit-sync]\\n';\n const log = [spoof, LANE_SYNC].join('\\x1e');\n expect(oldestCommitIsNonSync(log)).to.equal(true);\n });\n});\n\ndescribe('fingerprintIdVersions', () => {\n it('is a single 40-hex token, and stable under reordering', () => {\n // A single token survives being written into a commit trailer; order-stability keeps neither\n // listing order looking like a state change.\n const a = `${DEFAULT_SCOPE}/comp1@${SNAP_1}`;\n const b = `${DEFAULT_SCOPE}/comp2@${SNAP_2}`;\n expect(fingerprintIdVersions([a])).to.match(/^[0-9a-f]{40}$/);\n expect(fingerprintIdVersions([a, b])).to.equal(fingerprintIdVersions([b, a]));\n });\n});\n\ndescribe('branchStateFingerprint', () => {\n const laneIds = [`${DEFAULT_SCOPE}/comp1`, `${DEFAULT_SCOPE}/comp2`];\n\n it('equals the lane fingerprint when the branch records every lane component at the lane head', () => {\n // Computed the way `laneHeadFingerprint` computes it — the two sides must be comparable.\n const laneSide = fingerprintIdVersions([`${DEFAULT_SCOPE}/comp1@${SNAP_1}`, `${DEFAULT_SCOPE}/comp2@${SNAP_2}`]);\n expect(branchStateFingerprint(onLane(), laneIds)).to.equal(laneSide);\n });\n\n it('differs when the lane moved a component the branch has not caught up with', () => {\n const laneMoved = fingerprintIdVersions([\n `${DEFAULT_SCOPE}/comp1@${SNAP_1}`,\n `${DEFAULT_SCOPE}/comp2@${'c'.repeat(40)}`,\n ]);\n expect(branchStateFingerprint(onLane(), laneIds)).to.not.equal(laneMoved);\n });\n\n it('counts a lane component the branch does not have at all', () => {\n // Without the placeholder, \"the lane grew a component\" would fingerprint identically to \"converged\".\n const grown = [...laneIds, `${DEFAULT_SCOPE}/comp3`];\n expect(branchStateFingerprint(onLane(), grown)).to.not.equal(branchStateFingerprint(onLane(), laneIds));\n expect(branchStateFingerprint(onLane(), grown)).to.equal(\n fingerprintIdVersions([\n `${DEFAULT_SCOPE}/comp1@${SNAP_1}`,\n `${DEFAULT_SCOPE}/comp2@${SNAP_2}`,\n `${DEFAULT_SCOPE}/comp3@${ABSENT_ON_BRANCH}`,\n ])\n );\n });\n\n it('ignores components the branch has that are not on the lane', () => {\n // The `.bitmap` also carries non-lane components at their main versions; counting those would make\n // an untouched pair read as diverged after any unrelated release.\n const withExtra = parseBranchBitmap(\n bitmapContent({\n lane: { scope: DEFAULT_SCOPE, name: 'my-lane' },\n components: { comp1: SNAP_1, comp2: SNAP_2, comp9: '0.0.7' },\n }),\n DEFAULT_SCOPE\n ) as NonNullable<ReturnType<typeof parseBranchBitmap>>;\n expect(branchStateFingerprint(withExtra, laneIds)).to.equal(branchStateFingerprint(onLane(), laneIds));\n });\n});\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/ci",
3
- "version": "1.0.494",
3
+ "version": "1.0.497",
4
4
  "homepage": "https://bit.cloud/teambit/git/ci",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.git",
8
8
  "name": "ci",
9
- "version": "1.0.494"
9
+ "version": "1.0.497"
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "4.1.2",
@@ -15,32 +15,32 @@
15
15
  "semver": "7.7.1",
16
16
  "simple-git": "^3.28.0",
17
17
  "@teambit/harmony": "0.4.12",
18
- "@teambit/cli": "0.0.1364",
19
- "@teambit/logger": "0.0.1457",
20
18
  "@teambit/component-id": "1.2.4",
21
- "@teambit/component.modules.merge-helper": "0.0.88",
22
- "@teambit/component.snap-distance": "0.0.146",
23
19
  "@teambit/harmony.modules.concurrency": "0.0.42",
24
20
  "@teambit/lane-id": "0.0.312",
25
- "@teambit/legacy.consumer-component": "0.0.146",
26
21
  "@teambit/toolbox.promise.map-pool": "0.0.19",
27
22
  "@teambit/toolbox.string.random": "0.0.18",
28
23
  "@teambit/bit-error": "0.0.404",
29
24
  "@teambit/legacy.constants": "0.0.41",
30
- "@teambit/legacy.scope": "0.0.145",
31
- "@teambit/legacy.bit-map": "0.0.202",
32
25
  "@teambit/toolbox.crypto.sha1": "0.0.20",
33
- "@teambit/workspace": "1.0.1099",
34
- "@teambit/builder": "1.0.1099",
35
- "@teambit/checkout": "1.0.1100",
36
- "@teambit/config-merger": "0.0.966",
37
- "@teambit/dependency-resolver": "1.0.1099",
38
- "@teambit/export": "1.0.1099",
39
- "@teambit/importer": "1.0.1099",
40
- "@teambit/lanes": "1.0.1120",
41
- "@teambit/objects": "0.0.606",
42
- "@teambit/snapping": "1.0.1099",
43
- "@teambit/status": "1.0.1124"
26
+ "@teambit/cli": "0.0.1367",
27
+ "@teambit/logger": "0.0.1460",
28
+ "@teambit/workspace": "1.0.1102",
29
+ "@teambit/builder": "1.0.1102",
30
+ "@teambit/checkout": "1.0.1103",
31
+ "@teambit/component.modules.merge-helper": "0.0.91",
32
+ "@teambit/component.snap-distance": "0.0.149",
33
+ "@teambit/config-merger": "0.0.969",
34
+ "@teambit/dependency-resolver": "1.0.1102",
35
+ "@teambit/export": "1.0.1102",
36
+ "@teambit/importer": "1.0.1102",
37
+ "@teambit/lanes": "1.0.1123",
38
+ "@teambit/legacy.consumer-component": "0.0.149",
39
+ "@teambit/objects": "0.0.609",
40
+ "@teambit/snapping": "1.0.1102",
41
+ "@teambit/status": "1.0.1127",
42
+ "@teambit/legacy.scope": "0.0.148",
43
+ "@teambit/legacy.bit-map": "0.0.205"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/lodash": "4.14.165",
@@ -14,7 +14,9 @@ import {
14
14
  DEFAULT_GIT_USER_NAME,
15
15
  fetchRemoteHeads,
16
16
  isNonContentPath,
17
+ isNonFastForwardRejection,
17
18
  isStaleLeaseRejection,
19
+ redactUrlCredentials,
18
20
  parseLsRemoteSymref,
19
21
  parseOriginHeadRef,
20
22
  refetchBranchTip,
@@ -228,6 +230,64 @@ describe('deleteBranchArgs', () => {
228
230
  });
229
231
  });
230
232
 
233
+ describe('isNonFastForwardRejection', () => {
234
+ // The GitHub-style client-side check, in the shape this code actually receives: simple-git appends
235
+ // `--porcelain` to every push, so the rejected ref arrives as a TAB-separated status line, not the
236
+ // human `! [rejected]` wording.
237
+ const CLIENT_SIDE_REJECTION =
238
+ '!\tHEAD:refs/heads/feature\t[rejected] (fetch first)\n' +
239
+ "error: failed to push some refs to '/remote'\n" +
240
+ 'hint: Updates were rejected because the remote contains work that you do\n' +
241
+ 'hint: not have locally.';
242
+
243
+ // The server-side ref-lock check: a plain push racing a concurrent ref update on the local/file
244
+ // transport surfaces the SAME wording as a lease refusal, with no lease involved (reproduced in
245
+ // ci-sync.e2e.ts's "a rejected sync-commit push" test).
246
+ const SERVER_SIDE_REJECTION =
247
+ '!\tHEAD:refs/heads/import-race-lane\t[remote rejected] (failed to update ref)\n' +
248
+ "remote: error: cannot lock ref 'refs/heads/import-race-lane': is at abc123 but expected def456\n" +
249
+ "error: failed to push some refs to '/remote'";
250
+
251
+ it('recognizes the client-side non-fast-forward wording, porcelain and human', () => {
252
+ expect(isNonFastForwardRejection(CLIENT_SIDE_REJECTION)).to.equal(true);
253
+ expect(isNonFastForwardRejection('!\tmain:refs/heads/main\t[rejected] (non-fast-forward)')).to.equal(true);
254
+ // the human layout (no --porcelain) still matches — the token test is layout-independent
255
+ expect(isNonFastForwardRejection('! [rejected] main -> main (non-fast-forward)')).to.equal(true);
256
+ expect(isNonFastForwardRejection('To /remote\n ! [rejected] HEAD -> feature (fetch first)')).to.equal(true);
257
+ });
258
+
259
+ it('recognizes the server-side ref-lock wording a plain push can also hit', () => {
260
+ expect(isNonFastForwardRejection(SERVER_SIDE_REJECTION)).to.equal(true);
261
+ // Same wording `isStaleLeaseRejection` matches — a plain push can legitimately race into it too.
262
+ expect(isNonFastForwardRejection('! [rejected] (delete) -> my-lane (stale info)')).to.equal(true);
263
+ });
264
+
265
+ it('does not mistake a server-side hook decline for a race', () => {
266
+ // `[remote rejected]` alone (no ref-lock/fetch-first wording) — a protected-branch/hook decline,
267
+ // a real problem to surface, not a race to swallow.
268
+ expect(isNonFastForwardRejection('! [remote rejected] main -> main (protected branch hook declined)')).to.equal(
269
+ false
270
+ );
271
+ expect(
272
+ isNonFastForwardRejection('!\tHEAD:refs/heads/main\t[remote rejected] (pre-receive hook declined)')
273
+ ).to.equal(false);
274
+ expect(isNonFastForwardRejection('remote: GH006: Protected branch update failed')).to.equal(false);
275
+ });
276
+ });
277
+
278
+ describe('redactUrlCredentials', () => {
279
+ it('strips embedded credentials from a push-error remote URL', () => {
280
+ expect(
281
+ redactUrlCredentials("error: failed to push some refs to 'https://x-access-token:ghs_abc123@github.com/o/r'")
282
+ ).to.equal("error: failed to push some refs to 'https://***@github.com/o/r'");
283
+ });
284
+
285
+ it('leaves a credential-free message unchanged', () => {
286
+ const message = "error: failed to push some refs to 'https://github.com/o/r'\n ! [rejected] (fetch first)";
287
+ expect(redactUrlCredentials(message)).to.equal(message);
288
+ });
289
+ });
290
+
231
291
  describe('isNonContentPath', () => {
232
292
  it('covers the two paths the executors never treat as workspace content, and nothing adjacent', () => {
233
293
  ['.bit', '.bit/objects/ab/cdef', 'node_modules/lodash/index.js'].forEach((p) =>
package/sync/git-ops.ts CHANGED
@@ -178,6 +178,78 @@ export function isStaleLeaseRejection(message: string): boolean {
178
178
  return /stale info|force-with-lease|but expected|incorrect old value/i.test(message);
179
179
  }
180
180
 
181
+ /**
182
+ * Whether a failed push was a plain non-fast-forward rejection: a concurrent run already advanced
183
+ * `branch`, so ours is now stale. `commitAllAndPush` never uses `--force-with-lease`, yet a race can
184
+ * still surface the SAME wording `isStaleLeaseRejection` matches: git's ref-update transaction rejects
185
+ * an ordinary push exactly like a lease refusal once the remote's ref changed underneath it (confirmed
186
+ * empirically on the local/file transport this repo's e2e remotes use — "cannot lock ref … is at X but
187
+ * expected Y" — no lease involved). A GitHub-style remote instead rejects the ref with a
188
+ * `[rejected] … (fetch first)` or `(non-fast-forward)` line. That line's layout varies: simple-git
189
+ * appends `--porcelain` to every push, so the real shape is tab-separated
190
+ * (`!\tHEAD:refs/heads/x\t[rejected] (fetch first)`), NOT the human `! [rejected] …` — match the
191
+ * `[rejected]` token plus the reason, never the whitespace around them. `[remote rejected]` (a server
192
+ * hook decline, e.g. a protected branch) does not contain the bare `[rejected]` token and is excluded:
193
+ * that is a real problem to surface, not a race to swallow.
194
+ */
195
+ export function isNonFastForwardRejection(message: string): boolean {
196
+ if (isStaleLeaseRejection(message)) return true;
197
+ return /\[rejected\]/.test(message) && /fetch first|non-fast-forward/i.test(message);
198
+ }
199
+
200
+ /**
201
+ * Whether a rejected plain push was a CONFIRMED race: the wording matches a non-fast-forward rejection
202
+ * AND a re-fetch shows `branch` actually moved off `baseSha` — the tip the rejected push was based on.
203
+ * The wording alone is not proof: the regexes are deliberately broad, so a PERSISTENT, unrelated push
204
+ * failure (bad credentials, a flaky transport) could repeat the same wording forever; unconfirmed
205
+ * answers 'not-a-race' and the caller fails loud. A failed re-fetch (`undefined` tip) is UNKNOWN, not
206
+ * confirmation — never treat not knowing as proof of a race. The one `remoteMoved` expression also
207
+ * covers a brand-new branch (`baseSha === undefined`): a defined tip already proves the branch now
208
+ * exists on the remote. `refetchTip` is injectable so callers stay unit-testable without a real remote.
209
+ */
210
+ export async function confirmPushRace(
211
+ branch: string,
212
+ baseSha: string | undefined,
213
+ message: string,
214
+ refetchTip: (branch: string) => Promise<string | undefined> = refetchBranchTip
215
+ ): Promise<'confirmed-race' | 'not-a-race'> {
216
+ if (!isNonFastForwardRejection(message)) return 'not-a-race';
217
+ const currentTip = await refetchTip(branch);
218
+ const remoteMoved = currentTip !== undefined && currentTip !== baseSha;
219
+ return remoteMoved ? 'confirmed-race' : 'not-a-race';
220
+ }
221
+
222
+ /**
223
+ * Strip URL userinfo (`https://user:token@host` → `https://***@host`) from a git message before it
224
+ * reaches a log or a summary: git prints the remote URL in push errors, and a remote may embed
225
+ * credentials. Over-redaction is the safe direction — `ssh://git@host` loses nothing that matters.
226
+ */
227
+ export function redactUrlCredentials(text: string): string {
228
+ return text.replace(/\/\/[^\s@/]+@/g, '//***@');
229
+ }
230
+
231
+ /**
232
+ * Move the local `branch` back to `origin/<branch>`, dropping unpushed commits — the cleanup after a
233
+ * lost push race, so the orphan commit cannot trip `checkoutPristine`'s guard on the clone's next run.
234
+ * `reset --hard` writes the index, so a stale `index.lock` blocks it; the `update-ref` fallback does
235
+ * not touch the index and still moves the ref off the orphan commit (the callers force-clean the tree
236
+ * afterwards, in `restoreWorkspace`). Returns the failure message instead of throwing: the drop is
237
+ * best-effort, and a failed drop must not escalate a confirmed-benign race.
238
+ */
239
+ export async function dropLocalBranchToRemoteTip(branch: string): Promise<string | undefined> {
240
+ try {
241
+ await git.raw(['reset', '--hard', `origin/${branch}`]);
242
+ return undefined;
243
+ } catch (resetErr: any) {
244
+ try {
245
+ await git.raw(['update-ref', `refs/heads/${branch}`, `refs/remotes/origin/${branch}`]);
246
+ return undefined;
247
+ } catch {
248
+ return String(resetErr?.message || resetErr);
249
+ }
250
+ }
251
+ }
252
+
181
253
  /** Whether `origin` has the given branch. Assumes a `git fetch` isn't required (uses `ls-remote`). */
182
254
  export async function branchExistsOnRemote(branch: string): Promise<boolean> {
183
255
  const out = await git.raw(['ls-remote', '--heads', 'origin', branch]);